[
  {
    "path": ".cursor/skills/graphite-pr-workflow/SKILL.md",
    "content": "---\nname: graphite-pr-workflow\ndescription: Create branches, commits, and pull requests using Graphite CLI (gt) for the snowflake-jdbc repository. Use when the user asks to create a PR, submit a PR, commit changes, or push a branch. Covers Graphite commands, SNOW-ticket commit message conventions, PR description templates, and snowflake-jdbc-specific pre-commit formatting/checkstyle requirements.\n---\n\n# Graphite PR Workflow (snowflake-jdbc)\n\n## CRITICAL: Do Not Commit or Submit Without Explicit Instruction\n\n**NEVER run `gt commit create`, `git commit`, `gt submit`, or open a PR unless the user has explicitly asked you to.** Completing a feature is NOT permission to commit. Passing tests is NOT permission to commit. Pre-commit checks succeeding is NOT permission to commit.\n\nWhen you have finished implementing a feature or change, you MAY ask the user whether they want you to proceed with creating a commit and PR. Phrase it as a question and wait for explicit confirmation before doing anything that mutates git history or the remote.\n\nExamples of acceptable prompts after finishing work:\n- \"The change is implemented and tests pass. Would you like me to format, run checkstyle, and create a commit + PR?\"\n- \"Should I proceed with `gt commit create` and `gt submit`?\"\n\nDo NOT interpret ambiguous responses (\"looks good\", \"great\", \"thanks\") as approval to commit. Wait for an explicit \"yes, commit\" / \"create the PR\" / \"go ahead and submit\".\n\n## Required Pre-Commit Checks (snowflake-jdbc)\n\nBefore running ANY commit command (`gt commit create`, `git commit`, `gt commit amend`), you MUST run the following two Maven commands in order from the repo root and confirm they succeed:\n\n1. **Format the code** with the Spotify fmt plugin:\n\n   ```bash\n   mvn com.spotify.fmt:fmt-maven-plugin:format\n   ```\n\n2. **Validate checkstyle** passes cleanly:\n\n   ```bash\n   mvn clean validate --batch-mode --show-version -P check-style\n   ```\n\nIf either command fails, fix the reported issues (re-running the formatter for any new files, or addressing checkstyle violations manually) and re-run both commands until both succeed. Only then may you proceed with committing — and only if the user has explicitly asked you to commit (see section above).\n\n## Commit Message Convention\n\nPrefix with Jira ticket: `SNOW-{ticket}: {description}`\n\n```\nSNOW-3254915: Add QueryStatus enum and is_still_running/is_an_error static methods\n```\n\nUse `NO-SNOW:` for changes without a ticket.\n\n## Creating a PR (only after explicit user approval)\n\n```bash\n# 1. Create a tracked branch\ngt branch create {user}/{branch-name}\n\n# 2. Format and validate (REQUIRED before staging/committing — see Pre-Commit Checks above)\nmvn com.spotify.fmt:fmt-maven-plugin:format\nmvn clean validate --batch-mode --show-version -P check-style\n\n# 3. Stage files\ngit add <files>\n\n# 4. Commit (runs pre-commit hooks)\ngt commit create -m \"SNOW-{ticket}: {description}\"\n\n# 5. Push and create PR (draft mode in non-interactive)\ngt submit --no-edit\n```\n\nThen update title/description via `gh pr edit` or the Graphite web UI.\n\n## PR Description Template\n\n```markdown\n## Summary\n- Bullet points describing what changed and why\n\n## Context\nBrief explanation of how this fits into the larger effort (e.g., \"first of two PRs for...\")\n\n## Test plan\nExplain how the change was tested: what commands were run, what was\nverified, and the results. Be specific to this PR.\n```\n\n## Key Commands\n\n| Command | Purpose |\n|---------|---------|\n| `mvn com.spotify.fmt:fmt-maven-plugin:format` | Auto-format Java sources (REQUIRED before commit) |\n| `mvn clean validate --batch-mode --show-version -P check-style` | Run checkstyle validation (REQUIRED before commit) |\n| `gt branch create {user}/{branch-name}` | Create branch tracked by Graphite |\n| `gt commit create -m \"...\"` | Commit with message |\n| `gt commit amend` | Amend current commit |\n| `gt submit --no-edit` | Push and create/update PR |\n| `gt submit --stack` | Submit entire stack of PRs |\n| `gt restack` | Rebase stack after upstream changes |\n\n## Before Creating a PR: Required Checks\n\nBefore proceeding with branch creation or committing, you MUST:\n\n1. **Confirm the user has explicitly requested a commit/PR.** See the top of this file. Do not commit proactively.\n\n2. **Run the formatter and checkstyle commands** described in the Required Pre-Commit Checks section above and confirm both pass.\n\n3. **Ask for SNOW ticket number** if the user hasn't provided one. Use AskQuestion:\n   - Prompt: \"What is the SNOW Jira ticket number for this change?\"\n   - Options: a text-free response, or \"NO-SNOW (no ticket)\"\n   Do NOT proceed with a commit until the ticket number is confirmed.\n\n4. **Detect stacked PR situation.** Run `git log --oneline main..HEAD` or check `git branch --show-current` and its parent. If the current branch is NOT based directly off `main` (i.e., there are intermediate branches), ask:\n   - \"This branch is based off `{parent_branch}`, not `main`. Should this be a stacked PR?\"\n   - If yes: use `gt submit --stack` to submit the full stack\n   - If no: use `gt submit --no-edit` for just this branch\n\n## Notes\n\n- `gt submit --no-edit` creates PRs in **draft mode** when non-interactive\n- Pre-commit hooks run automatically on `gt commit create`, but they are NOT a substitute for running the formatter + checkstyle commands manually beforehand\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @snowflakedb/Client\n/src/main/java/net/snowflake/client/core/crl/** @snowflakedb/pki-oversight @snowflakedb/Client\n/src/main/java/net/snowflake/client/core/*TrustManager* @snowflakedb/pki-oversight @snowflakedb/Client\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG_REPORT.md",
    "content": "---\nname: Bug Report 🐞\nabout: Something isn't working as expected? Here is the right place to report.\nlabels: bug\n---\n\n:exclamation: If you need **urgent assistance** then [file a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge).\nOtherwise continue here.\n\n\nPlease answer these questions before submitting your issue. \nIn order to accurately debug the issue this information is required. Thanks!\n\n1. What version of JDBC driver are you using?\n\n   \n2. What operating system and processor architecture are you using?\n\n   \n3. What version of Java are you using?\n\n   \n4. What did you do?\n\n   If possible, provide a recipe for reproducing the error.\n   A complete runnable program is good.\n\n5. What did you expect to see?\n\n   What should have happened and what happened instead?\n\n6. Can you set logging to DEBUG and collect the logs?\n\n   https://community.snowflake.com/s/article/How-to-generate-log-file-on-Snowflake-connectors\n   \n   Before sharing any information, please be sure to review the log and remove any sensitive\n   information. \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/FEATURE_REQUEST.md",
    "content": "---\nname: Feature Request 💡\nabout: Suggest a new idea for the project.\nlabels: feature\n---\n\n<!--\nIf you need urgent assistance then file the feature request using the support process:\nhttps://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge\notherwise continue here.\n-->\n\n## What is the current behavior?\n\n## What is the desired behavior?\n\n## How would this improve `snowflake-jdbc`?\n\n## References, Other Background\n \n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# Overview\n\nSNOW-XXXXX\n\n## Pre-review self checklist\n- [ ] PR branch is updated with all the changes from `master` branch\n- [ ] The code is correctly formatted (run `mvn -P check-style validate`)\n- [ ] New public API is not unnecessary exposed (run `mvn verify` and inspect `target/japicmp/japicmp.html`)\n- [ ] The pull request name is prefixed with `SNOW-XXXX: `\n- [ ] Code is in compliance with internal logging requirements\n\n## External contributors - please answer these questions before submitting a pull request. Thanks!\n\n1. What GitHub issue is this PR addressing? Make sure that there is an accompanying issue to your PR.\n\n   Issue: #NNNN\n\n\n2. Fill out the following pre-review checklist:\n\n   - [ ] I am adding a new automated test(s) to verify correctness of my new code\n   - [ ] I am adding new logging messages\n   - [ ] I am modifying authorization mechanisms\n   - [ ] I am adding new credentials\n   - [ ] I am modifying OCSP code\n   - [ ] I am adding a new dependency or upgrading an existing one\n   - [ ] I am adding new public/protected component not marked with `@SnowflakeJdbcInternalApi` (note that public/protected methods/fields in classes marked with this annotation are already internal)\n\n3. Please describe how your code solves the related issue.\n\n   Please write a short description of how your code change solves the related issue.\n"
  },
  {
    "path": ".github/repo_meta.yaml",
    "content": "\n# point_of_contact: the owner of this repository, can be a GitHub user or GitHub team\npoint_of_contact: @snowflakedb/client \n\n# production: whether this repository meets the criteria for being \"production\", see https://snowflakecomputing.atlassian.net/wiki/spaces/CLO/pages/2239988967/Production+Repository+Criteria for criteria\nproduction: true\n\n# distributed: whether any source code in this repository is distributed directly to customers (e.g. driver and frontend software)\ndistributed: true\n\n# modified: whether any open source dependencies in this repository have been modified \nmodified: true\n\n# release_branches: list of release branch patterns, exact matches or regex is acceptable\nrelease_branches: \n  - master\n  - release.*\n\n\n# code_owners_file_present: whether there is a CODEOWNERS file in this repository\ncode_owners_file_present: true\n\n# jira_project_issue_type: the jira issuetype used to raise issues related to this repository in the SNOW Jira project\njira_project_issue_type: Bug\n\n\n\n# jira_area: the jira area that raised issues should use\njira_area: Developer Platform\n"
  },
  {
    "path": ".github/workflows/build-test.yml",
    "content": "name: Build and Test\n\non:\n    push:\n        branches:\n            - master\n        tags:\n            - v*\n    pull_request:\n        branches:\n            - master\n            - SNOW-**\n            - NO-SNOW-**\n        types: [opened, synchronize, reopened, labeled, unlabeled]\n    workflow_dispatch:\n        inputs:\n          logLevel:\n            default: warning\n            description: \"Log level\"\n            required: true\n          tags:\n            description: \"Test scenario tags\"\n\npermissions:\n  contents: read\n\nconcurrency:\n  # older builds for the same pull request numer or branch should be cancelled\n  cancel-in-progress: true\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n\njobs:\n    check-no-raw-system-calls:\n        name: Check No Raw System.getProperty/getenv/setProperty\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v5\n            - name: Check for raw System.getProperty/getenv/setProperty calls\n              shell: bash\n              run: ./ci/scripts/check_no_raw_system_calls.sh\n\n    build:\n        name: Build\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v5\n            - name: Build\n              shell: bash\n              env:\n                WHITESOURCE_API_KEY: ${{ secrets.WHITESOURCE_API_KEY }}\n              run: ./ci/build.sh\n\n    unit-test-linux:\n        name: Unit Tests Linux java 8\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: '8'\n                distribution: 'temurin'\n                cache: maven\n            - name: Unit Tests\n              shell: bash\n              env:\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx2g\"\n              run: ./mvnw -B -DjenkinsIT -Dskip.unitTests=false -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn test --batch-mode --show-version\n\n    unit-test-windows:\n        name: Unit Tests Windows java 8\n        runs-on: windows-latest\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: '8'\n                distribution: 'temurin'\n                cache: maven\n            - name: Unit Tests\n              shell: cmd\n              env:\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx2g\"\n              run: .\\mvnw.cmd -B -DjenkinsIT -Dskip.unitTests=false -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn test --batch-mode --show-version\n\n    unit-test-mac:\n        name: Unit Tests Mac java 8\n        runs-on: macos-latest\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: '8'\n                distribution: 'zulu'\n                cache: maven\n            - name: Unit Tests\n              shell: bash\n              env:\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx2g\"\n              run: ./mvnw -B -DjenkinsIT -Dskip.unitTests=false -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn test --batch-mode --show-version\n\n    test-windows:\n        needs: [build, unit-test-linux, unit-test-windows, unit-test-mac]\n        name: ${{ matrix.runConfig.cloud }} Windows java ${{ matrix.runConfig.javaVersion }} JDBC${{ matrix.additionalMavenProfile }} ${{ matrix.category.name }}\n        runs-on: windows-latest\n        strategy:\n            fail-fast: false\n            matrix:\n                runConfig: >-\n                    ${{\n                        fromJSON(\n                            (\n                                github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'FULL-TEST-MATRIX') &&\n                                '[\n                                    {\"cloud\":\"AWS\",\"javaVersion\":\"8\"},\n                                    {\"cloud\":\"GCP\",\"javaVersion\":\"17\"},\n                                    {\"cloud\":\"AZURE\",\"javaVersion\":\"21\"}\n                                  ]'\n                            ) ||\n                            '[\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"8\"},\n                                {\"cloud\":\"GCP\",\"javaVersion\":\"11\"},\n                                {\"cloud\":\"AZURE\",\"javaVersion\":\"17\"},\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"21\"}\n                              ]'\n                        )\n                    }}\n                category: [{suites: 'ResultSetTestSuite', name: 'TestCategoryResultSet'},\n                           {suites: 'StatementTestSuite,LoaderTestSuite', name: 'TestCategoryStatement,TestCategoryLoader'},\n                           {suites: 'OthersTestSuite', name: 'TestCategoryOthers'},\n                           {suites: 'DatabaseMetaDataTestSuite', name: 'TestCategoryDatabaseMetaData'},\n                           {suites: 'ArrowTestSuite,ConnectionTestSuite,CoreTestSuite,DiagnosticTestSuite', name: 'TestCategoryArrow,TestCategoryConnection,TestCategoryCore,TestCategoryDiagnostic'},\n                           {suites: 'FipsTestSuite', name: \"TestCategoryFips\"}]\n                additionalMavenProfile: ['']\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: ${{ matrix.runConfig.javaVersion }}\n                distribution: 'temurin'\n                cache: maven\n            - uses: actions/setup-python@v5\n              with:\n                python-version: '3.12'\n                architecture: 'x64'\n            - name: Tests\n              shell: cmd\n              env:\n                PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}\n                JDBC_PRIVATE_KEY_SECRET: ${{ secrets.JDBC_PRIVATE_KEY_SECRET }}\n                CLOUD_PROVIDER: ${{ matrix.runConfig.cloud }}\n                JDBC_TEST_SUITES: ${{ matrix.category.suites }}\n                ADDITIONAL_MAVEN_PROFILE: ${{ matrix.additionalMavenProfile }}\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx4g\"\n              run: ci\\\\test_windows.bat\n\n    test-mac:\n        needs: [build, unit-test-linux, unit-test-windows, unit-test-mac]\n        name: ${{ matrix.runConfig.cloud }} Mac java ${{ matrix.runConfig.javaVersion }} JDBC${{ matrix.additionalMavenProfile }} ${{ matrix.category.name }}\n        runs-on: macos-latest\n        strategy:\n            fail-fast: false\n            matrix:\n                runConfig: >-\n                    ${{\n                        fromJSON(\n                            (\n                                github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'FULL-TEST-MATRIX') &&\n                                '[\n                                    {\"cloud\":\"AWS\",\"javaVersion\":\"8\"},\n                                    {\"cloud\":\"GCP\",\"javaVersion\":\"17\"},\n                                    {\"cloud\":\"AZURE\",\"javaVersion\":\"21\"}\n                                  ]'\n                            ) ||\n                            '[\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"8\"},\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"21\"},\n                                {\"cloud\":\"GCP\",\"javaVersion\":\"11\"},\n                                {\"cloud\":\"AZURE\",\"javaVersion\":\"17\"},\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"21\"}\n                              ]'\n                        )\n                    }}\n                category: [{suites: 'ResultSetTestSuite', name: 'TestCategoryResultSet'},\n                           {suites: 'StatementTestSuite,LoaderTestSuite', name: 'TestCategoryStatement,TestCategoryLoader'},\n                           {suites: 'OthersTestSuite', name: 'TestCategoryOthers'},\n                           {suites: 'DatabaseMetaDataTestSuite', name: 'TestCategoryDatabaseMetaData'},\n                           {suites: 'ArrowTestSuite,ConnectionTestSuite,CoreTestSuite,DiagnosticTestSuite', name: 'TestCategoryArrow,TestCategoryConnection,TestCategoryCore,TestCategoryDiagnostic'},\n                           {suites: 'FipsTestSuite', name: \"TestCategoryFips\"}]\n                additionalMavenProfile: ['']\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: ${{ matrix.runConfig.javaVersion }}\n                distribution: 'zulu'\n                cache: maven\n            - uses: actions/setup-python@v5\n              with:\n                python-version: '3.12'\n            - name: Install Homebrew Bash\n              shell: bash\n              run: brew install bash\n            - name: Tests\n              shell: bash\n              env:\n                PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}\n                JDBC_PRIVATE_KEY_SECRET: ${{ secrets.JDBC_PRIVATE_KEY_SECRET }}\n                CLOUD_PROVIDER: ${{ matrix.runConfig.cloud }}\n                JDBC_TEST_SUITES: ${{ matrix.category.suites }}\n                ADDITIONAL_MAVEN_PROFILE: ${{ matrix.additionalMavenProfile }}\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx4g\" # Increase surefire memory because arm64 macOS machines have not enough memory by default\n              run: /opt/homebrew/bin/bash ./ci/test_mac.sh\n\n    test-rocky:\n        needs: [build, unit-test-linux, unit-test-windows, unit-test-mac]\n        name: ${{ matrix.runConfig.cloud }} Rocky9 java ${{ matrix.runConfig.javaVersion }} JDBC${{ matrix.additionalMavenProfile }} ${{ matrix.category.name }}\n        runs-on: ubuntu-latest\n        strategy:\n            fail-fast: false\n            matrix:\n                runConfig: >-\n                    ${{\n                        fromJSON(\n                            (\n                                github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'FULL-TEST-MATRIX') &&\n                                '[\n                                    {\"cloud\":\"AWS\",\"javaVersion\":\"17\",\"image\":\"jdbc-rockylinux9-openjdk17\"}\n                                  ]'\n                            ) ||\n                            '[\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"8\",\"image\":\"jdbc-rockylinux9-openjdk8\"},\n                                {\"cloud\":\"GCP\",\"javaVersion\":\"11\",\"image\":\"jdbc-rockylinux9-openjdk11\"},\n                                {\"cloud\":\"AZURE\",\"javaVersion\":\"17\",\"image\":\"jdbc-rockylinux9-openjdk17\"},\n                                {\"cloud\":\"AWS\",\"javaVersion\":\"21\",\"image\":\"jdbc-rockylinux9-openjdk21\"}\n                              ]'\n                        )\n                    }}\n                category: [{suites: 'ResultSetTestSuite', name: 'TestCategoryResultSet'},\n                           {suites: 'StatementTestSuite,LoaderTestSuite', name: 'TestCategoryStatement,TestCategoryLoader'},\n                           {suites: 'OthersTestSuite', name: 'TestCategoryOthers'},\n                           {suites: 'DatabaseMetaDataTestSuite', name: 'TestCategoryDatabaseMetaData'},\n                           {suites: 'ArrowTestSuite,ConnectionTestSuite,CoreTestSuite,DiagnosticTestSuite', name: 'TestCategoryArrow,TestCategoryConnection,TestCategoryCore,TestCategoryDiagnostic'},\n                           {suites: 'FipsTestSuite', name: \"TestCategoryFips\"}]\n                additionalMavenProfile: ['']\n        steps:\n            - uses: actions/checkout@v5\n            - name: Tests\n              shell: bash\n              env:\n                PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}\n                JDBC_PRIVATE_KEY_SECRET: ${{ secrets.JDBC_PRIVATE_KEY_SECRET }}\n                CLOUD_PROVIDER: ${{ matrix.runConfig.cloud }}\n                TARGET_DOCKER_TEST_IMAGE: ${{ matrix.runConfig.image }}\n                JDBC_TEST_SUITES: ${{ matrix.category.suites }}\n                ADDITIONAL_MAVEN_PROFILE: ${{ matrix.additionalMavenProfile }}\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx4g\"\n              run: ./ci/test.sh\n\n    test-linux:\n        needs: [build, unit-test-linux, unit-test-windows, unit-test-mac]\n        name: ${{ matrix.runConfig.cloud }} Linux java on ${{ matrix.runConfig.image }} JDBC${{ matrix.runConfig.additionalMavenProfile }} ${{ matrix.category.name }}\n        runs-on: ubuntu-latest\n        strategy:\n            fail-fast: false\n            matrix:\n                runConfig: >-\n                    ${{\n                        fromJSON(\n                            (\n                                github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'FULL-TEST-MATRIX') &&\n                                '[\n                                    {\"image\":\"jdbc-rockylinux8-openjdk8\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk11\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk21\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"AZURE\",\"additionalMavenProfile\":\"\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"GCP\",\"additionalMavenProfile\":\"\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk8\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"-Dthin-jar\"},\n                                    {\"image\":\"jdbc-rockylinux8-openjdk21\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"-Dthin-jar\"}\n                                  ]'\n                            ) ||\n                            '[\n                                {\"image\":\"jdbc-rockylinux8-openjdk8\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk11\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk21\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk8\",\"cloud\":\"AZURE\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk11\",\"cloud\":\"AZURE\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"AZURE\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk21\",\"cloud\":\"AZURE\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk8\",\"cloud\":\"GCP\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk11\",\"cloud\":\"GCP\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"GCP\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk21\",\"cloud\":\"GCP\",\"additionalMavenProfile\":\"\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk8\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"-Dthin-jar\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk21\",\"cloud\":\"AWS\",\"additionalMavenProfile\":\"-Dthin-jar\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"AZURE\",\"additionalMavenProfile\":\"-Dthin-jar\"},\n                                {\"image\":\"jdbc-rockylinux8-openjdk17\",\"cloud\":\"GCP\",\"additionalMavenProfile\":\"-Dthin-jar\"},\n                              ]'\n                        )\n                    }}\n                category: [{suites: 'ResultSetTestSuite', name: 'TestCategoryResultSet'},\n                           {suites: 'StatementTestSuite,LoaderTestSuite', name: 'TestCategoryStatement,TestCategoryLoader'},\n                           {suites: 'OthersTestSuite', name: 'TestCategoryOthers'},\n                           {suites: 'DatabaseMetaDataTestSuite', name: 'TestCategoryDatabaseMetaData'},\n                           {suites: 'ArrowTestSuite,ConnectionTestSuite,CoreTestSuite,DiagnosticTestSuite', name: 'TestCategoryArrow,TestCategoryConnection,TestCategoryCore,TestCategoryDiagnostic'},\n                           {suites: 'FipsTestSuite', name: \"TestCategoryFips\"}]\n        steps:\n            - uses: actions/checkout@v5\n            - name: Tests\n              shell: bash\n              env:\n                PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}\n                JDBC_PRIVATE_KEY_SECRET: ${{ secrets.JDBC_PRIVATE_KEY_SECRET }}\n                CLOUD_PROVIDER: ${{ matrix.runConfig.cloud }}\n                TARGET_DOCKER_TEST_IMAGE: ${{ matrix.runConfig.image }}\n                JDBC_TEST_SUITES: ${{ matrix.category.suites }}\n                ADDITIONAL_MAVEN_PROFILE: ${{ matrix.runConfig.additionalMavenProfile }}\n                JAVA_TOOL_OPTIONS: \"-Xms1g -Xmx4g\"\n              run: ./ci/test.sh\n\n    test-fat-jar-slf4j:\n        name: ${{ matrix.cloud }} Fat Jar SLF4J (${{ matrix.variant }}) Test App\n        runs-on: ubuntu-latest\n        strategy:\n            fail-fast: false\n            matrix:\n                cloud: [ 'AWS', 'GCP', 'AZURE' ]\n                variant: [ 'default', 'fips']\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: '11'\n                distribution: 'temurin'\n                cache: maven\n            - name: Test Fat Jar App (SLF4J)\n              shell: bash\n              env:\n                PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}\n                JDBC_PRIVATE_KEY_SECRET: ${{ secrets.JDBC_PRIVATE_KEY_SECRET }}\n                CLOUD_PROVIDER: ${{ matrix.cloud }}\n              run: ./fat-jar-test-app/run.sh ${{ matrix.variant }} slf4j\n\n    test-fat-jar-jul:\n        name: AWS Fat Jar JUL (${{ matrix.variant }}) Test App\n        runs-on: ubuntu-latest\n        strategy:\n            fail-fast: false\n            matrix:\n                variant: ['default', 'fips']\n        steps:\n            - uses: actions/checkout@v5\n            - uses: actions/setup-java@v5\n              with:\n                java-version: '11'\n                distribution: 'temurin'\n                cache: maven\n            - name: Test Fat Jar App (JUL)\n              shell: bash\n              env:\n                PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}\n                JDBC_PRIVATE_KEY_SECRET: ${{ secrets.JDBC_PRIVATE_KEY_SECRET }}\n                CLOUD_PROVIDER: AWS\n              run: ./fat-jar-test-app/run.sh ${{ matrix.variant }} jul\n"
  },
  {
    "path": ".github/workflows/changelog.yml",
    "content": "name: Changelog Check\npermissions:\n  contents: read\n\non:\n  pull_request:\n    types: [opened, synchronize, labeled, unlabeled]\n    branches:\n      - master\n\njobs:\n  check_change_log:\n    runs-on: ubuntu-latest\n    if: ${{!contains(github.event.pull_request.labels.*.name, 'NO-CHANGELOG-UPDATES')}}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n\n      - name: Ensure CHANGELOG.md is updated\n        run: git diff --name-only --diff-filter=ACMRT ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep -wq \"CHANGELOG.md\""
  },
  {
    "path": ".github/workflows/check-style.yml",
    "content": "name: Check Style\n\non:\n    pull_request:\n        branches:\n            - master\njobs:\n    check-style:\n        name: Check Style\n        runs-on: ubuntu-latest\n        steps:\n            - uses: actions/checkout@v4\n            - name: Check Style\n              shell: bash\n              run: mvn clean validate --batch-mode --show-version -P check-style\n"
  },
  {
    "path": ".github/workflows/cla_bot.yml",
    "content": "name: \"CLA Assistant\"\non:\n  issue_comment:\n    types: [created]\n  pull_request_target:\n    types: [opened,closed,synchronize]\n\njobs:\n  CLAAssistant:\n    runs-on: ubuntu-latest\n    permissions:\n      actions: write\n      contents: write\n      pull-requests: write\n      statuses: write\n    steps:\n      - name: \"CLA Assistant\"\n        if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'\n        uses: contributor-assistant/github-action/@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_BOT_TOKEN }}\n        with:\n          path-to-signatures: 'signatures/version1.json'\n          path-to-document: 'https://github.com/snowflakedb/CLA/blob/main/README.md'\n          branch: 'main'\n          allowlist: 'dependabot[bot],github-actions,Jenkins User,_jenkins,sfc-gh-snyk-sca-sa,snyk-bot'\n          remote-organization-name: 'snowflake-eng'\n          remote-repository-name: 'cla-db'\n"
  },
  {
    "path": ".github/workflows/jira_close.yml",
    "content": "name: Jira closure\n\non:\n  issues:\n    types: [closed, deleted]\n\njobs:\n  close-issue:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Extract issue from title\n        id: extract\n        env:\n          TITLE: '${{ github.event.issue.title }}'\n        run: |\n          jira=$(echo -n $TITLE | awk '{print $1}' | sed -e 's/://')\n          echo ::set-output name=jira::$jira\n\n      - name: Close Jira Issue\n        if: startsWith(steps.extract.outputs.jira, 'SNOW-')\n        env:\n          ISSUE_KEY: ${{ steps.extract.outputs.jira }}\n          JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}\n          JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}\n          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}\n        run: |\n          JIRA_API_URL=\"${JIRA_BASE_URL}/rest/api/2/issue/${ISSUE_KEY}/transitions\"\n\n          curl -X POST \\\n            --url \"$JIRA_API_URL\" \\\n            --user \"${JIRA_USER_EMAIL}:${JIRA_API_TOKEN}\" \\\n            --header \"Content-Type: application/json\" \\\n            --data \"{\n              \\\"update\\\": {\n                \\\"comment\\\": [\n                  { \\\"add\\\": { \\\"body\\\": \\\"Closed on GitHub\\\" } }\n                ]\n              },\n              \\\"fields\\\": {\n                \\\"customfield_12860\\\": { \\\"id\\\": \\\"11506\\\" },\n                \\\"customfield_10800\\\": { \\\"id\\\": \\\"-1\\\" },\n                \\\"customfield_12500\\\": { \\\"id\\\": \\\"11302\\\" },\n                \\\"customfield_12400\\\": { \\\"id\\\": \\\"-1\\\" },\n                \\\"resolution\\\": { \\\"name\\\": \\\"Done\\\" }\n              },\n              \\\"transition\\\": { \\\"id\\\": \\\"71\\\" }\n            }\"\n"
  },
  {
    "path": ".github/workflows/jira_comment.yml",
    "content": "name: Jira comment\n\non:\n  issue_comment:\n    types: [created]\n\njobs:\n  comment-issue:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Jira login\n        uses: atlassian/gajira-login@master\n        env:\n          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}\n          JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}\n          JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}\n      - name: Extract issue from title\n        id: extract\n        env:\n          TITLE: \"${{ github.event.issue.title }}\"\n        run: |\n          jira=$(echo -n $TITLE | awk '{print $1}' | sed -e 's/://')\n          echo ::set-output name=jira::$jira\n      - name: Comment on issue\n        uses: atlassian/gajira-comment@master\n        if: startsWith(steps.extract.outputs.jira, 'SNOW-')\n        with:\n          issue: \"${{ steps.extract.outputs.jira }}\"\n          comment: \"${{ github.event.comment.user.login }} commented:\\n\\n${{ github.event.comment.body }}\\n\\n${{ github.event.comment.html_url }}\"\n"
  },
  {
    "path": ".github/workflows/jira_issue.yml",
    "content": "name: Jira creation\n\non:\n  issues:\n    types: [opened]\n  issue_comment:\n    types: [created]\n\njobs:\n  create-issue:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    if: ((github.event_name == 'issue_comment' && github.event.comment.body == 'recreate jira' && github.event.comment.user.login == 'sfc-gh-mkeller') || (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]'))\n    steps:\n      - name: Create JIRA Ticket\n        id: create\n        env:\n          JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}\n          JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}\n          JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}\n          ISSUE_TITLE: ${{ github.event.issue.title }}\n          ISSUE_BODY: ${{ github.event.issue.body }}\n          ISSUE_URL: ${{ github.event.issue.html_url }}\n        run: |\n          # debug\n          #set -x\n          TMP_BODY=$(mktemp)\n          trap \"rm -f $TMP_BODY\" EXIT\n\n          # Escape special characters in title and body\n          TITLE=$(echo \"${ISSUE_TITLE//`/\\\\`}\" | sed 's/\"/\\\\\"/g' | sed \"s/'/\\\\\\'/g\")\n          echo \"${ISSUE_BODY//`/\\\\`}\" | sed 's/\"/\\\\\"/g' | sed \"s/'/\\\\\\'/g\" > $TMP_BODY\n          echo -e \"\\n\\n_Created from GitHub Action_ for $ISSUE_URL\" >> $TMP_BODY\n          BODY=$(cat \"$TMP_BODY\")\n\n          PAYLOAD=$(jq -n \\\n          --arg issuetitle \"$TITLE\" \\\n          --arg issuebody \"$BODY\" \\\n          '{\n            fields: {\n              project: { key: \"SNOW\" },\n              issuetype: { name: \"Bug\" },\n              summary: $issuetitle,\n              description: $issuebody,\n              customfield_11401: { id: \"14723\" },\n              assignee: { id: \"712020:e527ae71-55cc-4e02-9217-1ca4ca8028a2\" },\n              components: [{ id: \"19281\" }],\n              labels: [\"oss\"],\n              priority: { id: \"10001\" }\n            }\n          }')\n\n          # Create JIRA issue using REST API\n          RESPONSE=$(curl -s -X POST \\\n            -H \"Content-Type: application/json\" \\\n            -H \"Accept: application/json\" \\\n            -u \"$JIRA_USER_EMAIL:$JIRA_API_TOKEN\" \\\n            \"$JIRA_BASE_URL/rest/api/2/issue\" \\\n            -d \"$PAYLOAD\")\n\n          # Extract JIRA issue key from response\n          JIRA_KEY=$(echo \"$RESPONSE\" | jq -r '.key')\n\n          if [ \"$JIRA_KEY\" = \"null\" ] || [ -z \"$JIRA_KEY\" ]; then\n            echo \"Failed to create JIRA issue\"\n            echo \"Response: $RESPONSE\"\n            echo \"Request payload: $PAYLOAD\"\n            exit 1\n          fi\n\n          echo \"Created JIRA issue: $JIRA_KEY\"\n          echo \"jira_key=$JIRA_KEY\" >> $GITHUB_OUTPUT\n\n      - name: Update GitHub Issue\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          REPOSITORY: ${{ github.repository }}\n          ISSUE_NUMBER: ${{ github.event.issue.number }}\n          JIRA_KEY: ${{ steps.create.outputs.jira_key }}\n          ISSUE_TITLE: ${{ github.event.issue.title }}\n        run: |\n          TITLE=$(echo \"${ISSUE_TITLE//`/\\\\`}\" | sed 's/\"/\\\\\"/g' | sed \"s/'/\\\\\\'/g\")\n          PAYLOAD=$(jq -n \\\n          --arg issuetitle \"$TITLE\" \\\n          --arg jirakey \"$JIRA_KEY\" \\\n          '{\n            title: ($jirakey + \": \" + $issuetitle)\n          }')\n\n          # Update Github issue title with jira id\n          curl -s \\\n            -X PATCH \\\n            -H \"Authorization: Bearer $GITHUB_TOKEN\" \\\n            -H \"Accept: application/vnd.github+json\" \\\n            -H \"X-GitHub-Api-Version: 2022-11-28\" \\\n            \"https://api.github.com/repos/$REPOSITORY/issues/$ISSUE_NUMBER\" \\\n            -d \"$PAYLOAD\"\n\n          if [ \"$?\" != 0 ]; then\n            echo \"Failed to update GH issue. Payload was:\"\n            echo \"$PAYLOAD\"\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/semgrep.yml",
    "content": "---\nname: Run semgrep checks\n\non:\n  pull_request:\n    branches: [master]\n\npermissions:\n  contents: read\n\njobs:\n  run-semgrep-reusable-workflow:\n    uses: snowflakedb/reusable-workflows/.github/workflows/semgrep-v2.yml@main\n    secrets:\n      token: ${{ secrets.SEMGREP_APP_TOKEN }}\n\n"
  },
  {
    "path": ".github/workflows/snyk-issue.yml",
    "content": "name: Snyk Issue\n\non:\n  schedule:\n    - cron: '* */12 * * *'\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  issues: write\n  pull-requests: write\n\nconcurrency: snyk-issue\n\njobs:\n  snyk:\n    runs-on: ubuntu-latest\n    steps:\n    - name: checkout action\n      uses: actions/checkout@v4\n      with:\n        repository: snowflakedb/whitesource-actions\n        token: ${{ secrets.WHITESOURCE_ACTION_TOKEN }}\n        path: whitesource-actions\n    - name: set-env\n      run: echo \"REPO=$(basename $GITHUB_REPOSITORY)\" >> $GITHUB_ENV\n    - name: Jira Creation\n      uses: ./whitesource-actions/snyk-issue\n      with:\n        snyk_org: ${{ secrets.SNYK_ORG_ID_PUBLIC_REPO }}\n        snyk_token:  ${{ secrets.SNYK_GITHUB_INTEGRATION_TOKEN_PUBLIC_REPO }}\n        jira_token: ${{ secrets.JIRA_TOKEN_PUBLIC_REPO }}\n      env:\n        GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/snyk-pr.yml",
    "content": "name: snyk-pr\non:\n  pull_request:\n    branches:\n      - master\n\npermissions:\n  contents: read\n  issues: write\n  pull-requests: write\n\njobs:\n  snyk:\n    runs-on: ubuntu-latest\n    if: ${{ github.event.pull_request.user.login == 'sfc-gh-snyk-sca-sa' }}\n    steps:\n    - name: checkout\n      uses: actions/checkout@v4\n      with:\n        ref: ${{ github.event.pull_request.head.ref }}\n        fetch-depth: 0\n\n    - name: checkout action\n      uses: actions/checkout@v4\n      with:\n        repository: snowflakedb/whitesource-actions\n        token: ${{ secrets.WHITESOURCE_ACTION_TOKEN }}\n        path: whitesource-actions\n\n    - name: PR\n      uses: ./whitesource-actions/snyk-pr\n      env:\n        PR_TITLE: ${{ github.event.pull_request.title }}\n      with:\n        jira_token: ${{ secrets.JIRA_TOKEN_PUBLIC_REPO }}\n        gh_token: ${{ secrets.GITHUB_TOKEN }}\n        amend: false # true if you want the commit to be amended with the JIRA number\n"
  },
  {
    "path": ".github/workflows/snyk-scan.yml",
    "content": "name: Snyk Scan\n\non:\n  pull_request:\n    branches:\n      - master\n  schedule:\n    - cron: '0 6 * * 1'\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  pull-requests: read\n\njobs:\n  detect-changes:\n    name: Detect Changed Files\n    if: github.event_name == 'pull_request'\n    runs-on: ubuntu-latest\n    outputs:\n      any-pom: ${{ steps.changes.outputs.any-pom }}\n      parent-pom: ${{ steps.changes.outputs.parent-pom }}\n      public-pom: ${{ steps.changes.outputs.public-pom }}\n      thin-public-pom: ${{ steps.changes.outputs.thin-public-pom }}\n      fips-public-pom: ${{ steps.changes.outputs.fips-public-pom }}\n    steps:\n      - uses: dorny/paths-filter@v3\n        id: changes\n        with:\n          filters: |\n            any-pom:\n              - '**/pom.xml'\n            parent-pom:\n              - 'parent-pom.xml'\n            public-pom:\n              - 'public_pom.xml'\n            thin-public-pom:\n              - 'thin_public_pom.xml'\n            fips-public-pom:\n              - 'FIPS/public_pom.xml'\n\n  snyk-aggregate-test:\n    name: Snyk Aggregate Project Test\n    needs: detect-changes\n    if: needs.detect-changes.outputs.any-pom == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Run Snyk aggregate project test\n        uses: snyk/actions/maven@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n          SNYK_CFG_ORG: ${{ secrets.SNYK_ORG_ID }}\n          MAVEN_OPTS: -DskipTests -Dmaven.test.skip=true -Dmaven.main.skip=true\n        with:\n          args: --maven-aggregate-project --all-projects --policy-path=.snyk\n          command: test\n\n  snyk-parent-pom-test:\n    name: Snyk Parent POM Test\n    needs: detect-changes\n    if: needs.detect-changes.outputs.parent-pom == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Run Snyk parent-pom test\n        uses: snyk/actions/maven@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n          SNYK_CFG_ORG: ${{ secrets.SNYK_ORG_ID }}\n          MAVEN_OPTS: -DskipTests -Dmaven.test.skip=true -Dmaven.main.skip=true\n        with:\n          args: --file=parent-pom.xml --package-manager=maven\n          command: test\n\n  snyk-public-pom-test:\n    name: Snyk Public POM Test\n    needs: detect-changes\n    if: needs.detect-changes.outputs.public-pom == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Run Snyk public-pom test\n        uses: snyk/actions/maven@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n          SNYK_CFG_ORG: ${{ secrets.SNYK_ORG_ID }}\n          MAVEN_OPTS: -DskipTests -Dmaven.test.skip=true -Dmaven.main.skip=true\n        with:\n          args: --file=public_pom.xml --package-manager=maven\n          command: test\n\n  snyk-thin-public-pom-test:\n    name: Snyk Thin Public POM Test\n    needs: detect-changes\n    if: needs.detect-changes.outputs.thin-public-pom == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Run Snyk thin-public-pom test\n        uses: snyk/actions/maven@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n          SNYK_CFG_ORG: ${{ secrets.SNYK_ORG_ID }}\n          MAVEN_OPTS: -DskipTests -Dmaven.test.skip=true -Dmaven.main.skip=true\n        with:\n          args: --file=thin_public_pom.xml --package-manager=maven\n          command: test\n\n  snyk-fips-public-pom-test:\n    name: Snyk FIPS Public POM Test\n    needs: detect-changes\n    if: needs.detect-changes.outputs.fips-public-pom == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Run Snyk FIPS public-pom test\n        uses: snyk/actions/maven@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n          SNYK_CFG_ORG: ${{ secrets.SNYK_ORG_ID }}\n          MAVEN_OPTS: -DskipTests -Dmaven.test.skip=true -Dmaven.main.skip=true\n        with:\n          args: --file=FIPS/public_pom.xml --package-manager=maven\n          command: test\n\n  snyk-monitor:\n    name: Snyk Monitor (${{ matrix.pom }})\n    if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        pom:\n          - pom.xml\n          - parent-pom.xml\n          - public_pom.xml\n          - thin_public_pom.xml\n          - FIPS/pom.xml\n          - FIPS/public_pom.xml\n          - fat-jar-test-app/pom.xml\n          - ci/wif/aws-lambda/pom.xml\n          - ci/wif/azure-function/pom.xml\n          - ci/wif/gcp-function/pom.xml\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Snyk monitor ${{ matrix.pom }}\n        uses: snyk/actions/maven@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n          SNYK_CFG_ORG: ${{ secrets.SNYK_ORG_ID }}\n          MAVEN_OPTS: -DskipTests -Dmaven.test.skip=true -Dmaven.main.skip=true\n        with:\n          args: --file=${{ matrix.pom }} --package-manager=maven --policy-path=.snyk --project-name=${{ matrix.pom }} --remote-repo-url=https://github.com/snowflakedb/snowflake-jdbc\n          command: monitor\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Exception: Keep minicore native libraries\n!src/main/resources/minicore/**/*.dll\n!src/main/resources/minicore/**/*.so\n!src/main/resources/minicore/**/*.dylib\n!src/main/resources/minicore/**/*.a\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.rar\n*.tar\n*.zip\n\n# Logs and databases #\n######################\n*.log\n*.sql\n*.sqlite\n\n# OS generated files #\n######################\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\ntarget/*\n\n# IDE Specific files #\n######################\n.idea/*\n*.iml\nnb-configuration.xml\nnbactions.xml\n\n# For snowfalke internal use #\n##############################\ndependency-reduced-pom.xml\nhenplus/*\nlogin.defaults\nrt_build.sh\nsfsql\nsfsql.cmd\nsnowflake.jceks\nthin/*\n.svnignore\nmvn_settings.xml\ndeploy.sh\n*-maven-metadata.xml\nDockerfile.build\nparameters*.json\nsnowflake-whitelist\nGolang\nClientTelemetryFramework\nlib/*\n.wiremock/**\n\n# WhiteSource Scan\nwss*.config\nwss-unified-agent.jar\nwhitesource/\n*.swp\n\n#created in some tests\nplaceholder\n\n#vs code\n.vscode/\n\n# SSH private key for WIF tests\nci/wif/parameters/rsa_wif_aws_azure\nci/wif/parameters/rsa_wif_gcp\nci/wif/parameters/rsa_gcp_function\n\n# WIF function builds\nci/wif/*/target/\n"
  },
  {
    "path": ".mvn/wrapper/maven-wrapper.properties",
    "content": "# 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#   http://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# NOTE: Maven 3.9 is incompatible with the linkage checker rules\ndistributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n- repo: git@github.com:snowflakedb/casec_precommit.git\n  rev: v1.35.5\n  hooks:\n  - id: secret-scanner\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "#### For all official JDBC Release Notes please refer to https://docs.snowflake.com/en/release-notes/clients-drivers/jdbc\n\n# Changelog\n- v4.2.1-SNAPSHOT\n    - Fixed path traversal via server-controlled filenames in `SnowflakeFileTransferAgent` GET destination filename derivation; backslash separators are now stripped and traversal/absolute basenames are rejected (snowflakedb/snowflake-jdbc#2622).\n\n- v4.2.0\n    - Extended the `SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION` environment variable to also bypass permission verification on the `connections.toml` config file and on the credential cache file (`credential_cache_v1.json`), unblocking driver use in SPCS environments where strict 0600/0700 ownership cannot be guaranteed (snowflakedb/snowflake-jdbc#2614)\n    - Fixed NPE in `RestRequest.sendIBHttpErrorEvent` when `SFSession.getTelemetryClient()` returns null because the session URL is not yet set; a `NoOpTelemetryClient` is now returned instead, allowing the original HTTP error to be surfaced to the caller (snowflakedb/snowflake-jdbc#2610)\n    - Added support for attaching the SPCS service-identifier token (`SPCS_TOKEN`) to login requests when the driver is running inside an SPCS container (gated on the `SNOWFLAKE_RUNNING_INSIDE_SPCS` environment variable; token read from `/snowflake/session/spcs_token`) (snowflakedb/snowflake-jdbc#2603)\n    - Added libc family and version detection (`LIBC_FAMILY`, `LIBC_VERSION`) to the `CLIENT_ENVIRONMENT` section of the login request on Linux (snowflakedb/snowflake-jdbc#2596)\n    - Fixed NPE in `SFTrustManager.validateRevocationStatusMain` when the OCSP cache contains a non-SUCCESSFUL response (e.g. `unauthorized(6)`); the response is now surfaced as an `SFOCSPException` so cache eviction and fail-open run normally (snowflakedb/snowflake-jdbc#2597)\n    - Added IPv6 support for cloud metadata services so Workload Identity Federation and platform detection work on IPv6-only instances (snowflakedb/snowflake-jdbc#2586):\n      - GCP WIF attestation now uses hostname `metadata.google.internal` instead of the IPv4 link-local address.\n      - EC2 instance detection probes the IPv4 and IPv6 IMDS endpoints (`[fd00:ec2::254]`) in parallel so detection succeeds on IPv6-only instances without doubling the detection budget on dual-stack hosts.\n    - Added `enableCopyResultSet` connection property (default `false`): when `true`, `Statement.execute()` exposes the COPY INTO per-file metadata result set via `getResultSet()` instead of consuming it internally (snowflakedb/snowflake-jdbc#2592)\n    - Migrated CI test images from CentOS 7 (EOL) to Rocky Linux 8 (snowflakedb/snowflake-jdbc#2578)\n    - Fixed NPE \"The URI scheme of endpointOverride must not be null\" happening during file transfer (e.g. PUT) in some use-cases (snowflakedb/snowflake-jdbc#2572)\n    - Fixed connections.toml auto-configuration behaviour (snowflakedb/snowflake-jdbc#2591):\n      - now defaulting to port 443 instead of 80 when neither port nor protocol is specified\n      - config coming from the JDBC connection string are no longer ignored when auto-configuration sourced items also present (when both present, direct connection config takes precedence)\n    - Fixed protocol field in connections.toml being ignored, causing connections to always use HTTPS (snowflakedb/snowflake-jdbc#2585)\n    - Fixed SecurityException on credential cache file ownership check in containers where JVM returns '?' for user.name (snowflakedb/snowflake-jdbc#2600).\n    - Fixed credential cache delete operations ignoring clientStoreTemporaryCredential=false setting (snowflakedb/snowflake-jdbc#2600).\n    - Fixed S3 transfer thread pool leak during repeated PUT/GET operations causing possible OOM (snowflakedb/snowflake-jdbc#2602).\n    - Bumped BouncyCastle to 1.84 to address CVE-2026-0636, CVE-2026-5588, and CVE-2026-5598 (snowflakedb/snowflake-jdbc#2593).\n    - Added `workloadIdentityAwsExternalId` connection property to support AWS STS external ID in Workload Identity Federation role-chaining flows (snowflakedb/snowflake-jdbc#2565).\n    - Bumped grpc-java to 1.81.1 now that they also upgraded to netty 4.1.132.Final as the second part of PR 2561, and also netty itself to 4.1.133.Final to address several CVE (snowflakedb/snowflake-jdbc#2611).\n\n- v4.1.0\n    - Added warning about using plain HTTP OAuth endpoints (snowflakedb/snowflake-jdbc#2556).\n    - Fix initializing ObjectMapper when DATE_OUTPUT_FORMAT is specified (snowflakedb/snowflake-jdbc#2545).\n    - Fix Netty native library conflict in thin JAR (snowflakedb/snowflake-jdbc#2559)\n    - Bumped netty to 4.1.132.Final to address CVE-2026-33870 (High) and CVE-2026-33871 (High) (snowflakedb/snowflake-jdbc#2561)\n    - Added getRole, getWarehouse and getDatabase API extension methods (snowflakedb/snowflake-jdbc#2564)\n    - Fix driver failure when security manager prohibits access to system properties, environment variables and modifying security providers (snowflakedb/snowflake-jdbc#2563)\n    - Removed the io.netty.tryReflectionSetAccessible system property setting as it's no longer needed with modern Arrow/Netty versions (snowflakedb/snowflake-jdbc#2563)\n    - Fixed crash in getColumns operation when table contained unrecognised column type (snowflakedb/snowflake-jdbc#2568).\n    - Fixed session expiration when multiple sessions have different heartbeat intervals (snowflakedb/snowflake-jdbc#2566).\n    - Merge QueryContext from failed query responses (snowflakedb/snowflake-jdbc#2570)\n\n- v4.0.2\n    - Fix expired session token renewal when polling results (snowflakedb/snowflake-jdbc#2489)   \n    - Fix missing minicore async initialization that was dropped during public API restructuring in v4.0.0 (snowflakedb/snowflake-jdbc#2501)\n    - Adjust level of logging during Driver initialization (snowflakedb/snowflake-jdbc#2504)\n    - Add sanitization for nonProxyHosts RegEx patterns (snowflakedb/snowflake-jdbc#2506)\n    - Fix bug with malformed file during S3 upload (snowflakedb/snowflake-jdbc#2502)\n    - Added periodic closure of sockets closed by the remote end (snowflakedb/snowflake-jdbc#2481).\n    - Add internal API usage telemetry tracker (snowflakedb/snowflake-jdbc#2509)\n    - Change S3 Client's multipart threshold to 16MB (snowflakedb/snowflake-jdbc#2526)\n    - Fixed fat jar with S3 iteration, the problem of not finding class `software.amazon.awssdk.transfer.s3.internal.ApplyUserAgentInterceptor` (snowflakedb/snowflake-jdbc#2519).\n    - Removed Conscrypt from shading to prevent `failed to find class org/conscrypt/CryptoUpcalls` native error (snowflakedb/snowflake-jdbc#2519).\n    - Add logging implementation to CLIENT_ENVIRONMENT telemetry (snowflakedb/snowflake-jdbc#2527)\n    - Fix NPE when HOME directory cache is not available (snowflakedb/snowflake-jdbc#2534)\n    - Bumped `commons-compress` dependency to latest (1.28.0) to address CVE-2024-25710 and CVE-2024-26308 (snowflakedb/snowflake-jdbc#2538)\n    - Add SLF4J bridge from shaded dependencies to `SFLogger` (snowflakedb/snowflake-jdbc#2543)\n    - Fixed proxy authentication when connecting to GCP (snowflakedb/snowflake-jdbc#2540)\n    - Fixed bug where called-provided schema was ignored in getStreams() (snowflakedb/snowflake-jdbc#2546)\n    - Fixed S3 error handling manifested with `NullPointerException` (snowflakedb/snowflake-jdbc#2550)\n\n- v4.0.1\n    - Add /etc/os-release data to Minicore telemetry (snowflakedb/snowflake-jdbc#2470)\n    - Fix incorrect encryption algorithm chosen when a file was put to S3 with client_encryption_key_size account parameter set to 256 (snowflakedb/snowflake-jdbc#2472) \n    - Fixed fat jar with S3 iteration, the problem of not finding class `software.amazon.awssdk.transfer.s3.internal.ApplyUserAgentInterceptor` (snowflakedb/snowflake-jdbc#2474).\n    - Removed Conscrypt from shading to prevent `failed to find class org/conscrypt/CryptoUpcalls` native error (snowflakedb/snowflake-jdbc#2474).\n    - Update BouncyCastle dependencies to fix CVE-2025-8916 CVE-2025-8885 (snowflakedb/snowflake-jdbc#2479)\n    - Fix external browser authentication after changing enum name. Manifested with `Invalid connection URL: Invalid SSOUrl found` error (snowflakedb/snowflake-jdbc#2475).\n    - Rolled back external browser authenticator name to `externalbrowser` (snowflakedb/snowflake-jdbc#2475).\n\n__Due to some underlying issues, Snowflake recommends that AWS and Azure customers do not upgrade to this version if you use PUT or GET queries. Instead, Snowflake recommends that you upgrade directly to version 4.0.1. If you have already upgraded to this version, please upgrade to version 4.0.1 as soon as possible.__\n- v4.0.0\n    - Bumped netty to 4.1.130.Final to address CVE-2025-67735 (snowflakedb/snowflake-jdbc#2447)\n    - Fix OCSP HTTP client cache to honor per-connection proxy settings (snowflakedb/snowflake-jdbc#2449)\n    - Mask secrets in exception logging (snowflakedb/snowflake-jdbc#2457)\n    - Fix NPE when sending in-band telemetry without HTTP response (snowflakedb/snowflake-jdbc#2460)\n    - Migrate from AWS SDK v1 to AWS SDK v2 (snowflakedb/snowflake-jdbc#2385 snowflakedb/snowflake-jdbc#2393)\n    - Return column_size value in database metadata commands as in JDBC spec (snowflakedb/snowflake-jdbc#2418)\n    - Migrate Azure storage from v5 to v12 (snowflakedb/snowflake-jdbc#2417)\n    - Enable bundled BouncyCastle for private key decryption by default (snowflakedb/snowflake-jdbc#2452)\n    - Rename BouncyCastle JVM property from net.snowflake.jdbc.enableBouncyCastle to net.snowflake.jdbc.useBundledBouncyCastleForPrivateKeyDecryption (snowflakedb/snowflake-jdbc#2452).\n    - Major public API restructuring: move all public APIs to net.snowflake.client.api.* package hierarchy (snowflakedb/snowflake-jdbc#2403):\n      - Add new unified QueryStatus class in public API that replaces the deprecated QueryStatus enum and QueryStatusV2 class.\n      - Add new public API interfaces for stream upload/download configuration (DownloadStreamConfig, UploadStreamConfig).\n      - Add SnowflakeDatabaseMetaData interface to public API for database metadata operations.\n      - Add SnowflakeAsyncResultSet interface to public API for async query operations.\n      - Add SnowflakeResultSetSerializable interface to public API.\n      - Deprecate net.snowflake.client.jdbc.SnowflakeDriver in favor of new net.snowflake.client.api.driver.SnowflakeDriver.\n      - Move internal classes to net.snowflake.client.internal.* package hierarchy.\n      - Removed deprecated com.snowflake.client.jdbc.SnowflakeDriver class.\n      - Removed deprecated QueryStatus enum from net.snowflake.client.core package.\n      - Removed deprecated QueryStatusV2 class from net.snowflake.client.jdbc package.\n      - Removed deprecated SnowflakeType enum from net.snowflake.client.jdbc package.\n\n- v3.28.0\n    - Ability to choose connection configuration in auto configuration file by a parameter in JDBC url. (snowflakedb/snowflake-jdbc#2369)\n    - Bumped grpc-java to 1.77.0 to address CVE-2025-58057 from transient dep (snowflakedb/snowflake-jdbc#2415)\n    - Fix Connection and socket timeout are now propagated to HTTP client (snowflakedb/snowflake-jdbc#2394).\n    - Fix Azure 503 retries and configure it with the putGetMaxRetries parameter (snowflakedb/snowflake-jdbc#2422).\n    - Improved retries for SSLHandshakeException errors caused by transient EOFException (snowflakedb/snowflake-jdbc#2423)\n    - Introduced shared library([source code](https://github.com/snowflakedb/universal-driver/tree/main/sf_mini_core)) for extended telemetry to identify and prepare testing platform for native rust extensions (snowflakedb/snowflake-jdbc#2430)\n    - Bumped netty to 4.1.128.Final to address CVE-2025-59419 (snowflakedb/snowflake-jdbc#2389)\n\n- v3.27.1\n    - Added platform detection on login to set PLATFORM metric in CLIENT_ENVIRONMENT (snowflakedb/snowflake-jdbc#2351)\n    - Disable DatabaseMetaDataLatestIT::testUseConnectionCtx test (snowflakedb/snowflake-jdbc#2367)\n    - Fix IT tests to construct OAuth scopes correctly (snowflakedb/snowflake-jdbc#2366)\n    - Fix exponential backoff retry time for non-auth requests (snowflakedb/snowflake-jdbc#2370)\n    - Upgrade aws-sdk to 1.12.792 and add STS dependency (snowflakedb/snowflake-jdbc#2361)\n    - Add rockylinux9 CI tests as part of RHEL 9 support (snowflakedb/snowflake-jdbc#2368)\n    - Bumped grpc-java to 1.76.0 to address CVE-2025-58056 from transient dep (snowflakedb/snowflake-jdbc#2371)\n    - Added `workloadIdentityImpersonationPath` config option for `authenticator=WORKLOAD_IDENTITY` allowing workloads to authenticate as a different identity through transitive service account impersonation (snowflakedb/snowflake-jdbc#2348)\n    - Added support for authentication as a different identity through transitive IAM role impersonation for AWS (snowflakedb/snowflake-jdbc#2364)\n    - Add AWS identity detection with ARN validation (snowflakedb/snowflake-jdbc#2379)\n  \n- v3.27.0\n    - Added the `changelog.yml` GitHub workflow to ensure changelog is updated on release PRs (snowflakedb/snowflake-jdbc#2340).\n    - Added HTTP 307 & 308 retries in case of internal IP redirects (snowflakedb/snowflake-jdbc#2344)\n    - Make PAT creation return `ResultSet` when using `execute` method (snowflakedb/snowflake-jdbc#2343)\n    - Renamed CRL_REVOCATION_CHECK_MODE to CERT_REVOCATION_CHECK_MODE in CLIENT_ENVIRONMENT metrics (snowflakedb/snowflake-jdbc#2349)\n    - Test coverage for multistatement jdbc (snowflakedb/snowflake-jdbc#2318).\n    - Fixed permission check for .toml config file (snowflakedb/snowflake-jdbc#2270).\n    - Bumped netty to 4.1.127.Final to address CVE-2025-58056 and  CVE-2025-58057 (snowflakedb/snowflake-jdbc#2354)\n    - Add support for x-snowflake-session sticky HTTP session header returned by Snowflake (snowflakedb/snowflake-jdbc#2357)\n    - Added support for Interval Year-Month and Day-Time types in JDBC (snowflakedb/snowflake-jdbc#2345).\n    - Added support for Decfloat types in JDBC (snowflakedb/snowflake-jdbc#2329, snowflakedb/snowflake-jdbc#2332).\n    - Fixed pattern search for file when QUOTED_IDENTIFIERS_IGNORE_CASE enabled (snowflakedb/snowflake-jdbc#2333)\n    - Added support for CRL (certificate revocation list) (snowflakedb/snowflake-jdbc#2287).\n"
  },
  {
    "path": "FIPS/.gitignore",
    "content": ".idea/\nlib/\ntarget/\ndependency-reduced-pom.xml\ngenerated_public_pom.xml\n"
  },
  {
    "path": "FIPS/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>net.snowflake</groupId>\n    <artifactId>snowflake-jdbc-parent</artifactId>\n    <version>4.2.1-SNAPSHOT</version>\n    <relativePath>../parent-pom.xml</relativePath>\n  </parent>\n\n  <artifactId>snowflake-jdbc-fips</artifactId>\n  <version>4.2.1-SNAPSHOT</version>\n  <packaging>jar</packaging>\n\n  <name>snowflake-jdbc-fips</name>\n  <url>http://maven.apache.org</url>\n\n  <prerequisites>\n    <maven>3.3.9</maven>\n  </prerequisites>\n\n  <!-- dummy scm connection required to get the buildnumber plugin working -->\n  <scm>\n    <connection>scm:svn:http://127.0.0.1/svn/dummy</connection>\n    <developerConnection>scm:svn:https://127.0.0.1/svn/dummy</developerConnection>\n    <tag>HEAD</tag>\n    <url>http://127.0.0.1/websvn/dummy</url>\n  </scm>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bc-fips</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcpkix-fips</artifactId>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>${project.artifactId}</finalName>\n    <sourceDirectory>${basedir}/../src/main/java</sourceDirectory>\n    <testSourceDirectory>${basedir}/src/test/java</testSourceDirectory>\n    <resources>\n      <resource>\n        <directory>${basedir}/../src/main/resources</directory>\n        <filtering>true</filtering>\n        <excludes>\n          <exclude>**/*.dylib</exclude>\n          <exclude>**/*.so</exclude>\n          <exclude>**/*.dll</exclude>\n          <exclude>**/*.a</exclude>\n        </excludes>\n      </resource>\n      <resource>\n        <directory>${basedir}/../src/main/resources</directory>\n        <filtering>false</filtering>\n        <includes>\n          <include>**/*.dylib</include>\n          <include>**/*.so</include>\n          <include>**/*.dll</include>\n          <include>**/*.a</include>\n        </includes>\n      </resource>\n    </resources>\n    <testResources>\n      <testResource>\n        <directory>${basedir}/../src/test/resources</directory>\n      </testResource>\n    </testResources>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-install-plugin</artifactId>\n        <version>${version.plugin.install}</version>\n        <executions>\n          <execution>\n            <id>install-tika-core</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/tika-core-${tika.version}.jar</file>\n              <groupId>org.apache.tika</groupId>\n              <artifactId>tika-core</artifactId>\n              <version>${tika.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-pom</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/arrow-memory-${arrow.version}.pom</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>pom</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-core</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/arrow-memory-core-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-core</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-unsafe</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/arrow-memory-unsafe-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-unsafe</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-netty-buffer-patch</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/arrow-memory-netty-buffer-patch-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-netty-buffer-patch</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-format</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/arrow-format-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-format</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-vector</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${basedir}/../dependencies/arrow-vector-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-vector</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-clean-plugin</artifactId>\n        <version>${version.plugin.clean}</version>\n        <configuration>\n          <excludeDefaultDirectories/>\n          <filesets>\n            <fileset>\n              <directory>lib</directory>\n              <includes>\n                <include>*.jar</include>\n              </includes>\n            </fileset>\n          </filesets>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>${version.plugin.compiler}</version>\n        <configuration>\n          <showDeprecation>true</showDeprecation>\n          <showWarnings>true</showWarnings>\n          <executable>javac</executable>\n          <fork>true</fork>\n          <source>8</source>\n          <target>8</target>\n          <compilerArgs>\n            <arg>-Xlint:all,-path</arg>\n          </compilerArgs>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <version>${version.plugin.surefire}</version>\n        <configuration>\n          <excludes>\n            <exclude>**/*SFTrustManagerTest.java</exclude>\n          </excludes>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.jacoco</groupId>\n        <artifactId>jacoco-maven-plugin</artifactId>\n        <version>${version.plugin.jacoco}</version>\n        <executions>\n          <execution>\n            <id>pre-unit-test</id>\n            <goals>\n              <goal>prepare-agent</goal>\n            </goals>\n            <configuration>\n              <destFile>target/jacoco-ut.exec</destFile>\n            </configuration>\n          </execution>\n          <execution>\n            <id>post-unit-test</id>\n            <phase>test</phase>\n            <goals>\n              <goal>report</goal>\n            </goals>\n            <configuration>\n              <dataFile>target/jacoco-ut.exec</dataFile>\n              <outputDirectory>target/jacoco-ut</outputDirectory>\n            </configuration>\n          </execution>\n        </executions>\n        <configuration>\n          <skip>${jacoco.skip.instrument}</skip>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <version>${version.plugin.jar}</version>\n        <configuration>\n          <archive>\n            <manifest>\n              <mainClass>net.snowflake.client.api.driver.SnowflakeDriver</mainClass>\n              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n            </manifest>\n          </archive>\n        </configuration>\n        <executions>\n          <execution>\n            <goals>\n              <goal>test-jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <version>${version.plugin.dependency}</version>\n        <executions>\n          <execution>\n            <id>install-jar</id>\n            <phase>install</phase>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>${project.groupId}</groupId>\n                  <artifactId>${project.artifactId}</artifactId>\n                  <version>${project.version}</version>\n                </artifactItem>\n              </artifactItems>\n              <outputDirectory>lib</outputDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-source-plugin</artifactId>\n        <version>${version.plugin.source}</version>\n        <executions>\n          <execution>\n            <id>attach-sources</id>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-javadoc-plugin</artifactId>\n        <version>${version.plugin.javadoc}</version>\n        <configuration>\n          <source>8</source>\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    </plugins>\n  </build>\n  <reporting>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-project-info-reports-plugin</artifactId>\n        <version>${version.plugin.projectinforeports}</version>\n      </plugin>\n    </plugins>\n  </reporting>\n\n  <profiles>\n    <profile>\n      <id>self-contained-jar</id>\n      <activation>\n        <property>\n          <name>!not-self-contained-jar</name>\n        </property>\n      </activation>\n      <build>\n        <resources>\n          <resource>\n            <directory>${basedir}/../src/main/resources-fat-jar</directory>\n          </resource>\n        </resources>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>build-helper-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>add-fat-jar-sources</id>\n                <phase>generate-sources</phase>\n                <goals>\n                  <goal>add-source</goal>\n                </goals>\n                <configuration>\n                  <sources>\n                    <source>${basedir}/../src/main/java-fat-jar</source>\n                  </sources>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>buildnumber-maven-plugin</artifactId>\n            <version>${version.plugin.buildnumber}</version>\n            <executions>\n              <execution>\n                <phase>package</phase>\n                <goals>\n                  <goal>create-timestamp</goal>\n                </goals>\n              </execution>\n            </executions>\n            <configuration>\n              <timestampFormat>yyyyMMddHHmmss</timestampFormat>\n              <timestampPropertyName>buildNumber.timestamp</timestampPropertyName>\n              <doCheck>false</doCheck>\n              <revisionOnScmFailure/>\n              <doUpdate>false</doUpdate>\n              <!--- Note for those who come later.  If you specify \"buildNumber\" in the items field, it becomes an incrementing buildNumber\n                 AFAIK (and I spent a lot of time on this) it is impossible to get the SCM rev number and incrementing build number at the same time -->\n            </configuration>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-shade-plugin</artifactId>\n            <version>${version.plugin.shade}</version>\n            <configuration>\n            </configuration>\n            <executions>\n              <execution>\n                <phase>package</phase>\n                <goals>\n                  <goal>shade</goal>\n                </goals>\n                <configuration>\n                  <relocations>\n                    <relocation>\n                      <pattern>net.snowflake.common</pattern>\n                      <shadedPattern>${shadeBase}.snowflake.common</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.apache</pattern>\n                      <shadedPattern>${shadeBase}.apache</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.slf4j</pattern>\n                      <shadedPattern>${shadeBase}.org.slf4j</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.awssdk</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.awssdk</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.encryption.s3</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.encryption.s3</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.eventstream</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.eventstream</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.ion</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.ion</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.reactivestreams</pattern>\n                      <shadedPattern>${shadeBase}.reactivestreams</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.jvnet.staxex</pattern>\n                      <shadedPattern>${shadeBase}.jvnet.staxex</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>jakarta.xml.soap</pattern>\n                      <shadedPattern>${shadeBase}.jakarta.xml.soap</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>jakarta.activation</pattern>\n                      <shadedPattern>${shadeBase}.jakarta.activation</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.azure</pattern>\n                      <shadedPattern>${shadeBase}.azure</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.fasterxml</pattern>\n                      <shadedPattern>${shadeBase}.fasterxml</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.google</pattern>\n                      <shadedPattern>${shadeBase}.google</shadedPattern>\n                    </relocation>\n                    <!-- google packages should be relocated explicitly to avoid problems with properties files renaming -->\n                    <relocation>\n                      <pattern>google.api</pattern>\n                      <shadedPattern>${shadeBase}.google.api</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.apps</pattern>\n                      <shadedPattern>${shadeBase}.google.apps</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.cloud</pattern>\n                      <shadedPattern>${shadeBase}.google.cloud</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.geo</pattern>\n                      <shadedPattern>${shadeBase}.google.geo</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.iam</pattern>\n                      <shadedPattern>${shadeBase}.google.iam</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.logging</pattern>\n                      <shadedPattern>${shadeBase}.google.logging</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.longrunning</pattern>\n                      <shadedPattern>${shadeBase}.google.longrunning</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.monitoring</pattern>\n                      <shadedPattern>${shadeBase}.google.monitoring</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.protobuf</pattern>\n                      <shadedPattern>${shadeBase}.google.protobuf</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.rpc</pattern>\n                      <shadedPattern>${shadeBase}.google.rpc</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.shopping</pattern>\n                      <shadedPattern>${shadeBase}.google.shopping</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.storage</pattern>\n                      <shadedPattern>${shadeBase}.google.storage</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.type</pattern>\n                      <shadedPattern>${shadeBase}.google.type</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.joda</pattern>\n                      <shadedPattern>${shadeBase}.joda</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>javax.servlet</pattern>\n                      <shadedPattern>${shadeBase}.javax.servlet</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.jsoup</pattern>\n                      <shadedPattern>${shadeBase}.org.jsoup</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.nimbusds</pattern>\n                      <shadedPattern>${shadeBase}.com.nimbusds</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>javax.annotation</pattern>\n                      <shadedPattern>${shadeBase}.javax.annotation</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>net.jcip</pattern>\n                      <shadedPattern>${shadeBase}.net.jcip</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>net.minidev</pattern>\n                      <shadedPattern>${shadeBase}.net.minidev</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.objectweb</pattern>\n                      <shadedPattern>${shadeBase}.org.objectweb</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.netty</pattern>\n                      <shadedPattern>${shadeBase}.io.netty</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.carrotsearch</pattern>\n                      <shadedPattern>${shadeBase}.com.carrotsearch</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.opencensus</pattern>\n                      <shadedPattern>${shadeBase}.opencensus</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.opentelemetry</pattern>\n                      <shadedPattern>${shadeBase}.opentelemetry</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.threeten</pattern>\n                      <shadedPattern>${shadeBase}.threeten</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.grpc</pattern>\n                      <shadedPattern>${shadeBase}.grpc</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.io_grpc_netty_shaded_netty_tcnative</pattern>\n                      <shadedPattern>META-INF.native.${shadeNativeBase}_grpc_netty_shaded_netty_tcnative</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.libio_grpc_netty_shaded_netty_tcnative</pattern>\n                      <shadedPattern>META-INF.native.lib${shadeNativeBase}_grpc_netty_shaded_netty_tcnative</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.io_grpc_netty_shaded_netty_transport_native_epoll</pattern>\n                      <shadedPattern>META-INF.native.${shadeNativeBase}_grpc_netty_shaded_netty_transport_native_epoll</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.libio_grpc_netty_shaded_netty_transport_native_epoll</pattern>\n                      <shadedPattern>META-INF.native.lib${shadeNativeBase}_grpc_netty_shaded_netty_transport_native_epoll</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.checkerframework</pattern>\n                      <shadedPattern>${shadeBase}.org.checkerframework</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.codehaus</pattern>\n                      <shadedPattern>${shadeBase}.org.codehaus</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.perfmark</pattern>\n                      <shadedPattern>${shadeBase}.io.perfmark</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>opencensus</pattern>\n                      <shadedPattern>${shadeBase}.opencensus</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>grpc</pattern>\n                      <shadedPattern>${shadeBase}.grpc</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>android.annotation</pattern>\n                      <shadedPattern>${shadeBase}.android.annotation</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>reactor</pattern>\n                      <shadedPattern>${shadeBase}.reactor</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.reactivestreams</pattern>\n                      <shadedPattern>${shadeBase}.org.reactivestreams</shadedPattern>\n                    </relocation>\n                  </relocations>\n                  <filters>\n                    <filter>\n                      <artifact>*:*</artifact>\n                      <excludes>\n                        <exclude>META-INF/LICENSE*</exclude>\n                        <exclude>META-INF/NOTICE*</exclude>\n                        <exclude>META-INF/DEPENDENCIES</exclude>\n                        <exclude>META-INF/maven/**</exclude>\n                        <exclude>META-INF/services/com.fasterxml.*</exclude>\n                        <exclude>META-INF/versions/9/module-info.*</exclude>\n                        <exclude>META-INF/versions/11/module-info.*</exclude>\n                        <exclude>META-INF/*.xml</exclude>\n                        <exclude>META-INF/*.SF</exclude>\n                        <exclude>META-INF/*.DSA</exclude>\n                        <exclude>META-INF/*.RSA</exclude>\n                        <exclude>.netbeans_automatic_build</exclude>\n                        <exclude>git.properties</exclude>\n                        <exclude>arrow-git.properties</exclude>\n                        <exclude>google-http-client.properties</exclude>\n                        <exclude>pipes-fork-server-default-log4j2.xml</exclude>\n                        <exclude>dependencies.properties</exclude>\n                        <exclude>azure-*.properties</exclude>\n                        <exclude>VersionInfo.java</exclude>\n                        <exclude>project.properties</exclude>\n                      </excludes>\n                    </filter>\n                    <filter>\n                      <artifact>org.apache.arrow:arrow-vector</artifact>\n                      <excludes>\n                        <!-- codegen directory is used to generate java code for arrow vector package. Excludes them since we only need class file -->\n                        <exclude>codegen/**</exclude>\n                      </excludes>\n                    </filter>\n                    <filter>\n                      <artifact>com.google.guava:guava</artifact>\n                      <includes>\n                        <include>com/google/common/io/**</include>\n                        <include>com/google/common/base/**</include>\n                        <include>com/google/common/hash/**</include>\n                        <include>com/google/common/collect/**</include>\n                        <include>com/google/common/graph/**</include>\n                        <include>com/google/common/math/**</include>\n                        <include>com/google/common/util/concurrent/**</include>\n                      </includes>\n                    </filter>\n                    <filter>\n                      <artifact>commons-logging:commons-logging</artifact>\n                      <excludes>\n                        <exclude>org/apache/commons/logging/impl/AvalonLogger.class</exclude>\n                      </excludes>\n                    </filter>\n                  </filters>\n                  <transformers>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.ServicesResourceTransformer\"/>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\"/>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.AppendingTransformer\">\n                      <resource>META-INF/io.netty.versions.properties</resource>\n                    </transformer>\n                  </transformers>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <!-- relocate the META-INF/versions files manually due to the maven bug -->\n            <!-- https://issues.apache.org/jira/browse/MSHADE-406 -->\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-antrun-plugin</artifactId>\n            <version>${version.plugin.antrun}</version>\n            <executions>\n              <execution>\n                <id>repack</id>\n                <goals>\n                  <goal>run</goal>\n                </goals>\n                <phase>package</phase>\n                <configuration>\n                  <target>\n                    <unzip dest=\"${project.build.directory}/relocate\" src=\"${project.build.directory}/${project.build.finalName}.jar\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/22/${relocationBase}\"/>\n                    <!-- com.fasterxml.* packages are relocated to ${relocationBase}.fasterxml.* -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/11/com/fasterxml\" todir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/17/com/fasterxml\" todir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/21/com/fasterxml\" todir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <!-- io.opentelemetry.* packages are relocated to ${relocationBase}.io.opentelemetry.* -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/9/io\" todir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <!-- reactor.* packages are relocated to ${relocationBase}.reactor.* -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/11/reactor/core\" todir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/17/reactor/netty\" todir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/21/reactor/core\" todir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <!-- contains class name from before shading, loaded with Class.forName -->\n                    <replace file=\"${project.build.directory}/relocate/net/snowflake/client/jdbc/internal/software/amazon/awssdk/services/s3/execution.interceptors\"\n                             token=\"software.amazon.awssdk\"\n                             value=\"net.snowflake.client.jdbc.internal.software.amazon.awssdk\"/>\n                    <!-- Overwrite shaded SLF4JLogger with unshaded version.\n                         The shade plugin rewrites org.slf4j references to the shaded namespace,\n                         but SLF4JLogger must reference the user's real org.slf4j for SLF4J opt-in.\n                         SLF4JJCLWrapper is left shaded since it also implements the shaded\n                         org.apache.commons.logging.Log interface. -->\n                    <copy file=\"${project.build.outputDirectory}/net/snowflake/client/internal/log/SLF4JLogger.class\"\n                          todir=\"${project.build.directory}/relocate/net/snowflake/client/internal/log\"\n                          overwrite=\"true\"/>\n                    <zip basedir=\"${project.build.directory}/relocate\" destfile=\"${project.build.directory}/${project.build.finalName}.jar\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/22/${relocationBase}\"/>\n                  </target>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <!-- Run mvn -P check-style verify -->\n    <profile>\n      <id>check-style</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.coveo</groupId>\n            <artifactId>fmt-maven-plugin</artifactId>\n            <version>${version.plugin.fmt}</version>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>check</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>check-content</id>\n      <activation>\n        <os>\n          <family>!windows</family>\n        </os>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>exec-maven-plugin</artifactId>\n            <version>${version.plugin.exec}</version>\n            <executions>\n              <execution>\n                <id>check-shaded-content</id>\n                <phase>verify</phase>\n                <goals>\n                  <goal>exec</goal>\n                </goals>\n                <configuration>\n                  <executable>${basedir}/scripts/check_content.sh</executable>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>java-9</id>\n      <activation>\n        <jdk>(9,)</jdk>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <dependencies>\n              <dependency>\n                <groupId>org.apache.maven.surefire</groupId>\n                <artifactId>surefire-junit-platform</artifactId>\n                <version>${version.plugin.surefire}</version>\n              </dependency>\n            </dependencies>\n            <version>${version.plugin.failsafe}</version>\n            <configuration>\n              <argLine>\n                --add-opens=java.base/java.io=ALL-UNNAMED\n                --add-opens=java.base/java.nio=ALL-UNNAMED\n                --add-opens=java.base/java.lang=ALL-UNNAMED\n                --add-opens=java.base/java.lang.reflect=ALL-UNNAMED\n                --add-opens=java.base/java.util=ALL-UNNAMED\n                --add-exports=java.base/sun.nio.ch=ALL-UNNAMED\n                --add-exports=java.base/sun.security.internal.spec=ALL-UNNAMED\n                --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED\n              </argLine>\n            </configuration>\n          </plugin>\n          <plugin>\n            <artifactId>maven-surefire-plugin</artifactId>\n            <configuration>\n              <argLine>\n                --add-opens=java.base/java.io=ALL-UNNAMED\n                --add-opens=java.base/java.nio=ALL-UNNAMED\n                --add-opens=java.base/java.lang=ALL-UNNAMED\n                --add-opens=java.base/java.lang.reflect=ALL-UNNAMED\n                --add-opens=java.base/java.util=ALL-UNNAMED\n                --add-exports=java.base/sun.nio.ch=ALL-UNNAMED\n                --add-exports=java.base/sun.security.internal.spec=ALL-UNNAMED\n                --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED\n              </argLine>\n            </configuration>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>jenkinsIT</id>\n      <activation>\n        <property>\n          <name>jenkinsIT</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <dependencies>\n              <dependency>\n                <groupId>org.apache.maven.surefire</groupId>\n                <artifactId>surefire-junit-platform</artifactId>\n                <version>${version.plugin.surefire}</version>\n              </dependency>\n            </dependencies>\n            <version>${version.plugin.failsafe}</version>\n            <executions>\n              <execution>\n                <id>DefaultIT</id>\n                <goals>\n                  <goal>integration-test</goal>\n                </goals>\n                <configuration>\n                  <includes>\n                    <include>**/ConnectionFipsIT.java</include>\n                  </includes>\n                  <systemPropertyVariables>\n                    <net.snowflake.jdbc.loggerImpl>\n                      net.snowflake.client.internal.log.JDK14Logger\n                    </net.snowflake.jdbc.loggerImpl>\n                    <java.util.logging.config.file>\n                      ${basedir}/../src/test/resources/logging.properties\n                    </java.util.logging.config.file>\n                  </systemPropertyVariables>\n                </configuration>\n              </execution>\n              <execution>\n                <goals>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <!-- profiles deployed into ossrh stage area -->\n    <profile>\n      <id>ossrh-deploy</id>\n      <activation>\n        <property>\n          <name>ossrhDeploy</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <version>${version.plugin.gpg}</version>\n            <executions>\n              <execution>\n                <phase>deploy</phase>\n                <goals>\n                  <goal>sign-and-deploy-file</goal>\n                </goals>\n                <configuration>\n                  <file>target/${project.artifactId}.jar</file>\n                  <repositoryId>ossrh</repositoryId>\n                  <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>\n                  <pomFile>generated_public_pom.xml</pomFile>\n                  <javadoc>target/${project.artifactId}-javadoc.jar</javadoc>\n                  <sources>target/${project.artifactId}-sources.jar</sources>\n                  <keyname>${env.GPG_KEY_ID}</keyname>\n                  <passphrase>${env.GPG_KEY_PASSPHRASE}</passphrase>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <!-- skip default deploy plugin if we use gpg plugin to deploy -->\n          <plugin>\n            <artifactId>maven-deploy-plugin</artifactId>\n            <configuration>\n              <skip>true</skip>\n            </configuration>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>central-deploy</id>\n      <activation>\n        <property>\n          <name>central-deploy</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>build-helper-maven-plugin</artifactId>\n            <version>${version.plugin.buildhelper}</version>\n            <executions>\n              <execution>\n                <id>attach-public-pom</id>\n                <goals>\n                  <goal>attach-artifact</goal>\n                </goals>\n                <phase>package</phase>\n                <configuration>\n                  <artifacts>\n                    <artifact>\n                      <file>generated_public_pom.xml</file>\n                      <type>pom</type>\n                    </artifact>\n                  </artifacts>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <configuration>\n              <keyname>${env.GPG_KEY_ID}</keyname>\n              <passphrase>${env.GPG_KEY_PASSPHRASE}</passphrase>\n            </configuration>\n            <executions>\n              <execution>\n                <id>sign-artifacts</id>\n                <goals>\n                  <goal>sign</goal>\n                </goals>\n                <phase>verify</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.sonatype.central</groupId>\n            <artifactId>central-publishing-maven-plugin</artifactId>\n            <version>${version.plugin.publishing}</version>\n            <extensions>true</extensions>\n            <configuration>\n              <publishingServerId>ossrh</publishingServerId>\n              <autoPublish>true</autoPublish>\n              <waitUntil>published</waitUntil>\n            </configuration>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "FIPS/public_pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>net.snowflake</groupId>\n    <artifactId>snowflake-jdbc-fips</artifactId>\n    <version>1.0-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <name>Snowflake JDBC Driver</name>\n    <description>Snowflake JDBC Driver</description>\n    <url>https://www.snowflake.net/</url>\n\n    <licenses>\n      <license>\n        <name>The Apache Software License, Version 2.0</name>\n        <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n      </license>\n    </licenses>\n\n    <developers>\n      <developer>\n        <name>Snowflake Support Team</name>\n        <email>snowflake-java@snowflake.net</email>\n        <organization>Snowflake Computing</organization>\n        <organizationUrl>https://www.snowflake.net</organizationUrl>\n      </developer>\n    </developers>\n\n    <scm>\n      <connection>scm:git:git://github.com/snowflakedb/snowflake-jdbc</connection>\n      <url>http://github.com/snowflakedb/snowflake-jdbc/tree/master</url>\n    </scm>\n\n    <properties>\n      <bouncycastle.bcfips.version>1.0.2.6</bouncycastle.bcfips.version>\n      <bouncycastle.bcpkixfips.version>1.0.8</bouncycastle.bcpkixfips.version>\n      <jna.version>5.13.0</jna.version>\n    </properties>\n\n    <dependencies>\n      <dependency>\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bc-fips</artifactId>\n        <version>${bouncycastle.bcfips.version}</version>\n        <scope>runtime</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcpkix-fips</artifactId>\n        <version>${bouncycastle.bcpkixfips.version}</version>\n        <scope>runtime</scope>\n      </dependency>\n      <dependency>\n        <groupId>net.java.dev.jna</groupId>\n        <artifactId>jna</artifactId>\n        <version>${jna.version}</version>\n        <optional>true</optional>\n      </dependency>\n      <dependency>\n        <groupId>net.java.dev.jna</groupId>\n        <artifactId>jna-platform</artifactId>\n        <version>${jna.version}</version>\n        <optional>true</optional>\n      </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "FIPS/scripts/check_content.sh",
    "content": "#!/bin/bash -e\n\n# scripts used to check if all dependencies are shaded into snowflake internal path\n\nset -o pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null && pwd )\"\n\nif jar tvf $DIR/../target/snowflake-jdbc-fips.jar  | awk '{print $8}' | grep -v -E \"/$\" | grep -v -E \"^(net|com)/snowflake\" | grep -v -E \"(com|net)/\\$\" | grep -v -E \"^META-INF\" | grep -v -E \"^iso3166_\" | grep -v -E \"^mozilla\" | grep -v -E \"^com/sun/jna\" | grep -v com/sun/ | grep -v mime.types | grep -v -E \"^com/github/luben/zstd/\" | grep -v -E \"^aix/\" | grep -v -E \"^darwin/\" | grep -v -E \"^freebsd/\" | grep -v -E \"^linux/\" | grep -v -E \"^win/\" | grep -v -E \"^minicore/\" | grep -v -E \"^org/conscrypt/\"; then\n  echo \"[ERROR] JDBC jar includes class not under the snowflake namespace\"\n  exit 1\nfi\n"
  },
  {
    "path": "FIPS/src/test/java/net/snowflake/client/AbstractDriverIT.java",
    "content": "package net.snowflake.client;\n\nimport com.google.common.base.Strings;\n\nimport java.net.URL;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Timestamp;\nimport java.util.Calendar;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\n\n/** Base test class with common constants, data structures and methods */\npublic class AbstractDriverIT {\n  // This is required to use ConditionalIgnore annotation.\n\n  public static final String DRIVER_CLASS = \"net.snowflake.client.api.driver.SnowflakeDriver\";\n  public static final String DRIVER_CLASS_COM = \"com.snowflake.client.jdbc.SnowflakeDriver\";\n  public static final int DONT_INJECT_SOCKET_TIMEOUT = 0;\n\n  // data files\n  protected static final String TEST_DATA_FILE = \"orders_100.csv\";\n  protected static final String TEST_DATA_FILE_2 = \"orders_101.csv\";\n\n  protected static final String[] fileNames = {TEST_DATA_FILE, TEST_DATA_FILE_2};\n\n  private static Logger logger = Logger.getLogger(AbstractDriverIT.class.getName());\n\n  protected final int ERROR_CODE_BIND_VARIABLE_NOT_ALLOWED_IN_VIEW_OR_UDF_DEF = 2210;\n\n  protected final int ERROR_CODE_DOMAIN_OBJECT_DOES_NOT_EXIST = 2003;\n\n  public static Map<String, String> getConnectionParameters(String accountName) {\n    Map<String, String> params = new HashMap<>();\n    String account;\n    String host;\n    if (accountName == null) {\n      account = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\");\n      host = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_HOST\");\n    } else {\n      account = accountName;\n      // By default, the test will run against reg deployment.\n      // If developer needs to run in IntelliJ, you can set this env as \".dev.local\"\n      String deployment = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_DEPLOYMENT\");\n      if (Strings.isNullOrEmpty(deployment)) {\n        deployment = \".reg.local\";\n      }\n      host = accountName.trim() + deployment;\n    }\n    assertThat(\n        \"set SNOWFLAKE_TEST_ACCOUNT environment variable to the account name.\",\n        !Strings.isNullOrEmpty(account));\n    params.put(\"account\", account);\n\n    if (Strings.isNullOrEmpty(host)) {\n      host = account + \".snowflakecomputing.com\";\n    }\n\n    assertThat(\n        \"set SNOWFLAKE_TEST_HOST environment variable to the host name.\",\n        !Strings.isNullOrEmpty(host));\n    params.put(\"host\", host);\n\n    String protocol = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PROTOCOL\");\n    String ssl;\n    if (\"http\".equals(protocol)) {\n      ssl = \"off\";\n    } else {\n      ssl = \"on\";\n    }\n    params.put(\"ssl\", ssl);\n\n    String user = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_USER\");\n    assertThat(\"set SNOWFLAKE_TEST_USER environment variable.\", !Strings.isNullOrEmpty(user));\n    params.put(\"user\", user);\n\n    String privateKeyFile = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PRIVATE_KEY_FILE\");\n    if (!Strings.isNullOrEmpty(privateKeyFile)) {\n      String workspace = System.getenv(\"WORKSPACE\");\n      if (workspace != null) {\n        params.put(\"private_key_file\", java.nio.file.Paths.get(workspace, privateKeyFile).toString());\n      } else {\n        params.put(\"private_key_file\", privateKeyFile);\n      }\n      params.put(\"authenticator\", \"SNOWFLAKE_JWT\");\n\n      String privateKeyPwd = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PRIVATE_KEY_PWD\");\n      if (!Strings.isNullOrEmpty(privateKeyPwd)) {\n        params.put(\"private_key_pwd\", privateKeyPwd);\n      }\n\n    } else {\n      String password = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PASSWORD\");\n      if (!Strings.isNullOrEmpty(password)) {\n        params.put(\"password\", password);\n      } else {\n        throw new IllegalStateException(\"Neither SNOWFLAKE_TEST_PRIVATE_KEY_FILE nor SNOWFLAKE_TEST_PASSWORD environment variable is set. Please configure one of them for authentication.\");\n      }\n    }\n\n    String port = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PORT\");\n    if (Strings.isNullOrEmpty(port)) {\n      if (\"on\".equals(ssl)) {\n        port = \"443\";\n      } else {\n        port = \"80\";\n      }\n    }\n    assertThat(\"set SNOWFLAKE_TEST_PORT environment variable.\", !Strings.isNullOrEmpty(port));\n    params.put(\"port\", port);\n\n    String database = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_DATABASE\");\n    assertThat(\n        \"set SNOWFLAKE_TEST_DATABASE environment variable.\", !Strings.isNullOrEmpty(database));\n    params.put(\"database\", database);\n\n    String schema = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SCHEMA\");\n    assertThat(\"set SNOWFLAKE_TEST_SCHEMA environment variable.\", !Strings.isNullOrEmpty(schema));\n    params.put(\"schema\", schema);\n\n    String role = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ROLE\");\n    assertThat(\"set SNOWFLAKE_TEST_ROLE environment variable.\", !Strings.isNullOrEmpty(role));\n    params.put(\"role\", role);\n\n    String warehouse = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_WAREHOUSE\");\n    assertThat(\n        \"set SNOWFLAKE_TEST_WAREHOUSE environment variable.\", !Strings.isNullOrEmpty(warehouse));\n    params.put(\"warehouse\", warehouse);\n\n    params.put(\"uri\", String.format(\"jdbc:snowflake://%s:%s\", host, port));\n\n    String adminUser = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ADMIN_USER\");\n    params.put(\"adminUser\", adminUser);\n\n    String adminPassword = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ADMIN_PASSWORD\");\n    params.put(\"adminPassword\", adminPassword);\n\n    String ssoUser = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SSO_USER\");\n    params.put(\"ssoUser\", ssoUser);\n\n    String ssoPassword = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SSO_PASSWORD\");\n    params.put(\"ssoPassword\", ssoPassword);\n\n    return params;\n  }\n\n  public static Map<String, String> getConnectionParameters() {\n    return getConnectionParameters(null);\n  }\n\n  /**\n   * Gets a connection with default session parameter settings, but tunable query api version and\n   * socket timeout setting\n   *\n   * @param paramProperties connection properties\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(Properties paramProperties) throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, paramProperties, false, false);\n  }\n\n  /**\n   * Gets a connection with custom account name, but otherwise default settings\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(String accountName) throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, false, false, accountName);\n  }\n\n  /**\n   * Gets a connection with default settings\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection() throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, false, false);\n  }\n\n  /**\n   * Gets a connection with default session parameter settings, but tunable query api version and\n   * socket timeout setting\n   *\n   * @param injectSocketTimeout number of seconds to inject in connection\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(int injectSocketTimeout) throws SQLException {\n    return getConnection(injectSocketTimeout, null, false, false);\n  }\n\n  /**\n   * Gets a connection with Snowflake admin\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  protected static Connection getSnowflakeAdminConnection() throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, true, false);\n  }\n\n  /**\n   * Gets a connection with Snowflake admin\n   *\n   * @param paramProperties connection properties\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  protected static Connection getSnowflakeAdminConnection(Properties paramProperties)\n      throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, paramProperties, true, false);\n  }\n\n  /**\n   * Gets a connection in same way as function below but with default account (gotten from\n   * environment variables)\n   *\n   * @param injectSocketTimeout\n   * @param paramProperties\n   * @param isAdmin\n   * @param usesCom\n   * @return\n   * @throws SQLException\n   */\n  public static Connection getConnection(\n      int injectSocketTimeout, Properties paramProperties, boolean isAdmin, boolean usesCom)\n      throws SQLException {\n    return getConnection(injectSocketTimeout, paramProperties, isAdmin, usesCom, null);\n  }\n\n  /**\n   * Gets a connection for the custom session parameter settings and tunable query api version and\n   * socket timeout setting\n   *\n   * @param injectSocketTimeout number of seconds to inject in connection\n   * @param paramProperties connection properties\n   * @param isAdmin is Snowflake admin user?\n   * @param usesCom uses com.snowflake instead of net.snowflake?\n   * @return Connection database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(\n      int injectSocketTimeout,\n      Properties paramProperties,\n      boolean isAdmin,\n      boolean usesCom,\n      String accountName)\n      throws SQLException {\n    // Load Snowflake JDBC class\n    String driverClass = DRIVER_CLASS;\n    if (usesCom) {\n      driverClass = DRIVER_CLASS_COM;\n    }\n    try {\n      Class.forName(driverClass);\n    } catch (Exception e) {\n      logger.log(Level.SEVERE, \"Cannot find Driver\", e);\n      throw new RuntimeException(e.getCause());\n    }\n    Map<String, String> params = getConnectionParameters(accountName);\n\n    // build connection properties\n    Properties properties = new Properties();\n    if (isAdmin) {\n      assertThat(\n          \"set SNOWFLAKE_TEST_ADMIN_USER environment variable.\",\n          !Strings.isNullOrEmpty(params.get(\"adminUser\")));\n      assertThat(\n          \"set SNOWFLAKE_TEST_ADMIN_PASSWORD environment variable.\",\n          !Strings.isNullOrEmpty(params.get(\"adminPassword\")));\n\n      properties.put(\"user\", params.get(\"adminUser\"));\n      properties.put(\"password\", params.get(\"adminPassword\"));\n      properties.put(\"role\", \"accountadmin\");\n      properties.put(\"account\", \"snowflake\");\n    } else {\n      properties.put(\"user\", params.get(\"user\"));\n      properties.put(\"role\", params.get(\"role\"));\n      properties.put(\"account\", params.get(\"account\"));\n\n      if (!Strings.isNullOrEmpty(params.get(\"private_key_file\"))) {\n        properties.put(\"private_key_file\", params.get(\"private_key_file\"));\n        if (params.get(\"authenticator\") != null) {\n          properties.put(\"authenticator\", params.get(\"authenticator\"));\n        }\n        if (!Strings.isNullOrEmpty(params.get(\"private_key_pwd\"))) {\n          properties.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n        }\n      } else if (!Strings.isNullOrEmpty(params.get(\"password\"))) {\n        properties.put(\"password\", params.get(\"password\"));\n      }\n    }\n    \n    properties.put(\"db\", params.get(\"database\"));\n    properties.put(\"schema\", params.get(\"schema\"));\n    properties.put(\"warehouse\", params.get(\"warehouse\"));\n    properties.put(\"ssl\", params.get(\"ssl\"));\n\n    properties.put(\"internal\", Boolean.TRUE.toString()); // TODO: do we need this?\n\n    properties.put(\"insecureMode\", false); // use OCSP for all tests.\n\n    if (injectSocketTimeout > 0) {\n      properties.put(\"injectSocketTimeout\", String.valueOf(injectSocketTimeout));\n    }\n\n    // Set the session parameter properties\n    if (paramProperties != null) {\n      for (Map.Entry<?, ?> entry : paramProperties.entrySet()) {\n        properties.put(entry.getKey(), entry.getValue());\n      }\n    }\n    return DriverManager.getConnection(params.get(\"uri\"), properties);\n  }\n\n  /**\n   * Close SQL Objects\n   *\n   * @param resultSet a result set object\n   * @param statement a statement object\n   * @param connection a connection\n   * @throws SQLException raised if any error occurs\n   */\n  public void closeSQLObjects(ResultSet resultSet, Statement statement, Connection connection)\n      throws SQLException {\n    if (resultSet != null) {\n      resultSet.close();\n    }\n    if (statement != null) {\n      statement.close();\n    }\n    if (connection != null) {\n      connection.close();\n    }\n  }\n\n  /**\n   * Close SQL Objects\n   *\n   * @param statement a statement object\n   * @param connection a connection\n   * @throws SQLException raised if any error occurs\n   */\n  public void closeSQLObjects(Statement statement, Connection connection) throws SQLException {\n    if (statement != null) {\n      statement.close();\n    }\n    if (connection != null) {\n      connection.close();\n    }\n  }\n\n  /**\n   * Get a full path of the file in Resource\n   *\n   * @param fileName a file name\n   * @return a full path name of the file\n   */\n  public static String getFullPathFileInResource(String fileName) {\n    ClassLoader classLoader = AbstractDriverIT.class.getClassLoader();\n    URL url = classLoader.getResource(fileName);\n    if (url != null) {\n      return url.getFile();\n    } else {\n      throw new RuntimeException(\"No file is found: \" + fileName);\n    }\n  }\n\n  protected static Timestamp buildTimestamp(\n      int year, int month, int day, int hour, int minute, int second, int fractionInNanoseconds) {\n    Calendar cal = Calendar.getInstance();\n    cal.set(year, month, day, hour, minute, second);\n    Timestamp ts = new Timestamp(cal.getTime().getTime());\n    ts.setNanos(fractionInNanoseconds);\n    return ts;\n  }\n\n  protected static Date buildDate(int year, int month, int day) {\n    Calendar cal = Calendar.getInstance();\n    cal.set(year, month, day, 0, 0, 0);\n    cal.set(Calendar.MILLISECOND, 0);\n    return new Date(cal.getTime().getTime());\n  }\n\n  protected static Date buildDateWithTZ(int year, int month, int day, TimeZone tz) {\n    Calendar cal = Calendar.getInstance();\n    cal.setTimeZone(tz);\n    cal.set(year, month, day, 0, 0, 0);\n    cal.set(Calendar.MILLISECOND, 0);\n    return new Date(cal.getTime().getTime());\n  }\n}\n"
  },
  {
    "path": "FIPS/src/test/java/net/snowflake/client/DontRunOnGCP.java",
    "content": "package net.snowflake.client;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledIfEnvironmentVariable(named = \"CLOUD_PROVIDER\", matches = \"(?i)GCP(?-i)\")\npublic @interface DontRunOnGCP {}"
  },
  {
    "path": "FIPS/src/test/java/net/snowflake/client/DontRunOnGithubActions.java",
    "content": "package net.snowflake.client;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledIfEnvironmentVariable(named = \"GITHUB_ACTIONS\", matches = \".*\")\npublic @interface DontRunOnGithubActions {}\n"
  },
  {
    "path": "FIPS/src/test/java/net/snowflake/client/TestUtil.java",
    "content": "package net.snowflake.client;\n\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.junit.jupiter.api.Assertions;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\npublic class TestUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(TestUtil.class);\n  /**\n   * Util function to assert a piece will throw exception and assert on the error code\n   *\n   * @param errorCode expected error code\n   * @param testCode the code that will run and throws exception\n   */\n  public static void assertSFException(int errorCode, TestRunInterface testCode) {\n    try {\n      testCode.run();\n      Assertions.fail();\n    } catch (SFException e) {\n      assertThat(e.getVendorCode(), is(errorCode));\n    }\n  }\n\n  /** Functional interface used to run a piece of code which throws SFException */\n  @FunctionalInterface\n  public interface TestRunInterface {\n    void run() throws SFException;\n  }\n\n  /**\n   * System.getenv wrapper. If System.getenv raises a SecurityException, it is ignored and returns\n   * null.\n   * @deprecated This method should be replaced by SnowflakeUtil.systemGetEnv.\n   * <p>This is replicated from SnowflakeUtil.systemGetEnv, because the old driver doesn't have that\n   * function for the tests to use it. Replace this function call with SnowflakeUtil.systemGetEnv\n   * when it is available.\n   *\n   * @param env the environment variable name.\n   * @return the environment variable value if set, otherwise null.\n   */\n  @Deprecated\n  public static String systemGetEnv(String env) {\n    try {\n      return System.getenv(env);\n    } catch (SecurityException ex) {\n      logger.debug(\n          \"Failed to get environment variable {}. Security exception raised: {}\",\n          env,\n          ex.getMessage());\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "FIPS/src/test/java/net/snowflake/client/category/FipsTestSuite.java",
    "content": "package net.snowflake.client.category;\n\nimport org.junit.platform.suite.api.IncludeTags;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.platform.suite.api.ExcludePackages;\nimport org.junit.platform.suite.api.IncludeClassNamePatterns;\nimport org.junit.platform.suite.api.SelectPackages;\nimport org.junit.platform.suite.api.Suite;\nimport org.junit.platform.suite.api.SuiteDisplayName;\n\n@Suite\n@SelectPackages(\"net.snowflake.client\")\n@ExcludePackages(\"net.snowflake.client.suites\")\n@IncludeClassNamePatterns(\".+\")\npublic class FipsTestSuite {\n}\n"
  },
  {
    "path": "FIPS/src/test/java/net/snowflake/client/jdbc/ConnectionFipsIT.java",
    "content": "package net.snowflake.client.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.security.*;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport javax.net.ssl.HttpsURLConnection;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.DontRunOnGCP;\nimport net.snowflake.client.DontRunOnGithubActions;\nimport net.snowflake.client.internal.core.SecurityUtil;\nimport org.apache.commons.codec.binary.Base64;\nimport org.bouncycastle.crypto.CryptoServicesRegistrar;\nimport org.bouncycastle.crypto.fips.FipsStatus;\nimport org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n\n@Tag(\"fips\")\npublic class ConnectionFipsIT extends AbstractDriverIT {\n  private static final String JCE_PROVIDER_BOUNCY_CASTLE_FIPS = \"BCFIPS\";\n  private static final String JCE_PROVIDER_SUN_JCE = \"SunJCE\";\n  private static final String JCE_PROVIDER_SUN_RSA_SIGN = \"SunRsaSign\";\n  private static final String JCE_KEYSTORE_BOUNCY_CASTLE = \"BCFKS\";\n  private static final String JCE_KEYSTORE_JKS = \"JKS\";\n  private static final String BOUNCY_CASTLE_RNG_HYBRID_MODE = \"C:HYBRID;ENABLE{All};\";\n\n  private static final String SSL_ENABLED_PROTOCOLS = \"TLSv1.2,TLSv1.1,TLSv1\";\n  private static final String SSL_ENABLED_CIPHERSUITES =\n      \"TLS_AES_128_GCM_SHA256,\"\n          + \"TLS_AES_256_GCM_SHA384,\"\n          + \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,\"\n          + \"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,\"\n          + \"TLS_RSA_WITH_AES_256_GCM_SHA384,\"\n          + \"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,\"\n          + \"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,\"\n          + \"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,\"\n          + \"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(,\"\n          + \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_RSA_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,\"\n          + \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,\"\n          + \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,\"\n          + \"TLS_RSA_WITH_AES_256_CBC_SHA256,\"\n          + \"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,\"\n          + \"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,\"\n          + \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,\"\n          + \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,\"\n          + \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_RSA_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA,\"\n          + \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_RSA_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,\"\n          + \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,\"\n          + \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,\"\n          + \"TLS_RSA_WITH_AES_128_CBC_SHA,\"\n          + \"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,\"\n          + \"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,\"\n          + \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA,\"\n          + \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA\";\n\n  private static final String JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE = \"javax.net.ssl.keyStoreType\";\n  private static final String JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE =\n      \"javax.net.ssl.trustStoreType\";\n  private static final String JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS = \"jdk.tls.client.protocols\";\n  private static final String JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES = \"jdk.tls.client.cipherSuites\";\n  private static final String JAVA_SYSTEM_PROPERTY_SSL_NAMEDGROUPS = \"jdk.tls.namedGroups\";\n\n  private static String JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE_ORIGINAL_VALUE;\n  private static String JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE_ORIGINAL_VALUE;\n  private static String JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS_ORIGINAL_VALUE;\n  private static String JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES_ORIGINAL_VALUE;\n\n  private static Provider JCE_PROVIDER_SUN_JCE_PROVIDER_VALUE;\n  private static Provider JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_VALUE;\n  private static int JCE_PROVIDER_SUN_JCE_PROVIDER_POSITION;\n  private static int JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_POSITION;\n\n  @BeforeAll\n  public static void setup() throws Exception {\n    System.setProperty(\"javax.net.debug\", \"ssl\");\n    // Setting up the named group to avoid test failure on GCP environment.\n    System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_NAMEDGROUPS, \"secp256r1, secp384r1, ffdhe2048, ffdhe3072\");\n    // get keystore types for BouncyCastle libraries\n    JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE_ORIGINAL_VALUE =\n        System.getProperty(JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE);\n    JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE_ORIGINAL_VALUE =\n        System.getProperty(JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE);\n\n    // set keystore types for BouncyCastle libraries\n    System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE, JCE_KEYSTORE_BOUNCY_CASTLE);\n    System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE, JCE_KEYSTORE_JKS);\n    // remove Java's standard encryption and SSL providers\n    List<Provider> providers = Arrays.asList(Security.getProviders());\n    JCE_PROVIDER_SUN_JCE_PROVIDER_VALUE = Security.getProvider(JCE_PROVIDER_SUN_JCE);\n    JCE_PROVIDER_SUN_JCE_PROVIDER_POSITION = providers.indexOf(JCE_PROVIDER_SUN_JCE_PROVIDER_VALUE);\n    JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_VALUE = Security.getProvider(JCE_PROVIDER_SUN_RSA_SIGN);\n    JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_POSITION =\n        providers.indexOf(JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_VALUE);\n    Security.removeProvider(JCE_PROVIDER_SUN_JCE);\n    Security.removeProvider(JCE_PROVIDER_SUN_RSA_SIGN);\n\n    // workaround to connect to accounts.google.com over HTTPS, which consists\n    // of disabling TLS 1.3 and disabling default SSL cipher suites that are\n    // using CHACHA20_POLY1305 algorithms\n    JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS_ORIGINAL_VALUE =\n        System.getProperty(JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS);\n    JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES_ORIGINAL_VALUE =\n        System.getProperty(JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES);\n    System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS, SSL_ENABLED_PROTOCOLS);\n    System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES, SSL_ENABLED_CIPHERSUITES);\n    /*\n     * Insert BouncyCastle's FIPS-compliant encryption and SSL providers.\n     */\n    BouncyCastleFipsProvider bcFipsProvider =\n        new BouncyCastleFipsProvider(BOUNCY_CASTLE_RNG_HYBRID_MODE);\n\n    /*\n     * We remove BCFIPS provider pessimistically. This is a no-op if provider\n     * does not exist. This is necessary to always add it to the first\n     * position when calling insertProviderAt.\n     *\n     * JavaDoc for insertProviderAt states:\n     *   \"A provider cannot be added if it is already installed.\"\n     */\n    Security.removeProvider(JCE_PROVIDER_BOUNCY_CASTLE_FIPS);\n    Security.insertProviderAt(bcFipsProvider, 1);\n    if (!CryptoServicesRegistrar.isInApprovedOnlyMode()) {\n      if (FipsStatus.isReady()) {\n        CryptoServicesRegistrar.setApprovedOnlyMode(true);\n      } else {\n        throw new RuntimeException(\n            \"FIPS is not ready to be enabled and FIPS \" + \"mode is required for this test to run\");\n      }\n    }\n\n    // attempts an SSL connection to Google\n    // connectToGoogle();\n  }\n\n  @AfterAll\n  public static void teardown() throws Exception {\n    // Remove BouncyCastle FIPS Provider\n    Security.removeProvider(JCE_PROVIDER_BOUNCY_CASTLE_FIPS);\n\n    // Restore ciphers removed to connect to accounts.google.com\n    if (JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS_ORIGINAL_VALUE == null) {\n      System.clearProperty(JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS);\n    } else {\n      System.setProperty(\n          JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS, JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS_ORIGINAL_VALUE);\n    }\n    if (JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES_ORIGINAL_VALUE == null) {\n      System.clearProperty(JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE);\n    } else {\n      System.setProperty(\n          JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES,\n          JAVA_SYSTEM_PROPERTY_SSL_CIPHERSUITES_ORIGINAL_VALUE);\n    }\n\n    // remove Java's standard encryption and SSL providers\n    Security.insertProviderAt(\n        JCE_PROVIDER_SUN_JCE_PROVIDER_VALUE, JCE_PROVIDER_SUN_JCE_PROVIDER_POSITION);\n    Security.insertProviderAt(\n        JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_VALUE, JCE_PROVIDER_SUN_RSA_SIGN_PROVIDER_POSITION);\n\n    // Restore previous keystore values\n    if (JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE_ORIGINAL_VALUE == null) {\n      System.clearProperty(JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE);\n    } else {\n      System.setProperty(\n          JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE,\n          JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE_ORIGINAL_VALUE);\n    }\n    if (JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE_ORIGINAL_VALUE == null) {\n      System.clearProperty(JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE);\n    } else {\n      System.setProperty(\n          JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE,\n          JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE_ORIGINAL_VALUE);\n    }\n    System.clearProperty(SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM);\n    // clear the named group.\n    System.clearProperty(JAVA_SYSTEM_PROPERTY_SSL_NAMEDGROUPS);\n    // attempts an SSL connection to Google\n    // connectToGoogle();\n  }\n\n  @Test\n  public void connectWithFips() throws SQLException {\n    Connection con = getConnection();\n    Statement statement = con.createStatement();\n    ResultSet resultSet = statement.executeQuery(\"show parameters\");\n    assertTrue(resultSet.next());\n    assertFalse(con.isClosed());\n    statement.close();\n    con.close();\n    assertTrue(con.isClosed());\n    con.close(); // ensure no exception\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void connectWithFipsKeyPair() throws Exception {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n\n    Connection connection = getConnection();\n    Statement statement = connection.createStatement();\n    statement.execute(\"use role accountadmin\");\n    String pathfile = getFullPathFileInResource(\"rsa_key.pub\");\n    String pubKey = new String(Files.readAllBytes(Paths.get(pathfile)));\n    pubKey = pubKey.replace(\"-----BEGIN PUBLIC KEY-----\", \"\");\n    pubKey = pubKey.replace(\"-----END PUBLIC KEY-----\", \"\");\n    statement.execute(String.format(\"alter user %s set rsa_public_key='%s'\", testUser, pubKey));\n    connection.close();\n\n    // PKCS8 private key file. No PKCS1 is supported.\n    String privateKeyLocation = getFullPathFileInResource(\"rsa_key.p8\");\n    String uri = parameters.get(\"uri\") + \"/?private_key_file=\" + privateKeyLocation;\n    \n    // Create Properties with simple null checks to avoid NullPointerException\n    Properties properties = new Properties();\n    properties.put(\"user\", testUser);\n    if (parameters.get(\"account\") != null) {\n      properties.put(\"account\", parameters.get(\"account\"));\n    }\n    if (parameters.get(\"ssl\") != null) {\n      properties.put(\"ssl\", parameters.get(\"ssl\"));\n    }\n    if (parameters.get(\"port\") != null) {\n      properties.put(\"port\", parameters.get(\"port\"));\n    }\n    if (parameters.get(\"database\") != null) {\n      properties.put(\"db\", parameters.get(\"database\"));\n    }\n    if (parameters.get(\"schema\") != null) {\n      properties.put(\"schema\", parameters.get(\"schema\"));\n    }\n    \n    connection = DriverManager.getConnection(uri, properties);\n    assertNotNull(connection);\n    connection.close();\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testConnectUsingKeyPair() throws Exception {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n\n    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\", \"BCFIPS\");\n    SecureRandom random = SecureRandom.getInstance(\"DEFAULT\", \"BCFIPS\");\n    keyPairGenerator.initialize(2048, random);\n\n    KeyPair keyPair = keyPairGenerator.generateKeyPair();\n    PublicKey publicKey = keyPair.getPublic();\n    PrivateKey privateKey = keyPair.getPrivate();\n\n    try (Connection connection = getConnection()) {\n      Statement statement = connection.createStatement();\n      statement.execute(\"use role accountadmin\");\n      String encodePublicKey = Base64.encodeBase64String(publicKey.getEncoded());\n      statement.execute(\n          String.format(\"alter user %s set rsa_public_key='%s'\", testUser, encodePublicKey));\n    }\n\n    String uri = parameters.get(\"uri\");\n\n    Properties properties = new Properties();\n    properties.put(\"user\", testUser);\n    if (parameters.get(\"account\") != null) {\n      properties.put(\"account\", parameters.get(\"account\"));\n    }\n    if (parameters.get(\"ssl\") != null) {\n      properties.put(\"ssl\", parameters.get(\"ssl\"));\n    }\n    if (parameters.get(\"port\") != null) {\n      properties.put(\"port\", parameters.get(\"port\"));\n    }\n    if (parameters.get(\"database\") != null) {\n      properties.put(\"db\", parameters.get(\"database\"));\n    }\n    if (parameters.get(\"schema\") != null) {\n      properties.put(\"schema\", parameters.get(\"schema\"));\n    }\n\n    // test correct private key one\n    properties.put(\"privateKey\", privateKey);\n    DriverManager.getConnection(uri, properties).close();\n  }\n\n  /**\n   * Test case for connecting with FIPS and executing a query.\n   */\n  @Test\n  public void connectWithFipsAndQuery() throws SQLException {\n    try (Connection con = getConnection()) {\n      Statement statement = con.createStatement();\n      ResultSet resultSet =\n          statement.executeQuery(\n              \"select seq8(), randstr(100, random()) from table(generator(rowcount=>10000))\");\n      int cnt = 0;\n      while (resultSet.next()) {\n        assertNotNull(resultSet.getInt(1));\n        assertNotNull(resultSet.getString(2));\n        cnt++;\n      }\n      assertEquals(cnt, 10000);\n    }\n  }\n\n  @Test\n  public void connectWithFipsAndPut() throws Exception {\n    try (Connection con = getConnection()) {\n      // put files\n      ResultSet resultSet =\n          con.createStatement()\n              .executeQuery(\"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @~\");\n      int cnt = 0;\n      while (resultSet.next()) {\n        cnt++;\n      }\n      assertEquals(cnt, 1);\n    }\n  }\n\n  /** Added in > 3.15.1 */\n  @Test\n  @DontRunOnGithubActions\n  public void connectWithFipsKeyPairWithBouncyCastle() throws Exception {\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"true\");\n    connectWithFipsKeyPair();\n  }\n\n  /** Added in > 3.15.1 */\n  @Test\n  @DontRunOnGithubActions\n  public void testConnectUsingKeyPairWithBouncyCastle() throws Exception {\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"true\");\n    testConnectUsingKeyPair();\n  }\n\n  private static void connectToGoogle() throws Exception {\n    URL url = new URL(\"https://www.google.com/\");\n    HttpsURLConnection con = (HttpsURLConnection) url.openConnection();\n    int code = con.getResponseCode();\n    if (code != 200) {\n      throw new Exception(\"Got \" + code + \" instead of HTTP_OK\");\n    }\n\n    System.out.println(\"Connected to Google successfully\");\n  }\n}\n"
  },
  {
    "path": "Jenkinsfile",
    "content": "@Library('pipeline-utils')\nimport com.snowflake.DevEnvUtils\nimport groovy.json.JsonOutput\n\nclass JdbcJobDefinition {\n  String jdk\n  List params\n  String jobToRun\n  String runName\n}\n\npipeline {\n  agent { label 'regular-memory-node-snowos' }\n  options { timestamps() }\n  environment {\n    COMMIT_SHA_LONG = sh(returnStdout: true, script: \"echo \\$(git rev-parse \" + \"HEAD)\").trim()\n\n    // environment variables for semgrep_agent (for findings / analytics page)\n    // remove .git at the end\n    // remove SCM URL + .git at the end\n\n    BASELINE_BRANCH = \"${env.CHANGE_TARGET}\"\n  }\n  stages {\n    stage('Checkout') {\n      steps {\n        checkout scm\n      }\n    }\n  }\n}\n\ntimestamps {\n  node('regular-memory-node-snowos') {\n    stage('checkout') {\n      scmInfo = checkout scm\n      println(\"${scmInfo}\")\n      env.GIT_BRANCH = scmInfo.GIT_BRANCH\n    }\n\n    stage('Authenticate Artifactory') {\n      script {\n        new DevEnvUtils().withSfCli {\n          sh \"sf artifact oci auth\"\n        }\n      }\n    }\n\n    stage('Build') {\n      sh '''\\\n        |export JAVA_HOME=/usr/java/latest\n        |export PATH=$JAVA_HOME/bin:$PATH\n        |export GIT_BRANCH=${GIT_BRANCH}\n        |$WORKSPACE/ci/build.sh\n      '''.stripMargin()\n    }\n\n    jdkToParams = ['openjdk8': 'jdbc-rockylinux8-openjdk8', 'openjdk11': 'jdbc-rockylinux8-openjdk11', 'openjdk17': 'jdbc-rockylinux8-openjdk17', 'openjdk21': 'jdbc-rockylinux8-openjdk21'].collectEntries { jdk, image ->\n      return [(jdk): [\n        string(name: 'client_git_branch', value: scmInfo.GIT_BRANCH),\n        string(name: 'client_git_commit', value: scmInfo.GIT_COMMIT),\n        string(name: 'branch', value: 'main'),\n        string(name: 'TARGET_DOCKER_TEST_IMAGE', value: image),\n        string(name: 'parent_job', value: env.JOB_NAME),\n        string(name: 'parent_build_number', value: env.BUILD_NUMBER),\n        string(name: 'timeout_value', value: '420'),\n        string(name: 'PR_Key', value: scmInfo.GIT_BRANCH.substring(3)),\n        string(name: 'svn_revision', value: 'sut-stable')\n      ]]\n    }\n\n    jobDefinitions = jdkToParams.collectMany { jdk, params ->\n      return [\n        'RT-LanguageJDBC1-PC' : \"Test JDBC 1 - $jdk\",\n        'RT-LanguageJDBC2-PC' : \"Test JDBC 2 - $jdk\",\n        'RT-LanguageJDBC3-PC' : \"Test JDBC 3 - $jdk\",\n        'RT-LanguageJDBC4-PC' : \"Test JDBC 4 - $jdk\",\n      ].collect { jobToRun, runName ->\n        return new JdbcJobDefinition(\n          jdk: jdk,\n          params: params,\n          jobToRun: jobToRun,\n          runName: runName\n        )\n      }\n    }.collectEntries { jobDefinition ->\n      return [(jobDefinition.runName): { build job: jobDefinition.jobToRun, parameters: jobDefinition.params }]\n    }\n\n    jobDefinitions.put('JDBC-AIX-Unit', { build job: 'JDBC-AIX-UnitTests', parameters: [ string(name: 'BRANCH', value: scmInfo.GIT_BRANCH ) ] } )\n    jobDefinitions.put('Test Authentication', {\n      withCredentials([\n        string(credentialsId: 'sfctest0-parameters-secret', variable: 'PARAMETERS_SECRET'),\n      ]) {\n        sh '''\\\n      |#!/bin/bash\n      |set -e\n      |ci/test_authentication.sh\n    '''.stripMargin()\n      }\n    })\n\n    jobDefinitions.put('Test WIF', {\n      withCredentials([\n        string(credentialsId: 'sfctest0-parameters-secret', variable: 'PARAMETERS_SECRET'),\n      ]) {\n        sh '''\\\n      |#!/bin/bash\n      |set -e\n      |ci/test_wif.sh\n    '''.stripMargin()\n      }\n    })\n\n    jobDefinitions.put('Test Revocation Validation', {\n      withCredentials([\n        usernamePassword(credentialsId: 'jenkins-snowflakedb-github-app',\n          usernameVariable: 'GITHUB_USER',\n          passwordVariable: 'GITHUB_TOKEN')\n      ]) {\n        try {\n          sh '''\\\n        |#!/bin/bash -e\n        |chmod +x $WORKSPACE/ci/test_revocation.sh\n        |$WORKSPACE/ci/test_revocation.sh\n      '''.stripMargin()\n        } finally {\n          archiveArtifacts artifacts: 'revocation-results.json,revocation-report.html', allowEmptyArchive: true\n          publishHTML(target: [\n            allowMissing: true,\n            alwaysLinkToLastBuild: true,\n            keepAll: true,\n            reportDir: '.',\n            reportFiles: 'revocation-report.html',\n            reportName: 'Revocation Validation Report'\n          ])\n        }\n      }\n    })\n\n    stage('Test') {\n      parallel (jobDefinitions)\n    }\n  }\n}\n\ndef wgetUpdateGithub(String state, String folder, String targetUrl, String seconds) {\n    def ghURL = \"https://api.github.com/repos/snowflakedb/snowflake-jdbc/statuses/$COMMIT_SHA_LONG\"\n    def data = JsonOutput.toJson([state: \"${state}\", context: \"jenkins/${folder}\",target_url: \"${targetUrl}\"])\n    sh \"wget ${ghURL} --spider -q --header='Authorization: token $GIT_PASSWORD' --post-data='${data}'\"\n}\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://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 (c) 2013-2018 Snowflake Computing, Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   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"
  },
  {
    "path": "README.rst",
    "content": "Snowflake JDBC Driver\n*********************\n\n.. image:: https://github.com/snowflakedb/snowflake-jdbc/workflows/Build%20and%20Test/badge.svg?branch=master\n      :target: https://github.com/snowflakedb/snowflake-jdbc/actions?query=workflow%3A%22Build+and+Test%22+branch%3Amaster\n\n.. image:: https://codecov.io/gh/snowflakedb/snowflake-jdbc/branch/master/graph/badge.svg?token=Mj6uPxk0pV\n     :target: https://codecov.io/gh/snowflakedb/snowflake-jdbc\n\n.. image:: http://img.shields.io/:license-Apache%202-brightgreen.svg\n    :target: http://www.apache.org/licenses/LICENSE-2.0.txt\n\nSnowflake provides a JDBC type 4 driver that supports core functionality, allowing Java program to connect to Snowflake.\n\n.. |maven-snowflake-jdbc| image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc/badge.svg?style=plastic\n    :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/\n\n.. |maven-snowflake-jdbc-fips| image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc-fips/badge.svg?style=plastic\n    :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc-fips/\n\n.. |maven-snowflake-jdbc-thin| image:: https://maven-badges.herokuapp.com/maven-central/net.snowflake/snowflake-jdbc-thin/badge.svg?style=plastic\n    :target: https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc-thin/\n\n- snowflake-jdbc (fat-jar): |maven-snowflake-jdbc|\n- snowflake-jdbc-fips (FIPS compliant fat-jar): |maven-snowflake-jdbc-fips|\n- snowflake-jdbc-thin (thin-jar): |maven-snowflake-jdbc-thin|\n\nPrerequisites\n=============\n\nThe Snowflake JDBC driver requires Java 1.8 or higher. If the minimum required version of Java is not installed on the client machines where the JDBC driver is installed, you must install either Oracle Java or OpenJDK.\n\nInstallation\n============\n\nMaven\n-----\nAdd following dependency for fat-jar\n\n.. code-block:: xml\n\n    <dependency>\n      <groupId>net.snowflake</groupId>\n      <artifactId>snowflake-jdbc</artifactId>\n      <version>{version}</version>\n    </dependency>\n\nor for FIPS compliant fat-jar\n\n.. code-block:: xml\n\n    <dependency>\n      <groupId>net.snowflake</groupId>\n      <artifactId>snowflake-jdbc-fips</artifactId>\n      <version>{version}</version>\n    </dependency>\n\nor for thin-jar\n\n.. code-block:: xml\n\n    <dependency>\n      <groupId>net.snowflake</groupId>\n      <artifactId>snowflake-jdbc-thin</artifactId>\n      <version>{version}</version>\n    </dependency>\n\nBuild from Source Code \n----------------------\n1. Checkout source code from Github by running:\n\n.. code-block:: bash\n\n    git clone https://github.com/snowflakedb/snowflake-jdbc.git\n\n2. Build the fat-jar and install it in local maven repository by running:\n\n.. code-block:: bash\n\n    ./mvnw clean verify\n    ./mvnw org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file -Dfile=target/snowflake-jdbc.jar -DpomFile=./public_pom.xml\n\n3. Build the FIPS compliant fat-jar and install it in local maven repository by running:\n\n.. code-block:: bash\n\n    cd FIPS\n    ../mvnw clean verify\n    ../mvnw org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file -Dfile=target/snowflake-jdbc-fips.jar -DpomFile=./public_pom.xml\n    cd -\n\n4. Build the thin-jar and install it in local maven repository by running:\n\n.. code-block:: bash\n\n    ./mvnw clean verify -Dnot-self-contained-jar -Dthin-jar\n    ./mvnw org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file -Dfile=target/snowflake-jdbc-thin.jar -DpomFile=./thin_public_pom.xml -Dnot-self-contained-jar -Dthin-jar\n\n- ``thin-jar`` enables thin jar profile\n- ``not-self-contained-jar`` turns off fat jar profile (enabled by default)\n\n5. **Note that the built dependencies are installed with version 1.0-SNAPSHOT**\n\nUsage\n=====\n\nLoad Driver Class\n-----------------\n\n.. code-block:: java\n\n    Class.forName(\"net.snowflake.client.api.driver.SnowflakeDriver\")\n\nNote: The legacy driver class ``net.snowflake.client.jdbc.SnowflakeDriver`` is still available for backward compatibility but is deprecated.\n\nDatasource\n----------\n\nUse ``SnowflakeDataSourceFactory`` to create DataSource instances:\n\n.. code-block:: java\n\n    import net.snowflake.client.api.datasource.SnowflakeDataSource;\n    import net.snowflake.client.api.datasource.SnowflakeDataSourceFactory;\n\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setAccount(\"myaccount\");\n    ds.setUser(\"myuser\");\n    ds.setPassword(\"mypassword\");\n\nConnection String\n-----------------\n\nUS(West) Region:\n\n.. code-block:: bash\n\n    jdbc:snowflake://<account>.snowflakecomputing.com/?<connection_params>\n\n\nEU(Frankfurt) Region:\n\n.. code-block:: bash\n\n    jdbc:snowflake://<account>.eu-central-1.snowflakecomputing.com/?<connection_params>\n\nDocumentation\n=============\n\nFor detailed documentation, please refer to https://docs.snowflake.net/manuals/user-guide/jdbc.html\n\nDevelopment\n=============\n\nRun the maven command to check the coding style.\n\n.. code-block:: bash\n\n    mvn -P check-style validate\n\nFollow the instruction if any error occurs or run this command to fix the formats.\n\n.. code-block:: bash\n\n    mvn com.spotify.fmt:fmt-maven-plugin:format\n\nYou may import the coding style from IntelliJ so that the coding style can be applied on IDE:\n\n- In the **File** -> **Settings/Plugins**, and install `google-java-format` plugin.\n- Enable `google-java-format` for the JDBC project.\n- In the source code window, select **Code** -> **Reformat** to apply the coding style.\n- Additionally configure IDE in **File** -> **Editor** -> **Code Style** -> **Java** to\n  - not use wildcard imports (tab **Imports**):\n    - **Use single class import**\n    - **Class count to use import with '*'** to 1000\n    - **Names count to use static import with '*'** to 1000\n  - always use braces in ``if/while/for/do..while`` in (tab **Wrapping and Braces**)\n\nTests\n=====\n\nRun Tests\n---------\n\nSet the environment variables to specify the target database.\n\n.. code-block:: bash\n\n    export SNOWFLAKE_TEST_HOST=<your_host>\n    export SNOWFLAKE_TEST_ACCOUNT=<your_account>\n    export SNOWFLAKE_TEST_USER=<your_user>\n    export SNOWFLAKE_TEST_PASSWORD=<your_password>\n    export SNOWFLAKE_TEST_DATABASE=<your_database>\n    export SNOWFLAKE_TEST_SCHEMA=<your_schema>\n    export SNOWFLAKE_TEST_WAREHOUSE=<your_warehouse>\n    export SNOWFLAKE_TEST_ROLE=<your_role>\n\nRun the maven ``verify`` goal.\n\n.. code-block:: bash\n\n    mvn -DjenkinsIT -DtestCategory=net.snowflake.client.category.<category> verify\n\nwhere ``category`` is the class name under the package ``net.snowflake.client.category``.\n\nPrepare new version\n---------------\n\nRun script passing desired version:\n\n.. code-block:: bash\n\n   ./prepareNewVersion.sh 3.100.42\n\nAdd SNAPSHOT suffix when necessary:\n\n.. code-block:: bash\n\n   ./prepareNewVersion.sh 3.100.42-SNAPSHOT\n\nTest Class Naming Convention\n----------------------------\n\nThe test cases are fallen into a couple of criteria:\n\n- The unit test class names end with ``Test``. They run part of the JDBC build jobs.\n- The integration test class names end with ``IT``. They run part of the ``verify`` maven goal along with the test category specified by the parameter ``testCategory`` having ``net.snowflake.client.category`` classes.\n- The manual test class names end with ``Manual``. They don't run in the CI but you can run them manually.\n\nAside from the general test criteria, the test case class names ending with ``LatestIT`` run only with the latest JDBC driver.\nThe main motivation behind is to skip those tests for the old JDBC driver. See ``./TestOnly`` directory for further information.\n\nSupport\n=============\n\nFeel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:\nhttps://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge\n\nNotes\n----------\n\nThis driver support GCP regional endpoints starting from version 3.21.0. Please ensure that any workloads using through this driver below the version 3.21.0 do not require support for regional endpoints on GCP. If you have questions about this, please contact Snowflake Support.\n\nThe driver uses Rust library called sf_mini_core, you can find its source code [here](https://github.com/snowflakedb/universal-driver/tree/main/sf_mini_core)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n\nPlease refer to the Snowflake [HackerOne program](https://hackerone.com/snowflake?type=team) for our security policies and for reporting any security vulnerabilities.\n\nFor other security related questions and concerns, please contact the Snowflake security team at security@snowflake.com"
  },
  {
    "path": "TestOnly/.gitignore",
    "content": "target/\n.idea/\n"
  },
  {
    "path": "TestOnly/README.rst",
    "content": "Test Only Maven Project\n***********************\n\nThis directory includes a maven project to run the Snowflake JDBC tests with the specified JDBC version.\nThe primary goal is to run the tests against the oldest support JDBC driver to ensure no regression.\n\nRun Tests\n==========\n\n.. code-block:: bash\n\n    mvn -DjenkinsIT -DtestCategory=net.snowflake.client.category.<category> verify\n\nwhere ``category`` is the class name under the package ``net.snowflake.client.category``.\n\nUpdate the JDBC version\n=======================\n\nHere are the steps updating the target JDBC driver version.\n\n- Change the project version in ``pom.xml`` to the JDBC version that you want to run the tests.\n- Locate ``maven-compiler-plugin`` plugin in ``pom.xml``\n- Delete test case class files that should run along with the JDBC version.\n- Check ``*LatestIT.java`` and move the test cases that should run along with the JDBC to the base classes."
  },
  {
    "path": "TestOnly/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>net.snowflake</groupId>\n  <artifactId>snowflake-jdbc-test</artifactId>\n  <version>3.13.21</version>\n\n  <name>snowflake-jdbc-test</name>\n  <url>http://maven.apache.org</url>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <arrow.version>17.0.0</arrow.version>\n    <awaitility.version>4.2.0</awaitility.version>\n    <jacksondatabind.version>2.17.2</jacksondatabind.version>\n    <jacoco.version>0.8.4</jacoco.version>\n    <jacoco.skip.instrument>true</jacoco.skip.instrument>\n    <jna.version>5.13.0</jna.version>\n    <junit.version>5.11.1</junit.version>\n    <surefire.version>3.5.1</surefire.version>\n    <mockito.version>3.5.6</mockito.version>\n    <netty.version>4.1.133.Final</netty.version>\n    <apache.httpclient.version>4.5.14</apache.httpclient.version>\n    <bouncycastle.version>1.84</bouncycastle.version>\n    <shadeBase>net.snowflake.client.jdbc.internal</shadeBase>\n  </properties>\n\n  <dependencies>\n    <dependency> <!-- netty is not a direct dependency. It is used by arrow-vector -->\n      <groupId>io.netty</groupId>\n      <artifactId>netty-common</artifactId>\n      <version>${netty.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents</groupId>\n      <artifactId>httpclient</artifactId>\n      <version>${apache.httpclient.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>io.netty</groupId>\n      <artifactId>netty-buffer</artifactId>\n      <version>${netty.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.maven.plugins</groupId>\n      <artifactId>maven-failsafe-plugin</artifactId>\n      <version>3.0.0-M1</version>\n\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-api</artifactId>\n      <version>${junit.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n      <version>${junit.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n      <version>${junit.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-suite</artifactId>\n      <version>1.11.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-engine</artifactId>\n      <version>1.11.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-runner</artifactId>\n      <version>1.11.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-suite-api</artifactId>\n      <version>1.11.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-suite-engine</artifactId>\n      <version>1.11.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-launcher</artifactId>\n      <version>1.11.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.hamcrest</groupId>\n      <artifactId>hamcrest</artifactId>\n      <version>2.1</version>\n      <type>jar</type>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-inline</artifactId>\n      <version>${mockito.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n      <version>${awaitility.version}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>commons-dbcp</groupId>\n      <artifactId>commons-dbcp</artifactId>\n      <version>1.4</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>com.mchange</groupId>\n      <artifactId>c3p0</artifactId>\n      <version>0.9.5.4</version>\n      <type>jar</type>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>net.snowflake</groupId>\n      <artifactId>snowflake-jdbc</artifactId>\n      <version>${project.version}</version>\n      <type>jar</type>\n    </dependency>\n    <dependency>\n      <groupId>net.snowflake</groupId>\n      <artifactId>snowflake-common</artifactId>\n      <version>4.0.2-SNAPSHOT</version>\n      <type>jar</type>\n      <scope>compile</scope>\n    </dependency>\n    <dependency>\n      <groupId>commons-cli</groupId>\n      <artifactId>commons-cli</artifactId>\n      <version>1.2</version>\n      <type>jar</type>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <version>3.10</version>\n      <type>jar</type>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-text</artifactId>\n      <version>1.10.0</version>\n      <type>jar</type>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n      <version>2.2</version>\n      <type>jar</type>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.codehaus.mojo</groupId>\n      <artifactId>exec-maven-plugin</artifactId>\n      <version>1.2.1</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-compress</artifactId>\n      <version>1.28.0</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>net.minidev</groupId>\n      <artifactId>json-smart-mini</artifactId>\n      <version>1.0.6.3</version>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-databind</artifactId>\n      <version>${jacksondatabind.version}</version>\n      <type>jar</type>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-vector</artifactId>\n      <version>${arrow.version}</version>\n      <exclusions>\n        <exclusion> <!-- Exclude earlier version to avoid whitesource vulnerability. -->\n          <groupId>io.netty</groupId>\n          <artifactId>netty-common</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>io.netty</groupId>\n          <artifactId>netty-buffer</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-memory-unsafe</artifactId>\n      <version>${arrow.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-memory-netty-buffer-patch</artifactId>\n      <version>${arrow.version}</version>\n      <exclusions>\n        <exclusion> <!-- Exclude earlier version to avoid whitesource vulnerability. -->\n          <groupId>io.netty</groupId>\n          <artifactId>netty-common</artifactId>\n        </exclusion>\n        <exclusion>\n          <groupId>io.netty</groupId>\n          <artifactId>netty-buffer</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>com.zaxxer</groupId>\n      <artifactId>HikariCP</artifactId>\n      <version>2.4.3</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n       <groupId>com.google.guava</groupId>\n       <artifactId>guava</artifactId>\n       <version>32.0.0-jre</version>\n    </dependency>\n    <!-- http://mvnrepository.com/artifact/net.java.dev.jna/jna -->\n    <dependency>\n      <groupId>net.java.dev.jna</groupId>\n      <artifactId>jna</artifactId>\n      <version>${jna.version}</version>\n    </dependency>\n    <!-- http://mvnrepository.com/artifact/net.java.dev.jna/jna-platform -->\n    <dependency>\n      <groupId>net.java.dev.jna</groupId>\n      <artifactId>jna-platform</artifactId>\n      <version>${jna.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcpkix-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <sourceDirectory>${basedir}/../src/main/java</sourceDirectory>\n    <testSourceDirectory>${basedir}/../src/test/java</testSourceDirectory>\n    <resources>\n      <resource>\n        <directory>${basedir}/../src/main/resources</directory>\n        <filtering>true</filtering>\n      </resource>\n    </resources>\n    <testResources>\n      <testResource>\n        <directory>${basedir}/../src/test/resources</directory>\n      </testResource>\n    </testResources>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-clean-plugin</artifactId>\n        <version>3.0.0</version>\n        <configuration>\n          <excludeDefaultDirectories/>\n          <filesets>\n            <fileset>\n              <directory>lib</directory>\n                <includes>\n                  <include>*.jar</include>\n                </includes>\n            </fileset>\n          </filesets>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-install-plugin</artifactId>\n        <version>3.0.0-M1</version>\n        <executions>\n          <execution>\n            <id>install-arrow-memory</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${project.basedir}/../dependencies/arrow-memory-${arrow.version}.pom</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>pom</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-core</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${project.basedir}/../dependencies/arrow-memory-core-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-core</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-unsafe</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${project.basedir}/../dependencies/arrow-memory-unsafe-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-unsafe</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-netty-buffer-patch</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${project.basedir}/../dependencies/arrow-memory-netty-buffer-patch-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-netty</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-format</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${project.basedir}/../dependencies/arrow-format-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-format</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-vector</id>\n            <phase>validate</phase>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <configuration>\n              <file>${project.basedir}/../dependencies/arrow-vector-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-vector</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.8.0</version>\n        <configuration>\n          <skipMain>true</skipMain>\n          <source>8</source>\n          <target>8</target>\n          <testExcludes>\n            <!-- arrow tests -->\n            <exclude>**/net/snowflake/client/core/arrow/*.java</exclude>\n            <exclude>**/ResultSetArrow*.java</exclude>\n            <exclude>**/PreparedStatementArrow*IT.java</exclude>\n            <exclude>**/SFArrowResultSetIT.java</exclude>\n            <!-- structured types tests -->\n            <exclude>**/structuredtypes/sqldata/*.java</exclude>\n            <!-- Latest JDBC driver tests that cannot run with the oldest driver -->\n            <exclude>**/*LatestIT.java</exclude>            <!-- Latest JDBC driver tests that cannot run with the oldest driver -->\n            <exclude>**/*WiremockIT.java</exclude>\n            <!-- Unit Tests -->\n            <exclude>**/*Test.java</exclude>\n            <!-- Manual Tests -->\n            <exclude>**/*Manual.java</exclude>\n            <!-- Telemetry API is new -->\n            <exclude>**/TelemetryServiceIT.java</exclude>\n            <exclude>**/TelemetryIT.java</exclude>\n            <!-- OCSP implementation is new -->\n            <exclude>**/SFTrustManagerIT.java</exclude>\n            <!-- Logger improvement is new -->\n            <exclude>**/SLF4JLoggerIT.java</exclude>\n            <!-- Async support is new -->\n            <exclude>**/ResultSetAsyncIT.java</exclude>\n            <!-- ResultSet serialization is new -->\n            <exclude>**/SnowflakeResultSetSerializable*IT.java</exclude>\n            <!-- AuthTestHelper is a new class for authentication tests -->\n            <exclude>**/AuthTestHelper.java</exclude>\n          </testExcludes>\n          <useIncrementalCompilation>false</useIncrementalCompilation>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <dependencies>\n          <dependency>\n            <groupId>org.apache.maven.surefire</groupId>\n            <artifactId>surefire-junit-platform</artifactId>\n            <version>${surefire.version}</version>\n          </dependency>\n        </dependencies>\n        <version>${surefire.version}</version>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-failsafe-plugin</artifactId>\n        <dependencies>\n          <dependency>\n            <groupId>org.apache.maven.surefire</groupId>\n            <artifactId>surefire-junit-platform</artifactId>\n            <version>${surefire.version}</version>\n          </dependency>\n        </dependencies>\n        <version>${surefire.version}</version>\n      </plugin>\n    </plugins>\n  </build>\n  <profiles>\n    <profile>\n      <id>jenkinsIT</id>\n      <activation>\n        <property>\n          <name>jenkinsIT</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-surefire-plugin</artifactId>\n            <configuration>\n              <failIfNoTests>false</failIfNoTests>\n            </configuration>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>test</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n              <execution>\n                <id>DefaultIT</id>\n                <goals>\n                  <goal>integration-test</goal>\n                </goals>\n              <configuration>\n                <systemPropertyVariables>\n                  <net.snowflake.jdbc.loggerImpl>net.snowflake.client.internal.log.JDK14Logger</net.snowflake.jdbc.loggerImpl>\n                  <java.util.logging.config.file>${basedir}/src/test/resources/logging.properties</java.util.logging.config.file>\n                </systemPropertyVariables>\n                  <test>${integrationTestSuites}</test>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <!-- collect code coverage -->\n          <plugin>\n            <groupId>org.jacoco</groupId>\n            <artifactId>jacoco-maven-plugin</artifactId>\n            <version>${jacoco.version}</version>\n            <executions>\n              <execution>\n                <id>pre-integration-test</id>\n                <phase>pre-integration-test</phase>\n                <goals>\n                  <goal>prepare-agent</goal>\n                </goals>\n                <configuration>\n                  <destFile>target/jacoco-it.exec</destFile>\n                </configuration>\n              </execution>\n              <execution>\n                <id>post-integration-test</id>\n                <phase>post-integration-test</phase>\n                <goals>\n                  <goal>report</goal>\n                </goals>\n                <configuration>\n                  <dataFile>target/jacoco-it.exec</dataFile>\n                  <outputDirectory>target/jacoco-it</outputDirectory>\n                </configuration>\n              </execution>\n            </executions>\n            <configuration>\n              <skip>${jacoco.skip.instrument}</skip>\n            </configuration>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "ci/_init.sh",
    "content": "#!/usr/local/bin/env bash\nset -e\n\nexport PLATFORM=$(echo $(uname) | tr '[:upper:]' '[:lower:]')\nexport INTERNAL_REPO=artifactory.ci1.us-west-2.aws-dev.app.snowflake.com/internal-development-docker-drivers-local\nif [[ -z \"$GITHUB_ACTIONS\" ]]; then\n    # Use the internal Docker Registry\n    export DOCKER_REGISTRY_NAME=$INTERNAL_REPO/snowflakedb\n    export WORKSPACE=${WORKSPACE:-/tmp}\nelse\n    # Use Docker Hub\n    export DOCKER_REGISTRY_NAME=snowflakedb\n    export WORKSPACE=$GITHUB_WORKSPACE\nfi\nmkdir -p $WORKSPACE\n\nexport DRIVER_NAME=jdbc\n\n# Test Images\nTEST_IMAGE_VERSION=1\n\ndeclare -A TEST_IMAGE_NAMES=(\n    [$DRIVER_NAME-rockylinux8-openjdk8]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux8-openjdk8-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux8-openjdk11]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux8-openjdk11-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux8-openjdk17]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux8-openjdk17-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux8-openjdk21]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux8-openjdk21-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux9-openjdk8]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux9-openjdk8-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux9-openjdk11]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux9-openjdk11-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux9-openjdk17]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux9-openjdk17-test:$TEST_IMAGE_VERSION\n    [$DRIVER_NAME-rockylinux9-openjdk21]=$DOCKER_REGISTRY_NAME/client-$DRIVER_NAME-rockylinux9-openjdk21-test:$TEST_IMAGE_VERSION\n)\nexport TEST_IMAGE_NAMES\n\ndeclare -A TEST_IMAGE_DOCKERFILES=(\n    [$DRIVER_NAME-rockylinux8-openjdk8]=jdbc-rockylinux8-openjdk-test\n    [$DRIVER_NAME-rockylinux8-openjdk11]=jdbc-rockylinux8-openjdk-test\n    [$DRIVER_NAME-rockylinux8-openjdk17]=jdbc-rockylinux8-openjdk-test\n    [$DRIVER_NAME-rockylinux8-openjdk21]=jdbc-rockylinux8-openjdk-test\n    [$DRIVER_NAME-rockylinux9-openjdk8]=jdbc-rockylinux-openjdk-test\n    [$DRIVER_NAME-rockylinux9-openjdk11]=jdbc-rockylinux-openjdk-test\n    [$DRIVER_NAME-rockylinux9-openjdk17]=jdbc-rockylinux-openjdk-test\n    [$DRIVER_NAME-rockylinux9-openjdk21]=jdbc-rockylinux-openjdk-test\n)\n\ndeclare -A TEST_IMAGE_BUILD_ARGS=(\n    [$DRIVER_NAME-rockylinux8-openjdk8]=\"--target jdbc-rockylinux8-openjdk8\"\n    [$DRIVER_NAME-rockylinux8-openjdk11]=\"--target jdbc-rockylinux8-openjdk11\"\n    [$DRIVER_NAME-rockylinux8-openjdk17]=\"--target jdbc-rockylinux8-openjdk17\"\n    [$DRIVER_NAME-rockylinux8-openjdk21]=\"--target jdbc-rockylinux8-openjdk21\"\n    [$DRIVER_NAME-rockylinux9-openjdk8]=\"--target jdbc-rockylinux-openjdk8\"\n    [$DRIVER_NAME-rockylinux9-openjdk11]=\"--target jdbc-rockylinux-openjdk11\"\n    [$DRIVER_NAME-rockylinux9-openjdk17]=\"--target jdbc-rockylinux-openjdk17\"\n    [$DRIVER_NAME-rockylinux9-openjdk21]=\"--target jdbc-rockylinux-openjdk21\"\n)\n\n"
  },
  {
    "path": "ci/build.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n#\n# Build JDBC driver\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nexport WORKSPACE=${WORKSPACE:=/tmp}\n$THIS_DIR/container/build_component.sh\n"
  },
  {
    "path": "ci/container/build_component.sh",
    "content": "#!/bin/bash -e\n#\n# Build JDBC driver\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nJDBC_ROOT=$(cd \"${THIS_DIR}/../../\" && pwd)\n\ncd $JDBC_ROOT\nrm -f lib/*.jar\nmvn clean install -DskipTests --batch-mode --show-version\n\ncd FIPS\nrm -f lib/*.jar\nmvn clean install -DskipTests -Dsurefire.argLine=\"-Djavax.net.debug=ssl:handshake\" -Dfailsafe.argLine=\"-Djavax.net.debug=ssl:handshake\" --batch-mode --show-version\n$THIS_DIR/upload_artifact.sh\n"
  },
  {
    "path": "ci/container/change_snowflake_test_pwd.py",
    "content": "#!/usr/bin/env python\n#\n# Set a complex password for test user snowman\n#\n\nimport os\nimport sys\nimport snowflake.connector\n\nparams = {\n    'account': '',\n    'user': os.getenv(\"SNOWFLAKE_TEST_USER\"),\n    'password': os.getenv(\"SNOWFLAKE_TEST_PASSWORD\"),\n    'database': os.getenv(\"SNOWFLAKE_TEST_DATABASE\"),\n    'role': os.getenv(\"SNOWFLAKE_TEST_ROLE\"),\n    'host': os.getenv(\"SNOWFLAKE_TEST_HOST\"),\n    'port': os.getenv(\"SNOWFLAKE_TEST_PORT\"),\n    'protocol': os.getenv(\"SNOWFLAKE_TEST_PROTOCOL\"),\n}\n\nfor account in [\"testaccount\", \"s3testaccount\", \"azureaccount\", \"gcpaccount\"]:\n   params['account'] = account\n   conn = snowflake.connector.connect(**params)\n   conn.cursor().execute(\"use role accountadmin\")\n   cmd = \"alter user set password = '{}'\".format(os.getenv(\"SNOWFLAKE_TEST_PASSWORD_NEW\"))\n   print(cmd)\n   conn.cursor().execute(cmd)\n   conn.close()\n\n"
  },
  {
    "path": "ci/container/create_schema.py",
    "content": "#!/usr/bin/env python\n#\n# Create test schema\n#\nimport os\nimport sys\nimport snowflake.connector\n\nimport sf_test_utils\n\ntest_schema = sf_test_utils.get_test_schema()\nif not test_schema:\n    sys.exit(2)\n\nparams = sf_test_utils.init_connection_params()\n\ncon = snowflake.connector.connect(**params)\ncon.cursor().execute(\"create or replace schema {0}\".format(test_schema))\n\nsys.exit(0)\n"
  },
  {
    "path": "ci/container/download_artifact.sh",
    "content": "#!/bin/bash -e\n#\n# Download Artifact\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nJDBC_ROOT=$(cd \"${THIS_DIR}/../../\" && pwd)\n\nif [[ -z \"$GITHUB_ACTIONS\" ]] ;then\n    export GIT_BRANCH=${GIT_BRANCH:-origin/$(git rev-parse --abbrev-ref HEAD)}\n\n    BRANCH=$(basename ${GIT_BRANCH})\n\n    # Place to hold downloaded library\n    export LIB_DIR=$WORKSPACE/lib\n\n    if [[ \"$is_old_driver\" != \"true\" ]]; then\n        # Not Old Driver test\n        mkdir -p $LIB_DIR\n        pushd $LIB_DIR >& /dev/null\n            base_stage=s3://sfc-eng-jenkins/repository/jdbc/${BRANCH}\n            export GIT_COMMIT=${GIT_COMMIT:-$(aws s3 cp $base_stage/latest_commit -)}\n            source_stage=$base_stage/${GIT_COMMIT}\n            echo \"[INFO] downloading ${source_stage}/\"\n            aws s3 cp --only-show-errors $source_stage/ . --recursive\n        popd >& /dev/null\n        mkdir -p /mnt/host/lib\n        cp -p $LIB_DIR/*.jar /mnt/host/lib\n    fi\nelse\n    export GIT_BRANCH=origin/$(basename ${GITHUB_REF})\n    export GIT_COMMIT=${GITHUB_SHA}\nfi\n"
  },
  {
    "path": "ci/container/drop_schema.py",
    "content": "#!/usr/bin/env python\n#\n# Create test schema\n#\nimport os\nimport sys\nimport snowflake.connector\n\nimport sf_test_utils\n\ntest_schema = sf_test_utils.get_test_schema()\nif not test_schema:\n    sys.exit(0)\n\nparams = sf_test_utils.init_connection_params()\n\ncon = snowflake.connector.connect(**params)\ncon.cursor().execute(\"drop schema if exists {0}\".format(test_schema))\n\nsys.exit(0)\n"
  },
  {
    "path": "ci/container/hang_webserver.py",
    "content": "#!/usr/bin/env python3\nimport sys\nfrom http.server import BaseHTTPRequestHandler,HTTPServer\nfrom socketserver import ThreadingMixIn\nimport threading\nimport time\n\nclass HTTPRequestHandler(BaseHTTPRequestHandler):\n    def do_POST(self):\n        if self.path.startswith('/403'):\n            self.send_response(403)\n            self.send_header('Content-Type', 'text/plain')\n            self.end_headers()\n        elif self.path.startswith('/404'):\n            self.send_response(404)\n            self.send_header('Content-Type', 'text/plain')\n            self.end_headers()\n        elif self.path.startswith('/hang'):\n            time.sleep(300)\n            self.send_response(200, 'OK')\n            self.send_header('Content-Type', 'text/plain')\n            self.end_headers()\n        else:\n            self.send_response(200, 'OK')\n            self.send_header('Content-Type', 'text/plain')\n            self.end_headers()\n    do_GET = do_POST\n\nclass ThreadedHTTPServer(ThreadingMixIn, HTTPServer):\n  allow_reuse_address = True\n\n  def shutdown(self):\n    self.socket.close()\n    HTTPServer.shutdown(self)\n\nclass SimpleHttpServer():\n  def __init__(self, ip, port):\n    self.server = ThreadedHTTPServer((ip,port), HTTPRequestHandler)\n\n  def start(self):\n    self.server_thread = threading.Thread(target=self.server.serve_forever)\n    self.server_thread.daemon = True\n    self.server_thread.start()\n\n  def waitForThread(self):\n    self.server_thread.join()\n\n  def stop(self):\n    self.server.shutdown()\n    self.waitForThread()\n\nif __name__=='__main__':\n    if len(sys.argv) != 2:\n        print(\"Usage: python3 {} PORT\".format(sys.argv[0]))\n        sys.exit(2)\n\n    PORT = int(sys.argv[1])\n\n    server = SimpleHttpServer('localhost', PORT)\n    print('HTTP Server Running on PORT {}..........'.format(PORT))\n    server.start()\n    server.waitForThread()\n\n"
  },
  {
    "path": "ci/container/sf_test_utils.py",
    "content": "#!/usr/bin/env python\n#\n# Snowflake test utils\n#\nimport os\nimport sys\n\ndef get_test_schema():\n    return os.getenv(\"TARGET_SCHEMA_NAME\", \"LOCAL_reg_1\")\n\n\ndef init_connection_params():\n    params = {\n        'account': os.getenv(\"SNOWFLAKE_TEST_ACCOUNT\"),\n        'user': os.getenv(\"SNOWFLAKE_TEST_USER\"),\n        'database': os.getenv(\"SNOWFLAKE_TEST_DATABASE\"),\n        'role': os.getenv(\"SNOWFLAKE_TEST_ROLE\"),\n    }\n    \n    private_key_file = os.getenv(\"SNOWFLAKE_TEST_PRIVATE_KEY_FILE\")\n    if private_key_file:\n        workspace = os.getenv(\"WORKSPACE\")\n        if workspace:\n            key_path = os.path.join(workspace, private_key_file)\n        else:\n            key_path = private_key_file\n            \n        try:\n            from cryptography.hazmat.primitives import serialization\n            from cryptography.hazmat.primitives.serialization import load_pem_private_key\n            from cryptography.hazmat.backends import default_backend\n            \n            with open(key_path, 'rb') as key_file:\n                pem_data = key_file.read()\n            \n            private_key_pwd = os.getenv(\"SNOWFLAKE_TEST_PRIVATE_KEY_PWD\")\n            private_key_pwd_bytes = None\n            if private_key_pwd:\n                private_key_pwd_bytes = private_key_pwd.encode('utf-8')\n                \n            private_key_obj = load_pem_private_key(pem_data, password=private_key_pwd_bytes, backend=default_backend())\n            der_data = private_key_obj.private_bytes(\n                encoding=serialization.Encoding.DER,\n                format=serialization.PrivateFormat.PKCS8,\n                encryption_algorithm=serialization.NoEncryption()\n            )\n            \n            params['private_key'] = der_data\n            params['authenticator'] = 'SNOWFLAKE_JWT'\n            \n            if private_key_pwd:\n                params['private_key_pwd'] = private_key_pwd\n                \n        except Exception as e:\n            print(f\"ERROR: Failed to read private key file {key_path}: {e}\")\n            sys.exit(1)\n    else:\n        params['password'] = os.getenv(\"SNOWFLAKE_TEST_PASSWORD\")\n    host = os.getenv(\"SNOWFLAKE_TEST_HOST\")\n    if host:\n        params['host'] = host\n    port = os.getenv(\"SNOWFLAKE_TEST_PORT\")\n    if port:\n        params['port'] = port\n    protocol = os.getenv(\"SNOWFLAKE_TEST_PROTOCOL\")\n    if protocol:\n        params['protocol'] = protocol\n    warehouse = os.getenv(\"SNOWFLAKE_TEST_WAREHOUSE\")\n    if warehouse:\n        params['warehouse'] = warehouse\n\n    return params\n"
  },
  {
    "path": "ci/container/test_authentication.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\n\nexport WORKSPACE=${WORKSPACE:-/mnt/workspace}\nexport SOURCE_ROOT=${SOURCE_ROOT:-/mnt/host}\nMVNW_EXE=$SOURCE_ROOT/mvnw\n\nAUTH_PARAMETER_FILE=./.github/workflows/parameters_aws_auth_tests.json\neval $(jq -r '.authtestparams | to_entries | map(\"export \\(.key)=\\(.value|tostring)\")|.[]' $AUTH_PARAMETER_FILE)\n\n$MVNW_EXE -DjenkinsIT \\\n    -Dnet.snowflake.jdbc.temporaryCredentialCacheDir=/mnt/workspace/abc \\\n    -Dnet.snowflake.jdbc.ocspResponseCacheDir=/mnt/workspace/abc \\\n    -Djava.io.tmpdir=$WORKSPACE \\\n    -Djacoco.skip.instrument=true \\\n    -Dskip.unitTests=true \\\n    -DintegrationTestSuites=AuthenticationTestSuite \\\n    -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n    -Dnot-self-contained-jar \\\n    -Denforcer.skip=true \\\n    clean verify \\\n    --batch-mode --show-version\n"
  },
  {
    "path": "ci/container/test_component.sh",
    "content": "#!/bin/bash -e\n#\n# Test JDBC for Linux/MAC\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nexport WORKSPACE=${WORKSPACE:-/mnt/workspace}\nexport SOURCE_ROOT=${SOURCE_ROOT:-/mnt/host}\nMVNW_EXE=$SOURCE_ROOT/mvnw\n\necho \"[INFO] Download JDBC Integration test cases and libraries\"\nsource $THIS_DIR/download_artifact.sh\n\nif [[ -f \"$WORKSPACE/parameters.json\" ]]; then\n    echo \"[INFO] Found parameter file in $WORKSPACE\"\n    PARAMETER_FILE=$WORKSPACE/parameters.json\nelse\n    echo \"[INFO] Use the default test parameters.json\"\n    PARAMETER_FILE=$SOURCE_ROOT/src/test/resources/parameters.json\nfi\neval $(jq -r '.testconnection | to_entries | map(\"export \\(.key)=\\(.value|tostring)\")|.[]' $PARAMETER_FILE)\n\nif [[ -n \"$GITHUB_SHA\" ]]; then\n    # Github Action\n    export TARGET_SCHEMA_NAME=${RUNNER_TRACKING_ID//-/_}_${GITHUB_SHA}\n\n    function finish() {\n        pushd $SOURCE_ROOT/ci/container >& /dev/null\n            echo \"[INFO] Drop schema $TARGET_SCHEMA_NAME\"\n            python3 drop_schema.py\n        popd >& /dev/null\n    }\n    trap finish EXIT\n\n    pushd $SOURCE_ROOT/ci/container >& /dev/null\n        echo \"[INFO] Create schema $TARGET_SCHEMA_NAME\"\n        if python3 create_schema.py; then\n            export SNOWFLAKE_TEST_SCHEMA=$TARGET_SCHEMA_NAME\n        else\n            echo \"[WARN] SNOWFLAKE_TEST_SCHEMA: $SNOWFLAKE_TEST_SCHEMA\"\n        fi\n    popd >& /dev/null\nfi\n\n# we change password, create SSM_KNOWN_FILE\nsource $THIS_DIR/../log_analyze_setup.sh\nif [[ \"${ENABLE_CLIENT_LOG_ANALYZE}\" == \"true\" ]]; then\n    echo \"[INFO] Log Analyze is enabled.\"\n\n    setup_log_env\n\n    if [[ \"$SNOWFLAKE_TEST_HOST\" == *\"snowflake.reg\"*\".local\"* && \"$SNOWFLAKE_TEST_ACCOUNT\" == \"s3testaccount\" && \"$SNOWFLAKE_TEST_USER\" == \"snowman\" && \"$SNOWFLAKE_TEST_PASSWORD\" == \"test\" ]]; then\n        echo \"[INFO] Run test with local instance. Will set a more complex password\"\n\n        python3 $THIS_DIR/change_snowflake_test_pwd.py\n        export SNOWFLAKE_TEST_PASSWORD=$SNOWFLAKE_TEST_PASSWORD_NEW\n\n        echo $SNOWFLAKE_TEST_PASSWORD >> $CLIENT_KNOWN_SSM_FILE_PATH\n    else\n        echo \"[INFO] Not running test with local instance. Won't set a new password\"\n    fi\nfi\n\nenv | grep SNOWFLAKE_ | grep -v -E \"(PASS|KEY|SECRET|TOKEN)\" | sort\n\necho \"[INFO] Running Hang Web Server\"\nkill -9 $(ps -ewf | grep hang_webserver | grep -v grep | awk '{print $2}') || true\npython3 $THIS_DIR/hang_webserver.py 12345&\n\n# Avoid connection timeouts\nexport MAVEN_OPTS=\"$MAVEN_OPTS -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3 -Dmaven.wagon.httpconnectionManager.ttlSeconds=120\"\necho $MAVEN_OPTS\n\ncd $SOURCE_ROOT\n\n# Avoid connection timeout on plugin dependency fetch or fail-fast when dependency cannot be fetched after 3 retries\n# Retry dependency:go-offline up to 3 times if it fails\nfor attempt in 1 2 3; do\n    echo \"[INFO] maven dependency:go-offline attempt $attempt/3\"\n    if \"$MVNW_EXE\" --batch-mode --show-version dependency:go-offline; then\n        break\n    fi\n    if [ $attempt -eq 3 ]; then\n        exit 1\n    fi\n    echo \"[WARN] Retrying in 5 seconds...\"\n    sleep 5\ndone\n\nif [[ \"$is_old_driver\" == \"true\" ]]; then\n    pushd TestOnly >& /dev/null\n        JDBC_VERSION=$($MVNW_EXE org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version --batch-mode | grep -v \"[INFO]\")\n        echo \"[INFO] Run JDBC $JDBC_VERSION tests\"\n        $MVNW_EXE -DjenkinsIT \\\n            -Dskip.unitTests=true \\\n            -Djava.io.tmpdir=$WORKSPACE \\\n            -Djacoco.skip.instrument=false \\\n            -DintegrationTestSuites=\"$JDBC_TEST_SUITES\" \\\n            -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n            verify \\\n            --batch-mode --show-version\n    popd >& /dev/null\nelif [[ \"$JDBC_TEST_SUITES\" == \"FipsTestSuite\" ]]; then\n    pushd FIPS >& /dev/null\n        echo \"[INFO] Run Fips tests\"\n        $MVNW_EXE -DjenkinsIT \\\n            -Dskip.unitTests=true \\\n            -Djava.io.tmpdir=$WORKSPACE \\\n            -Djacoco.skip.instrument=false \\\n            -DintegrationTestSuites=FipsTestSuite \\\n            -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n            -Dnot-self-contained-jar \\\n            verify \\\n            --batch-mode --show-version\n    popd >& /dev/null\nelse\n    echo \"[INFO] Run $JDBC_TEST_SUITES tests\"\n    $MVNW_EXE -DjenkinsIT \\\n        -Dskip.unitTests=true \\\n        -Djava.io.tmpdir=$WORKSPACE \\\n        -Djacoco.skip.instrument=false \\\n        -DintegrationTestSuites=\"$JDBC_TEST_SUITES\" \\\n        -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar $ADDITIONAL_MAVEN_PROFILE \\\n        verify \\\n        --batch-mode --show-version\nfi\nIFS=' '\n"
  },
  {
    "path": "ci/container/upload_artifact.sh",
    "content": "#!/bin/bash -e\n#\n# Upload jar files to S3\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nJDBC_ROOT=$(cd \"${THIS_DIR}/../../\" && pwd)\n\nif [[ -z \"$GITHUB_ACTIONS\" ]] ;then\n    export GIT_BRANCH=${GIT_BRANCH:-origin/$(git rev-parse --abbrev-ref HEAD)}\n    export GIT_COMMIT=${GIT_COMMIT:-$(git rev-parse HEAD)}\n    export WORKSPACE=${WORKSPACE:-/tmp}\n    echo \"[INFO] Git Branch is $GIT_BRANCH\"\n    if [[ \"$GIT_BRANCH\" == PR-* ]]; then\n      BRANCH=$GIT_BRANCH\n    else\n      BRANCH=$(basename ${GIT_BRANCH})\n    fi\n\n    target_stage=s3://sfc-eng-jenkins/repository/jdbc/$BRANCH/${GIT_COMMIT}\n    echo \"[INFO] Uploading jar to $target_stage/\"\n    aws s3 cp --only-show-errors $JDBC_ROOT/lib/ $target_stage/ --recursive --exclude \"*\" --include \"*.jar\"\n    aws s3 cp --only-show-errors $JDBC_ROOT/FIPS/lib $target_stage/ --recursive --exclude \"*\" --include \"*.jar\"\n\n    COMMIT_FILE=$(mktemp)\n    cat > $COMMIT_FILE <<COMMIT_FILE_CONTENTS\n${GIT_COMMIT}\nCOMMIT_FILE_CONTENTS\n\n    latest_commit_file=$(dirname $target_stage)/latest_commit\n    echo \"[INFO] updating ${latest_commit_file}\"\n    aws s3 cp --only-show-errors $COMMIT_FILE $latest_commit_file\n    rm -f $COMMIT_FILE\nelse\n    export GIT_BRANCH=origin/$(basename ${GITHUB_REF})\n    export GIT_COMMIT=${GITHUB_SHA}\n    export WORKSPACE=$GITHUB_WORKSPACE\n    mkdir -p $WORKSPACE/artifacts\n    cp -p $JDBC_ROOT/lib/*.jar $WORKSPACE/artifacts\n    cp -p $JDBC_ROOT/FIPS/lib/*.jar $WORKSPACE/artifacts\n    ls $WORKSPACE/artifacts\nfi\n"
  },
  {
    "path": "ci/image/.gitignore",
    "content": "pom.xml\ndependencies/\n*.jar"
  },
  {
    "path": "ci/image/Dockerfile.jdbc-rockylinux-openjdk-test",
    "content": "FROM rockylinux:9 AS jdbc-rockylinux-openjdk-base\n\n# update OS\nRUN dnf -y update && \\\n    dnf -y install epel-release\n\n# install Development tools\nRUN dnf -y groupinstall \"Development Tools\" && \\\n    dnf -y install zlib-devel which\n\n# git\nRUN dnf -y install git\n\n# python\nRUN dnf -y install python3 python3-pip\nRUN ln -sf /usr/bin/python3 /usr/local/bin/python3\nRUN pip3 install -U pip\nRUN pip3 install -U snowflake-connector-python\n\n# aws\nRUN pip3 install -U awscli\n\n# zstd\nRUN dnf -y install zstd\n\n# jq\nRUN dnf -y install jq\n\n# gosu\nRUN curl -o /usr/local/bin/gosu -SL \"https://github.com/tianon/gosu/releases/download/1.11/gosu-amd64\"\nRUN chmod +x /usr/local/bin/gosu\nCOPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh\nRUN chmod +x /usr/local/bin/entrypoint.sh\n\n# Maven\nRUN curl -o - https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz | tar xfz - -C /opt && \\\n    ln -s /opt/apache-maven-3.6.3/bin/mvn /usr/local/bin/mvn\n\n# workspace\nRUN mkdir -p /home/user && \\\n    chmod 777 /home/user\nWORKDIR /home/user\n\nCOPY pom.xml /root\nCOPY dependencies /root/dependencies\n\nENTRYPOINT [\"/usr/local/bin/entrypoint.sh\"]\n\n###### OpenJDK 8 from dnf\nFROM jdbc-rockylinux-openjdk-base AS jdbc-rockylinux-openjdk8\n\n# Java\nRUN dnf -y install java-1.8.0-openjdk-devel\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME='$(dirname $(dirname $(readlink $(readlink $(which javac)))))\n\nRUN cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n###### OpenJDK 11 from dnf\nFROM jdbc-rockylinux-openjdk-base AS jdbc-rockylinux-openjdk11\n\n# Java\nRUN dnf -y install java-11-openjdk-devel\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME='$(dirname $(dirname $(readlink $(readlink $(which javac)))))\n\nRUN cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n###### OpenJDK 17 from dnf\nFROM jdbc-rockylinux-openjdk-base AS jdbc-rockylinux-openjdk17\n\n# Java\nRUN dnf -y install java-17-openjdk-devel\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME='$(dirname $(dirname $(readlink $(readlink $(which javac)))))\n\nRUN cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n###### OpenJDK 21 from archive (not available in Rocky 9 repos yet)\nFROM jdbc-rockylinux-openjdk-base AS jdbc-rockylinux-openjdk21\n\n# Java\nRUN curl -o - https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz | tar xfz - -C /opt && \\\n    ln -s /opt/jdk-21.0.2 /opt/jdk-21\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME=/opt/jdk-21'\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export PATH=$JAVA_HOME/bin:$PATH'\n\nRUN export JAVA_HOME=/opt/jdk-21 && \\\n    export PATH=$JAVA_HOME/bin:$PATH && \\\n    cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n"
  },
  {
    "path": "ci/image/Dockerfile.jdbc-rockylinux8-openjdk-test",
    "content": "FROM rockylinux:8 AS jdbc-rockylinux8-openjdk-base\n\n# update OS\nRUN dnf -y update && \\\n    dnf -y install epel-release\n\n# install Development tools\nRUN dnf -y groupinstall \"Development Tools\" && \\\n    dnf -y install zlib-devel which\n\n# git\nRUN dnf -y install git\n\n# python\nRUN dnf -y install python39 python39-pip\nRUN ln -sf /usr/bin/python3.9 /usr/local/bin/python3\nRUN pip3.9 install -U pip\nRUN pip3.9 install -U snowflake-connector-python\n\n# aws\nRUN pip3.9 install -U awscli\n\n# zstd\nRUN dnf -y install zstd\n\n# jq\nRUN dnf -y install jq\n\n# gosu\nRUN curl -o /usr/local/bin/gosu -SL \"https://github.com/tianon/gosu/releases/download/1.11/gosu-amd64\"\nRUN chmod +x /usr/local/bin/gosu\nCOPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh\nRUN chmod +x /usr/local/bin/entrypoint.sh\n\n# Maven\nRUN curl -o - https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz | tar xfz - -C /opt && \\\n    ln -s /opt/apache-maven-3.6.3/bin/mvn /usr/local/bin/mvn\n\n# workspace\nRUN mkdir -p /home/user && \\\n    chmod 777 /home/user\nWORKDIR /home/user\n\nCOPY pom.xml /root\nCOPY dependencies /root/dependencies\n\nENTRYPOINT [\"/usr/local/bin/entrypoint.sh\"]\n\n###### OpenJDK 8 from dnf\nFROM jdbc-rockylinux8-openjdk-base AS jdbc-rockylinux8-openjdk8\n\n# Java\nRUN dnf -y install java-1.8.0-openjdk-devel\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME='$(dirname $(dirname $(readlink $(readlink $(which javac)))))\n\nRUN cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n###### OpenJDK 11 from dnf\nFROM jdbc-rockylinux8-openjdk-base AS jdbc-rockylinux8-openjdk11\n\n# Java\nRUN dnf -y install java-11-openjdk-devel\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME='$(dirname $(dirname $(readlink $(readlink $(which javac)))))\n\nRUN cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n###### OpenJDK 17 from dnf\nFROM jdbc-rockylinux8-openjdk-base AS jdbc-rockylinux8-openjdk17\n\n# Java\nRUN dnf -y install java-17-openjdk-devel\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME='$(dirname $(dirname $(readlink $(readlink $(which javac)))))\n\nRUN cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n\n###### OpenJDK 21 from archive (not available in Rocky 8 repos)\nFROM jdbc-rockylinux8-openjdk-base AS jdbc-rockylinux8-openjdk21\n\n# Java\nRUN curl -o - https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz | tar xfz - -C /opt && \\\n    ln -s /opt/jdk-21.0.2 /opt/jdk-21\n\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export JAVA_HOME=/opt/jdk-21'\nRUN sed -i /usr/local/bin/entrypoint.sh -e '/^exec/i export PATH=$JAVA_HOME/bin:$PATH'\n\nRUN export JAVA_HOME=/opt/jdk-21 && \\\n    export PATH=$JAVA_HOME/bin:$PATH && \\\n    cd /root && \\\n    mvn -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n        -Dnot-self-contained-jar \\\n        --batch-mode --fail-never compile && \\\n    mv $HOME/.m2 /home/user && \\\n    chmod -R 777 /home/user/.m2\n"
  },
  {
    "path": "ci/image/build.sh",
    "content": "#!/usr/bin/env bash\nset -e\n#\n# Build Docker images\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource $THIS_DIR/../_init.sh\n\ncp -p $THIS_DIR/../../pom.xml $THIS_DIR\nmkdir -p dependencies && cp -rp $THIS_DIR/../../dependencies/ $THIS_DIR/dependencies\n\nfor name in \"${!TEST_IMAGE_NAMES[@]}\"; do\n    echo \"Building $name\"\n    docker build \\\n        --progress=plain \\\n        --platform=linux/x86_64 \\\n        --pull \\\n        --file $THIS_DIR/Dockerfile.$(echo ${TEST_IMAGE_DOCKERFILES[$name]}) \\\n        --label snowflake \\\n        --label $DRIVER_NAME \\\n        $(echo ${TEST_IMAGE_BUILD_ARGS[$name]}) \\\n        --tag ${TEST_IMAGE_NAMES[$name]} .\ndone\n"
  },
  {
    "path": "ci/image/scripts/aws.sh",
    "content": "#!/bin/bash\nsource /opt/rh/rh-python36/enable\naws \"$@\"\n"
  },
  {
    "path": "ci/image/scripts/entrypoint.sh",
    "content": "#!/bin/bash -x\n# Add local user\n# Either use the LOCAL_USER_ID if passed in at runtime or\n# fallback\n\nUSER_ID=${LOCAL_USER_ID:-9001}\n\necho \"Starting with UID : $USER_ID\"\nuseradd --shell /bin/bash -u $USER_ID -o -c \"\" -m user\nexport HOME=/home/user\n\nexec /usr/local/bin/gosu user \"$@\"\n"
  },
  {
    "path": "ci/image/scripts/git.sh",
    "content": "#!/bin/bash\nsource /opt/rh/rh-git29/enable\ngit \"$@\"\n"
  },
  {
    "path": "ci/image/scripts/npmrc",
    "content": "registry=https://nexus.int.snowflakecomputing.com/repository/sf_npm_proxy/\n"
  },
  {
    "path": "ci/image/scripts/pip.sh",
    "content": "#!/bin/bash\n\nsource /opt/rh/rh-python36/enable\npip \"$@\"\n"
  },
  {
    "path": "ci/image/scripts/python3.6.sh",
    "content": "#!/bin/bash\n\nsource /opt/rh/rh-python36/enable\npython3.6 \"$@\"\n"
  },
  {
    "path": "ci/image/update.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n#\n# Build Docker images\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource $THIS_DIR/../_init.sh\n\nsource $THIS_DIR/../scripts/login_docker.sh\n\nfor image in $(docker images --format \"{{.ID}},{{.Repository}}:{{.Tag}}\" | grep \"artifactory.ci1.us-west-2.aws-dev.app.snowflake.com\" | grep \"client-$DRIVER_NAME\"); do\n    target_id=$(echo $image | awk -F, '{print $1}')\n    target_name=$(echo $image | awk -F, '{print $2}')\n    for name in \"${!TEST_IMAGE_NAMES[@]}\"; do\n        if [[ \"$target_name\" == \"${TEST_IMAGE_NAMES[$name]}\" ]]; then\n            echo $name\n            docker_hub_image_name=$(echo ${TEST_IMAGE_NAMES[$name]/$DOCKER_REGISTRY_NAME/snowflakedb})\n            set -x\n            docker tag $target_id $docker_hub_image_name\n            set +x\n            docker push \"${TEST_IMAGE_NAMES[$name]}\"\n            docker push \"$docker_hub_image_name\"\n        fi\n    done\ndone\n"
  },
  {
    "path": "ci/log_analyze_setup.sh",
    "content": "#!/bin/bash -e\n#\n# preparation for log analyze\n#\n\n# Note, this need to be consistent with docker bind parameter. Might need to come up with a better sync up solution\nLOCAL_CLIENT_LOG_DIR_PATH_DOCKER=/mnt/workspace/jenkins_rt_logs\nLOCAL_CLIENT_LOG_DIR_PATH=$WORKSPACE/jenkins_rt_logs\necho \"[INFO] LOCAL_CLIENT_LOG_DIR_PATH=$LOCAL_CLIENT_LOG_DIR_PATH\"\necho \"[INFO] LOCAL_CLIENT_LOG_DIR_PATH_DOCKER=$LOCAL_CLIENT_LOG_DIR_PATH_DOCKER\"\n\nexport CLIENT_LOG_FILE_PATH_DOCKER=$LOCAL_CLIENT_LOG_DIR_PATH_DOCKER/snowflake_ssm_rt.log\nexport CLIENT_LOG_FILE_PATH=$LOCAL_CLIENT_LOG_DIR_PATH/snowflake_ssm_rt.log\necho \"[INFO] CLIENT_LOG_FILE_PATH=$CLIENT_LOG_FILE_PATH\"\necho \"[INFO] CLIENT_LOG_FILE_PATH_DOCKER=$CLIENT_LOG_FILE_PATH_DOCKER\"\n\nexport CLIENT_KNOWN_SSM_FILE_PATH_DOCKER=$LOCAL_CLIENT_LOG_DIR_PATH_DOCKER/rt_jenkins_log_known_ssm.txt\nexport CLIENT_KNOWN_SSM_FILE_PATH=$LOCAL_CLIENT_LOG_DIR_PATH/rt_jenkins_log_known_ssm.txt\necho \"[INFO] CLIENT_KNOWN_SSM_FILE_PATH=$CLIENT_KNOWN_SSM_FILE_PATH\"\necho \"[INFO] CLIENT_KNOWN_SSM_FILE_PATH_DOCKER=$CLIENT_KNOWN_SSM_FILE_PATH_DOCKER\"\n\n# To close log analyze, just set ENABLE_CLIENT_LOG_ANALYZE to not \"true\", e.g. \"false\".\nif [[ \"$is_old_driver\" != \"true\" ]]; then\n    export ENABLE_CLIENT_LOG_ANALYZE=true\nelse\n    # Disable the secret log analyzer for the old client tests, because the old drivers don't necessarily have the fixes.\n    # It is good enough to check for the latest JDBC driver\n    export ENABLE_CLIENT_LOG_ANALYZE=false\nfi\n\n# The new complex password we use for jenkins test\nexport SNOWFLAKE_TEST_PASSWORD_NEW=\"ThisIsRandomPassword123!\"\n\nLOG_PROPERTY_FILE=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/..\"; pwd)/src/test/resources/logging.properties\n\nexport CLIENT_DRIVER_NAME=JDBC\n\nfunction setup_log_env() {\n    if [[ \"$WORKSPACE\" == \"/mnt/workspace\" ]]; then\n        CLIENT_LOG_DIR_PATH=$LOCAL_CLIENT_LOG_DIR_PATH_DOCKER\n        CLIENT_LOG_FILE_PATH=$CLIENT_LOG_FILE_PATH_DOCKER\n        CLIENT_KNOWN_SSM_FILE_PATH=$CLIENT_KNOWN_SSM_FILE_PATH_DOCKER\n    else\n        CLIENT_LOG_DIR_PATH=$LOCAL_CLIENT_LOG_DIR_PATH\n        CLIENT_LOG_FILE_PATH=$CLIENT_LOG_FILE_PATH\n        CLIENT_KNOWN_SSM_FILE_PATH=$CLIENT_KNOWN_SSM_FILE_PATH\n    fi\n    echo \"[INFO] CLIENT_LOG_DIR_PATH=$CLIENT_LOG_DIR_PATH\"  \n    echo \"[INFO] CLIENT_LOG_FILE_PATH=$CLIENT_LOG_FILE_PATH\"\n    echo \"[INFO] CLIENT_KNOWN_SSM_FILE_PATH=$CLIENT_KNOWN_SSM_FILE_PATH\"\n    echo \"[INFO] Replace file handler for log file $LOG_PROPERTY_FILE\"\n\n    sed  -i'' -e \"s|^java.util.logging.FileHandler.pattern.*|java.util.logging.FileHandler.pattern = $CLIENT_LOG_FILE_PATH|\" ${LOG_PROPERTY_FILE}\n\n    if [[ ! -d ${CLIENT_LOG_DIR_PATH} ]]; then\n      echo \"[INFO] create client log directory $CLIENT_LOG_DIR_PATH\"\n      mkdir -p ${CLIENT_LOG_DIR_PATH}\n    fi\n\n    if [[ -f $CLIENT_KNOWN_SSM_FILE_PATH ]]; then\n        rm -f $CLIENT_KNOWN_SSM_FILE_PATH\n    fi\n\n    touch $CLIENT_KNOWN_SSM_FILE_PATH\n    echo \"[INFO] finish setup log env\"\n}\n"
  },
  {
    "path": "ci/scripts/check_content.sh",
    "content": "#!/bin/bash -e\n\n# scripts used to check if all dependency is shaded into snowflake internal path\n\npackage_modifier=$1\n\nset -o pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null && pwd )\"\n\nif jar tvf $DIR/../../target/snowflake-jdbc${package_modifier}.jar | awk '{print $8}' | grep -v -E \"/$\" | grep -v -E \"^(net|com)/snowflake\" | grep -v -E \"(com|net)/\\$\" | grep -v -E \"^META-INF\" | grep -v -E \"^iso3166_\" | grep -v -E \"^mozilla\" | grep -v -E \"^com/sun/jna\" | grep -v com/sun/ | grep -v mime.types | grep -v -E \"^com/github/luben/zstd/\" | grep -v -E \"^aix/\" | grep -v -E \"^darwin/\" | grep -v -E \"^freebsd/\" | grep -v -E \"^linux/\" | grep -v -E \"^win/\" | grep -v -E \"^minicore/\" | grep -v -E \"^org/conscrypt/\"; then\n  echo \"[ERROR] JDBC jar includes class not under the snowflake namespace\"\n  exit 1\nfi\n\nif jar tvf $DIR/../../target/snowflake-jdbc${package_modifier}.jar | awk '{print $8}' | grep -E \"^META-INF/versions/.*.class\" | grep -v -E \"^META-INF/versions/.*/(net|com)/snowflake\"; then\n  echo \"[ERROR] JDBC jar includes multi-release classes not under the snowflake namespace\"\n  exit 1\nfi\n"
  },
  {
    "path": "ci/scripts/check_no_raw_system_calls.sh",
    "content": "#!/usr/bin/env bash\n#\n# Architectural guard: ensures no raw System.getProperty(), System.getenv(),\n# or System.setProperty() calls exist in production code outside SnowflakeUtil.java.\n#\n# Use SnowflakeUtil.systemGetProperty() / systemGetEnv() / systemSetProperty()\n# instead of raw calls to ensure proper SecurityException handling.\n#\nset -euo pipefail\n\nSRC_DIR=\"${1:-src/main/java}\"\nWRAPPER_FILE=\"SnowflakeUtil.java\"\n\nviolations=$(\n    grep -rn --include='*.java' -E 'System\\.(getProperty|getenv|setProperty)\\s*\\(' \"$SRC_DIR\" \\\n    | grep -v \"/${WRAPPER_FILE}:\" \\\n    || true\n)\n\nif [[ -n \"$violations\" ]]; then\n    echo \"ERROR: Found raw System.getProperty/getenv/setProperty calls outside ${WRAPPER_FILE}:\"\n    echo \"\"\n    echo \"$violations\"\n    echo \"\"\n    echo \"Replace with SnowflakeUtil.systemGetProperty() / systemGetEnv() / systemSetProperty().\"\n    exit 1\nfi\n\necho \"OK: No raw System.getProperty/getenv/setProperty calls found outside ${WRAPPER_FILE}.\"\n"
  },
  {
    "path": "ci/scripts/login_docker.sh",
    "content": "#!/bin/bash -e\n#\n# Login the Docker Hub\n#\necho \"[INFO] Login the Docker Hub\"\nif [[ -z \"$DOCKER_HUB_USER\" ]] || [[ -z \"$DOCKER_HUB_TOKEN\" ]]; then\n    echo \"[ERROR] Set DOCKER_HUB_USER and DOCKER_HUB_TOKEN to push the images to the Docker Hub\"\n    exit 1\nfi\ndocker login --username \"$DOCKER_HUB_USER\" --password \"$DOCKER_HUB_TOKEN\"\n"
  },
  {
    "path": "ci/scripts/set_git_info.sh",
    "content": "#!/bin/bash -e\n#\n# Set GIT info\n#\nif [[ -z \"$GITHUB_ACTIONS\" ]]; then\n    #\n    # set Jenkins GIT parameters propagated from Build job.\n    # \n    export client_git_url=${client_git_url:-https://github.com/snowflakedb/snowflake-jdbc.git}\n    export client_git_branch=${client_git_branch:-origin/$(git rev-parse --abbrev-ref HEAD)}\n    export client_git_commit=${client_git_commit:-$(git log --pretty=oneline | head -1 | awk '{print $1}')}\nelse\n    #\n    # GITHUB Actions\n    if [[ \"$CLOUD_PROVIDER\" == \"AZURE\" ]]; then\n        SOURCE_PARAMETER_FILE=parameters_azure.json.gpg\n        RSA_KEY_FILE=rsa_key_jdbc_azure.p8.gpg\n    elif [[ \"$CLOUD_PROVIDER\" == \"GCP\" ]]; then\n        SOURCE_PARAMETER_FILE=parameters_gcp.json.gpg\n        RSA_KEY_FILE=rsa_key_jdbc_gcp.p8.gpg\n    else\n        SOURCE_PARAMETER_FILE=parameters_aws.json.gpg\n        RSA_KEY_FILE=rsa_key_jdbc_aws.p8.gpg\n    fi\n    gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output $WORKSPACE/parameters.json $THIS_DIR/../.github/workflows/$SOURCE_PARAMETER_FILE\n    gpg --quiet --batch --yes --decrypt --passphrase=\"$JDBC_PRIVATE_KEY_SECRET\" --output $WORKSPACE/rsa_key_jdbc.p8 $THIS_DIR/../.github/workflows/rsa_keys/$RSA_KEY_FILE\n    export client_git_url=https://github.com/${GITHUB_REPOSITORY}.git\n    export client_git_branch=origin/$(basename ${GITHUB_REF})\n    export client_git_commit=${GITHUB_SHA}\nfi\n\n#\n# set GIT parameters used in the following scripts\n#\nexport GIT_URL=$client_git_url\nexport GIT_BRANCH=$client_git_branch\nexport GIT_COMMIT=$client_git_commit\necho \"GIT_URL: $GIT_URL, GIT_BRANCH: $GIT_BRANCH, GIT_COMMIT: $GIT_COMMIT\"\n"
  },
  {
    "path": "ci/scripts/setup_gpg.sh",
    "content": "#!/bin/bash\n# GPG setup script for creating unique GPG home directory\nsetup_gpg_home() {\n  # Create unique GPG home directory\n  export GNUPGHOME=\"${THIS_DIR}/.gnupg_$$_$(date +%s%N)_${BUILD_NUMBER:-}\"\n  mkdir -p \"$GNUPGHOME\"\n  chmod 700 \"$GNUPGHOME\"\n  cleanup_gpg() {\n    if [[ -n \"$GNUPGHOME\" && -d \"$GNUPGHOME\" ]]; then\n      rm -rf \"$GNUPGHOME\"\n    fi\n  }\n  trap cleanup_gpg EXIT\n}\nsetup_gpg_home\n\n"
  },
  {
    "path": "ci/test.sh",
    "content": "#!/bin/bash -e\n#\n# Test JDBC\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nJDBC_ROOT=\"$(cd \"${THIS_DIR}/..\" && pwd)\"\n\nsource $THIS_DIR/_init.sh\nsource $THIS_DIR/scripts/set_git_info.sh\n\ndeclare -A TARGET_TEST_IMAGES\nif [[ -n \"$TARGET_DOCKER_TEST_IMAGE\" ]]; then\n    echo \"[INFO] TARGET_DOCKER_TEST_IMAGE: $TARGET_DOCKER_TEST_IMAGE\"\n    IMAGE_NAME=${TEST_IMAGE_NAMES[$TARGET_DOCKER_TEST_IMAGE]}\n    if [[ -z \"$IMAGE_NAME\" ]]; then\n        echo \"[ERROR] The target platform $TARGET_DOCKER_TEST_IMAGE doesn't exist. Check $THIS_DIR/_init.sh\"\n        exit 1\n    fi\n    TARGET_TEST_IMAGES=([$TARGET_DOCKER_TEST_IMAGE]=$IMAGE_NAME)\nelse\n    echo \"[ERROR] Set TARGET_DOCKER_TEST_IMAGE to the docker image name to run the test\"\n    for name in \"${!TEST_IMAGE_NAMES[@]}\"; do\n        echo \"  \" $name\n    done\n    exit 2\nfi\n\nif [[ -z \"$JDBC_TEST_SUITES\" ]]; then\n    echo \"[ERROR] Set JDBC_TEST_SUITES to the JDBC test category.\"\n    find $THIS_DIR/../src/test/java -type f -exec grep -E \"^import net.snowflake.client.category\" {} \\; | sort | uniq | awk -F. '{print $NF}' | awk -F\\; '{print $1}'\n    exit 2\nfi\n\nfor name in \"${!TARGET_TEST_IMAGES[@]}\"; do\n    echo \"[INFO] Testing $DRIVER_NAME on $name\"\n    # docker pull \"${TEST_IMAGE_NAMES[$name]}\"\n    docker container run \\\n        --rm \\\n        --network=host \\\n        -v $JDBC_ROOT:/mnt/host \\\n        -v $WORKSPACE:/mnt/workspace \\\n        -e LOCAL_USER_ID=$(id -u ${USER}) \\\n        -e TERM=xterm \\\n        -e GIT_COMMIT \\\n        -e GIT_BRANCH \\\n        -e GIT_URL \\\n        -e AWS_ACCESS_KEY_ID \\\n        -e AWS_SECRET_ACCESS_KEY \\\n        -e GITHUB_ACTIONS \\\n        -e GITHUB_SHA \\\n        -e GITHUB_REF \\\n        -e RUNNER_TRACKING_ID \\\n        -e JOB_NAME \\\n        -e BUILD_NUMBER \\\n        -e JDBC_TEST_SUITES \\\n        -e ADDITIONAL_MAVEN_PROFILE \\\n        -e CLOUD_PROVIDER \\\n        -e is_old_driver \\\n        ${TEST_IMAGE_NAMES[$name]} \\\n        /mnt/host/ci/container/test_component.sh\ndone\n"
  },
  {
    "path": "ci/test_authentication.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\nexport THIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource \"$THIS_DIR/scripts/setup_gpg.sh\"\nexport WORKSPACE=${WORKSPACE:-/tmp}\nexport INTERNAL_REPO=artifactory.ci1.us-west-2.aws-dev.app.snowflake.com/internal-production-docker-snowflake-virtual\n\n\nCI_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nif [[ -n \"$JENKINS_HOME\" ]]; then\n  ROOT_DIR=\"$(cd \"${CI_DIR}/..\" && pwd)\"\n  export WORKSPACE=${WORKSPACE:-/tmp}\n  source $CI_DIR/_init.sh\nfi\n\ngpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output $THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json \"$THIS_DIR/../.github/workflows/parameters_aws_auth_tests.json.gpg\"\n\ndocker run \\\n  -v $(cd $THIS_DIR/.. && pwd):/mnt/host \\\n  -v $WORKSPACE:/mnt/workspace \\\n  --rm \\\n  artifactory.ci1.us-west-2.aws-dev.app.snowflake.com/internal-production-docker-snowflake-virtual/docker/snowdrivers-test-external-browser-jdbc:4 \\\n  \"/mnt/host/ci/container/test_authentication.sh\"\n"
  },
  {
    "path": "ci/test_mac.sh",
    "content": "#!/bin/bash -e\n#\n# Test JDBC for Mac\n#\n\necho \"DOWNLOADED\"\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nsource $THIS_DIR/_init.sh\nsource $THIS_DIR/scripts/set_git_info.sh\n\n\nexport WORKSPACE=$GITHUB_WORKSPACE\nexport SOURCE_ROOT=$GITHUB_WORKSPACE\n\npython3 --version\npython3 -m venv venv\nsource venv/bin/activate\npip3 install -U pip\npip3 install -U snowflake-connector-python\n$THIS_DIR/container/test_component.sh\n"
  },
  {
    "path": "ci/test_revocation.sh",
    "content": "#!/bin/bash -e\n#\n# Test certificate revocation validation using the revocation-validation framework.\n#\n\nset -o pipefail\n\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nJDBC_ROOT=\"$( dirname \"${THIS_DIR}\")\"\nWORKSPACE=${WORKSPACE:-${JDBC_ROOT}}\n\necho \"[Info] Starting revocation validation tests\"\n\n# Detect JDBC version using Maven Wrapper (reliable, handles property interpolation)\nJDBC_VERSION=$(cd \"$JDBC_ROOT\" && ./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout 2>/dev/null)\nif [ -z \"$JDBC_VERSION\" ]; then\n    echo \"[Error] Failed to determine JDBC version from pom.xml\"\n    exit 1\nfi\necho \"[Info] JDBC driver version: $JDBC_VERSION\"\n\n# Ensure parent POM is also in ~/.m2 (needed for Maven dependency resolution)\nif [ ! -f \"$HOME/.m2/repository/net/snowflake/snowflake-jdbc-parent/$JDBC_VERSION/\"*.pom ]; then\n    echo \"[Info] Installing parent POM to local Maven repo...\"\n    if ! (cd \"$JDBC_ROOT\" && ./mvnw install -N -f parent-pom.xml -Dmaven.test.skip=true -q --batch-mode); then\n        echo \"[Error] Failed to install parent POM\"\n        exit 1\n    fi\nfi\n\n# Clone revocation-validation framework\nREVOCATION_DIR=\"/tmp/revocation-validation\"\nREVOCATION_BRANCH=\"${REVOCATION_BRANCH:-main}\"\n\nrm -rf \"$REVOCATION_DIR\"\nif [ -n \"$GITHUB_USER\" ] && [ -n \"$GITHUB_TOKEN\" ]; then\n    git clone --depth 1 --branch \"$REVOCATION_BRANCH\" \"https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/snowflake-eng/revocation-validation.git\" \"$REVOCATION_DIR\"\nelse\n    git clone --depth 1 --branch \"$REVOCATION_BRANCH\" \"https://github.com/snowflake-eng/revocation-validation.git\" \"$REVOCATION_DIR\"\nfi\n\ncd \"$REVOCATION_DIR\"\n\n# Point the wrapper's pom.xml at the locally-built JDBC version (already in ~/.m2 from Build stage)\nWRAPPER_POM=\"$REVOCATION_DIR/validation/clients/snowflake-jdbc/java/pom.xml\"\nawk -v ver=\"$JDBC_VERSION\" '\n    /<groupId>net\\.snowflake<\\/groupId>/  { in_sf=1 }\n    in_sf && /<artifactId>snowflake-jdbc<\\/artifactId>/ { found=1 }\n    found && /<version>/ { sub(/<version>[^<]*<\\/version>/, \"<version>\" ver \"</version>\"); found=0; in_sf=0 }\n    { print }\n' \"$WRAPPER_POM\" > \"${WRAPPER_POM}.tmp\" && mv \"${WRAPPER_POM}.tmp\" \"$WRAPPER_POM\"\n\n# Verify the version was correctly set\nif ! grep -q \"<version>${JDBC_VERSION}</version>\" \"$WRAPPER_POM\"; then\n    echo \"[Error] Failed to update JDBC version in wrapper pom.xml\"\n    exit 1\nfi\necho \"[Info] Updated wrapper to use JDBC $JDBC_VERSION\"\n\necho \"[Info] Running tests with Go $(go version | grep -oE 'go[0-9]+\\.[0-9]+')...\"\n\nset +e\ngo run . \\\n    --client snowflake-jdbc \\\n    --output \"${WORKSPACE}/revocation-results.json\" \\\n    --output-html \"${WORKSPACE}/revocation-report.html\" \\\n    --log-level debug\nEXIT_CODE=$?\nset -e\n\nif [ -f \"${WORKSPACE}/revocation-results.json\" ] && [ -f \"${WORKSPACE}/revocation-report.html\" ]; then\n    echo \"[Info] Results: ${WORKSPACE}/revocation-results.json\"\n    echo \"[Info] Report: ${WORKSPACE}/revocation-report.html\"\nelse\n    echo \"[Warn] Expected output files were not generated\"\nfi\n\nexit $EXIT_CODE\n"
  },
  {
    "path": "ci/test_wif.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\n\nexport THIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nexport RSA_KEY_PATH_AWS_AZURE=\"$THIS_DIR/wif/parameters/rsa_wif_aws_azure\"\nexport RSA_KEY_PATH_GCP=\"$THIS_DIR/wif/parameters/rsa_wif_gcp\"\nexport RSA_GCP_FUNCTION_KEY=\"$THIS_DIR/wif/parameters/rsa_gcp_function\"\nexport PARAMETERS_FILE_PATH=\"$THIS_DIR/wif/parameters/parameters_wif.json\"\nexport PARAMETERS_FUNCTIONS_FILE_PATH=\"$THIS_DIR/wif/parameters/parameters_wif_function.json\"\n\nrun_tests_and_set_result() {\n  local provider=\"$1\"\n  local host=\"$2\"\n  local snowflake_host=\"$3\"\n  local rsa_key_path=\"$4\"\n  local snowflake_user=\"$5\"\n  local impersonation_path=\"$6\"\n  local snowflake_user_for_impersonation=\"$7\"\n  local impersonation_external_id=\"${8:-}\"\n\n  ssh -i \"$rsa_key_path\" -o IdentitiesOnly=yes -p 443 \"$host\" env BRANCH=\"$BRANCH\" SNOWFLAKE_TEST_WIF_HOST=\"$snowflake_host\" SNOWFLAKE_TEST_WIF_PROVIDER=\"$provider\" SNOWFLAKE_TEST_WIF_ACCOUNT=\"$SNOWFLAKE_TEST_WIF_ACCOUNT\" SNOWFLAKE_TEST_WIF_USERNAME=\"$snowflake_user\" SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH=\"$impersonation_path\" SNOWFLAKE_TEST_WIF_USERNAME_IMPERSONATION=\"$snowflake_user_for_impersonation\" SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID=\"$impersonation_external_id\" SNOWFLAKE_TEST_WIF_IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID=\"$SNOWFLAKE_TEST_WIF_IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID\" SNOWFLAKE_TEST_WIF_IMPERSONATION_USER_WITH_EXTERNAL_ID=\"$SNOWFLAKE_TEST_WIF_IMPERSONATION_USER_WITH_EXTERNAL_ID\" bash << EOF\n      set -e\n      set -o pipefail\n      docker run \\\n        --rm \\\n        --cpus=1 \\\n        -m 2g \\\n        -e BRANCH \\\n        -e SNOWFLAKE_TEST_WIF_PROVIDER \\\n        -e SNOWFLAKE_TEST_WIF_HOST \\\n        -e SNOWFLAKE_TEST_WIF_ACCOUNT \\\n        -e SNOWFLAKE_TEST_WIF_USERNAME \\\n        -e SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH \\\n        -e SNOWFLAKE_TEST_WIF_USERNAME_IMPERSONATION \\\n        -e SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID \\\n        -e SNOWFLAKE_TEST_WIF_IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID \\\n        -e SNOWFLAKE_TEST_WIF_IMPERSONATION_USER_WITH_EXTERNAL_ID \\\n        -e SF_ENABLE_WIF_AWS_EXTERNAL_ID=true \\\n        snowflakedb/client-jdbc-rockylinux8-openjdk17-test:1 \\\n          bash -c \"\n            echo 'Running tests on branch: \\$BRANCH'\n            mkdir -p /tmp/maven-repo /tmp/workspace\n            chmod 755 /tmp/maven-repo /tmp/workspace\n            if [[ \\\"\\$BRANCH\\\" =~ ^PR-[0-9]+\\$ ]]; then\n              curl -L https://github.com/snowflakedb/snowflake-jdbc/archive/refs/pull/\\$(echo \\$BRANCH | cut -d- -f2)/head.tar.gz | tar -xz\n              mv snowflake-jdbc-* snowflake-jdbc\n            else\n              curl -L https://github.com/snowflakedb/snowflake-jdbc/archive/refs/heads/\\$BRANCH.tar.gz | tar -xz\n              mv snowflake-jdbc-\\$BRANCH snowflake-jdbc\n            fi\n            cd snowflake-jdbc\n            bash ci/wif/test_wif.sh\n          \"\nEOF\n  local status=$?\n\n  if [[ $status -ne 0 ]]; then\n    echo \"$provider tests failed with exit status: $status\"\n    EXIT_STATUS=1\n  else\n    echo \"$provider tests passed\"\n  fi\n}\n\nget_branch() {\n  local branch\n  if [[ -n \"${GIT_BRANCH}\" ]]; then\n    # Jenkins\n    branch=\"${GIT_BRANCH}\"\n  else\n    branch=$(git rev-parse --abbrev-ref HEAD)\n    if [[ \"$branch\" == \"HEAD\" ]]; then\n      branch=$(git name-rev --name-only HEAD | sed 's#^remotes/origin/##;s#^origin/##')\n    fi\n  fi\n  echo \"$branch\"\n}\n\nrun_azure_function() {\n  if ! bash \"$THIS_DIR/wif/azure-function/test.sh\"; then\n    EXIT_STATUS=1\n  fi\n}\n\nrun_aws_function() {\n  if ! bash \"$THIS_DIR/wif/aws-lambda/test.sh\"; then\n    EXIT_STATUS=1\n  fi\n}\n\nrun_gcp_function() {\n  if ! bash \"$THIS_DIR/wif/gcp-function/test.sh\"; then\n    EXIT_STATUS=1\n  fi\n}\n\nsetup_parameters() {\n  source \"$THIS_DIR/scripts/setup_gpg.sh\"\n  gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output \"$RSA_KEY_PATH_AWS_AZURE\" \"${RSA_KEY_PATH_AWS_AZURE}.gpg\"\n  gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output \"$RSA_KEY_PATH_GCP\" \"${RSA_KEY_PATH_GCP}.gpg\"\n  gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output \"$RSA_GCP_FUNCTION_KEY\" \"${RSA_GCP_FUNCTION_KEY}.gpg\"\n  chmod 600 \"$RSA_KEY_PATH_AWS_AZURE\"\n  chmod 600 \"$RSA_KEY_PATH_GCP\"\n  chmod 600 \"$RSA_GCP_FUNCTION_KEY\"\n  gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output \"$PARAMETERS_FILE_PATH\" \"${PARAMETERS_FILE_PATH}.gpg\"\n  eval $(jq -r '.wif | to_entries | map(\"export \\(.key)=\\(.value|tostring)\")|.[]' $PARAMETERS_FILE_PATH)\n  gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" --output \"$PARAMETERS_FUNCTIONS_FILE_PATH\" \"${PARAMETERS_FUNCTIONS_FILE_PATH}.gpg\"\n  eval $(jq -r '.wif | to_entries | map(\"export \\(.key)=\\(.value|tostring)\")|.[]' $PARAMETERS_FUNCTIONS_FILE_PATH)\n}\n\nBRANCH=$(get_branch)\nexport BRANCH\nsetup_parameters\n\n# Run tests for all cloud providers\nEXIT_STATUS=0\nset +e  # Don't exit on first failure\n\n# WIF E2E tests on functions\nrun_aws_function\nrun_azure_function\nrun_gcp_function\n# WIF E2E tests on VMs\nrun_tests_and_set_result \"AZURE\" \"$HOST_AZURE\" \"$SNOWFLAKE_TEST_WIF_HOST_AZURE\" \"$RSA_KEY_PATH_AWS_AZURE\" \"$SNOWFLAKE_TEST_WIF_USERNAME_AZURE\" \"$SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH_AZURE\" \"$SNOWFLAKE_TEST_WIF_USERNAME_AZURE_IMPERSONATION\"\nrun_tests_and_set_result \"AWS\" \"$HOST_AWS\" \"$SNOWFLAKE_TEST_WIF_HOST_AWS\" \"$RSA_KEY_PATH_AWS_AZURE\" \"$SNOWFLAKE_TEST_WIF_USERNAME_AWS\" \"$SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH_AWS\" \"$SNOWFLAKE_TEST_WIF_USERNAME_AWS_IMPERSONATION\" \"$SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID\"\nrun_tests_and_set_result \"GCP\" \"$HOST_GCP\" \"$SNOWFLAKE_TEST_WIF_HOST_GCP\" \"$RSA_KEY_PATH_GCP\" \"$SNOWFLAKE_TEST_WIF_USERNAME_GCP\" \"$SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH_GCP\" \"$SNOWFLAKE_TEST_WIF_USERNAME_GCP_IMPERSONATION\"\n\nset -e  # Re-enable exit on error\necho \"Exit status: $EXIT_STATUS\"\nexit $EXIT_STATUS\n"
  },
  {
    "path": "ci/test_windows.bat",
    "content": "REM \nREM Tests JDBC Driver on Windows\nREM\nsetlocal\nsetlocal EnableDelayedExpansion\npython -m venv venv\ncall venv\\scripts\\activate\npip install -U snowflake-connector-python\n\ncd %GITHUB_WORKSPACE%\n\nif \"%CLOUD_PROVIDER%\"==\"AZURE\" (\n  set ENCODED_PARAMETERS_FILE=.github/workflows/parameters_azure.json.gpg\n  set ENCODED_RSA_KEY_FILE=.github/workflows/rsa_keys/rsa_key_jdbc_azure.p8.gpg\n) else if \"%CLOUD_PROVIDER%\"==\"GCP\" (\n  set ENCODED_PARAMETERS_FILE=.github/workflows/parameters_gcp.json.gpg\n  set ENCODED_RSA_KEY_FILE=.github/workflows/rsa_keys/rsa_key_jdbc_gcp.p8.gpg\n) else if \"%CLOUD_PROVIDER%\"==\"AWS\" (\n  set ENCODED_PARAMETERS_FILE=.github/workflows/parameters_aws.json.gpg\n  set ENCODED_RSA_KEY_FILE=.github/workflows/rsa_keys/rsa_key_jdbc_aws.p8.gpg\n) else (\n  echo === unknown cloud provider\n  exit /b 1\n)\n\ngpg --quiet --batch --yes --decrypt --passphrase=%PARAMETERS_SECRET% --output parameters.json %ENCODED_PARAMETERS_FILE%\ngpg --quiet --batch --yes --decrypt --passphrase=%JDBC_PRIVATE_KEY_SECRET% --output rsa_key_jdbc.p8 %ENCODED_RSA_KEY_FILE%\n\nREM DON'T FORGET TO include @echo off here or the password may be leaked!\necho @echo off>parameters.bat\njq -r \".testconnection | to_entries | map(\\\"set \\(.key)=\\(.value)\\\") | .[]\" parameters.json >> parameters.bat\ncall parameters.bat\nREM Set the private key file path directly to avoid encoding issues\nset \"SNOWFLAKE_TEST_PRIVATE_KEY_FILE=%GITHUB_WORKSPACE%\\rsa_key_jdbc.p8\"\nset \"SNOWFLAKE_TEST_AUTHENTICATOR=SNOWFLAKE_JWT\"\nif %ERRORLEVEL% NEQ 0 (\n    echo === failed to set the test parameters\n    exit /b 1\n)\nset SNOWFLAKE_TEST_SCHEMA=%RUNNER_TRACKING_ID:-=_%_%GITHUB_SHA%\nset TARGET_SCHEMA_NAME=%SNOWFLAKE_TEST_SCHEMA%\n\necho [INFO] Account:   %SNOWFLAKE_TEST_ACCOUNT%\necho [INFO] User   :   %SNOWFLAKE_TEST_USER%\necho [INFO] Database:  %SNOWFLAKE_TEST_DATABASE%\necho [INFO] Schema:    %SNOWFLAKE_TEST_SCHEMA%\necho [INFO] Warehouse: %SNOWFLAKE_TEST_WAREHOUSE%\necho [INFO] Role:      %SNOWFLAKE_TEST_ROLE%\necho [INFO] PROVIDER:  %CLOUD_PROVIDER%\n\necho [INFO] Creating schema %SNOWFLAKE_TEST_SCHEMA%\npushd %GITHUB_WORKSPACE%\\ci\\container\npython create_schema.py\npopd\n\nREM setup log\n\nset CLIENT_LOG_DIR_PATH=%GITHUB_WORKSPACE%\\jenkins_rt_logs\necho \"[INFO] CLIENT_LOG_DIR_PATH=%CLIENT_LOG_DIR_PATH%\"\n\nset CLIENT_LOG_FILE_PATH=%CLIENT_LOG_DIR_PATH%\\ssnowflake_ssm_rt.log\necho \"[INFO] CLIENT_LOG_FILE_PATH=%CLIENT_LOG_FILE_PATH%\"\n\nset CLIENT_KNOWN_SSM_FILE_PATH=%CLIENT_LOG_DIR_PATH%\\rt_jenkins_log_known_ssm.txt\necho \"[INFO] CLIENT_KNOWN_SSM_FILE_PATH=%CLIENT_KNOWN_SSM_FILE_PATH%\"\n\nREM To close log analyze, just set ENABLE_CLIENT_LOG_ANALYZE to not \"true\", e.g. \"false\".\nset ENABLE_CLIENT_LOG_ANALYZE=true\n\nREM The new complex password we use for jenkins test\nset SNOWFLAKE_TEST_PASSWORD_NEW=\"ThisIsRandomPassword123!\"\n\nset LOG_PROPERTY_FILE=%GITHUB_WORKSPACE%\\src\\test\\resources\\logging.properties\n\necho \"[INFO] LOG_PROPERTY_FILE=%LOG_PROPERTY_FILE%\"\n\nset CLIENT_DRIVER_NAME=JDBC\n\npowershell -Command \"(Get-Content %LOG_PROPERTY_FILE%) | Foreach-Object { $_ -replace '^java.util.logging.FileHandler.pattern.*', 'java.util.logging.FileHandler.pattern = %CLIENT_LOG_FILE_PATH%' } | Set-Content %LOG_PROPERTY_FILE%\"\n\necho \"[INFO] Create log directory\"\n\nIF NOT EXIST %CLIENT_LOG_DIR_PATH% MD %CLIENT_LOG_DIR_PATH% 2>nul\n\necho \"[INFO] Delete ssm file\"\nIF EXIST \"%CLIENT_KNOWN_SSM_FILE_PATH%\" DEL /F /Q \"%CLIENT_KNOWN_SSM_FILE_PATH%\"\n\necho \"[INFO] Create ssm file\"\necho.>\"%CLIENT_KNOWN_SSM_FILE_PATH%\"\n\necho \"[INFO] Finish log setup\"\nREM end setup log\n\nfor /F \"tokens=1,* delims==\" %%i in ('set ^| findstr /I /R \"^SNOWFLAKE_[^=]*$\" ^| findstr /I /V /R \"^SNOWFLAKE_(PASS|.*KEY|.*SECRET|.*TOKEN)_[^=]*$\" ^| sort') do (\n  echo %%i=%%j\n)\n\necho [INFO] Starting hang_webserver.py 12345\npushd %GITHUB_WORKSPACE%\\ci\\container\nstart /b python hang_webserver.py 12345 > hang_webserver.out 2>&1\npopd\n\necho [INFO] Testing\n\nset MVNW_EXE=%GITHUB_WORKSPACE%\\mvnw.cmd\n\nREM Avoid connection timeouts\nset MAVEN_OPTS=\"-Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3 -Dmaven.wagon.httpconnectionManager.ttlSeconds=120\"\necho \"MAVEN OPTIONS %MAVEN_OPTS%\"\n\nREM Avoid connection timeout on plugin dependency fetch or fail-fast when dependency cannot be fetched after 3 retries\nREM Retry dependency:go-offline up to 3 times if it fails\nfor /l %%i in (1,1,3) do (\n    echo [INFO] maven dependency:go-offline attempt %%i/3\n    cmd /c \"%MVNW_EXE%\" --batch-mode --show-version dependency:go-offline\n    if !errorlevel! equ 0 goto :success\n    if %%i equ 3 exit /b 1\n    echo [WARN] Retrying in 5 seconds...\n    timeout /t 5 /nobreak >nul\n)\n:success\n\nif \"%JDBC_TEST_SUITES%\"==\"FipsTestSuite\" (\n    pushd FIPS\n    echo \"[INFO] Run Fips tests\"\n    cmd /c %MVNW_EXE% -B -DjenkinsIT ^\n        -Dskip.unitTests=true ^\n        -Djava.io.tmpdir=%GITHUB_WORKSPACE% ^\n        -Djacoco.skip.instrument=false ^\n        -DintegrationTestSuites=FipsTestSuite ^\n        -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn ^\n        -Dnot-self-contained-jar ^\n        verify ^\n        --batch-mode --show-version > log.txt & type log.txt\n    echo \"[INFO] Check for test execution status\"\n    find /i /c \"BUILD FAILURE\" log.txt > NUL\n    set isfound=!errorlevel!\n    if !isfound! equ 0 (\n        echo [ERROR] Failed run %%a test\n        exit /b 1\n    ) else (\n        echo [INFO] Success run %%a test\n    )\n    popd\n) else (\n    echo \"[INFO] Run %JDBC_TEST_SUITES% tests\"\n    cmd /c %MVNW_EXE% -B -DjenkinsIT ^\n        -Dskip.unitTests=true ^\n        -Djava.io.tmpdir=%GITHUB_WORKSPACE% ^\n        -Djacoco.skip.instrument=false ^\n        -DintegrationTestSuites=\"%JDBC_TEST_SUITES%\" ^\n        -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn ^\n        -Dnot-self-contained-jar %ADDITIONAL_MAVEN_PROFILE% ^\n        verify ^\n        --batch-mode --show-version > log.txt & type log.txt\n    echo \"[INFO] Check for test execution status\"\n    find /i /c \"BUILD FAILURE\" log.txt > NUL\n    set isfound=!errorlevel!\n    if !isfound! equ 0 (\n        echo [ERROR] Failed run %%a test\n        exit /b 1\n    ) else (\n        echo [INFO] Success run %%a test\n    )\n)\n\necho [INFO] Dropping schema %SNOWFLAKE_TEST_SCHEMA%\npushd %GITHUB_WORKSPACE%\\ci\\container\npython drop_schema.py\npopd\n"
  },
  {
    "path": "ci/wif/aws-lambda/README.md",
    "content": "# AWS Lambda Function for WIF E2E Testing\n\n## Deployment Steps\n1. Install AWS CLI\n2. Configure AWS credentials: `aws configure`. Use credentials: WIF E2E AWS LAMBDA DEPLOY\n3. It may be required to unset AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY if it points to other aws account.\n4. Run deployment script: `./deploy.sh`\n"
  },
  {
    "path": "ci/wif/aws-lambda/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.snowflake.wif</groupId>\n    <artifactId>aws-lambda</artifactId>\n    <version>1.0-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>AWS Lambda Functions</name>\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <java.version>17</java.version>\n        <aws.lambda.java.core.version>1.2.3</aws.lambda.java.core.version>\n        <aws.lambda.java.events.version>3.11.3</aws.lambda.java.events.version>\n        <maven.shade.plugin.version>3.4.1</maven.shade.plugin.version>\n        \n        <!-- AWS Lambda Configuration -->\n        <lambda.functionName>drivers-wif-e2e</lambda.functionName>\n        <lambda.region>us-west-2</lambda.region>\n        <lambda.runtime>java17</lambda.runtime>\n        <lambda.timeout>480</lambda.timeout>\n        <lambda.memorySize>3008</lambda.memorySize>\n        <lambda.handler>com.snowflake.wif.aws.WifLambdaFunctionE2e::handleRequest</lambda.handler>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.amazonaws</groupId>\n            <artifactId>aws-lambda-java-core</artifactId>\n            <version>${aws.lambda.java.core.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>com.amazonaws</groupId>\n            <artifactId>aws-lambda-java-events</artifactId>\n            <version>${aws.lambda.java.events.version}</version>\n        </dependency>\n\n        <!-- JSON processing -->\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.15.2</version>\n        </dependency>\n        \n        <!-- TAR/GZIP extraction -->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-compress</artifactId>\n            <version>1.28.0</version>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>5.9.2</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-core</artifactId>\n            <version>5.3.1</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <sourceDirectory>src/main/java</sourceDirectory>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>build-helper-maven-plugin</artifactId>\n                <version>3.4.0</version>\n                <executions>\n                    <execution>\n                        <phase>generate-sources</phase>\n                        <goals>\n                            <goal>add-source</goal>\n                        </goals>\n                        <configuration>\n                            <sources>\n                                <source>../shared</source>\n                            </sources>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.11.0</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>${maven.shade.plugin.version}</version>\n                <configuration>\n                    <createDependencyReducedPom>false</createDependencyReducedPom>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <artifactId>maven-clean-plugin</artifactId>\n                <version>3.2.0</version>\n                <configuration>\n                    <filesets>\n                        <fileset>\n                            <directory>target</directory>\n                        </fileset>\n                    </filesets>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>"
  },
  {
    "path": "ci/wif/aws-lambda/src/main/java/com/snowflake/wif/aws/WifLambdaFunctionE2e.java",
    "content": "package com.snowflake.wif.aws;\n\nimport com.amazonaws.services.lambda.runtime.Context;\nimport com.amazonaws.services.lambda.runtime.RequestHandler;\nimport com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.snowflake.wif.common.WifTestHelper;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class WifLambdaFunctionE2e implements RequestHandler<Object, APIGatewayProxyResponseEvent> {\n\n    private static final ObjectMapper objectMapper = new ObjectMapper();\n\n    private static class LambdaLogger implements WifTestHelper.WifLogger {\n        private final Context context;\n        \n        public LambdaLogger(Context context) {\n            this.context = context;\n        }\n        \n        @Override\n        public void log(String message) {\n            context.getLogger().log(message);\n        }\n    }\n\n    @Override\n    public APIGatewayProxyResponseEvent handleRequest(Object input, Context context) {\n        WifTestHelper.WifLogger logger = new LambdaLogger(context);\n        String workingDirectory = null;\n        \n        try {\n            logger.log(\"=== WIF AWS Lambda Function E2E started ===\");\n            logger.log(\"Input received: \" + objectMapper.writeValueAsString(input));\n            \n            Map<String, String> queryParameters = extractQueryParameters(input, logger);\n            \n            WifTestHelper.validateQueryParameters(queryParameters);\n\n            String branch = queryParameters.get(\"BRANCH\");\n            String tarballUrl = WifTestHelper.buildTarballUrl(branch);\n\n            workingDirectory = WifTestHelper.downloadAndExtractRepository(tarballUrl, logger);\n            String repoFolderPath = WifTestHelper.findRepositoryFolder(workingDirectory);\n            WifTestHelper.makeExecutable(repoFolderPath, logger);\n            int mavenExitCode = WifTestHelper.executeMavenBuild(repoFolderPath, System.getProperty(\"java.io.tmpdir\"), logger, queryParameters);\n\n            return createResponse(mavenExitCode);\n        } catch (Exception e) {\n            logger.log(\"Error: \" + e.getMessage());\n            return createErrorResponse(500, \"Error: \" + e.getMessage());\n        } finally {\n            WifTestHelper.cleanupWorkingDirectory(workingDirectory, logger);\n        }\n    }\n    \n    private Map<String, String> extractQueryParameters(Object input, WifTestHelper.WifLogger logger) throws Exception {\n        JsonNode inputNode = objectMapper.valueToTree(input);\n        \n        // Handle Lambda Function URL format (query parameters in queryStringParameters)\n        if (inputNode.has(\"queryStringParameters\")) {\n            JsonNode queryParamsNode = inputNode.get(\"queryStringParameters\");\n            if (queryParamsNode != null && !queryParamsNode.isNull()) {\n                logger.log(\"Processing Lambda Function URL with queryStringParameters\");\n                return objectMapper.convertValue(queryParamsNode, Map.class);\n            }\n        }\n        \n        // Handle direct invocation format (parameters at root level)\n        if (inputNode.has(\"SNOWFLAKE_TEST_WIF_HOST\")) {\n            logger.log(\"Processing direct invocation with parameters at root level\");\n            return objectMapper.convertValue(inputNode, Map.class);\n        }\n        \n        throw new IllegalArgumentException(\"Invalid input format: expected Lambda Function URL event or direct invocation with required parameters\");\n    }\n    \n    private APIGatewayProxyResponseEvent createResponse(int mavenExitCode) {\n        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();\n        Map<String, String> headers = new HashMap<>();\n        headers.put(\"Content-Type\", \"text/plain\");\n        response.setHeaders(headers);\n\n        String responseBody = WifTestHelper.createMavenResultMessage(mavenExitCode);\n        \n        if (mavenExitCode == 0) {\n            response.setStatusCode(200);\n        } else {\n            response.setStatusCode(500);\n        }\n        \n        response.setBody(responseBody);\n        return response;\n    }\n\n    private APIGatewayProxyResponseEvent createErrorResponse(int statusCode, String message) {\n        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();\n        Map<String, String> headers = new HashMap<>();\n        headers.put(\"Content-Type\", \"text/plain\");\n        response.setHeaders(headers);\n        response.setStatusCode(statusCode);\n        response.setBody(message);\n        return response;\n    }\n}"
  },
  {
    "path": "ci/wif/aws-lambda/test.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\n\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\nrun_aws_function() {\n  if [[ -z \"$WIF_E2E_AWS_ACCESS_KEY\" ]] || [[ -z \"$WIF_E2E_AWS_SECRET_ACCESS_KEY\" ]]; then\n    echo \"Error: WIF_E2E_AWS_ACCESS_KEY and WIF_E2E_AWS_SECRET_ACCESS_KEY environment variables must be set\"\n    return 1\n  fi\n  \n  # Clear potentially conflicting AWS environment variables\n  unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN\n  unset AWS_PROFILE AWS_CONFIG_FILE AWS_SHARED_CREDENTIALS_FILE\n\n  # Set AWS credentials for CLI\n  export AWS_ACCESS_KEY_ID=\"$WIF_E2E_AWS_ACCESS_KEY\"\n  export AWS_SECRET_ACCESS_KEY=\"$WIF_E2E_AWS_SECRET_ACCESS_KEY\"\n  export AWS_DEFAULT_REGION=\"us-west-2\"\n  export AWS_REGION=\"us-west-2\"\n  \n  # Check AWS CLI version to determine the correct command format\n  local aws_version\n  aws_version=$(aws --version 2>&1 | head -n1)\n\n  local payload_json=\"{\\\"queryStringParameters\\\":{\\\"SNOWFLAKE_TEST_WIF_HOST\\\":\\\"${SNOWFLAKE_TEST_WIF_HOST_AWS}\\\",\\\"SNOWFLAKE_TEST_WIF_ACCOUNT\\\":\\\"${SNOWFLAKE_TEST_WIF_ACCOUNT}\\\",\\\"SNOWFLAKE_TEST_WIF_PROVIDER\\\":\\\"AWS\\\",\\\"BRANCH\\\":\\\"${BRANCH}\\\"}}\"\n  local function_name=\"drivers-wif-automated-tests\"\n  \n  local cli_response_file=\"/tmp/aws_cli_response_$$.json\"\n  local cli_error_file=\"/tmp/aws_cli_error_$$.txt\"\n    \n  # Use different command based on AWS CLI version\n  if [[ \"$aws_version\" =~ aws-cli/2\\. ]]; then\n    # AWS CLI v2 - needs --cli-binary-format flag\n    echo \"Using AWS CLI v2 format\"\n    aws lambda invoke \\\n      --function-name \"$function_name\" \\\n      --region \"us-west-2\" \\\n      --cli-binary-format raw-in-base64-out \\\n      --payload \"$payload_json\" \\\n      --cli-read-timeout 1000 \\\n      --cli-connect-timeout 60 \\\n      \"$cli_response_file\" >/dev/null 2>\"$cli_error_file\"\n  else\n    # AWS CLI v1 - no --cli-binary-format flag needed, but include timeouts\n    echo \"Using AWS CLI v1 format\"\n    aws lambda invoke \\\n      --function-name \"$function_name\" \\\n      --region \"us-west-2\" \\\n      --payload \"$payload_json\" \\\n      --cli-read-timeout 1000 \\\n      --cli-connect-timeout 60 \\\n      \"$cli_response_file\" >/dev/null 2>\"$cli_error_file\"\n  fi\n  \n  local invoke_result=$?\n  if [[ $invoke_result -eq 0 ]]; then\n\n    if [[ -f \"$cli_response_file\" ]]; then\n      # Check if the response indicates success (HTTP 200)\n      if grep -q '\"statusCode\":200' \"$cli_response_file\" 2>/dev/null; then\n        echo \"AWS Lambda Function test passed (HTTP 200)\"\n        rm -f \"$cli_response_file\" \"$cli_error_file\"\n        return 0\n      else\n        echo \"AWS Lambda Function test failed (non-200 status)\"\n        rm -f \"$cli_response_file\" \"$cli_error_file\"\n        return 1\n      fi\n    else\n      echo \"No response file found\"\n      rm -f \"$cli_response_file\" \"$cli_error_file\"\n      return 1\n    fi\n  else\n    echo \"AWS CLI invocation failed\"\n    if [[ -f \"$cli_error_file\" ]]; then\n      echo \"CLI Error:\"\n      cat \"$cli_error_file\"\n    fi\n    rm -f \"$cli_response_file\" \"$cli_error_file\"\n    return 1\n  fi\n}\n\nif [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then\n  run_aws_function\n  exit $?\nfi\n"
  },
  {
    "path": "ci/wif/azure-function/Dockerfile",
    "content": "# Build stage\nFROM --platform=linux/amd64 mcr.microsoft.com/azure-functions/java:4-java17-build AS build\n\nCOPY azure-function /src/azure-function\nCOPY shared /src/shared\n\n# Build the function\nRUN cd /src/azure-function && \\\n    MAVEN_OPTS=\"-Xmx4g -Xms1g\" mvn clean package -DskipTests && \\\n    mkdir -p /home/site/wwwroot && \\\n    cp -r target/azure-functions/drivers-wif-e2e/* /home/site/wwwroot/\n\n# Runtime stage\nFROM --platform=linux/amd64 mcr.microsoft.com/azure-functions/java:4-java17\n\nRUN apt-get update && \\\n    apt-get install -y openjdk-17-jdk && \\\n    apt-get clean && \\\n    rm -rf /var/lib/apt/lists/*\n\nENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \\\n    PATH=\"/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH\" \\\n    AzureWebJobsScriptRoot=/home/site/wwwroot \\\n    AzureFunctionsJobHost__Logging__Console__IsEnabled=true \\\n    MAVEN_OPTS=\"-Xmx4g -Xms1g\"\n\nCOPY --from=build /home/site/wwwroot /home/site/wwwroot\n"
  },
  {
    "path": "ci/wif/azure-function/README.md",
    "content": "# Azure Function for WIF E2E Testing\n\n## Deployment Steps\n1. Install Azure CLI\n2. Authenticate: `az login`, login in browser.\n3. Run deployment script: `./deploy.sh`\n"
  },
  {
    "path": "ci/wif/azure-function/host.json",
    "content": "{\n  \"version\": \"2.0\",\n  \"extensionBundle\": {\n    \"id\": \"Microsoft.Azure.Functions.ExtensionBundle\",\n    \"version\": \"[4.0.0, 5.0.0)\"\n  },\n  \"functionTimeout\": \"00:08:00\",\n  \"concurrency\": {\n    \"dynamicConcurrencyEnabled\": true,\n    \"snapshotPersistenceEnabled\": true\n  },\n  \"logging\": {\n    \"logLevel\": {\n      \"default\": \"Information\"\n    }\n  }\n}\n"
  },
  {
    "path": "ci/wif/azure-function/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.snowflake.wif</groupId>\n    <artifactId>azure-function</artifactId>\n    <version>1.0-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>Azure Java Functions</name>\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <java.version>17</java.version>\n        <azure.functions.maven.plugin.version>1.35.0</azure.functions.maven.plugin.version>\n        <azure.functions.java.library.version>2.2.0</azure.functions.java.library.version>\n        <functionAppName>drivers-wif-e2e</functionAppName>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.microsoft.azure.functions</groupId>\n            <artifactId>azure-functions-java-library</artifactId>\n            <version>${azure.functions.java.library.version}</version>\n        </dependency>\n        <!-- Test -->\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>5.4.2</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-core</artifactId>\n            <version>2.23.4</version>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- JSON processing - needed for WifTestHelper -->\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.15.2</version>\n        </dependency>\n        \n        <!-- TAR/GZIP extraction - needed for WifTestHelper -->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-compress</artifactId>\n            <version>1.28.0</version>\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.4.0</version>\n                <executions>\n                    <execution>\n                        <phase>generate-sources</phase>\n                        <goals>\n                            <goal>add-source</goal>\n                        </goals>\n                        <configuration>\n                            <sources>\n                                <source>../shared</source>\n                            </sources>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.11.0</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                    <encoding>${project.build.sourceEncoding}</encoding>\n                </configuration>\n            </plugin>\n            <!-- Azure Functions Maven plugin to generate function metadata -->\n            <plugin>\n                <groupId>com.microsoft.azure</groupId>\n                <artifactId>azure-functions-maven-plugin</artifactId>\n                <version>${azure.functions.maven.plugin.version}</version>\n                <configuration>\n                    <appName>${functionAppName}</appName>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>package-functions</id>\n                        <goals>\n                            <goal>package</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <artifactId>maven-clean-plugin</artifactId>\n                <version>3.2.0</version>\n                <configuration>\n                    <filesets>\n                        <fileset>\n                            <directory>obj</directory>\n                        </fileset>\n                    </filesets>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "ci/wif/azure-function/src/main/java/com/snowflake/wif/azure/WifAzureFunctionE2e.java",
    "content": "package com.snowflake.wif.azure;\n\nimport com.microsoft.azure.functions.*;\nimport com.microsoft.azure.functions.annotation.*;\nimport com.snowflake.wif.common.WifTestHelper;\n\nimport java.io.File;\nimport java.util.Optional;\n\npublic class WifAzureFunctionE2e {\n\n    private static class AzureLogger implements WifTestHelper.WifLogger {\n        private final ExecutionContext context;\n        \n        public AzureLogger(ExecutionContext context) {\n            this.context = context;\n        }\n        \n        @Override\n        public void log(String message) {\n            context.getLogger().info(message);\n        }\n    }\n\n    @FunctionName(\"WifAzureFunctionE2e\")\n    public HttpResponseMessage run(\n            @HttpTrigger(\n                    name = \"req\",\n                    methods = {HttpMethod.GET, HttpMethod.POST},\n                    authLevel = AuthorizationLevel.FUNCTION)\n            HttpRequestMessage<Optional<String>> request,\n            final ExecutionContext context) {\n        WifTestHelper.WifLogger logger = new AzureLogger(context);\n        String workingDirectory = null;\n        \n        try {\n            logger.log(\"=== WIF Azure Function E2E started ===\");\n            WifTestHelper.validateQueryParameters(request.getQueryParameters());\n\n            String branch = request.getQueryParameters().get(\"BRANCH\");\n            String tarballUrl = WifTestHelper.buildTarballUrl(branch);\n\n            // Download and extract to timestamp-based directory\n            workingDirectory = WifTestHelper.downloadAndExtractRepository(tarballUrl, logger);\n            String repoFolderPath = WifTestHelper.findRepositoryFolder(workingDirectory);\n            WifTestHelper.makeExecutable(repoFolderPath, logger);\n            int mavenExitCode = WifTestHelper.executeMavenBuild(repoFolderPath, System.getProperty(\"java.io.tmpdir\"), logger, request.getQueryParameters());\n\n            return createResponse(request, mavenExitCode);\n        } catch (Exception e) {\n            logger.log(\"Error: \" + e.getMessage());\n            return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).body(\"Error: \" + e.getMessage()).build();\n        } finally {\n            WifTestHelper.cleanupWorkingDirectory(workingDirectory, logger);\n        }\n    }\n    \n    private HttpResponseMessage createResponse(HttpRequestMessage<Optional<String>> request, int mavenExitCode) {\n        String responseBody = WifTestHelper.createMavenResultMessage(mavenExitCode);\n        \n        if (mavenExitCode == 0) {\n            return request.createResponseBuilder(HttpStatus.OK).body(responseBody).build();\n        } else {\n            return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).body(responseBody).build();\n        }\n    }\n}"
  },
  {
    "path": "ci/wif/azure-function/test.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\n\nrun_azure_function() {\n  echo \"Running Azure Function E2E test...\"\n  \n  local url=\"${AZURE_FUNCTION_BASE_URL}?code=${AZURE_FUNCTION_CODE}\"\n  url=\"${url}&BRANCH=${BRANCH}\"\n  url=\"${url}&SNOWFLAKE_TEST_WIF_HOST=${SNOWFLAKE_TEST_WIF_HOST_AZURE}\"\n  url=\"${url}&SNOWFLAKE_TEST_WIF_ACCOUNT=${SNOWFLAKE_TEST_WIF_ACCOUNT}\"\n  url=\"${url}&SNOWFLAKE_TEST_WIF_PROVIDER=AZURE\"\n \n  local http_code\n  http_code=$(curl -s -o /dev/null -w \"%{http_code}\" --max-time 1200 \"$url\")\n  \n  if [[ \"$http_code\" == \"200\" ]]; then\n    echo \"Azure Function test passed\"\n    return 0\n  else\n    echo \"Azure Function test failed with HTTP $http_code\"\n    return 1\n  fi\n}\n\nif [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then\n  run_azure_function\n  exit $?\nfi\n"
  },
  {
    "path": "ci/wif/gcp-function/README.md",
    "content": "# GCP Cloud Function for WIF E2E Testing\n\n## Deployment Steps\n1. Install Google Cloud SDK.\n2. Authenticate: `gcloud auth login`, login in browser.\n3. Run deployment script: `./deploy.sh <PROJECT_ID> <SERVICE_ACCOUNT>`. Use credentials: WIF E2E GCP FUNCTION DEPLOY\n"
  },
  {
    "path": "ci/wif/gcp-function/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.snowflake.wif</groupId>\n    <artifactId>gcp-function</artifactId>\n    <version>1.0-SNAPSHOT</version>\n    <packaging>jar</packaging>\n\n    <name>GCP Cloud Functions</name>\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <java.version>17</java.version>\n        <functions.framework.version>1.1.0</functions.framework.version>\n        <function.maven.plugin.version>0.11.0</function.maven.plugin.version>\n        \n        <!-- GCP Function Configuration -->\n        <function.target>com.snowflake.wif.gcp.WifGcpFunctionE2e</function.target>\n        <function.name>drivers-wif-e2e-gcp</function.name>\n        <function.region>us-central1</function.region>\n        <function.memory>1024MB</function.memory>\n        <function.timeout>480</function.timeout>\n    </properties>\n\n    <dependencies>\n        <!-- Google Cloud Functions Framework -->\n        <dependency>\n            <groupId>com.google.cloud.functions</groupId>\n            <artifactId>functions-framework-api</artifactId>\n            <version>${functions.framework.version}</version>\n            <scope>provided</scope>\n        </dependency>\n        \n        <!-- Test dependencies -->\n        <dependency>\n            <groupId>org.junit.jupiter</groupId>\n            <artifactId>junit-jupiter</artifactId>\n            <version>5.4.2</version>\n            <scope>test</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-core</artifactId>\n            <version>2.23.4</version>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- JSON processing - needed for WifTestHelper -->\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <version>2.15.2</version>\n        </dependency>\n        \n        <!-- TAR/GZIP extraction - needed for WifTestHelper -->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-compress</artifactId>\n            <version>1.28.0</version>\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.4.0</version>\n                <executions>\n                    <execution>\n                        <phase>generate-sources</phase>\n                        <goals>\n                            <goal>add-source</goal>\n                        </goals>\n                        <configuration>\n                            <sources>\n                                <source>../shared</source>\n                            </sources>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>3.8.1</version>\n                <configuration>\n                    <source>${java.version}</source>\n                    <target>${java.version}</target>\n                </configuration>\n            </plugin>\n            \n            <!-- Google Cloud Functions Maven Plugin -->\n            <plugin>\n                <groupId>com.google.cloud.functions</groupId>\n                <artifactId>function-maven-plugin</artifactId>\n                <version>${function.maven.plugin.version}</version>\n                <configuration>\n                    <functionTarget>${function.target}</functionTarget>\n                    <port>8080</port>\n                </configuration>\n            </plugin>\n            \n            <!-- Maven Shade Plugin for creating fat JAR -->\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>3.4.1</version>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                        <configuration>\n                            <createDependencyReducedPom>false</createDependencyReducedPom>\n                            <outputDirectory>target/deploy</outputDirectory>\n                            <finalName>${function.name}</finalName>\n                            <transformers>\n                                <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\">\n                                    <mainClass>${function.target}</mainClass>\n                                </transformer>\n                            </transformers>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "ci/wif/gcp-function/src/main/java/com/snowflake/wif/gcp/WifGcpFunctionE2e.java",
    "content": "package com.snowflake.wif.gcp;\n\nimport com.google.cloud.functions.HttpFunction;\nimport com.google.cloud.functions.HttpRequest;\nimport com.google.cloud.functions.HttpResponse;\nimport com.snowflake.wif.common.WifTestHelper;\n\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.logging.Logger;\n\nimport static java.net.URLDecoder.*;\nimport static java.util.stream.Collectors.*;\n\npublic class WifGcpFunctionE2e implements HttpFunction {\n    \n    private static final Logger logger = Logger.getLogger(WifGcpFunctionE2e.class.getName());\n\n    private static class GcpLogger implements WifTestHelper.WifLogger {\n        private final Logger logger;\n        \n        public GcpLogger(Logger logger) {\n            this.logger = logger;\n        }\n        \n        @Override\n        public void log(String message) {\n            logger.info(message);\n        }\n    }\n\n    private static String createGcpResponseMessage(int mavenExitCode, String additionalInfo) {\n        StringBuilder response = new StringBuilder();\n        response.append(\"=== WIF GCP Function E2E Test Results ===\\n\");\n        response.append(WifTestHelper.createMavenResultMessage(mavenExitCode));\n        \n        if (additionalInfo != null && !additionalInfo.trim().isEmpty()) {\n            response.append(\"\\n\\nAdditional Info:\\n\");\n            response.append(additionalInfo);\n        }\n        \n        response.append(\"\\n\\nFunction Runtime: Google Cloud Functions (Java 17)\");\n        response.append(\"\\nExecution Environment: GCP\");\n        \n        return response.toString();\n    }\n\n    private static void performGcpCleanup(String workingDirectory, WifTestHelper.WifLogger logger) {\n        logger.log(\"Performing GCP-specific cleanup...\");\n        \n        try {\n            WifTestHelper.cleanupWorkingDirectory(workingDirectory, logger);\n            logger.log(\"GCP cleanup completed successfully\");\n        } catch (Exception e) {\n            logger.log(\"Warning: GCP cleanup encountered issues: \" + e.getMessage());\n        }\n    }\n\n    @Override\n    public void service(HttpRequest request, HttpResponse response) throws IOException {\n        WifTestHelper.WifLogger wifLogger = new GcpLogger(logger);\n        String workingDirectory = null;\n        \n        try {\n            wifLogger.log(\"=== WIF GCP Function E2E started ===\");\n            Map<String, String> queryParameters = extractQueryParameters(request);\n            String branch = queryParameters.get(\"BRANCH\");\n            String tarballUrl = WifTestHelper.buildTarballUrl(branch);\n\n            // Download and extract to timestamp-based directory\n            workingDirectory = WifTestHelper.downloadAndExtractRepository(tarballUrl, wifLogger);\n            String repoFolderPath = WifTestHelper.findRepositoryFolder(workingDirectory);\n            WifTestHelper.makeExecutable(repoFolderPath, wifLogger);\n            \n            int mavenExitCode = WifTestHelper.executeMavenBuild(repoFolderPath, System.getProperty(\"java.io.tmpdir\"), wifLogger, queryParameters);\n\n            createResponse(response, mavenExitCode, wifLogger);\n        } catch (Exception e) {\n            wifLogger.log(\"Error occurred: \" + e.getMessage());\n            if (e.getCause() != null) {\n                wifLogger.log(\"Caused by: \" + e.getCause().getMessage());\n            }\n            createErrorResponse(response, 500, \"Error: \" + e.getMessage());\n        } finally {\n            performGcpCleanup(workingDirectory, wifLogger);\n        }\n    }\n    \n    private Map<String, String> extractQueryParameters(HttpRequest request) throws IOException {\n        Map<String, String> params = new HashMap<>();\n        \n        // Handle GET request query parameters\n        Map<String, List<String>> queryParams = request.getQueryParameters();\n        for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {\n            if (!entry.getValue().isEmpty()) {\n                params.put(entry.getKey(), entry.getValue().get(0));\n            }\n        }\n        \n        // Handle POST request form data\n        if (\"POST\".equalsIgnoreCase(request.getMethod())) {\n            String contentType = request.getContentType().orElse(\"\");\n            if (contentType.contains(\"application/x-www-form-urlencoded\")) {\n                String body = request.getReader().lines().collect(joining(\"\\n\"));\n                if (body != null && !body.trim().isEmpty()) {\n                    String[] pairs = body.split(\"&\");\n                    for (String pair : pairs) {\n                        String[] keyValue = pair.split(\"=\", 2);\n                        if (keyValue.length == 2) {\n                            try {\n                                String key = decode(keyValue[0], \"UTF-8\");\n                                String value = decode(keyValue[1], \"UTF-8\");\n                                params.put(key, value);\n                            } catch (Exception e) {\n                                // Log but continue with other parameters\n                                logger.warning(\"Failed to decode parameter: \" + pair);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        \n        return params;\n    }\n    \n    private void createResponse(HttpResponse response, int mavenExitCode, WifTestHelper.WifLogger logger) throws IOException {\n        String responseBody = createGcpResponseMessage(mavenExitCode, \"GCP Function execution completed\");\n        \n        logger.log(\"Maven build completed with exit code: \" + mavenExitCode);\n        \n        if (mavenExitCode == 0) {\n            response.setStatusCode(200);\n            logger.log(\"Returning success response\");\n        } else {\n            response.setStatusCode(500);\n            logger.log(\"Returning error response due to Maven build failure\");\n        }\n        \n        response.setContentType(\"text/plain; charset=utf-8\");\n        try (BufferedWriter writer = response.getWriter()) {\n            writer.write(responseBody);\n        }\n    }\n\n    private void createErrorResponse(HttpResponse response, int statusCode, String message) throws IOException {\n        response.setStatusCode(statusCode);\n        response.setContentType(\"text/plain; charset=utf-8\");\n        try (BufferedWriter writer = response.getWriter()) {\n            writer.write(message);\n        }\n    }\n}\n"
  },
  {
    "path": "ci/wif/gcp-function/test.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\n\ngenerate_gcp_jwt() {\n  # For ID token request, aud should be Google's token endpoint, target_audience is the service\n  local target_audience=\"${GCP_FUNCTION_BASE_URL}\"\n  \n  local iat=$(date +%s)\n  local exp=$((iat + 3600))  # 1 hour expiration\n  \n  # Create JWT header\n  local header='{\"alg\":\"RS256\",\"typ\":\"JWT\"}'\n  local header_b64=$(echo -n \"$header\" | base64 -w 0 | tr -d '=' | tr '/+' '_-')\n  \n  # Create JWT payload for ID token reques\n  local payload=\"{\\\"iss\\\":\\\"$GCP_SERVICE_ACCOUNT\\\",\\\"aud\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"target_audience\\\":\\\"$target_audience\\\",\\\"exp\\\":$exp,\\\"iat\\\":$iat}\"\n  local payload_b64=$(echo -n \"$payload\" | base64 -w 0 | tr -d '=' | tr '/+' '_-')\n  \n  # Create signature using private key from file\n  local to_sign=\"$header_b64.$payload_b64\"\n  local signature=$(echo -n \"$to_sign\" | openssl dgst -sha256 -sign \"$RSA_GCP_FUNCTION_KEY\" | base64 -w 0 | tr -d '=' | tr '/+' '_-')\n  \n  echo \"$header_b64.$payload_b64.$signature\"\n}\n\nget_gcp_id_token() {\n  local jwt=\"$1\"\n  \n  local response=$(curl -s -X POST https://oauth2.googleapis.com/token \\\n    -H \"Content-Type: application/x-www-form-urlencoded\" \\\n    -d \"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=$jwt\")\n  \n  echo \"$response\" | grep -o '\"id_token\":\"[^\"]*\"' | cut -d'\"' -f4\n}\n\nrun_gcp_function() {\n  echo \"Running GCP Cloud Function E2E test...\"\n  \n  local jwt=$(generate_gcp_jwt)\n  if [[ -z \"$jwt\" ]]; then\n    echo \"Error: Failed to generate JWT\"\n    return 1\n  fi\n  \n  local id_token=$(get_gcp_id_token \"$jwt\")\n  \n  if [[ -z \"$id_token\" ]]; then\n    echo \"Error: Failed to get GCP ID token\"\n    return 1\n  fi\n  \n  local url=\"${GCP_FUNCTION_BASE_URL}?SNOWFLAKE_TEST_WIF_HOST=${SNOWFLAKE_TEST_WIF_HOST_GCP}\"\n  url=\"${url}&SNOWFLAKE_TEST_WIF_ACCOUNT=${SNOWFLAKE_TEST_WIF_ACCOUNT}\"\n  url=\"${url}&SNOWFLAKE_TEST_WIF_PROVIDER=GCP\"\n  url=\"${url}&BRANCH=${BRANCH}\"\n  url=\"${url}&IS_GCP_FUNCTION=true\"\n  \n  local http_code\n  http_code=$(curl -s -o /dev/null -w \"%{http_code}\" --max-time 1200 \\\n    -H \"Authorization: Bearer $id_token\" \\\n    \"$url\")\n  \n  if [[ \"$http_code\" == \"200\" ]]; then\n    echo \"GCP Cloud Function test passed\"\n    return 0\n  else\n    echo \"GCP Cloud Function test failed with HTTP $http_code\"\n    return 1\n  fi\n}\n\nif [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then\n  run_gcp_function\n  exit $?\nfi\n"
  },
  {
    "path": "ci/wif/parameters/rsa_wif_aws_azure.gpg",
    "content": "\r\u0004\t\u0003\b髃6K\u00015%܇ټ飐\u000f|eRk]n\u000fc-TloB,\u0011ܐ͒R7B]<ER-|\u00044u'K2<\u0013:BC&fԳeX%i9\u00060@LG\u0011$a\u0004hOnTejB@\u001a)~Nto&\u0003Ȍ\u0012\u0013ru/i\u001cuq%0\u0012!]ݮ2-lw\u0018`!ʚgF߬\u0019i\u0010DDĘX\u0013..d\"^\ruA,ۅA\u000es\u001a\\ؕa\u000ebf\u0007\u0016^qF\u001c\nʷ5S\u0010_|{<镉!Y?\\9']|)\u0015RZF+ZN\u001eZc9~Sd+5\u001d1ޱX\u001e\u0018\n{TgG_t5Fw\u0011sJ9\u0011f`+!M"
  },
  {
    "path": "ci/wif/parameters/rsa_wif_gcp.gpg",
    "content": "\r\u0004\t\u0003\b髃6K\u00015%܇ټ飐\u000f|eRk]n\u000fc-TloB,\u0011ܐ͒R7B]<ER-|\u00044u'K2<\u0013:BC&fԳeX%i9\u00060@LG\u0011$a\u0004hOnTejB@\u001a)~Nto&\u0003Ȍ\u0012\u0013ru/i\u001cuq%0\u0012!]ݮ2-lw\u0018`!ʚgF߬\u0019i\u0010DDĘX\u0013..d\"^\ruA,ۅA\u000es\u001a\\ؕa\u000ebf\u0007\u0016^qF\u001c\nʷ5S\u0010_|{<镉!Y?\\9']|)\u0015RZF+ZN\u001eZc9~Sd+5\u001d1ޱX\u001e\u0018\n{TgG_t5Fw\u0011sJ9\u0011f`+!M"
  },
  {
    "path": "ci/wif/shared/com/snowflake/wif/common/WifTestHelper.java",
    "content": "package com.snowflake.wif.common;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Pattern;\nimport java.util.zip.GZIPInputStream;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\n\n/**\n * Helper class containing common functions for WIF E2E tests that can be reused\n * across different cloud function implementations (AWS Lambda, Azure Functions).\n */\npublic class WifTestHelper {\n\n    private static String generateUniqueSessionId() {\n        return System.currentTimeMillis() + \"-\" + UUID.randomUUID().toString().substring(0, 8);\n    }\n\n    public static void validateQueryParameters(Map<String, String> queryParams) {\n        if (queryParams == null) {\n            throw new IllegalArgumentException(\"Missing query parameters\");\n        }\n\n        String wifHost = queryParams.get(\"SNOWFLAKE_TEST_WIF_HOST\");\n        String wifAccount = queryParams.get(\"SNOWFLAKE_TEST_WIF_ACCOUNT\");\n        String wifProvider = queryParams.get(\"SNOWFLAKE_TEST_WIF_PROVIDER\");\n        String branch = queryParams.get(\"BRANCH\");\n\n        if (wifHost == null) {\n            throw new IllegalArgumentException(\"Missing required query parameter: SNOWFLAKE_TEST_WIF_HOST\");\n        }\n        if (wifAccount == null) {\n            throw new IllegalArgumentException(\"Missing required query parameter: SNOWFLAKE_TEST_WIF_ACCOUNT\");\n        }\n        if (wifProvider == null) {\n            throw new IllegalArgumentException(\"Missing required query parameter: SNOWFLAKE_TEST_WIF_PROVIDER\");\n        }\n        if (branch == null) {\n            throw new IllegalArgumentException(\"Missing required query parameter: BRANCH\");\n        }\n    }\n\n    public static String buildTarballUrl(String branch) {\n        if (Pattern.matches(\"^PR-\\\\d+$\", branch)) {\n            String prNumber = branch.substring(3);\n            return \"https://github.com/snowflakedb/snowflake-jdbc/archive/refs/pull/\" + prNumber + \"/head.tar.gz\";\n        } else {\n            return \"https://github.com/snowflakedb/snowflake-jdbc/archive/refs/heads/\" + branch + \".tar.gz\";\n        }\n    }\n\n    public static String findRepositoryFolder(String workingDirectory) {\n        if (workingDirectory == null) {\n            throw new RuntimeException(\"Working directory must be specified\");\n        }\n        \n        File workDir = new File(workingDirectory);\n        File[] files = workDir.listFiles();\n        \n        if (files != null) {\n            for (File file : files) {\n                if (file.isDirectory() && file.getName().startsWith(\"snowflake-jdbc-\")) {\n                    return file.getAbsolutePath();\n                }\n            }\n        }\n        \n        throw new RuntimeException(\"Driver repository folder not found in: \" + workingDirectory);\n    }\n\n    public static int executeMavenBuild(String repoFolderPath, String tempDirectory, WifLogger logger, Map<String, String> queryParams) {\n        Process process = null;\n        File mavenRepo = null;\n        File workspace = null;\n        File mavenHomeDir = null;\n        try {\n            // Use unique session ID to avoid conflicts with parallel test runs\n            String sessionId = generateUniqueSessionId();\n            mavenRepo = new File(tempDirectory, \"maven-repo-\" + sessionId);\n            workspace = new File(tempDirectory, \"workspace-\" + sessionId);\n            mavenRepo.mkdirs();\n            workspace.mkdirs();\n\n            ProcessBuilder pb = new ProcessBuilder(\n                \"bash\", \"-c\",\n                \"cd \" + repoFolderPath + \" && \" +\n                \"./mvnw -Dmaven.repo.local=\" + mavenRepo.getAbsolutePath() + \" \" +\n                \"-DjenkinsIT \" +\n                \"-Dnet.snowflake.jdbc.temporaryCredentialCacheDir=\" + workspace.getAbsolutePath() + \" \" +\n                \"-Dnet.snowflake.jdbc.ocspResponseCacheDir=\" + workspace.getAbsolutePath() + \" \" +\n                \"-Djava.io.tmpdir=\" + workspace.getAbsolutePath() + \" \" +\n                \"-Djacoco.skip.instrument=true \" +\n                \"-Dskip.unitTests=true \" +\n                \"-DintegrationTestSuites=WIFTestSuite \" +\n                \"-Dmaven.artifact.threads=4 \" +\n                \"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \" +\n                \"-Dnot-self-contained-jar \" +\n                \"-Dmaven.test.failure.ignore=false \" +\n                \"-Dmaven.compile.fork=false \" +\n                \"-Dmaven.javadoc.skip=true \" +\n                \"-Dmaven.source.skip=true \" +\n                \"-Dcheckstyle.skip=true \" +\n                \"-Dspotbugs.skip=true \" +\n                \"-Denforcer.skip=true \" +\n                \"-T 2C \" +\n                \"test-compile verify --batch-mode --show-version --fail-fast --no-transfer-progress\"\n            );\n            pb.redirectErrorStream(true);\n            \n            // Redirect all Maven directories to temp directory to avoid read-only filesystem issues\n            String mavenHome = tempDirectory + \"/maven-home-\" + sessionId;\n            mavenHomeDir = new File(mavenHome);\n            mavenHomeDir.mkdirs();\n            \n            logger.log(\"Setting Maven home to: \" + mavenHome);\n            \n            pb.environment().put(\"MAVEN_USER_HOME\", mavenHome);\n            pb.environment().put(\"HOME\", tempDirectory);\n            pb.environment().put(\"USER_HOME\", tempDirectory);\n            pb.environment().put(\"MAVEN_OPTS\", \"-Xmx2g -Xms512m -Duser.home=\" + tempDirectory + \" -Djava.io.tmpdir=\" + tempDirectory);\n            \n            pb.environment().put(\"SNOWFLAKE_TEST_WIF_HOST\", queryParams.get(\"SNOWFLAKE_TEST_WIF_HOST\"));\n            pb.environment().put(\"SNOWFLAKE_TEST_WIF_ACCOUNT\", queryParams.get(\"SNOWFLAKE_TEST_WIF_ACCOUNT\"));\n            pb.environment().put(\"SNOWFLAKE_TEST_WIF_PROVIDER\", queryParams.get(\"SNOWFLAKE_TEST_WIF_PROVIDER\"));\n            pb.environment().put(\"SF_ENABLE_EXPERIMENTAL_AUTHENTICATION\", \"true\");\n            pb.environment().put(\"SF_ENABLE_WIF_AWS_EXTERNAL_ID\", \"true\");\n            pb.environment().put(\"IS_GCP_FUNCTION\", queryParams.getOrDefault(\"IS_GCP_FUNCTION\", \"false\"));\n\n            String externalId = queryParams.get(\"SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID\");\n            if (externalId != null) {\n                pb.environment().put(\"SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID\", externalId);\n            }\n\n            process = pb.start();\n            \n            final InputStream processInputStream = process.getInputStream();\n            Thread outputThread = new Thread(() -> {\n                try (BufferedReader reader = new BufferedReader(new InputStreamReader(processInputStream))) {\n                    String line;\n                    while ((line = reader.readLine()) != null) {\n                        logger.log(line);\n                    }\n                } catch (IOException e) {\n                    logger.log(\"Error reading Maven output: \" + e.getMessage());\n                }\n            });\n            outputThread.start();\n            \n            if (!process.waitFor(6, TimeUnit.MINUTES)) {\n                process.destroyForcibly();\n                return -2; // timeout\n            }\n            return process.exitValue();\n            \n        } catch (Exception e) {\n            logger.log(\"Maven build error: \" + e.getMessage());\n            if (process != null && process.isAlive()) {\n                process.destroyForcibly();\n            }\n            return -1;\n        } finally {\n            if (process != null && process.isAlive()) {\n                process.destroyForcibly();\n                try {\n                    process.waitFor(10, TimeUnit.SECONDS);\n                } catch (InterruptedException ie) {\n                    Thread.currentThread().interrupt();\n                }\n            }\n            \n            // Clean up Maven temporary directories\n            cleanupMavenTempDirectories(mavenRepo, workspace, mavenHomeDir, logger);\n        }\n    }\n    \n    private static void cleanupMavenTempDirectories(File mavenRepo, File workspace, File mavenHomeDir, WifLogger logger) {\n        logger.log(\"Cleaning up Maven temporary directories...\");\n        \n        cleanupDirectory(mavenRepo, \"Maven repository\", logger);\n        cleanupDirectory(workspace, \"workspace\", logger);\n        cleanupDirectory(mavenHomeDir, \"Maven home\", logger);\n        \n        logger.log(\"Maven temporary directories cleanup completed\");\n    }\n    \n    private static void cleanupDirectory(File directory, String description, WifLogger logger) {\n        if (directory != null && directory.exists()) {\n            try {\n                ProcessBuilder pb = new ProcessBuilder(\"rm\", \"-rf\", directory.getAbsolutePath());\n                Process process = pb.start();\n                int exitCode = process.waitFor();\n                \n                if (exitCode == 0) {\n                    logger.log(\"Successfully cleaned up \" + description + \": \" + directory.getAbsolutePath());\n                } else {\n                    logger.log(\"Warning: Failed to clean up \" + description + \" (exit code \" + exitCode + \"): \" + directory.getAbsolutePath());\n                }\n            } catch (Exception e) {\n                logger.log(\"Warning: Exception during \" + description + \" cleanup: \" + e.getMessage());\n            }\n        } else if (directory != null) {\n            logger.log(\"Directory already cleaned or doesn't exist: \" + directory.getAbsolutePath());\n        }\n    }\n    \n    /**\n     * Makes mvnw and check_content.sh executable after downloading repository.\n     * This ensures the Maven wrapper and CI scripts can be executed in cloud environments.\n     * \n     * @param repoFolderPath Path to the repository folder\n     * @param logger Logger for output\n     */\n    public static void makeExecutable(String repoFolderPath, WifLogger logger) {\n        try {\n            File mvnwFile = new File(repoFolderPath, \"mvnw\");\n            if (mvnwFile.exists()) {\n                boolean wasExecutable = mvnwFile.canExecute();\n                boolean success = mvnwFile.setExecutable(true, false);\n                \n                logger.log(String.format(\n                    \"Found mvnw: %s (was executable: %s, set executable: %s, now executable: %s)\",\n                    mvnwFile.getAbsolutePath(),\n                    wasExecutable,\n                    success,\n                    mvnwFile.canExecute()\n                ));\n                \n                mvnwFile.setReadable(true, false);\n            } else {\n                logger.log(\"Warning: mvnw file not found at \" + mvnwFile.getAbsolutePath());\n            }\n            \n            File checkContentFile = new File(repoFolderPath, \"ci/scripts/check_content.sh\");\n            if (checkContentFile.exists()) {\n                boolean wasExecutable = checkContentFile.canExecute();\n                boolean success = checkContentFile.setExecutable(true, false);\n                \n                logger.log(String.format(\n                    \"Found check_content.sh: %s (was executable: %s, set executable: %s, now executable: %s)\",\n                    checkContentFile.getAbsolutePath(),\n                    wasExecutable,\n                    success,\n                    checkContentFile.canExecute()\n                ));\n                checkContentFile.setReadable(true, false);\n            } else {\n                logger.log(\"Warning: check_content.sh file not found at \" + checkContentFile.getAbsolutePath());\n            }\n        } catch (Exception e) {\n            logger.log(\"Warning: Failed to fix file permissions: \" + e.getMessage());\n        }\n    }\n\n    public static void cleanupWorkingDirectory(String workingDirectory, WifLogger logger) {\n        try {\n            if (workingDirectory == null) {\n                logger.log(\"No working directory to clean up\");\n                return;\n            }\n            \n            File workDir = new File(workingDirectory);\n            if (!workDir.exists()) {\n                logger.log(\"Working directory does not exist: \" + workingDirectory);\n                return;\n            }\n            \n            logger.log(\"Cleaning up working directory: \" + workingDirectory);\n            \n            try {\n                ProcessBuilder pb = new ProcessBuilder(\"rm\", \"-rf\", workingDirectory);\n                Process process = pb.start();\n                int exitCode = process.waitFor();\n                \n                if (exitCode == 0) {\n                    logger.log(\"Successfully cleaned up working directory\");\n                } else {\n                    logger.log(\"rm -rf command failed with exit code: \" + exitCode);\n                }\n                \n            } catch (Exception e) {\n                logger.log(\"rm -rf command failed: \" + e.getMessage());\n            }\n            \n            // Also clean up any truly old orphaned Maven directories (older than 1 hour)\n            cleanupOrphanedMavenDirectories(logger);\n            \n        } catch (Exception e) {\n            logger.log(\"Working directory cleanup warning: \" + e.getMessage());\n        }\n    }\n    \n    /**\n     * Clean up any orphaned Maven temporary directories that might have been left behind\n     * from previous failed executions. This is a defensive cleanup measure that only\n     * removes directories older than 1 hour to avoid conflicts with parallel test runs.\n     */\n    private static void cleanupOrphanedMavenDirectories(WifLogger logger) {\n        try {\n            String tempDir = System.getProperty(\"java.io.tmpdir\");\n            File tempDirectory = new File(tempDir);\n            \n            if (!tempDirectory.exists() || !tempDirectory.isDirectory()) {\n                return;\n            }\n            \n            logger.log(\"Scanning for old orphaned Maven directories in: \" + tempDir);\n            \n            File[] files = tempDirectory.listFiles();\n            if (files != null) {\n                int cleanedCount = 0;\n                long oneHourAgo = System.currentTimeMillis() - (60 * 60 * 1000); // 1 hour ago\n                \n                for (File file : files) {\n                    if (file.isDirectory() && (\n                        file.getName().startsWith(\"maven-repo-\") ||\n                        file.getName().startsWith(\"workspace-\") ||\n                        file.getName().startsWith(\"maven-home-\") ||\n                        file.getName().startsWith(\"wif-function-\")\n                    )) {\n                        // Only clean up directories older than 1 hour to avoid interfering with parallel runs\n                        if (file.lastModified() < oneHourAgo) {\n                            cleanupDirectory(file, \"old orphaned directory\", logger);\n                            cleanedCount++;\n                        } else {\n                            logger.log(\"Skipping recent directory (likely from parallel run): \" + file.getName());\n                        }\n                    }\n                }\n                \n                if (cleanedCount > 0) {\n                    logger.log(\"Cleaned up \" + cleanedCount + \" old orphaned Maven/WIF directories\");\n                } else {\n                    logger.log(\"No old orphaned Maven/WIF directories found\");\n                }\n            }\n            \n        } catch (Exception e) {\n            logger.log(\"Warning: Failed to clean up orphaned directories: \" + e.getMessage());\n        }\n    }\n\n    public static String downloadAndExtractRepository(String tarballUrl, WifLogger logger) throws IOException, InterruptedException {\n        // Create unique session-based directory for complete isolation\n        String sessionId = generateUniqueSessionId();\n        String workDir = \"/tmp/wif-function-\" + sessionId;\n        File workingDirectory = new File(workDir);\n\n        if (!workingDirectory.exists()) {\n            workingDirectory.mkdirs();\n        }\n        \n        logger.log(\"Extracting to timestamp-based directory: \" + workingDirectory.getAbsolutePath());\n        logger.log(\"Downloading using Java HTTP client: \" + tarballUrl);\n        \n        try {\n            downloadAndExtractWithJava(tarballUrl, workingDirectory, logger);\n            logger.log(\"Download and extraction completed successfully\");\n            return workingDirectory.getAbsolutePath();\n            \n        } catch (Exception e) {\n            throw new RuntimeException(\"Failed to download and extract: \" + e.getMessage(), e);\n        }\n    }\n\n    public static void downloadAndExtractWithJava(String tarballUrl, File workingDirectory, WifLogger logger) throws IOException, InterruptedException {\n        logger.log(\"Creating HTTP client...\");\n        \n        HttpClient client = HttpClient.newBuilder()\n            .connectTimeout(Duration.ofSeconds(30))\n            .followRedirects(HttpClient.Redirect.ALWAYS)\n            .build();\n            \n        HttpRequest request = HttpRequest.newBuilder()\n            .uri(URI.create(tarballUrl))\n            .timeout(Duration.ofMinutes(5))\n            .header(\"User-Agent\", \"WIF-Function\")\n            .GET()\n            .build();\n            \n        logger.log(\"Sending HTTP request...\");\n        \n        try {\n            HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());\n            \n            if (response.statusCode() != 200) {\n                throw new IOException(\"HTTP \" + response.statusCode() + \" when downloading \" + tarballUrl);\n            }\n            \n            logger.log(\"HTTP response received, extracting tar.gz...\");\n            \n            try (InputStream responseStream = response.body();\n                 GZIPInputStream gzipStream = new GZIPInputStream(responseStream);\n                 TarArchiveInputStream tarStream = new TarArchiveInputStream(gzipStream)) {\n                \n                TarArchiveEntry entry;\n                while ((entry = tarStream.getNextTarEntry()) != null) {\n                    File outputFile = createOutputFile(workingDirectory, entry.getName());\n                    \n                    if (entry.isDirectory()) {\n                        outputFile.mkdirs();\n                    } else {\n                        outputFile.getParentFile().mkdirs();\n                        try (FileOutputStream fos = new FileOutputStream(outputFile)) {\n                            tarStream.transferTo(fos);\n                        }\n                    }\n                }\n            }\n            \n            logger.log(\"Tar extraction completed\");\n            \n        } catch (Exception e) {\n            logger.log(\"Download/extract error: \" + e.getMessage());\n            throw new IOException(\"Failed to download and extract: \" + e.getMessage(), e);\n        }\n    }\n\n    private static File createOutputFile(File workingDirectory, String entryName) throws IOException {\n        File outputFile = new File(workingDirectory, entryName);\n        \n        String workingDirCanonical = workingDirectory.getCanonicalPath();\n        String outputFileCanonical = outputFile.getCanonicalPath();\n        \n        if (!outputFileCanonical.startsWith(workingDirCanonical + File.separator) && \n            !outputFileCanonical.equals(workingDirCanonical)) {\n            throw new IOException(\"Archive entry '\" + entryName + \"' would extract outside the target directory: \" + outputFileCanonical);\n        }\n        \n        return outputFile;\n    }\n\n    public static String createMavenResultMessage(int mavenExitCode) {\n        if (mavenExitCode == 0) {\n            return \"WIF tests completed successfully\";\n        } else {\n            String mavenResult;\n            String testResults;\n            \n            if (mavenExitCode == -2) {\n                mavenResult = \"TIMEOUT (exit code: \" + mavenExitCode + \")\";\n                testResults = \"Build timed out after 6 minutes\";\n            } else {\n                mavenResult = \"FAILED (exit code: \" + mavenExitCode + \")\";\n                testResults = \"Build or tests failed\";\n            }\n            \n            return String.format(\n                    \"MAVEN_RESULT=%s\\nTEST_STATUS=%s\",\n                    mavenResult, testResults\n            );\n        }\n    }\n\n    public interface WifLogger {\n        void log(String message);\n    }\n}\n"
  },
  {
    "path": "ci/wif/test_wif.sh",
    "content": "#!/bin/bash -e\n\nset -o pipefail\n\nexport SF_ENABLE_EXPERIMENTAL_AUTHENTICATION=true\n\n./mvnw -Dmaven.repo.local=/tmp/maven-repo \\\n  -DjenkinsIT \\\n  -Dnet.snowflake.jdbc.temporaryCredentialCacheDir=/tmp/workspace \\\n  -Dnet.snowflake.jdbc.ocspResponseCacheDir=/tmp/workspace \\\n  -Djava.io.tmpdir=/tmp/workspace \\\n  -Djacoco.skip.instrument=true \\\n  -Dskip.unitTests=true \\\n  -DintegrationTestSuites=WIFTestSuite \\\n  -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn \\\n  -Dnot-self-contained-jar \\\n  -Denforcer.skip=true \\\n  -Dmaven.javadoc.skip=true \\\n  verify \\\n  --batch-mode --show-version\n"
  },
  {
    "path": "codecov/codecov.SHA256SUM",
    "content": "0c9b79119b0d8dbe7aaf460dc3bd7c3094ceda06e5ae32b0d11a8ff56e2cc5c5  codecov"
  },
  {
    "path": "codecov/codecov.SHA256SUM.sig",
    "content": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEJwNOf9uFDgu8LGL/gGuyiu13mGkFAmSRyEgACgkQgGuyiu13\nmGnHdQ//WMx0TS7fJlgo04LCD9tQj3quUFm7oPMuDs8CzSx22W0V9FmPiN7s1uYI\niKnRd32LW0a9eIymhyz1Tv3HXV4k61pruVVNq5ghx24i5jtDqfnGDXNhDWxo/Dk+\n62v4/17dcFM+QPBsXvXQkaUqXvkmenn7PXSqCtztcOa54MFEZo5RRSm+3JbKoLYc\npB7yTG6gC+ZMYNoEoQG0Ax+lfYVUBdesbst0nXdrMzOs8134KPMazccO2EmEIPUA\nr3b2KQzz89kt7tvTEthycjOu12ZIUYOftdU3XEv1VjdfsVJs4BlIhPTzRJ0tYYoO\ndcNBwvNbreDITpikM3m6zzxkzbPsLM+bk2MURzQR6wWRPtG+2tMS9a7bV0O4Qufk\n9kzk6NG3i+z/vwwUYzcRTrDWQurrr4hDNGKfHWVHkgoVXCZBQc8X3wCiYwjLovP5\nHDChwPK0liQgHJx2FG+/lVtFUbGQ0I9OvWb02VizRInFxr4hWt14Nd3DJNBaOzuT\nT/u6IecHq6xSqV+Cj/qcD9Gxclj1Ge7DrLSRlv2hwrvph4PA4ehTY0lQTMSvgwFf\nm5ZVwwW8QwPefCdbvcQoLFQQN1U6RxX1bjxaZKtmSOwxdkpgQEiWrdFirwZhNA9u\nmdOlQA0SnvAjopcJYQqMSkgLaMt4cJDIxvxO5ihlNSsgcRx32zY=\n=oOVB\n-----END PGP SIGNATURE-----\n"
  },
  {
    "path": "codecov.yml",
    "content": "parsers:\n  jacoco:\n    partials_as_hits: true"
  },
  {
    "path": "dependencies/Readme.md",
    "content": "Arrow dependencies are built from internal branch `upgradeTo17.0.0-v3`. This build was applied the AIX fix and the customer logger instead of slf4j logger.\n\n"
  },
  {
    "path": "dependencies/arrow-memory-17.0.0.pom",
    "content": "<?xml version=\"1.0\"?>\n<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor\n  license agreements. See the NOTICE file distributed with this work for additional\n  information regarding copyright ownership. The ASF licenses this file to\n  You under the Apache License, Version 2.0 (the \"License\"); you may not use\n  this file except in compliance with the License. You may obtain a copy of\n  the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required\n  by applicable law or agreed to in writing, software distributed under the\n  License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS\n  OF ANY KIND, either express or implied. See the License for the specific\n  language governing permissions and limitations under the License. -->\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n  <parent>\n    <groupId>org.apache.arrow</groupId>\n    <artifactId>arrow-java-root</artifactId>\n    <version>17.0.0</version>\n  </parent>\n  <artifactId>arrow-memory</artifactId>\n  <name>Arrow Memory</name>\n  <packaging>pom</packaging>\n\n  <modules>\n    <module>memory-core</module>\n    <module>memory-unsafe</module>\n    <module>memory-netty-buffer-patch</module>\n  </modules>\n\n</project>\n"
  },
  {
    "path": "fat-jar-test-app/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>net.snowflake</groupId>\n    <artifactId>fat-jar-test-app</artifactId>\n    <version>1.0-SNAPSHOT</version>\n\n    <properties>\n        <maven.compiler.source>8</maven.compiler.source>\n        <maven.compiler.target>8</maven.compiler.target>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <exec.mainClass>net.snowflake.FatJarTestApp</exec.mainClass>\n    </properties>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>exec-maven-plugin</artifactId>\n                <version>3.1.0</version>\n                <configuration>\n                    <executable>java</executable>\n                    <arguments>\n                        <argument>-classpath</argument>\n                        <classpath/>\n                        <argument>${exec.mainClass}</argument>\n                    </arguments>\n                    <classpathScope>compile</classpathScope>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n    <profiles>\n        <profile>\n            <id>default</id>\n            <activation>\n                <activeByDefault>true</activeByDefault>\n            </activation>\n            <dependencies>\n                <dependency>\n                    <groupId>net.snowflake</groupId>\n                    <artifactId>snowflake-jdbc</artifactId>\n                    <version>ignored</version>\n                    <scope>system</scope>\n                    <systemPath>${project.basedir}/../target/snowflake-jdbc.jar</systemPath>\n                </dependency>\n            </dependencies>\n        </profile>\n\n        <profile>\n            <id>fips</id>\n            <build>\n                <plugins>\n                    <!-- Add FIPS-specific source directory -->\n                    <plugin>\n                        <groupId>org.codehaus.mojo</groupId>\n                        <artifactId>build-helper-maven-plugin</artifactId>\n                        <version>3.4.0</version>\n                        <executions>\n                            <execution>\n                                <id>add-fips-sources</id>\n                                <phase>generate-sources</phase>\n                                <goals>\n                                    <goal>add-source</goal>\n                                </goals>\n                                <configuration>\n                                    <sources>\n                                        <source>${project.basedir}/src/main/fips-java</source>\n                                    </sources>\n                                </configuration>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n            <dependencies>\n                <dependency>\n                    <groupId>net.snowflake</groupId>\n                    <artifactId>snowflake-jdbc-fips</artifactId>\n                    <version>ignored</version>\n                    <scope>system</scope>\n                    <systemPath>${project.basedir}/../FIPS/target/snowflake-jdbc-fips.jar</systemPath>\n                </dependency>\n                <dependency>\n                    <groupId>org.bouncycastle</groupId>\n                    <artifactId>bc-fips</artifactId>\n                    <version>1.0.2.6</version>\n                </dependency>\n                <dependency>\n                    <groupId>org.bouncycastle</groupId>\n                    <artifactId>bcpkix-fips</artifactId>\n                    <version>1.0.8</version>\n                </dependency>\n            </dependencies>\n        </profile>\n\n        <profile>\n            <id>slf4j</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.codehaus.mojo</groupId>\n                        <artifactId>exec-maven-plugin</artifactId>\n                        <configuration>\n                            <arguments combine.children=\"replace\">\n                                <argument>-Dnet.snowflake.jdbc.loggerImpl=net.snowflake.client.log.SLF4JLogger</argument>\n                                <argument>-classpath</argument>\n                                <classpath/>\n                                <argument>${exec.mainClass}</argument>\n                            </arguments>\n                        </configuration>\n                    </plugin>\n                </plugins>\n            </build>\n            <dependencies>\n                <dependency>\n                    <groupId>org.slf4j</groupId>\n                    <artifactId>slf4j-api</artifactId>\n                    <version>2.0.13</version>\n                </dependency>\n                <dependency>\n                    <groupId>ch.qos.logback</groupId>\n                    <artifactId>logback-classic</artifactId>\n                    <version>1.3.14</version>\n                </dependency>\n                <dependency>\n                    <groupId>ch.qos.logback</groupId>\n                    <artifactId>logback-core</artifactId>\n                    <version>1.3.14</version>\n                </dependency>\n            </dependencies>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "fat-jar-test-app/run.sh",
    "content": "#!/bin/bash -ex\n#\n# Test fat jar by running a sample app that connects to Snowflake\n#\nset -o pipefail\nTHIS_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nJDBC_ROOT=\"$(cd \"${THIS_DIR}/..\" && pwd)\"\n\nJDBC_JAR=$1\nLOGGING_MODE=${2:-}\n\nmkdir -p $THIS_DIR/target\n\n# Setup GPG home directory if running in GitHub Actions\nif [[ -n \"$GITHUB_ACTIONS\" ]]; then\n    source $JDBC_ROOT/ci/scripts/setup_gpg.sh\n\n    # Select the encrypted files based on CLOUD_PROVIDER\n    if [[ \"$CLOUD_PROVIDER\" == \"AZURE\" ]]; then\n        ENCODED_PARAMETERS_FILE=.github/workflows/parameters_azure.json.gpg\n        ENCODED_RSA_KEY_FILE=.github/workflows/rsa_keys/rsa_key_jdbc_azure.p8.gpg\n    elif [[ \"$CLOUD_PROVIDER\" == \"GCP\" ]]; then\n        ENCODED_PARAMETERS_FILE=.github/workflows/parameters_gcp.json.gpg\n        ENCODED_RSA_KEY_FILE=.github/workflows/rsa_keys/rsa_key_jdbc_gcp.p8.gpg\n    elif [[ \"$CLOUD_PROVIDER\" == \"AWS\" ]]; then\n        ENCODED_PARAMETERS_FILE=.github/workflows/parameters_aws.json.gpg\n        ENCODED_RSA_KEY_FILE=.github/workflows/rsa_keys/rsa_key_jdbc_aws.p8.gpg\n    else\n        echo \"[ERROR] Unknown cloud provider: $CLOUD_PROVIDER\"\n        exit 1\n    fi\n\n    # Decrypt parameters file\n    echo \"[INFO] Decrypting parameters file for $CLOUD_PROVIDER\"\n    gpg --quiet --batch --yes --decrypt --passphrase=\"$PARAMETERS_SECRET\" \\\n        --output \"$JDBC_ROOT/parameters.json\" \"$JDBC_ROOT/$ENCODED_PARAMETERS_FILE\"\n\n    # Decrypt RSA key file\n    echo \"[INFO] Decrypting RSA key file for $CLOUD_PROVIDER\"\n    gpg --quiet --batch --yes --decrypt --passphrase=\"$JDBC_PRIVATE_KEY_SECRET\" \\\n        --output \"$THIS_DIR/target/rsa_key_jdbc.p8\" \"$JDBC_ROOT/$ENCODED_RSA_KEY_FILE\"\n\n    # Parse parameters.json and export as SNOWFLAKE_TEST_* environment variables\n    echo \"[INFO] Setting up environment variables from parameters.json\"\n    eval $(jq -r '.testconnection | to_entries | map(\"export \\(.key)=\\(.value|tostring)\")|.[]' \"$JDBC_ROOT/parameters.json\")\n\n    # Set RSA key authentication\n    export SNOWFLAKE_TEST_PRIVATE_KEY_FILE=\"$THIS_DIR/target/rsa_key_jdbc.p8\"\n    export SNOWFLAKE_TEST_AUTHENTICATOR=\"SNOWFLAKE_JWT\"\n\n    # Print env vars (excluding sensitive ones)\n    env | grep SNOWFLAKE_ | grep -v -E \"(PASS|KEY|SECRET|TOKEN)\" | sort\nfi\n\necho \"[INFO] Building fat jar\"\ncd \"$JDBC_ROOT\"\nif [ \"$JDBC_JAR\" == \"fips\" ] ; then\n    MAVEN_PROFILE=\"fips\"\n    ./mvnw -f FIPS/pom.xml package -DskipTests -Dmaven.test.skip=true --quiet\nelse\n    MAVEN_PROFILE=\"default\"\n    ./mvnw package -DskipTests -Dmaven.test.skip=true --quiet\nfi\n\n# Build Maven profiles list\nPROFILES=\"$MAVEN_PROFILE\"\nif [[ \"$LOGGING_MODE\" == \"slf4j\" ]]; then\n    PROFILES=\"$PROFILES,slf4j\"\nfi\n\necho \"[INFO] Running fat jar test app with Maven profiles: $PROFILES\"\ncd \"$THIS_DIR\"\n../mvnw compile exec:exec -P \"$PROFILES\"\n"
  },
  {
    "path": "fat-jar-test-app/src/main/fips-java/net/snowflake/FipsInitializer.java",
    "content": "package net.snowflake;\n\nimport java.security.Provider;\nimport java.security.Security;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.bouncycastle.crypto.CryptoServicesRegistrar;\nimport org.bouncycastle.crypto.fips.FipsStatus;\nimport org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;\n\n/**\n * FIPS initialization class that registers BouncyCastle FIPS provider\n * before the main application runs. This class is only compiled when\n * the 'fips' Maven profile is active.\n */\npublic class FipsInitializer {\n\n    private static final String JCE_PROVIDER_BOUNCY_CASTLE_FIPS = \"BCFIPS\";\n    private static final String JCE_PROVIDER_SUN_JCE = \"SunJCE\";\n    private static final String JCE_PROVIDER_SUN_RSA_SIGN = \"SunRsaSign\";\n    private static final String JCE_KEYSTORE_BOUNCY_CASTLE = \"BCFKS\";\n    private static final String JCE_KEYSTORE_JKS = \"JKS\";\n    private static final String BOUNCY_CASTLE_RNG_HYBRID_MODE = \"C:HYBRID;ENABLE{All};\";\n    private static final String SSL_ENABLED_PROTOCOLS = \"TLSv1.2,TLSv1.1,TLSv1\";\n    private static final String JAVA_SYSTEM_PROPERTY_SSL_NAMEDGROUPS = \"jdk.tls.namedGroups\";\n    private static final String JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE = \"javax.net.ssl.keyStoreType\";\n    private static final String JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE = \"javax.net.ssl.trustStoreType\";\n    private static final String JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS = \"jdk.tls.client.protocols\";\n\n    // Static initializer block executes when class is loaded\n    static {\n        try {\n            initializeFipsMode();\n            System.out.println(\"[FIPS] Initialization completed successfully\");\n        } catch (Exception e) {\n            System.err.println(\"[FIPS] Initialization failed: \" + e.getMessage());\n            e.printStackTrace();\n            throw new RuntimeException(\"Failed to initialize FIPS mode\", e);\n        }\n    }\n\n    private static void initializeFipsMode() {\n        // Set named groups to avoid test failure on GCP environment\n        System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_NAMEDGROUPS,\n            \"secp256r1, secp384r1, ffdhe2048, ffdhe3072\");\n\n        // Set keystore types for BouncyCastle libraries\n        System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_KEYSTORE_TYPE, JCE_KEYSTORE_BOUNCY_CASTLE);\n        System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_TRUSTSTORE_TYPE, JCE_KEYSTORE_JKS);\n\n        // Set SSL protocols\n        System.setProperty(JAVA_SYSTEM_PROPERTY_SSL_PROTOCOLS, SSL_ENABLED_PROTOCOLS);\n\n        // Remove Java's standard encryption and SSL providers\n        List<Provider> providers = Arrays.asList(Security.getProviders());\n        Provider sunJceProvider = Security.getProvider(JCE_PROVIDER_SUN_JCE);\n        Provider sunRsaSignProvider = Security.getProvider(JCE_PROVIDER_SUN_RSA_SIGN);\n\n        if (sunJceProvider != null) {\n            Security.removeProvider(JCE_PROVIDER_SUN_JCE);\n        }\n        if (sunRsaSignProvider != null) {\n            Security.removeProvider(JCE_PROVIDER_SUN_RSA_SIGN);\n        }\n\n        /*\n         * Insert BouncyCastle's FIPS-compliant encryption and SSL providers.\n         */\n        BouncyCastleFipsProvider bcFipsProvider =\n            new BouncyCastleFipsProvider(BOUNCY_CASTLE_RNG_HYBRID_MODE);\n\n        /*\n         * We remove BCFIPS provider pessimistically. This is a no-op if provider\n         * does not exist. This is necessary to always add it to the first\n         * position when calling insertProviderAt.\n         *\n         * JavaDoc for insertProviderAt states:\n         *   \"A provider cannot be added if it is already installed.\"\n         */\n        Security.removeProvider(JCE_PROVIDER_BOUNCY_CASTLE_FIPS);\n        Security.insertProviderAt(bcFipsProvider, 1);\n\n        // Enable approved-only mode\n        if (!CryptoServicesRegistrar.isInApprovedOnlyMode()) {\n            if (FipsStatus.isReady()) {\n                CryptoServicesRegistrar.setApprovedOnlyMode(true);\n            } else {\n                throw new RuntimeException(\n                    \"FIPS is not ready to be enabled and FIPS mode is required\");\n            }\n        }\n    }\n\n    /**\n     * Force class loading - this method will be called from FatJarTestApp.\n     * The method body is empty; just loading the class is enough to trigger\n     * the static initializer block.\n     */\n    public static void ensureInitialized() {\n        // Method body is empty; just loading the class is enough\n    }\n}\n"
  },
  {
    "path": "fat-jar-test-app/src/main/java/net/snowflake/FatJarTestApp.java",
    "content": "package net.snowflake;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.net.URISyntaxException;\nimport java.nio.file.Files;\nimport java.util.logging.LogManager;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.Properties;\n\npublic class FatJarTestApp {\n\n  private static final String LOGGER_IMPL_PROPERTY = \"net.snowflake.jdbc.loggerImpl\";\n  private static final String SLF4J_LOGGER_CLASS = \"net.snowflake.client.log.SLF4JLogger\";\n\n  private static final String LOGGER_IMPL = System.getProperty(LOGGER_IMPL_PROPERTY);\n\n  private static final String[] CLOUD_SDK_LOGGER_PATTERNS = {\n    \"net.snowflake.client.jdbc.internal.software.amazon\",\n    \"net.snowflake.client.jdbc.internal.google\",\n    \"net.snowflake.client.jdbc.internal.azure\"\n  };\n\n  private static final File logFile =\n      new File(System.getProperty(\"java.io.tmpdir\"), \"fat-jar-test.log\");\n\n  // Static initializer to load FIPS configuration if available\n  static {\n    try {\n      // This will only succeed when FipsInitializer.class is on the classpath\n      // (i.e., when compiled with the fips profile)\n      Class<?> fipsInitializer = Class.forName(\"net.snowflake.FipsInitializer\");\n      fipsInitializer.getMethod(\"ensureInitialized\").invoke(null);\n      System.out.println(\"[INFO] Running in FIPS mode\");\n    } catch (ClassNotFoundException e) {\n      // FIPS not available - normal mode\n      System.out.println(\"[INFO] Running in normal (non-FIPS) mode\");\n    } catch (Exception e) {\n      throw new RuntimeException(\"Failed to initialize FIPS mode\", e);\n    }\n  }\n\n  public static void main(String[] args) throws Exception {\n    setupLogging();\n    runQueries(args);\n    verifyLogs();\n  }\n\n  private static void runQueries(String[] args) throws Exception {\n    try (Connection connection = getConnection(args); Statement stmt = connection.createStatement()) {\n      System.out.println(\"RUNNING SELECT 1\");\n      ResultSet resultSet = stmt.executeQuery(\"SELECT 1\");\n      if (!resultSet.next()) {\n        throw new RuntimeException(\"No data found\");\n      }\n      if (resultSet.getInt(1) != 1) {\n        throw new RuntimeException(\"Wrong data found: \" + resultSet.getInt(1));\n      }\n\n      System.out.println(\"CREATING A STAGE\");\n      stmt.execute(\"CREATE OR REPLACE TEMPORARY STAGE fat_jar_stage\");\n      System.out.println(\"PUTTING A FILE\");\n      stmt.execute(\"PUT file://\" + getTestFilePath() + \" @fat_jar_stage\");\n    }\n  }\n\n  private static void setupLogging() throws Exception {\n    if (SLF4J_LOGGER_CLASS.equals(LOGGER_IMPL)) {\n      System.setProperty(\"fatjar.logfile\", logFile.getAbsolutePath());\n      System.out.println(\"[INFO] SLF4J logging to: \" + logFile.getAbsolutePath());\n    } else {\n      try (InputStream is = FatJarTestApp.class.getResourceAsStream(\"/logging.properties\")) {\n        LogManager.getLogManager().readConfiguration(is);\n      }\n      System.out.println(\"[INFO] JUL logging to: \" + logFile.getAbsolutePath());\n    }\n  }\n\n  private static void verifyLogs() throws Exception {\n    String logOutput = new String(Files.readAllBytes(logFile.toPath()));\n    String mode = SLF4J_LOGGER_CLASS.equals(LOGGER_IMPL) ? \"SLF4J\" : \"JUL\";\n    System.out.println(\"[INFO] Verifying \" + mode + \" log output (\" + logOutput.length() + \" chars)\");\n    String logsPrelude = logOutput.substring(0, Math.min(2000, logOutput.length()));\n\n    boolean hasOpeningSession = logOutput.contains(\"Opening session\");\n    if (!hasOpeningSession) {\n      System.err.println(\"[FAIL] Log output does not contain 'Opening session' (expected from SFSession)\");\n      System.err.println(\"[DEBUG] First 2000 chars of log output:\");\n      System.err.println(logsPrelude);\n      System.exit(1);\n    }\n    System.out.println(\"[PASS] Found 'Opening session' in \" + mode + \" log output\");\n\n    boolean hasCloudSdkLog = false;\n    for (String pattern : CLOUD_SDK_LOGGER_PATTERNS) {\n      if (logOutput.contains(pattern)) {\n        hasCloudSdkLog = true;\n        System.out.println(\"[PASS] Found cloud SDK logger: \" + pattern);\n        break;\n      }\n    }\n    if (!hasCloudSdkLog) {\n      System.err.println(\"[FAIL] Log output does not contain any cloud SDK logger name\");\n      System.err.println(\"[FAIL] Expected one of: \" + Arrays.toString(CLOUD_SDK_LOGGER_PATTERNS));\n      System.err.println(\"[DEBUG] First 2000 chars of log output:\");\n      System.err.println(logsPrelude);\n      System.exit(1);\n    }\n\n    System.out.println(\"[PASS] All \" + mode + \" logging verifications passed\");\n  }\n\n  private static String getTestFilePath() throws URISyntaxException {\n    if (new File(\"/tmp/test.csv\").exists()) {\n      return \"/tmp/test.csv\";\n    } else {\n      return new File(FatJarTestApp.class.getClassLoader().getResource(\"test.csv\").toURI()).getAbsolutePath();\n    }\n  }\n\n  private static Connection getConnection(String[] args) throws Exception {\n    Properties properties = new Properties();\n    properties.put(\"user\", getSfEnv(\"USER\"));\n    String authenticator = getSfEnv(\"AUTHENTICATOR\");\n    if (\"SNOWFLAKE_JWT\".equals(authenticator)) {\n      String privateKeyFile = getSfEnv(\"PRIVATE_KEY_FILE\");\n      properties.put(\"private_key_file\", privateKeyFile);\n      properties.put(\"authenticator\", \"SNOWFLAKE_JWT\");\n    } else {\n      String password = getSfEnv(\"PASSWORD\");\n      properties.put(\"password\", password);\n    }\n    properties.put(\"role\", getSfEnv(\"ROLE\"));\n    properties.put(\"account\", getSfEnv(\"ACCOUNT\"));\n    properties.put(\"db\", getSfEnv(\"DATABASE\"));\n    properties.put(\"schema\", getSfEnv(\"SCHEMA\"));\n    properties.put(\"warehouse\", getSfEnv(\"WAREHOUSE\"));\n    properties.put(\"ssl\", true);\n    String uri = \"jdbc:snowflake://\" + getSfEnv(\"ACCOUNT\") + \".snowflakecomputing.com\";\n    if (Arrays.asList(args).contains(\"enableDiagnostics\")) {\n      uri += \"?ENABLE_DIAGNOSTICS=true&DIAGNOSTICS_ALLOWLIST_FILE=/allowlist.json\";\n    }\n    if (Arrays.asList(args).contains(\"useProxy\")) {\n      properties.put(\"useProxy\", \"true\");\n      properties.put(\"proxyHost\", \"localhost\");\n      properties.put(\"proxyPort\", \"8080\");\n    }\n    Class.forName(\"net.snowflake.client.api.driver.SnowflakeDriver\");\n    return DriverManager.getConnection(uri, properties);\n  }\n\n  private static String getSfEnv(String param) {\n    return System.getenv(\"SNOWFLAKE_TEST_\" + param);\n  }\n}\n"
  },
  {
    "path": "fat-jar-test-app/src/main/resources/logback.xml",
    "content": "<configuration>\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n    <appender name=\"FILE\" class=\"ch.qos.logback.core.FileAppender\">\n        <file>${fatjar.logfile}</file>\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n    <logger name=\"net.snowflake\" level=\"DEBUG\"/>\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n        <appender-ref ref=\"FILE\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "fat-jar-test-app/src/main/resources/logging.properties",
    "content": "handlers = java.util.logging.FileHandler\n.level = FINE\n\njava.util.logging.FileHandler.pattern = %t/fat-jar-test.log\njava.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\njava.util.logging.FileHandler.level = ALL\n"
  },
  {
    "path": "fat-jar-test-app/src/main/resources/test.csv",
    "content": "abc,1\ndef,2"
  },
  {
    "path": "linkage-checker-exclusion-rules.xml",
    "content": "<LinkageCheckerFilter>\n    <LinkageError>\n        <Target><Package name=\"org.graalvm.nativeimage\"/></Target>\n        <Source><Package name=\"com.google.api.gax.nativeimage\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"com.google.crypto.tink\"/></Target>\n        <Source><Package name=\"com.nimbusds.jose\"/></Source>\n        <Reason>Optional</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"org.bouncycastle.jcajce.provider\"/></Target>\n        <Source><Package name=\"com.nimbusds.jose\"/></Source>\n        <Reason>Optional</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"org.brotli.dec\"/></Target>\n        <Source><Package name=\"org.apache.commons.compress.compressors\"/></Source>\n        <Reason>Optional</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"com.google.appengine.api.urlfetch\"/></Target>\n        <Source><Package name=\"com.google.api.client.extensions.appengine\"/></Source>\n        <Reason>provided appengine</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"com.oracle\"/></Target>\n        <Source><Package name=\"com.google.api.gax\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n\n    <LinkageError>\n        <Target><Package name=\"org.osgi\"/></Target>\n        <Source><Package name=\"org.apache.tika.config\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"org.bouncycastle\"/></Target>\n        <Source><Package name=\"org.bouncycastle.pkix\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"java.nio\"/></Target>\n        <Source><Package name=\"org.bouncycastle.jcajce.provider.symmetric.util\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"java.nio\"/></Target>\n        <Source><Package name=\"org.bouncycastle.pqc.legacy.crypto.ntru\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"org.cryptomator\"/></Target>\n        <Source><Package name=\"com.nimbusds\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"org.opensaml\"/></Target>\n        <Source><Package name=\"com.nimbusds\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"jakarta.servlet\"/></Target>\n        <Source><Package name=\"com.nimbusds\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"net.shibboleth.utilities\"/></Target>\n        <Source><Package name=\"com.nimbusds\"/></Source>\n        <Reason>?</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"org.joda.time\"/></Target>\n        <Source><Package name=\"com.nimbusds.oauth2.sdk.assertions.saml2\"/></Source>\n        <Reason>Optional</Reason>\n    </LinkageError>\n    <!-- AWS SDK v1 exclusions - com.amazonaws.util is not used in runtime -->\n    <LinkageError>\n        <Target><Package name=\"com.amazonaws.util\"/></Target>\n        <Source><Package name=\"net.snowflake.common.core.SFBinary\"/></Source>\n        <Reason>com.amazonaws.util is not used in runtime</Reason>\n    </LinkageError>\n    <!-- AWS CRT exclusions - CRT is excluded from dependencies and not used in runtime -->\n    <LinkageError>\n        <Target><Package name=\"software.amazon.awssdk.crt\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services.s3.internal.crt\"/></Source>\n        <Reason>CRT dependency excluded, not used in runtime</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"software.amazon.awssdk.crt\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.http.auth.aws.crt.internal\"/></Source>\n        <Reason>CRT dependency excluded, not used in runtime</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"software.amazon.awssdk.crt\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.crtcore\"/></Source>\n        <Reason>CRT dependency excluded, not used in runtime</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"software.amazon.awssdk.crt\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.transfer\"/></Source>\n        <Reason>CRT dependency excluded, not used in runtime</Reason>\n    </LinkageError>\n    <!-- AWS SDK SignerProperty fields - false positives from linkage checker 1.5.13 -->\n    <!-- These fields exist in the actual JARs and are resolvable in IDEs, but linkage checker incorrectly reports them as missing -->\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner\" name=\"SERVICE_SIGNING_NAME\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner\" name=\"SERVICE_SIGNING_NAME\"/></Target>\n        <Source><Package name=\"net.snowflake.client.internal.core.auth.wif\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner\" name=\"DOUBLE_URL_ENCODE\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner\" name=\"PAYLOAD_SIGNING_ENABLED\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner\" name=\"NORMALIZE_PATH\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner\" name=\"CHUNK_ENCODING_ENABLED\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner\" name=\"DOUBLE_URL_ENCODE\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Field className=\"software.amazon.awssdk.http.auth.aws.signer.AwsV4aHttpSigner\" name=\"SERVICE_SIGNING_NAME\"/></Target>\n        <Source><Package name=\"software.amazon.awssdk.services\"/></Source>\n        <Reason>False positive from linkage checker - field exists in actual JAR</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"io.micrometer\"/></Target>\n        <Source><Package name=\"reactor.core\"/></Source>\n        <Reason>Optional</Reason>\n    </LinkageError>\n    <LinkageError>\n        <Target><Package name=\"io.micrometer\"/></Target>\n        <Source><Package name=\"reactor.util\"/></Source>\n        <Reason>Optional</Reason>\n    </LinkageError>\n    <!--\n    <LinkageError>\n        <Target><Package name=\"\"/></Target>\n        <Source><Package name=\"\"/></Source>\n        <Reason></Reason>\n    </LinkageError>\n    -->\n</LinkageCheckerFilter>\n"
  },
  {
    "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#    http://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# Apache Maven Wrapper startup batch script, version 3.2.0\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\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 /usr/local/etc/mavenrc ] ; then\n    . /usr/local/etc/mavenrc\n  fi\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        JAVA_HOME=\"$(/usr/libexec/java_home)\"; export JAVA_HOME\n      else\n        JAVA_HOME=\"/Library/Java/Home\"; export 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\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\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 \"$JAVA_HOME\" ] && [ -d \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"$(cd \"$JAVA_HOME\" || (echo \"cannot cd into $JAVA_HOME.\"; exit 1); pwd)\"\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=\"$(\\unset -f command 2>/dev/null; \\command -v 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\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  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/..\" || exit 1; pwd)\n    fi\n    # end of workaround\n  done\n  printf '%s' \"$(cd \"$basedir\" || exit 1; pwd)\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    # Remove \\r in case we run on Windows within Git Bash\n    # and check out the repository with auto CRLF management\n    # enabled. Otherwise, we may read lines that are delimited with\n    # \\r\\n and produce $'-Xarg\\r' rather than -Xarg due to word\n    # splitting rules.\n    tr -s '\\r\\n' ' ' < \"$1\"\n  fi\n}\n\nlog() {\n  if [ \"$MVNW_VERBOSE\" = true ]; then\n    printf '%s\\n' \"$1\"\n  fi\n}\n\nBASE_DIR=$(find_maven_basedir \"$(dirname \"$0\")\")\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\nMAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}; export MAVEN_PROJECTBASEDIR\nlog \"$MAVEN_PROJECTBASEDIR\"\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##########################################################################################\nwrapperJarPath=\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\"\nif [ -r \"$wrapperJarPath\" ]; then\n    log \"Found $wrapperJarPath\"\nelse\n    log \"Couldn't find $wrapperJarPath, downloading it ...\"\n\n    if [ -n \"$MVNW_REPOURL\" ]; then\n      wrapperUrl=\"$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar\"\n    else\n      wrapperUrl=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar\"\n    fi\n    while IFS=\"=\" read -r key value; do\n      # Remove '\\r' from value to allow usage on windows as IFS does not consider '\\r' as a separator ( considers space, tab, new line ('\\n'), and custom '=' )\n      safeValue=$(echo \"$value\" | tr -d '\\r')\n      case \"$key\" in (wrapperUrl) wrapperUrl=\"$safeValue\"; break ;;\n      esac\n    done < \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties\"\n    log \"Downloading from: $wrapperUrl\"\n\n    if $cygwin; then\n      wrapperJarPath=$(cygpath --path --windows \"$wrapperJarPath\")\n    fi\n\n    if command -v wget > /dev/null; then\n        log \"Found wget ... using wget\"\n        [ \"$MVNW_VERBOSE\" = true ] && QUIET=\"\" || QUIET=\"--quiet\"\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            wget $QUIET \"$wrapperUrl\" -O \"$wrapperJarPath\" || rm -f \"$wrapperJarPath\"\n        else\n            wget $QUIET --http-user=\"$MVNW_USERNAME\" --http-password=\"$MVNW_PASSWORD\" \"$wrapperUrl\" -O \"$wrapperJarPath\" || rm -f \"$wrapperJarPath\"\n        fi\n    elif command -v curl > /dev/null; then\n        log \"Found curl ... using curl\"\n        [ \"$MVNW_VERBOSE\" = true ] && QUIET=\"\" || QUIET=\"--silent\"\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            curl $QUIET -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L || rm -f \"$wrapperJarPath\"\n        else\n            curl $QUIET --user \"$MVNW_USERNAME:$MVNW_PASSWORD\" -o \"$wrapperJarPath\" \"$wrapperUrl\" -f -L || rm -f \"$wrapperJarPath\"\n        fi\n    else\n        log \"Falling back to using Java to download\"\n        javaSource=\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        javaClass=\"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class\"\n        # For Cygwin, switch paths to Windows format before running javac\n        if $cygwin; then\n          javaSource=$(cygpath --path --windows \"$javaSource\")\n          javaClass=$(cygpath --path --windows \"$javaClass\")\n        fi\n        if [ -e \"$javaSource\" ]; then\n            if [ ! -e \"$javaClass\" ]; then\n                log \" - Compiling MavenWrapperDownloader.java ...\"\n                (\"$JAVA_HOME/bin/javac\" \"$javaSource\")\n            fi\n            if [ -e \"$javaClass\" ]; then\n                log \" - Running MavenWrapperDownloader.java ...\"\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$wrapperUrl\" \"$wrapperJarPath\") || rm -f \"$wrapperJarPath\"\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\n# If specified, validate the SHA-256 sum of the Maven wrapper jar file\nwrapperSha256Sum=\"\"\nwhile IFS=\"=\" read -r key value; do\n  case \"$key\" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;\n  esac\ndone < \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties\"\nif [ -n \"$wrapperSha256Sum\" ]; then\n  wrapperSha256Result=false\n  if command -v sha256sum > /dev/null; then\n    if echo \"$wrapperSha256Sum  $wrapperJarPath\" | sha256sum -c > /dev/null 2>&1; then\n      wrapperSha256Result=true\n    fi\n  elif command -v shasum > /dev/null; then\n    if echo \"$wrapperSha256Sum  $wrapperJarPath\" | shasum -a 256 -c > /dev/null 2>&1; then\n      wrapperSha256Result=true\n    fi\n  else\n    echo \"Checksum validation was requested but neither 'sha256sum' or 'shasum' are available.\"\n    echo \"Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties.\"\n    exit 1\n  fi\n  if [ $wrapperSha256Result = false ]; then\n    echo \"Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.\" >&2\n    echo \"Investigate or delete $wrapperJarPath to attempt a clean download.\" >&2\n    echo \"If you updated your Maven version, you need to update the specified wrapperSha256Sum property.\" >&2\n    exit 1\n  fi\nfi\n\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 \"$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\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $*\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\n# shellcheck disable=SC2086 # safe args\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  $MAVEN_DEBUG_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\r\n@REM Licensed to the Apache Software Foundation (ASF) under one\r\n@REM or more contributor license agreements.  See the NOTICE file\r\n@REM distributed with this work for additional information\r\n@REM regarding copyright ownership.  The ASF licenses this file\r\n@REM to you under the Apache License, Version 2.0 (the\r\n@REM \"License\"); you may not use this file except in compliance\r\n@REM with the License.  You may obtain a copy of the License at\r\n@REM\r\n@REM    http://www.apache.org/licenses/LICENSE-2.0\r\n@REM\r\n@REM Unless required by applicable law or agreed to in writing,\r\n@REM software distributed under the License is distributed on an\r\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\n@REM KIND, either express or implied.  See the License for the\r\n@REM specific language governing permissions and limitations\r\n@REM under the License.\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM ----------------------------------------------------------------------------\r\n@REM Apache Maven Wrapper startup batch script, version 3.2.0\r\n@REM\r\n@REM Required ENV vars:\r\n@REM JAVA_HOME - location of a JDK home dir\r\n@REM\r\n@REM Optional ENV vars\r\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\r\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\r\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\r\n@REM     e.g. to debug Maven itself, use\r\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\r\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\r\n@REM ----------------------------------------------------------------------------\r\n\r\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\r\n@echo off\r\n@REM set title of command window\r\ntitle %0\r\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\r\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\r\n\r\n@REM set %HOME% to equivalent of $HOME\r\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\r\n\r\n@REM Execute a user defined script before this one\r\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\r\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%USERPROFILE%\\mavenrc_pre.bat\" call \"%USERPROFILE%\\mavenrc_pre.bat\" %*\r\nif exist \"%USERPROFILE%\\mavenrc_pre.cmd\" call \"%USERPROFILE%\\mavenrc_pre.cmd\" %*\r\n:skipRcPre\r\n\r\n@setlocal\r\n\r\nset ERROR_CODE=0\r\n\r\n@REM To isolate internal variables from possible post scripts, we use another setlocal\r\n@setlocal\r\n\r\n@REM ==== START VALIDATION ====\r\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\r\n\r\necho.\r\necho Error: JAVA_HOME not found in your environment. >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n:OkJHome\r\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\r\n\r\necho.\r\necho Error: JAVA_HOME is set to an invalid directory. >&2\r\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\r\necho Please set the JAVA_HOME variable in your environment to match the >&2\r\necho location of your Java installation. >&2\r\necho.\r\ngoto error\r\n\r\n@REM ==== END VALIDATION ====\r\n\r\n:init\r\n\r\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\r\n@REM Fallback to current working directory if not found.\r\n\r\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\r\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\r\n\r\nset EXEC_DIR=%CD%\r\nset WDIR=%EXEC_DIR%\r\n:findBaseDir\r\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\r\ncd ..\r\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\r\nset WDIR=%CD%\r\ngoto findBaseDir\r\n\r\n:baseDirFound\r\nset MAVEN_PROJECTBASEDIR=%WDIR%\r\ncd \"%EXEC_DIR%\"\r\ngoto endDetectBaseDir\r\n\r\n:baseDirNotFound\r\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\r\ncd \"%EXEC_DIR%\"\r\n\r\n:endDetectBaseDir\r\n\r\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\r\n\r\n@setlocal EnableExtensions EnableDelayedExpansion\r\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\r\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\r\n\r\n:endReadAdditionalConfig\r\n\r\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\r\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\r\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\r\n\r\nset WRAPPER_URL=\"https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar\"\r\n\r\nFOR /F \"usebackq tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\r\n    IF \"%%A\"==\"wrapperUrl\" SET WRAPPER_URL=%%B\r\n)\r\n\r\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\r\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\r\nif exist %WRAPPER_JAR% (\r\n    if \"%MVNW_VERBOSE%\" == \"true\" (\r\n        echo Found %WRAPPER_JAR%\r\n    )\r\n) else (\r\n    if not \"%MVNW_REPOURL%\" == \"\" (\r\n        SET WRAPPER_URL=\"%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar\"\r\n    )\r\n    if \"%MVNW_VERBOSE%\" == \"true\" (\r\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\r\n        echo Downloading from: %WRAPPER_URL%\r\n    )\r\n\r\n    powershell -Command \"&{\"^\r\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\r\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\r\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\r\n\t\t\"}\"^\r\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')\"^\r\n\t\t\"}\"\r\n    if \"%MVNW_VERBOSE%\" == \"true\" (\r\n        echo Finished downloading %WRAPPER_JAR%\r\n    )\r\n)\r\n@REM End of extension\r\n\r\n@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file\r\nSET WRAPPER_SHA_256_SUM=\"\"\r\nFOR /F \"usebackq tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\r\n    IF \"%%A\"==\"wrapperSha256Sum\" SET WRAPPER_SHA_256_SUM=%%B\r\n)\r\nIF NOT %WRAPPER_SHA_256_SUM%==\"\" (\r\n    powershell -Command \"&{\"^\r\n       \"$hash = (Get-FileHash \\\"%WRAPPER_JAR%\\\" -Algorithm SHA256).Hash.ToLower();\"^\r\n       \"If('%WRAPPER_SHA_256_SUM%' -ne $hash){\"^\r\n       \"  Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';\"^\r\n       \"  Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';\"^\r\n       \"  Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';\"^\r\n       \"  exit 1;\"^\r\n       \"}\"^\r\n       \"}\"\r\n    if ERRORLEVEL 1 goto error\r\n)\r\n\r\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\r\n@REM work with both Windows and non-Windows executions.\r\nset MAVEN_CMD_LINE_ARGS=%*\r\n\r\n%MAVEN_JAVA_EXE% ^\r\n  %JVM_CONFIG_MAVEN_PROPS% ^\r\n  %MAVEN_OPTS% ^\r\n  %MAVEN_DEBUG_OPTS% ^\r\n  -classpath %WRAPPER_JAR% ^\r\n  \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" ^\r\n  %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\r\nif ERRORLEVEL 1 goto error\r\ngoto end\r\n\r\n:error\r\nset ERROR_CODE=1\r\n\r\n:end\r\n@endlocal & set ERROR_CODE=%ERROR_CODE%\r\n\r\nif not \"%MAVEN_SKIP_RC%\"==\"\" goto skipRcPost\r\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\r\nif exist \"%USERPROFILE%\\mavenrc_post.bat\" call \"%USERPROFILE%\\mavenrc_post.bat\"\r\nif exist \"%USERPROFILE%\\mavenrc_post.cmd\" call \"%USERPROFILE%\\mavenrc_post.cmd\"\r\n:skipRcPost\r\n\r\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\r\nif \"%MAVEN_BATCH_PAUSE%\"==\"on\" pause\r\n\r\nif \"%MAVEN_TERMINATE_CMD%\"==\"on\" exit %ERROR_CODE%\r\n\r\ncmd /C exit /B %ERROR_CODE%\r\n"
  },
  {
    "path": "output.json",
    "content": ""
  },
  {
    "path": "parent-pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n     xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>net.snowflake</groupId>\n  <artifactId>snowflake-jdbc-parent</artifactId>\n  <version>4.2.1-SNAPSHOT</version>\n  <packaging>pom</packaging>\n\n  <modules>\n    <module>.</module>\n    <module>FIPS</module>\n  </modules>\n\n  <properties>\n    <animal.sniffer.annotations.version>1.27</animal.sniffer.annotations.version>\n    <apache.commons.compress.version>1.28.0</apache.commons.compress.version>\n    <apache.commons.lang3.version>3.18.0</apache.commons.lang3.version>\n    <apache.commons.text.version>1.10.0</apache.commons.text.version>\n    <apache.httpclient.version>4.5.14</apache.httpclient.version>\n    <apache.httpcore.version>4.4.16</apache.httpcore.version>\n    <zstd-jni.version>1.5.6-5</zstd-jni.version>\n    <arrow.version>17.0.0</arrow.version>\n    <asm.version>9.7.1</asm.version>\n    <avro.version>1.8.1</avro.version>\n    <awaitility.version>4.2.0</awaitility.version>\n    <awssdk.version>2.37.5</awssdk.version>\n    <azure.core.version>1.57.0</azure.core.version>\n    <azure.storage.blob.version>12.32.0</azure.storage.blob.version>\n    <azure.storage.common.version>12.31.0</azure.storage.common.version>\n    <awssdk.encryption.s3.version>3.4.0</awssdk.encryption.s3.version>\n    <bouncycastle.version>1.84</bouncycastle.version>\n    <bouncycastle.bcfips.version>1.0.2.6</bouncycastle.bcfips.version>\n    <bouncycastle.bcpkixfips.version>1.0.8</bouncycastle.bcpkixfips.version>\n    <bytebuddy.version>1.14.17</bytebuddy.version>\n    <classworlds.version>1.1</classworlds.version>\n    <checkerframework.version>3.48.2</checkerframework.version>\n    <commons.cli.version>1.2</commons.cli.version>\n    <commons.codec.version>1.19.0</commons.codec.version>\n    <commons.dbcp.version>1.4</commons.dbcp.version>\n    <commons.io.version>2.20.0</commons.io.version>\n    <commons.logging.version>1.2</commons.logging.version>\n    <commons.pool.version>1.5.4</commons.pool.version>\n    <c3p0.version>0.9.5.4</c3p0.version>\n    <google.api.grpc.version>2.48.0</google.api.grpc.version>\n    <google.auth.library.oauth2.http.version>1.29.0</google.auth.library.oauth2.http.version>\n    <google.cloud.core.version>2.47.0</google.cloud.core.version>\n    <google.cloud.storage.version>2.44.1</google.cloud.storage.version>\n    <google.http.client.version>1.45.0</google.http.client.version>\n    <google.code.gson.version>2.13.1</google.code.gson.version>\n    <google.errorprone.version>2.35.1</google.errorprone.version>\n    <google.flatbuffers.version>24.3.25</google.flatbuffers.version>\n    <google.gax.version>2.57.0</google.gax.version>\n    <google.guava.version>33.3.1-jre</google.guava.version>\n    <google.j2objc-annotations.version>3.0.0</google.j2objc-annotations.version>\n    <google.jsr305.version>3.0.2</google.jsr305.version>\n    <google.protobuf.java.version>4.28.2</google.protobuf.java.version>\n    <grpc.version>1.81.0</grpc.version>\n    <hamcrest.version>2.2</hamcrest.version>\n    <hikaricp.version>2.4.3</hikaricp.version>\n    <jackson.version>2.18.4.1</jackson.version>\n    <jacoco.skip.instrument>true</jacoco.skip.instrument>\n    <javax.servlet.version>3.1.0</javax.servlet.version>\n    <jna.version>5.13.0</jna.version>\n    <json.smart.version>2.5.2</json.smart.version>\n    <junit4.version>4.13.2</junit4.version>\n    <junit.version>5.11.1</junit.version>\n    <junit.platform.version>1.11.1</junit.platform.version>\n    <jsoup.version>1.15.3</jsoup.version>\n    <logback.version>1.3.6</logback.version>\n    <mockito.version>4.11.0</mockito.version>\n    <nimbusds.oauth2.version>11.30.1</nimbusds.oauth2.version>\n    <netty.version>4.1.133.Final</netty.version>\n    <nimbusds.version>10.6</nimbusds.version>\n    <opencensus.version>0.31.1</opencensus.version>\n    <plexus.container.version>1.0-alpha-9-stable-1</plexus.container.version>\n    <plexus.utils.version>3.4.2</plexus.utils.version>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <relocationBase>net/snowflake/client/jdbc/internal</relocationBase>\n    <shadeBase>net.snowflake.client.jdbc.internal</shadeBase>\n    <shadeNativeBase>net_snowflake_client_jdbc_internal</shadeNativeBase>\n    <skip.unitTests>false</skip.unitTests>\n    <slf4j.version>2.0.13</slf4j.version>\n    <snowflake.common.version>5.1.4</snowflake.common.version>\n    <integrationTestSuites>UnitTestSuite</integrationTestSuites>\n    <tika.version>2.4.1</tika.version>\n    <tukaani.version>1.9</tukaani.version>\n    <version.maven>3.6.3</version.maven>\n    <version.plugin.antrun>3.1.0</version.plugin.antrun>\n    <version.plugin.buildnumber>3.0.0</version.plugin.buildnumber>\n    <version.plugin.checkstyle>3.3.1</version.plugin.checkstyle>\n    <version.plugin.clean>3.2.0</version.plugin.clean>\n    <version.plugin.compiler>3.11.0</version.plugin.compiler>\n    <version.plugin.dependency>3.5.0</version.plugin.dependency>\n    <version.plugin.deploy>3.1.1</version.plugin.deploy>\n    <version.plugin.enforcer>3.0.0-M3</version.plugin.enforcer>\n    <version.plugin.exec>3.1.0</version.plugin.exec>\n    <version.plugin.failsafe>3.5.1</version.plugin.failsafe>\n    <version.plugin.fmt>2.19</version.plugin.fmt>\n    <version.plugin.gpg>3.0.1</version.plugin.gpg>\n    <version.plugin.install>3.1.1</version.plugin.install>\n    <version.plugin.jacoco>0.8.8</version.plugin.jacoco>\n    <version.plugin.japicmp>0.23.0</version.plugin.japicmp>\n    <version.plugin.jar>3.3.0</version.plugin.jar>\n    <version.plugin.javadoc>3.5.0</version.plugin.javadoc>\n    <version.plugin.projectinforeports>3.4.2</version.plugin.projectinforeports>\n    <version.plugin.shade>3.6.2</version.plugin.shade>\n    <version.plugin.sortpom>3.0.1</version.plugin.sortpom>\n    <version.plugin.source>3.2.1</version.plugin.source>\n    <version.plugin.surefire>3.5.1</version.plugin.surefire>\n    <version.plugin.wiremock>3.8.0</version.plugin.wiremock>\n    <version.plugin.buildhelper>3.6.1</version.plugin.buildhelper>\n    <version.plugin.publishing>0.8.0</version.plugin.publishing>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>software.amazon.awssdk</groupId>\n        <artifactId>bom</artifactId>\n        <version>${awssdk.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.fasterxml.jackson</groupId>\n        <artifactId>jackson-bom</artifactId>\n        <version>${jackson.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.google.protobuf</groupId>\n        <artifactId>protobuf-bom</artifactId>\n        <version>${google.protobuf.java.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>io.grpc</groupId>\n        <artifactId>grpc-bom</artifactId>\n        <version>${grpc.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>io.netty</groupId>\n        <artifactId>netty-bom</artifactId>\n        <version>${netty.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>animal-sniffer-annotations</artifactId>\n        <version>${animal.sniffer.annotations.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>classworlds</groupId>\n        <artifactId>classworlds</artifactId>\n        <version>${classworlds.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.api</groupId>\n        <artifactId>gax</artifactId>\n        <version>${google.gax.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.api.grpc</groupId>\n        <artifactId>proto-google-common-protos</artifactId>\n        <version>${google.api.grpc.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.auth</groupId>\n        <artifactId>google-auth-library-oauth2-http</artifactId>\n        <version>${google.auth.library.oauth2.http.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.cloud</groupId>\n        <artifactId>google-cloud-core</artifactId>\n        <version>${google.cloud.core.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.cloud</groupId>\n        <artifactId>google-cloud-core-http</artifactId>\n        <version>${google.cloud.core.version}</version>\n        <exclusions>\n          <exclusion>\n            <groupId>com.google.api-client</groupId>\n            <artifactId>google-api-client</artifactId>\n          </exclusion>\n        </exclusions>\n      </dependency>\n      <dependency>\n        <groupId>com.google.cloud</groupId>\n        <artifactId>google-cloud-storage</artifactId>\n        <version>${google.cloud.storage.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.http-client</groupId>\n        <artifactId>google-http-client</artifactId>\n        <version>${google.http.client.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.http-client</groupId>\n        <artifactId>google-http-client-apache-v2</artifactId>\n        <version>${google.http.client.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.code.findbugs</groupId>\n        <artifactId>jsr305</artifactId>\n        <version>${google.jsr305.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.code.gson</groupId>\n        <artifactId>gson</artifactId>\n        <version>${google.code.gson.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.errorprone</groupId>\n        <artifactId>error_prone_annotations</artifactId>\n        <version>${google.errorprone.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.guava</groupId>\n        <artifactId>guava</artifactId>\n        <version>${google.guava.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.google.j2objc</groupId>\n        <artifactId>j2objc-annotations</artifactId>\n        <version>${google.j2objc-annotations.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.azure</groupId>\n        <artifactId>azure-core</artifactId>\n        <version>${azure.core.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.azure</groupId>\n        <artifactId>azure-storage-common</artifactId>\n        <version>${azure.storage.common.version}</version>\n        <exclusions>\n          <exclusion>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-tcnative-boringssl-static</artifactId>\n          </exclusion>\n        </exclusions>\n      </dependency>\n      <dependency>\n        <groupId>com.azure</groupId>\n        <artifactId>azure-storage-blob</artifactId>\n        <version>${azure.storage.blob.version}</version>\n        <exclusions>\n          <exclusion>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-tcnative-boringssl-static</artifactId>\n          </exclusion>\n        </exclusions>\n      </dependency>\n      <dependency>\n        <groupId>com.nimbusds</groupId>\n        <artifactId>nimbus-jose-jwt</artifactId>\n        <version>${nimbusds.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.nimbusds</groupId>\n        <artifactId>oauth2-oidc-sdk</artifactId>\n        <version>${nimbusds.oauth2.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>commons-cli</groupId>\n        <artifactId>commons-cli</artifactId>\n        <version>${commons.cli.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>commons-codec</groupId>\n        <artifactId>commons-codec</artifactId>\n        <version>${commons.codec.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>commons-io</groupId>\n        <artifactId>commons-io</artifactId>\n        <version>${commons.io.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>commons-logging</groupId>\n        <artifactId>commons-logging</artifactId>\n        <version>${commons.logging.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>commons-dbcp</groupId>\n        <artifactId>commons-dbcp</artifactId>\n        <version>${commons.dbcp.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>commons-pool</groupId>\n        <artifactId>commons-pool</artifactId>\n        <version>${commons.pool.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>io.opencensus</groupId>\n        <artifactId>opencensus-api</artifactId>\n        <version>${opencensus.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>junit</groupId>\n        <artifactId>junit</artifactId>\n        <version>${junit4.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter</artifactId>\n        <version>${junit.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter-api</artifactId>\n        <version>${junit.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter-engine</artifactId>\n        <version>${junit.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter-params</artifactId>\n        <version>${junit.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.platform</groupId>\n        <artifactId>junit-platform-suite</artifactId>\n        <version>${junit.platform.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.platform</groupId>\n        <artifactId>junit-platform-engine</artifactId>\n        <version>${junit.platform.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.platform</groupId>\n        <artifactId>junit-platform-runner</artifactId>\n        <version>${junit.platform.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.platform</groupId>\n        <artifactId>junit-platform-suite-api</artifactId>\n        <version>${junit.platform.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.platform</groupId>\n        <artifactId>junit-platform-suite-engine</artifactId>\n        <version>${junit.platform.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.junit.platform</groupId>\n        <artifactId>junit-platform-launcher</artifactId>\n        <version>${junit.platform.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.avro</groupId>\n        <artifactId>avro</artifactId>\n        <version>${avro.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-compress</artifactId>\n        <version>${apache.commons.compress.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-lang3</artifactId>\n        <version>${apache.commons.lang3.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.commons</groupId>\n        <artifactId>commons-text</artifactId>\n        <version>${apache.commons.text.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>javax.servlet</groupId>\n        <artifactId>javax.servlet-api</artifactId>\n        <version>${javax.servlet.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>net.minidev</groupId>\n        <artifactId>json-smart</artifactId>\n        <version>${json.smart.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>net.snowflake</groupId>\n        <artifactId>snowflake-common</artifactId>\n        <version>${snowflake.common.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.arrow</groupId>\n        <artifactId>arrow-memory-core</artifactId>\n        <version>${arrow.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.arrow</groupId>\n        <artifactId>arrow-vector</artifactId>\n        <version>${arrow.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.httpcomponents</groupId>\n        <artifactId>httpclient</artifactId>\n        <version>${apache.httpclient.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.httpcomponents</groupId>\n        <artifactId>httpcore</artifactId>\n        <version>${apache.httpcore.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>com.github.luben</groupId>\n        <artifactId>zstd-jni</artifactId>\n        <version>${zstd-jni.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.tika</groupId>\n        <artifactId>tika-core</artifactId>\n        <version>${tika.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.jsoup</groupId>\n        <artifactId>jsoup</artifactId>\n        <version>${jsoup.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>net.java.dev.jna</groupId>\n        <artifactId>jna</artifactId>\n        <version>${jna.version}</version>\n        <scope>provided</scope>\n      </dependency>\n      <dependency>\n        <groupId>net.java.dev.jna</groupId>\n        <artifactId>jna-platform</artifactId>\n        <version>${jna.version}</version>\n        <scope>provided</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.checkerframework</groupId>\n        <artifactId>checker-qual</artifactId>\n        <version>${checkerframework.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.codehaus.plexus</groupId>\n        <artifactId>plexus-container-default</artifactId>\n        <version>${plexus.container.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.codehaus.plexus</groupId>\n        <artifactId>plexus-utils</artifactId>\n        <version>${plexus.utils.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.hamcrest</groupId>\n        <artifactId>hamcrest</artifactId>\n        <version>${hamcrest.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.hamcrest</groupId>\n        <artifactId>hamcrest-core</artifactId>\n        <version>${hamcrest.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.ow2.asm</groupId>\n        <artifactId>asm</artifactId>\n        <version>${asm.version}</version>\n      </dependency>\n      <dependency>\n        <groupId>org.slf4j</groupId>\n        <artifactId>slf4j-api</artifactId>\n        <version>${slf4j.version}</version>\n      </dependency>\n      <dependency>\n        <!-- used by apache arrow -->\n        <groupId>com.google.flatbuffers</groupId>\n        <artifactId>flatbuffers-java</artifactId>\n        <version>${google.flatbuffers.version}</version>\n        <scope>runtime</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.arrow</groupId>\n        <artifactId>arrow-format</artifactId>\n        <version>${arrow.version}</version>\n        <scope>runtime</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.arrow</groupId>\n        <artifactId>arrow-memory-netty-buffer-patch</artifactId>\n        <version>${arrow.version}</version>\n        <scope>runtime</scope>\n        <exclusions>\n          <!-- We explicitly add netty dependencies in higher version, here only excluding for removing snyk issue since snyk does not understand our dependency tree -->\n          <exclusion>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-common</artifactId>\n          </exclusion>\n          <exclusion>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-buffer</artifactId>\n          </exclusion>\n        </exclusions>\n      </dependency>\n      <dependency>\n        <groupId>org.apache.arrow</groupId>\n        <artifactId>arrow-memory-unsafe</artifactId>\n        <version>${arrow.version}</version>\n        <scope>runtime</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      <dependency>\n        <groupId>ch.qos.logback</groupId>\n        <artifactId>logback-core</artifactId>\n        <version>${logback.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.mchange</groupId>\n        <artifactId>c3p0</artifactId>\n        <version>${c3p0.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.zaxxer</groupId>\n        <artifactId>HikariCP</artifactId>\n        <version>${hikaricp.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <!-- for non-FIPS JAR -->\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcpkix-jdk18on</artifactId>\n        <version>${bouncycastle.version}</version>\n      </dependency>\n      <dependency>\n        <!-- for non-FIPS JAR -->\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcprov-jdk18on</artifactId>\n        <version>${bouncycastle.version}</version>\n      </dependency>\n      <dependency>\n        <!-- for non-FIPS JAR -->\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcutil-jdk18on</artifactId>\n        <version>${bouncycastle.version}</version>\n      </dependency>\n      <dependency>\n        <!-- for FIPS JAR -->\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bc-fips</artifactId>\n        <version>${bouncycastle.bcfips.version}</version>\n        <scope>provided</scope>\n      </dependency>\n      <dependency>\n        <!-- for FIPS JAR -->\n        <groupId>org.bouncycastle</groupId>\n        <artifactId>bcpkix-fips</artifactId>\n        <version>${bouncycastle.bcpkixfips.version}</version>\n        <scope>provided</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.tukaani</groupId>\n        <artifactId>xz</artifactId>\n        <version>${tukaani.version}</version>\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.mockito</groupId>\n        <artifactId>mockito-inline</artifactId>\n        <version>${mockito.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>net.bytebuddy</groupId>\n        <artifactId>byte-buddy</artifactId>\n        <version>${bytebuddy.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.awaitility</groupId>\n        <artifactId>awaitility</artifactId>\n        <version>${awaitility.version}</version>\n        <scope>test</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.wiremock</groupId>\n        <artifactId>wiremock-standalone</artifactId>\n        <version>${version.plugin.wiremock}</version>\n        <scope>test</scope>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <dependencies>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>s3</artifactId>\n      <exclusions>\n        <!-- aws-crt cannot be shaded https://github.com/awslabs/aws-crt-java/issues/166 -->\n        <!-- We are not using it indirectly or directly so we can exclude this -->\n        <exclusion>\n          <groupId>software.amazon.awssdk.crt</groupId>\n          <artifactId>aws-crt</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>s3-transfer-manager</artifactId>\n      <exclusions>\n        <!-- aws-crt cannot be shaded https://github.com/awslabs/aws-crt-java/issues/166 -->\n        <!-- We are not using it indirectly or directly so we can exclude this -->\n        <exclusion>\n          <groupId>software.amazon.awssdk.crt</groupId>\n          <artifactId>aws-crt</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>sdk-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>netty-nio-client</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>http-client-spi</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>identity-spi</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>aws-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>auth</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>http-auth-aws</artifactId>\n      <exclusions>\n        <exclusion>\n          <groupId>software.amazon.awssdk.crt</groupId>\n          <artifactId>aws-crt</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>http-auth-spi</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>regions</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>sts</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.azure</groupId>\n      <artifactId>azure-core</artifactId>\n      <version>${azure.core.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.azure</groupId>\n      <artifactId>azure-storage-common</artifactId>\n      <version>${azure.storage.common.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.azure</groupId>\n      <artifactId>azure-storage-blob</artifactId>\n      <version>${azure.storage.blob.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-annotations</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-databind</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.dataformat</groupId>\n      <artifactId>jackson-dataformat-toml</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.api</groupId>\n      <artifactId>gax</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.auth</groupId>\n      <artifactId>google-auth-library-oauth2-http</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-core-http</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-storage</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.http-client</groupId>\n      <artifactId>google-http-client</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.http-client</groupId>\n      <artifactId>google-http-client-apache-v2</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.nimbusds</groupId>\n      <artifactId>nimbus-jose-jwt</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.nimbusds</groupId>\n      <artifactId>oauth2-oidc-sdk</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-logging</groupId>\n      <artifactId>commons-logging</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>net.minidev</groupId>\n      <artifactId>json-smart</artifactId>\n    </dependency>\n    <dependency> <!-- netty is not a direct dependency. It is used by arrow-vector -->\n      <groupId>io.netty</groupId>\n      <artifactId>netty-common</artifactId>\n      <scope>runtime</scope>\n    </dependency>\n    <dependency>\n    <groupId>io.netty</groupId>\n      <artifactId>netty-buffer</artifactId>\n      <scope>runtime</scope>\n    </dependency>\n    <dependency>\n      <groupId>net.snowflake</groupId>\n      <artifactId>snowflake-common</artifactId>\n      <exclusions>\n        <!-- Exclude AWS SDK v1 dependencies -->\n        <exclusion>\n          <groupId>com.amazonaws</groupId>\n          <artifactId>aws-java-sdk-core</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-memory-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-vector</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents</groupId>\n      <artifactId>httpclient</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents</groupId>\n      <artifactId>httpcore</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.github.luben</groupId>\n      <artifactId>zstd-jni</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.tika</groupId>\n      <artifactId>tika-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.jsoup</groupId>\n      <artifactId>jsoup</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>net.java.dev.jna</groupId>\n      <artifactId>jna</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>net.java.dev.jna</groupId>\n      <artifactId>jna-platform</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.datatype</groupId>\n      <artifactId>jackson-datatype-jsr310</artifactId>\n      <scope>runtime</scope>\n    </dependency>\n    <dependency>\n      <!-- used by apache arrow -->\n      <groupId>com.google.flatbuffers</groupId>\n      <artifactId>flatbuffers-java</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-format</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-memory-netty-buffer-patch</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.arrow</groupId>\n      <artifactId>arrow-memory-unsafe</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-classic</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>ch.qos.logback</groupId>\n      <artifactId>logback-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.mchange</groupId>\n      <artifactId>c3p0</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.zaxxer</groupId>\n      <artifactId>HikariCP</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-cli</groupId>\n      <artifactId>commons-cli</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-dbcp</groupId>\n      <artifactId>commons-dbcp</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>commons-pool</groupId>\n      <artifactId>commons-pool</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-engine</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.jupiter</groupId>\n      <artifactId>junit-jupiter-params</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-suite</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-engine</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-runner</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-suite-api</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-suite-engine</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.junit.platform</groupId>\n      <artifactId>junit-platform-launcher</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.avro</groupId>\n      <artifactId>avro</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-compress</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-text</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.hamcrest</groupId>\n      <artifactId>hamcrest</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-inline</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.awaitility</groupId>\n      <artifactId>awaitility</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.wiremock</groupId>\n      <artifactId>wiremock-standalone</artifactId>\n    </dependency>\n  </dependencies>\n</project>\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n     xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <parent>\n    <groupId>net.snowflake</groupId>\n    <artifactId>snowflake-jdbc-parent</artifactId>\n    <version>4.2.1-SNAPSHOT</version>\n    <relativePath>./parent-pom.xml</relativePath>\n  </parent>\n\n  <!-- Maven complains about using property here, but it makes install and deploy process easier to override final package names and localization -->\n  <artifactId>${artifactId}</artifactId>\n  <version>4.2.1-SNAPSHOT</version>\n  <packaging>jar</packaging>\n\n  <name>${artifactId}</name>\n  <url>https://github.com/snowflakedb/snowflake-jdbc</url>\n\n  <scm>\n    <connection>scm:git:https://github.com/snowflakedb/snowflake-jdbc.git</connection>\n    <url>https://github.com/snowflakedb/snowflake-jdbc</url>\n  </scm>\n\n  <properties>\n    <artifactId>snowflake-jdbc</artifactId>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcpkix-jdk18on</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcprov-jdk18on</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcutil-jdk18on</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.tngtech.archunit</groupId>\n      <artifactId>archunit</artifactId>\n      <version>1.4.1</version>\n      <scope>test</scope>\n    </dependency>\n  </dependencies>\n\n  <build>\n    <finalName>${project.artifactId}</finalName>\n    <resources>\n      <resource>\n        <filtering>true</filtering>\n        <directory>src/main/resources</directory>\n        <excludes>\n          <exclude>**/*.dylib</exclude>\n          <exclude>**/*.so</exclude>\n          <exclude>**/*.dll</exclude>\n          <exclude>**/*.a</exclude>\n        </excludes>\n      </resource>\n      <resource>\n        <filtering>false</filtering>\n        <directory>src/main/resources</directory>\n        <includes>\n          <include>**/*.dylib</include>\n          <include>**/*.so</include>\n          <include>**/*.dll</include>\n          <include>**/*.a</include>\n        </includes>\n      </resource>\n    </resources>\n    <pluginManagement>\n      <plugins>\n        <plugin>\n          <groupId>com.github.ekryd.sortpom</groupId>\n          <artifactId>sortpom-maven-plugin</artifactId>\n          <version>${version.plugin.sortpom}</version>\n        </plugin>\n        <plugin>\n          <groupId>com.github.siom79.japicmp</groupId>\n          <artifactId>japicmp-maven-plugin</artifactId>\n          <version>${version.plugin.japicmp}</version>\n        </plugin>\n        <plugin>\n          <groupId>com.spotify.fmt</groupId>\n          <artifactId>fmt-maven-plugin</artifactId>\n          <version>${version.plugin.fmt}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-antrun-plugin</artifactId>\n          <version>${version.plugin.antrun}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-checkstyle-plugin</artifactId>\n          <version>${version.plugin.checkstyle}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-clean-plugin</artifactId>\n          <version>${version.plugin.clean}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-compiler-plugin</artifactId>\n          <version>${version.plugin.compiler}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-dependency-plugin</artifactId>\n          <version>${version.plugin.dependency}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-deploy-plugin</artifactId>\n          <version>${version.plugin.deploy}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-enforcer-plugin</artifactId>\n          <version>${version.plugin.enforcer}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-failsafe-plugin</artifactId>\n          <version>${version.plugin.failsafe}</version>\n          <dependencies>\n            <dependency>\n              <groupId>org.apache.maven.surefire</groupId>\n              <artifactId>surefire-junit-platform</artifactId>\n              <version>${version.plugin.surefire}</version>\n            </dependency>\n          </dependencies>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-gpg-plugin</artifactId>\n          <version>${version.plugin.gpg}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-install-plugin</artifactId>\n          <version>${version.plugin.install}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-jar-plugin</artifactId>\n          <version>${version.plugin.jar}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-javadoc-plugin</artifactId>\n          <version>${version.plugin.javadoc}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-project-info-reports-plugin</artifactId>\n          <version>${version.plugin.projectinforeports}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-shade-plugin</artifactId>\n          <version>${version.plugin.shade}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-source-plugin</artifactId>\n          <version>${version.plugin.source}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.apache.maven.plugins</groupId>\n          <artifactId>maven-surefire-plugin</artifactId>\n          <version>${version.plugin.surefire}</version>\n          <dependencies>\n            <dependency>\n              <groupId>org.apache.maven.surefire</groupId>\n              <artifactId>surefire-junit-platform</artifactId>\n              <version>${version.plugin.surefire}</version>\n            </dependency>\n          </dependencies>\n        </plugin>\n        <plugin>\n          <groupId>org.codehaus.mojo</groupId>\n          <artifactId>buildnumber-maven-plugin</artifactId>\n          <version>${version.plugin.buildnumber}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.codehaus.mojo</groupId>\n          <artifactId>exec-maven-plugin</artifactId>\n          <version>${version.plugin.exec}</version>\n        </plugin>\n        <plugin>\n          <groupId>org.jacoco</groupId>\n          <artifactId>jacoco-maven-plugin</artifactId>\n          <version>${version.plugin.jacoco}</version>\n        </plugin>\n      </plugins>\n    </pluginManagement>\n    <plugins>\n      <plugin>\n        <groupId>com.github.ekryd.sortpom</groupId>\n        <artifactId>sortpom-maven-plugin</artifactId>\n        <configuration>\n          <createBackupFile>false</createBackupFile>\n          <expandEmptyElements>false</expandEmptyElements>\n          <indentSchemaLocation>true</indentSchemaLocation>\n          <sortDependencies>scope,groupId,artifactId</sortDependencies>\n          <sortDependencyExclusions>groupId,artifactId</sortDependencyExclusions>\n          <sortExecutions>true</sortExecutions>\n          <sortModules>true</sortModules>\n          <sortProperties>true</sortProperties>\n        </configuration>\n        <executions>\n          <execution>\n            <goals>\n              <goal>verify</goal>\n            </goals>\n            <phase>validate</phase>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-clean-plugin</artifactId>\n        <configuration>\n          <excludeDefaultDirectories/>\n          <filesets>\n            <fileset>\n              <directory>lib</directory>\n              <includes>\n                <include>*.jar</include>\n              </includes>\n            </fileset>\n          </filesets>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <configuration>\n          <showDeprecation>true</showDeprecation>\n          <showWarnings>true</showWarnings>\n          <executable>javac</executable>\n          <fork>true</fork>\n          <source>8</source>\n          <target>8</target>\n          <compilerArgs>\n            <arg>-Xlint:all,-path</arg>\n          </compilerArgs>\n        </configuration>\n        <executions>\n          <execution>\n            <id>default-testCompile</id>\n            <goals>\n              <goal>testCompile</goal>\n            </goals>\n            <phase>test-compile</phase>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-dependency-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>analyze</id>\n            <goals>\n              <goal>analyze-only</goal>\n            </goals>\n            <configuration>\n              <failOnWarning>true</failOnWarning>\n              <ignoreNonCompile>true</ignoreNonCompile>\n              <ignoredUnusedDeclaredDependencies>\n                <ignoredUnusedDeclaredDependency>javax.servlet:javax.servlet-api</ignoredUnusedDeclaredDependency>\n              </ignoredUnusedDeclaredDependencies>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-jar</id>\n            <goals>\n              <goal>copy</goal>\n            </goals>\n            <phase>install</phase>\n            <configuration>\n              <artifactItems>\n                <artifactItem>\n                  <groupId>${project.groupId}</groupId>\n                  <artifactId>${project.artifactId}</artifactId>\n                  <version>${project.version}</version>\n                </artifactItem>\n              </artifactItems>\n              <outputDirectory>lib</outputDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-enforcer-plugin</artifactId>\n        <dependencies>\n          <dependency>\n            <groupId>com.google.cloud.tools</groupId>\n            <artifactId>linkage-checker-enforcer-rules</artifactId>\n            <version>1.5.13</version>\n          </dependency>\n          <dependency>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>extra-enforcer-rules</artifactId>\n            <version>1.3</version>\n            <exclusions>\n              <exclusion>\n                <groupId>org.eclipse.aether</groupId>\n                <artifactId>aether-util</artifactId>\n              </exclusion>\n            </exclusions>\n          </dependency>\n        </dependencies>\n        <executions>\n          <execution>\n            <id>enforce-best-practices</id>\n            <goals>\n              <goal>enforce</goal>\n            </goals>\n            <configuration>\n              <rules>\n                <banDuplicateClasses>\n                  <findAllDuplicates>true</findAllDuplicates>\n                  <ignoreWhenIdentical>true</ignoreWhenIdentical>\n                  <dependencies>\n                    <dependency>\n                      <artifactId>arrow-memory-unsafe</artifactId>\n                      <ignoreClasses>\n                        <ignoreClass>org.apache.arrow.memory.DefaultAllocationManagerFactory</ignoreClass>\n                      </ignoreClasses>\n                    </dependency>\n                  </dependencies>\n                </banDuplicateClasses>\n                <banDuplicatePomDependencyVersions/>\n                <bannedDependencies/>\n                <dependencyConvergence/>\n                <requireUpperBoundDeps/>\n              </rules>\n            </configuration>\n          </execution>\n          <execution>\n            <id>enforce-maven</id>\n            <goals>\n              <goal>enforce</goal>\n            </goals>\n            <configuration>\n              <rules>\n                <requireMavenVersion>\n                  <version>${version.maven}</version>\n                </requireMavenVersion>\n              </rules>\n            </configuration>\n          </execution>\n          <execution>\n            <id>enforce-linkage-checker</id>\n            <goals>\n              <goal>enforce</goal>\n            </goals>\n            <phase>verify</phase>\n            <configuration>\n              <rules>\n                <LinkageCheckerRule implementation=\"com.google.cloud.tools.dependencies.enforcer.LinkageCheckerRule\">\n                  <reportOnlyReachable>true</reportOnlyReachable>\n                  <exclusionFile>linkage-checker-exclusion-rules.xml</exclusionFile>\n                </LinkageCheckerRule>\n              </rules>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-install-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>install-arrow-format</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/arrow-format-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-format</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-core</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/arrow-memory-core-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-core</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-netty-buffer-patch</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/arrow-memory-netty-buffer-patch-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-netty-buffer-patch</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-arrow-memory-pom</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/arrow-memory-${arrow.version}.pom</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>pom</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n\n          <execution>\n            <id>install-arrow-memory-unsafe</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/arrow-memory-unsafe-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-memory-unsafe</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n\n          <execution>\n            <id>install-arrow-vector</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/arrow-vector-${arrow.version}.jar</file>\n              <groupId>org.apache.arrow</groupId>\n              <artifactId>arrow-vector</artifactId>\n              <version>${arrow.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n          <execution>\n            <id>install-tika-core</id>\n            <goals>\n              <goal>install-file</goal>\n            </goals>\n            <phase>validate</phase>\n            <configuration>\n              <file>${project.basedir}/dependencies/tika-core-${tika.version}.jar</file>\n              <groupId>org.apache.tika</groupId>\n              <artifactId>tika-core</artifactId>\n              <version>${tika.version}</version>\n              <packaging>jar</packaging>\n              <generatePom>true</generatePom>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifest>\n              <mainClass>net.snowflake.client.api.driver.SnowflakeDriver</mainClass>\n              <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n            </manifest>\n          </archive>\n        </configuration>\n        <executions>\n          <execution>\n            <goals>\n              <goal>test-jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-javadoc-plugin</artifactId>\n        <configuration>\n          <source>8</source>\n          <overview>${project.basedir}/src/main/javadoc/overview.html</overview>\n          <stylesheet>java</stylesheet>\n          <helpfile>${project.basedir}/src/main/javadoc/licenses.html</helpfile>\n          <excludePackageNames>net.snowflake.client.internal.*:net.snowflake.client.jdbc.internal.*</excludePackageNames>\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      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-source-plugin</artifactId>\n        <executions>\n          <execution>\n            <id>attach-sources</id>\n            <goals>\n              <goal>jar</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.jacoco</groupId>\n        <artifactId>jacoco-maven-plugin</artifactId>\n        <configuration>\n          <skip>${jacoco.skip.instrument}</skip>\n        </configuration>\n        <executions>\n          <execution>\n            <id>pre-unit-test</id>\n            <goals>\n              <goal>prepare-agent</goal>\n            </goals>\n            <configuration>\n              <destFile>target/jacoco-ut.exec</destFile>\n            </configuration>\n          </execution>\n          <execution>\n            <id>post-unit-test</id>\n            <goals>\n              <goal>report</goal>\n            </goals>\n            <phase>test</phase>\n            <configuration>\n              <dataFile>target/jacoco-ut.exec</dataFile>\n              <outputDirectory>target/jacoco-ut</outputDirectory>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n  <reporting>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-project-info-reports-plugin</artifactId>\n      </plugin>\n    </plugins>\n  </reporting>\n\n  <profiles>\n    <profile>\n      <id>check-style</id>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>com.spotify.fmt</groupId>\n            <artifactId>fmt-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>fmt</id>\n                <goals>\n                  <goal>check</goal>\n                </goals>\n                <phase>validate</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-checkstyle-plugin</artifactId>\n            <configuration>\n              <checkstyleRules>\n                <module name=\"Checker\">\n                  <module name=\"TreeWalker\">\n                    <module name=\"AvoidStarImport\"/>\n                    <module name=\"NeedBraces\"/>\n                  </module>\n                </module>\n              </checkstyleRules>\n              <consoleOutput>true</consoleOutput>\n              <failsOnError>true</failsOnError>\n              <includeTestSourceDirectory>true</includeTestSourceDirectory>\n              <violationSeverity>warning</violationSeverity>\n            </configuration>\n            <executions>\n              <execution>\n                <id>checkstyle</id>\n                <goals>\n                  <goal>check</goal>\n                </goals>\n                <phase>validate</phase>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>thin-jar</id>\n      <activation>\n        <property>\n          <name>thin-jar</name>\n        </property>\n      </activation>\n      <properties>\n        <artifactId>snowflake-jdbc-thin</artifactId>\n      </properties>\n      <build>\n        <plugins>\n          <plugin>\n            <!-- google linkage checker doesn't work well with shaded jar, disable the check in this case for now -->\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-enforcer-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>enforce-linkage-checker</id>\n                <goals>\n                  <goal>enforce</goal>\n                </goals>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-shade-plugin</artifactId>\n            <configuration/>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>shade</goal>\n                </goals>\n                <phase>package</phase>\n                <configuration>\n                  <artifactSet>\n                    <includes>\n                      <include>net.snowflake:snowflake-common</include>\n                      <include>org.apache.arrow:*</include>\n                      <include>org.apache.tika:tika-core</include>\n                      <include>io.netty:netty-common</include>\n                      <include>io.netty:netty-buffer</include>\n                    </includes>\n                  </artifactSet>\n                  <relocations>\n                    <!-- We list only packages that we need to include form dependencies + snowflake-common-->\n                    <relocation>\n                      <pattern>net.snowflake.common</pattern>\n                      <shadedPattern>${shadeBase}.snowflake.common</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>mozilla</pattern>\n                      <shadedPattern>${shadeBase}.mozilla</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.apache.arrow</pattern>\n                      <shadedPattern>${shadeBase}.apache.arrow</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.apache.tika</pattern>\n                      <shadedPattern>${shadeBase}.apache.tika</shadedPattern>\n                    </relocation>\n                    <!-- io.netty are dependencies for arrow and arrow packages have some of the io.netty classes internally -->\n                    <relocation>\n                      <pattern>io.netty</pattern>\n                      <shadedPattern>${shadeBase}.io.netty</shadedPattern>\n                    </relocation>\n                  </relocations>\n                  <filters>\n                    <filter>\n                      <artifact>*:*</artifact>\n                      <excludes>\n                        <exclude>META-INF/LICENSE*</exclude>\n                        <exclude>META-INF/NOTICE*</exclude>\n                        <exclude>META-INF/DEPENDENCIES</exclude>\n                        <exclude>META-INF/maven/**</exclude>\n                        <exclude>META-INF/*.xml</exclude>\n                        <exclude>META-INF/*.SF</exclude>\n                        <exclude>META-INF/*.DSA</exclude>\n                        <exclude>META-INF/*.RSA</exclude>\n                        <exclude>.netbeans_automatic_build</exclude>\n                        <exclude>git.properties</exclude>\n                        <exclude>arrow-git.properties</exclude>\n                        <exclude>google-http-client.properties</exclude>\n                        <exclude>storage.v1.json</exclude>\n                        <!-- This is just a documentation file, not needed-->\n                        <exclude>pipes-fork-server-default-log4j2.xml</exclude>\n                        <exclude>dependencies.properties</exclude>\n                        <exclude>pipes-fork-server-default-log4j2.xml</exclude>\n                      </excludes>\n                    </filter>\n                    <filter>\n                      <artifact>org.apache.arrow:arrow-vector</artifact>\n                      <excludes>\n                        <!-- codegen directory is used to generate java code for arrow vector package. Excludes them since we only need class file -->\n                        <exclude>codegen/**</exclude>\n                      </excludes>\n                    </filter>\n                  </filters>\n                  <transformers>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.ServicesResourceTransformer\"/>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\"/>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.AppendingTransformer\">\n                      <resource>META-INF/io.netty.versions.properties</resource>\n                    </transformer>\n                  </transformers>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>buildnumber-maven-plugin</artifactId>\n            <configuration>\n              <timestampFormat>yyyyMMddHHmmss</timestampFormat>\n              <timestampPropertyName>buildNumber.timestamp</timestampPropertyName>\n              <doCheck>false</doCheck>\n              <revisionOnScmFailure/>\n              <doUpdate>false</doUpdate>\n              <!--- Note for those who come later.  If you specify \"buildNumber\" in the items field, it becomes an incrementing buildNumber\n                AFAIK (and I spent a lot of time on this) it is impossible to get the SCM rev number and incrementing build number at the same time -->\n            </configuration>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>create-timestamp</goal>\n                </goals>\n                <phase>package</phase>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>self-contained-jar</id>\n      <activation>\n        <property>\n          <name>!not-self-contained-jar</name>\n        </property>\n      </activation>\n      <build>\n        <resources>\n          <resource>\n            <directory>${basedir}/src/main/resources-fat-jar</directory>\n          </resource>\n        </resources>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>build-helper-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>add-fat-jar-sources</id>\n                <goals>\n                  <goal>add-source</goal>\n                </goals>\n                <phase>generate-sources</phase>\n                <configuration>\n                  <sources>\n                    <source>${basedir}/src/main/java-fat-jar</source>\n                  </sources>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <!-- google linkage checker doesn't work well with shaded jar, disable the check in this case for now -->\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-enforcer-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>enforce-linkage-checker</id>\n                <goals>\n                  <goal>enforce</goal>\n                </goals>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-shade-plugin</artifactId>\n            <configuration/>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>shade</goal>\n                </goals>\n                <phase>package</phase>\n                <configuration>\n                  <relocations>\n                    <relocation>\n                      <pattern>mozilla</pattern>\n                      <shadedPattern>${shadeBase}.mozilla</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>net.snowflake.common</pattern>\n                      <shadedPattern>${shadeBase}.snowflake.common</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.apache</pattern>\n                      <shadedPattern>${shadeBase}.apache</shadedPattern>\n                      <excludes>\n                        <exclude>org.apache.log4j.*</exclude>\n                      </excludes>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.slf4j</pattern>\n                      <shadedPattern>${shadeBase}.org.slf4j</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.awssdk</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.awssdk</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.encryption.s3</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.encryption.s3</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.eventstream</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.eventstream</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>software.amazon.ion</pattern>\n                      <shadedPattern>${shadeBase}.software.amazon.ion</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.reactivestreams</pattern>\n                      <shadedPattern>${shadeBase}.reactivestreams</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.jvnet.staxex</pattern>\n                      <shadedPattern>${shadeBase}.jvnet.staxex</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>jakarta.xml.soap</pattern>\n                      <shadedPattern>${shadeBase}.jakarta.xml.soap</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>jakarta.activation</pattern>\n                      <shadedPattern>${shadeBase}.jakarta.activation</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.azure</pattern>\n                      <shadedPattern>${shadeBase}.azure</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.fasterxml</pattern>\n                      <shadedPattern>${shadeBase}.fasterxml</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.google</pattern>\n                      <shadedPattern>${shadeBase}.google</shadedPattern>\n                    </relocation>\n                    <!-- google packages should be relocated explicitly to avoid problems with properties files renaming -->\n                    <relocation>\n                      <pattern>google.api</pattern>\n                      <shadedPattern>${shadeBase}.google.api</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.apps</pattern>\n                      <shadedPattern>${shadeBase}.google.apps</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.cloud</pattern>\n                      <shadedPattern>${shadeBase}.google.cloud</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.geo</pattern>\n                      <shadedPattern>${shadeBase}.google.geo</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.iam</pattern>\n                      <shadedPattern>${shadeBase}.google.iam</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.logging</pattern>\n                      <shadedPattern>${shadeBase}.google.logging</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.longrunning</pattern>\n                      <shadedPattern>${shadeBase}.google.longrunning</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.monitoring</pattern>\n                      <shadedPattern>${shadeBase}.google.monitoring</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.protobuf</pattern>\n                      <shadedPattern>${shadeBase}.google.protobuf</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.rpc</pattern>\n                      <shadedPattern>${shadeBase}.google.rpc</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.shopping</pattern>\n                      <shadedPattern>${shadeBase}.google.shopping</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.storage</pattern>\n                      <shadedPattern>${shadeBase}.google.storage</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>google.type</pattern>\n                      <shadedPattern>${shadeBase}.google.type</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.joda</pattern>\n                      <shadedPattern>${shadeBase}.joda</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>javax.servlet</pattern>\n                      <shadedPattern>${shadeBase}.javax.servlet</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.jsoup</pattern>\n                      <shadedPattern>${shadeBase}.org.jsoup</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.bouncycastle</pattern>\n                      <shadedPattern>${shadeBase}.org.bouncycastle</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.nimbusds</pattern>\n                      <shadedPattern>${shadeBase}.com.nimbusds</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>javax.annotation</pattern>\n                      <shadedPattern>${shadeBase}.javax.annotation</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>net.jcip</pattern>\n                      <shadedPattern>${shadeBase}.net.jcip</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>net.minidev</pattern>\n                      <shadedPattern>${shadeBase}.net.minidev</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.objectweb</pattern>\n                      <shadedPattern>${shadeBase}.org.objectweb</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.netty</pattern>\n                      <shadedPattern>${shadeBase}.io.netty</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>com.carrotsearch</pattern>\n                      <shadedPattern>${shadeBase}.com.carrotsearch</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.opencensus</pattern>\n                      <shadedPattern>${shadeBase}.opencensus</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.opentelemetry</pattern>\n                      <shadedPattern>${shadeBase}.opentelemetry</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.threeten</pattern>\n                      <shadedPattern>${shadeBase}.threeten</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.grpc</pattern>\n                      <shadedPattern>${shadeBase}.grpc</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.io_grpc_netty_shaded_netty_tcnative</pattern>\n                      <shadedPattern>META-INF.native.${shadeNativeBase}_grpc_netty_shaded_netty_tcnative</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.libio_grpc_netty_shaded_netty_tcnative</pattern>\n                      <shadedPattern>META-INF.native.lib${shadeNativeBase}_grpc_netty_shaded_netty_tcnative</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.io_grpc_netty_shaded_netty_transport_native_epoll</pattern>\n                      <shadedPattern>META-INF.native.${shadeNativeBase}_grpc_netty_shaded_netty_transport_native_epoll</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>META-INF.native.libio_grpc_netty_shaded_netty_transport_native_epoll</pattern>\n                      <shadedPattern>META-INF.native.lib${shadeNativeBase}_grpc_netty_shaded_netty_transport_native_epoll</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.checkerframework</pattern>\n                      <shadedPattern>${shadeBase}.org.checkerframework</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.codehaus</pattern>\n                      <shadedPattern>${shadeBase}.org.codehaus</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>io.perfmark</pattern>\n                      <shadedPattern>${shadeBase}.io.perfmark</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>opencensus</pattern>\n                      <shadedPattern>${shadeBase}.opencensus</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>grpc</pattern>\n                      <shadedPattern>${shadeBase}.grpc</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>android.annotation</pattern>\n                      <shadedPattern>${shadeBase}.android.annotation</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>reactor</pattern>\n                      <shadedPattern>${shadeBase}.reactor</shadedPattern>\n                    </relocation>\n                    <relocation>\n                      <pattern>org.reactivestreams</pattern>\n                      <shadedPattern>${shadeBase}.org.reactivestreams</shadedPattern>\n                    </relocation>\n                  </relocations>\n                  <filters>\n                    <filter>\n                      <artifact>*:*</artifact>\n                      <excludes>\n                        <exclude>META-INF/LICENSE*</exclude>\n                        <exclude>META-INF/NOTICE*</exclude>\n                        <exclude>META-INF/DEPENDENCIES</exclude>\n                        <exclude>META-INF/maven/**</exclude>\n                        <exclude>META-INF/services/com.fasterxml.*</exclude>\n                        <exclude>META-INF/versions/9/module-info.*</exclude>\n                        <exclude>META-INF/versions/11/module-info.*</exclude>\n                        <exclude>META-INF/*.xml</exclude>\n                        <exclude>META-INF/*.SF</exclude>\n                        <exclude>META-INF/*.DSA</exclude>\n                        <exclude>META-INF/*.RSA</exclude>\n                        <exclude>.netbeans_automatic_build</exclude>\n                        <exclude>git.properties</exclude>\n                        <exclude>arrow-git.properties</exclude>\n                        <exclude>google-http-client.properties</exclude>\n                        <exclude>storage.v1.json</exclude>\n                        <!-- This is just a documentation file, not needed-->\n                        <exclude>pipes-fork-server-default-log4j2.xml</exclude>\n                        <exclude>dependencies.properties</exclude>\n                        <exclude>pipes-fork-server-default-log4j2.xml</exclude>\n                        <exclude>azure-*.properties</exclude>\n                        <exclude>VersionInfo.java</exclude>\n                        <exclude>project.properties</exclude>\n                      </excludes>\n                    </filter>\n                    <filter>\n                      <artifact>org.apache.arrow:arrow-vector</artifact>\n                      <excludes>\n                        <!-- codegen directory is used to generate java code for arrow vector package. Excludes them since we only need class file -->\n                        <exclude>codegen/**</exclude>\n                      </excludes>\n                    </filter>\n                    <filter>\n                      <artifact>com.google.guava:guava</artifact>\n                      <includes>\n                        <include>com/google/common/io/**</include>\n                        <include>com/google/common/base/**</include>\n                        <include>com/google/common/hash/**</include>\n                        <include>com/google/common/collect/**</include>\n                        <include>com/google/common/graph/**</include>\n                        <include>com/google/common/math/**</include>\n                        <include>com/google/common/util/concurrent/**</include>\n                      </includes>\n                    </filter>\n                    <filter>\n                      <artifact>commons-logging:commons-logging</artifact>\n                      <excludes>\n                        <exclude>org/apache/commons/logging/impl/AvalonLogger.class</exclude>\n                      </excludes>\n                    </filter>\n                  </filters>\n                  <transformers>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.ServicesResourceTransformer\"/>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\"/>\n                    <transformer implementation=\"org.apache.maven.plugins.shade.resource.AppendingTransformer\">\n                      <resource>META-INF/io.netty.versions.properties</resource>\n                    </transformer>\n                  </transformers>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <!-- relocate the META-INF/versions files manually due to the maven bug -->\n            <!-- https://issues.apache.org/jira/browse/MSHADE-406 -->\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-antrun-plugin</artifactId>\n            <version>${version.plugin.antrun}</version>\n            <executions>\n              <execution>\n                <id>repack</id>\n                <goals>\n                  <goal>run</goal>\n                </goals>\n                <phase>package</phase>\n                <configuration>\n                  <target>\n                    <unzip dest=\"${project.build.directory}/relocate\" src=\"${project.build.directory}/${project.build.finalName}.jar\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/15/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/22/${relocationBase}\"/>\n                    <mkdir dir=\"${project.build.directory}/relocate/META-INF/versions/25/${relocationBase}\"/>\n                    <!-- org.bouncycastle.* packages are relocated to ${relocationBase}.org.bouncycastle.* -->\n                    <!-- this below list must mirror META-INF/versions/* in bcprov-jdk18on-<version>.jar - re-check on every BC bump!\n                         also verify the mkdir  (above) and delete (below) directives; and add there too if there's a new version -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/9/org\" todir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/11/org\" todir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/15/org\" todir=\"${project.build.directory}/relocate/META-INF/versions/15/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/17/org\" todir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/25/org\" todir=\"${project.build.directory}/relocate/META-INF/versions/25/${relocationBase}\"/>\n                    <!-- com.fasterxml.* packages are relocated to ${relocationBase}.fasterxml.* -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/11/com/fasterxml\" todir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/17/com/fasterxml\" todir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/21/com/fasterxml\" todir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <!-- io.opentelemetry.* packages are relocated to ${relocationBase}.io.opentelemetry.* -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/9/io\" todir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <!-- reactor.* packages are relocated to ${relocationBase}.reactor.* -->\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/11/reactor/core\" todir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/17/reactor/netty\" todir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <move file=\"${project.build.directory}/relocate/META-INF/versions/21/reactor/core\" todir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <!-- contains class name from before shading, loaded with Class.forName -->\n                    <replace file=\"${project.build.directory}/relocate/net/snowflake/client/jdbc/internal/software/amazon/awssdk/services/s3/execution.interceptors\" token=\"software.amazon.awssdk\" value=\"net.snowflake.client.jdbc.internal.software.amazon.awssdk\"/>\n                    <!-- Overwrite shaded SLF4JLogger with unshaded version.\n                         The shade plugin rewrites org.slf4j references to the shaded namespace,\n                         but SLF4JLogger must reference the user's real org.slf4j for SLF4J opt-in.\n                         SLF4JJCLWrapper is left shaded since it also implements the shaded\n                         org.apache.commons.logging.Log interface. -->\n                    <copy file=\"${project.build.outputDirectory}/net/snowflake/client/internal/log/SLF4JLogger.class\"\n                          todir=\"${project.build.directory}/relocate/net/snowflake/client/internal/log\"\n                          overwrite=\"true\"/>\n                    <zip basedir=\"${project.build.directory}/relocate\" destfile=\"${project.build.directory}/${project.build.finalName}.jar\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/9/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/11/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/15/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/17/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/21/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/22/${relocationBase}\"/>\n                    <delete dir=\"${project.build.directory}/relocate/META-INF/versions/25/${relocationBase}\"/>\n                  </target>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>buildnumber-maven-plugin</artifactId>\n            <configuration>\n              <timestampFormat>yyyyMMddHHmmss</timestampFormat>\n              <timestampPropertyName>buildNumber.timestamp</timestampPropertyName>\n              <doCheck>false</doCheck>\n              <revisionOnScmFailure/>\n              <doUpdate>false</doUpdate>\n              <!--- Note for those who come later.  If you specify \"buildNumber\" in the items field, it becomes an incrementing buildNumber\n                AFAIK (and I spent a lot of time on this) it is impossible to get the SCM rev number and incrementing build number at the same time -->\n            </configuration>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>create-timestamp</goal>\n                </goals>\n                <phase>package</phase>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>java-9</id>\n      <activation>\n        <jdk>(9,)</jdk>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <configuration>\n              <argLine>--add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/sun.util.calendar=ALL-UNNAMED --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED</argLine>\n            </configuration>\n          </plugin>\n          <plugin>\n            <artifactId>maven-surefire-plugin</artifactId>\n            <configuration>\n              <skipTests>${skip.unitTests}</skipTests>\n              <argLine>--add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/sun.util.calendar=ALL-UNNAMED --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED</argLine>\n            </configuration>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>jenkinsIT</id>\n      <activation>\n        <property>\n          <name>jenkinsIT</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <!--\n            Downloading\n              1. japicmp\n              2. maven-dependency-plugin analyst\n            in jenkins node would cause http connection failure. Temporarily disable them.\n          -->\n          <plugin>\n            <groupId>com.github.siom79.japicmp</groupId>\n            <artifactId>japicmp-maven-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>japicmp</id>\n                <goals>\n                  <goal>cmp</goal>\n                </goals>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-dependency-plugin</artifactId>\n            <executions>\n              <execution>\n                <id>analyze</id>\n                <goals>\n                  <goal>analyze-only</goal>\n                </goals>\n                <phase>none</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-surefire-plugin</artifactId>\n            <configuration>\n              <test>UnitTestSuite</test>\n              <skipTests>${skip.unitTests}</skipTests>\n            </configuration>\n            <dependencies>\n              <dependency>\n                <groupId>org.apache.maven.surefire</groupId>\n                <artifactId>surefire-junit-platform</artifactId>\n                <version>${version.plugin.surefire}</version>\n              </dependency>\n            </dependencies>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>test</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n              <execution>\n                <id>DefaultIT</id>\n                <goals>\n                  <goal>integration-test</goal>\n                </goals>\n                <configuration>\n                  <systemPropertyVariables>\n                    <net.snowflake.jdbc.loggerImpl>net.snowflake.client.internal.log.JDK14Logger</net.snowflake.jdbc.loggerImpl>\n                    <java.util.logging.config.file>${basedir}/src/test/resources/logging.properties</java.util.logging.config.file>\n                  </systemPropertyVariables>\n                  <test>${integrationTestSuites}</test>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.jacoco</groupId>\n            <artifactId>jacoco-maven-plugin</artifactId>\n            <configuration>\n              <skip>${jacoco.skip.instrument}</skip>\n            </configuration>\n            <executions>\n              <execution>\n                <id>pre-integration-test</id>\n                <goals>\n                  <goal>prepare-agent</goal>\n                </goals>\n                <phase>pre-integration-test</phase>\n                <configuration>\n                  <destFile>target/jacoco-it.exec</destFile>\n                </configuration>\n              </execution>\n              <execution>\n                <id>post-integration-test</id>\n                <goals>\n                  <goal>report</goal>\n                </goals>\n                <phase>post-integration-test</phase>\n                <configuration>\n                  <dataFile>target/jacoco-it.exec</dataFile>\n                  <outputDirectory>target/jacoco-it</outputDirectory>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n\n    <profile>\n      <id>check-content</id>\n      <activation>\n        <os>\n          <family>!windows</family>\n        </os>\n        <property>\n          <name>!thin-jar</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>exec-maven-plugin</artifactId>\n            <version>${version.plugin.exec}</version>\n            <executions>\n              <execution>\n                <id>check-shaded-content</id>\n                <goals>\n                  <goal>exec</goal>\n                </goals>\n                <phase>verify</phase>\n                <configuration>\n                  <executable>${basedir}/ci/scripts/check_content.sh</executable>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n\n    <profile>\n      <id>check-content-thin</id>\n      <activation>\n        <os>\n          <family>!windows</family>\n        </os>\n        <property>\n          <name>thin-jar</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>exec-maven-plugin</artifactId>\n            <version>${version.plugin.exec}</version>\n            <executions>\n              <execution>\n                <id>check-shaded-content</id>\n                <goals>\n                  <goal>exec</goal>\n                </goals>\n                <phase>verify</phase>\n                <configuration>\n                  <executable>${basedir}/ci/scripts/check_content.sh</executable>\n                  <arguments>\n                    <argument>-thin</argument>\n                  </arguments>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>qa1IT</id>\n      <activation>\n        <property>\n          <name>qa1IT</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <configuration>\n              <includes>\n                <include>**/*IT.java</include>\n              </includes>\n            </configuration>\n            <dependencies>\n              <dependency>\n                <groupId>org.apache.maven.surefire</groupId>\n                <artifactId>surefire-junit-platform</artifactId>\n                <version>${version.plugin.surefire}</version>\n              </dependency>\n            </dependencies>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>DellBoomi</id>\n      <activation>\n        <property>\n          <name>dellBoomiIT</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <configuration>\n              <includes>\n                <include>**/*IT.java</include>\n              </includes>\n            </configuration>\n            <dependencies>\n              <dependency>\n                <groupId>org.apache.maven.surefire</groupId>\n                <artifactId>surefire-junit-platform</artifactId>\n                <version>${version.plugin.surefire}</version>\n              </dependency>\n            </dependencies>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>preprod3IT</id>\n      <activation>\n        <property>\n          <name>preprod3IT</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-failsafe-plugin</artifactId>\n            <configuration>\n              <includes>\n                <include>**/*IT.java</include>\n              </includes>\n            </configuration>\n            <dependencies>\n              <dependency>\n                <groupId>org.apache.maven.surefire</groupId>\n                <artifactId>surefire-junit-platform</artifactId>\n                <version>${version.plugin.surefire}</version>\n              </dependency>\n            </dependencies>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>verify</goal>\n                </goals>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>ossrh-deploy</id>\n      <activation>\n        <property>\n          <name>ossrhDeploy</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <artifactId>maven-deploy-plugin</artifactId>\n            <configuration>\n              <skip>true</skip>\n            </configuration>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <executions>\n              <execution>\n                <goals>\n                  <goal>sign-and-deploy-file</goal>\n                </goals>\n                <phase>deploy</phase>\n                <configuration>\n                  <file>target/${project.artifactId}.jar</file>\n                  <repositoryId>ossrh</repositoryId>\n                  <url>https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2</url>\n                  <pomFile>generated_public_pom.xml</pomFile>\n                  <javadoc>target/${project.artifactId}-javadoc.jar</javadoc>\n                  <sources>target/${project.artifactId}-sources.jar</sources>\n                  <keyname>${env.GPG_KEY_ID}</keyname>\n                  <passphrase>${env.GPG_KEY_PASSPHRASE}</passphrase>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n    <profile>\n      <id>central-deploy</id>\n      <activation>\n        <property>\n          <name>central-deploy</name>\n        </property>\n      </activation>\n      <build>\n        <plugins>\n          <plugin>\n            <groupId>org.codehaus.mojo</groupId>\n            <artifactId>build-helper-maven-plugin</artifactId>\n            <version>${version.plugin.buildhelper}</version>\n            <executions>\n              <execution>\n                <id>attach-public-pom</id>\n                <goals>\n                  <goal>attach-artifact</goal>\n                </goals>\n                <phase>package</phase>\n                <configuration>\n                  <artifacts>\n                    <artifact>\n                      <file>generated_public_pom.xml</file>\n                      <type>pom</type>\n                    </artifact>\n                  </artifacts>\n                </configuration>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.apache.maven.plugins</groupId>\n            <artifactId>maven-gpg-plugin</artifactId>\n            <configuration>\n              <keyname>${env.GPG_KEY_ID}</keyname>\n              <passphrase>${env.GPG_KEY_PASSPHRASE}</passphrase>\n            </configuration>\n            <executions>\n              <execution>\n                <id>sign-artifacts</id>\n                <goals>\n                  <goal>sign</goal>\n                </goals>\n                <phase>verify</phase>\n              </execution>\n            </executions>\n          </plugin>\n          <plugin>\n            <groupId>org.sonatype.central</groupId>\n            <artifactId>central-publishing-maven-plugin</artifactId>\n            <version>${version.plugin.publishing}</version>\n            <extensions>true</extensions>\n            <configuration>\n              <publishingServerId>ossrh</publishingServerId>\n              <autoPublish>true</autoPublish>\n              <waitUntil>published</waitUntil>\n            </configuration>\n          </plugin>\n        </plugins>\n      </build>\n    </profile>\n  </profiles>\n</project>\n"
  },
  {
    "path": "prepareNewVersion.sh",
    "content": "#!/bin/bash -e\n\nif [[ -z \"$1\" ]]; then\n    echo First argument must be new version to set\n    exit 1\nfi\n\nversion=$1\nversion_without_snapshot=${version%-*}\n\n# prepare release with maven (version.properties is populated by Maven resource filtering)\n./mvnw -f parent-pom.xml versions:set -DnewVersion=$version -DgenerateBackupPoms=false\n\nif [[ \"$version\" == *-SNAPSHOT ]]; then\nsed -i '' '3a\\\n- v'\"$version\n\" CHANGELOG.md\nfi\n\n# add changelog entry but only when releasing version without snapshot\nif [[ \"$version\" == \"$version_without_snapshot\" ]]; then\n  sed -i '' \"4s/.*/- v$version/\" CHANGELOG.md\nfi\n"
  },
  {
    "path": "prober/Dockerfile",
    "content": "FROM ubuntu:25.10\n\n# boilerplate labels required by validation when pushing to ACR, ECR & GCR\nLABEL org.opencontainers.image.source=\"https://github.com/snowflakedb/snowflake-jdbc\"\nLABEL com.snowflake.owners.email=\"triage-snow-drivers-warsaw-dl@snowflake.com\"\nLABEL com.snowflake.owners.slack=\"triage-snow-drivers-warsaw-dl\"\nLABEL com.snowflake.owners.team=\"Snow Drivers\"\nLABEL com.snowflake.owners.jira_area=\"Developer Platform\"\nLABEL com.snowflake.owners.jira_component=\"JDBC Driver\"\n# fake layers label to pass the validation\nLABEL com.snowflake.ugcbi.layers=\"sha256:850959b749c07b254308a4d1a84686fd7c09fcb94aeae33cc5748aa07e5cb232,sha256:b79d3c4628a989cbb8bc6f0bf0940ff33a68da2dca9c1ffbf8cfb2a27ac8d133,sha256:1cbcc0411a84fbce85e7ee2956c8c1e67b8e0edc81746a33d9da48c852037c3e,sha256:07e89b796f91d37255c6eec926b066d6818f3f2edc344a584d1b9566f77e1c27,sha256:84ff92691f909a05b224e1c56abb4864f01b4f8e3c854e4bb4c7baf1d3f6d652,sha256:3ab72684daee4eea64c3ae78a43ea332b86358446b6f2904dca4b634712e1537\"\n\nENV DEBIAN_FRONTEND=noninteractive\n\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends \\\n    curl \\\n    zip \\\n    unzip \\\n    jq \\\n    ca-certificates \\\n    && rm -rf /var/lib/apt/lists/*\n\nENV SDKMAN_DIR=\"/app/.sdkman\"\n\nRUN curl -s \"https://get.sdkman.io?rcupdate=false&ci=false\" | bash\n\nARG MATRIX_VERSION='{\"11.0.27-tem\": [\"3.24.2\", \"3.20.0\", \"3.22.0\", \"3.13.6\", \"3.15.0\", \"3.18.0\", \"3.16.1\", \"3.23.2\", \"3.14.1\", \"3.14.4\", \"3.13.30\"], \"21.0.7-tem\": [\"3.24.2\", \"3.20.0\", \"3.22.0\", \"3.13.6\", \"3.15.0\", \"3.18.0\", \"3.16.1\", \"3.23.2\", \"3.14.1\", \"3.14.4\", \"3.13.30\"]}'\nENV DRIVERS_DIR=\"/opt/jdbc_drivers\"\n\nRUN \\\n    set -ex && \\\n    mkdir -p ${DRIVERS_DIR} && \\\n    \\\n    for java_version_full in $(echo \"${MATRIX_VERSION}\" | jq -r 'keys_unsorted[]'); do \\\n      bash -c \"source ${SDKMAN_DIR}/bin/sdkman-init.sh && sdk install java ${java_version_full}\"; \\\n    done && \\\n    \\\n    for jdbc_version in $(echo \"${MATRIX_VERSION}\" | jq -r '.[][]' | sort -u); do \\\n      DRIVER_URL=\"https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/${jdbc_version}/snowflake-jdbc-${jdbc_version}.jar\" && \\\n      curl -fSL -o \"${DRIVERS_DIR}/snowflake-jdbc-${jdbc_version}.jar.tmp\" \"${DRIVER_URL}\" && \\\n      mv \"${DRIVERS_DIR}/snowflake-jdbc-${jdbc_version}.jar.tmp\" \"${DRIVERS_DIR}/snowflake-jdbc-${jdbc_version}.jar\"; \\\n    done\n\nWORKDIR /app\n\n# Copy the Prober.java source code into the correct package structure\nCOPY /src/main/java/com/snowflake/client/jdbc/prober/Prober.java com/snowflake/client/jdbc/prober/Prober.java\n\nRUN \\\n    set -ex && \\\n    # Find the first available Java version to use as the compiler\n    COMPILE_JAVA_VERSION=$(echo \"${MATRIX_VERSION}\" | jq -r 'keys_unsorted[0]') && \\\n    \\\n    # Select the first JDBC version from the first Java version's array\n    COMPILE_JDBC_VERSION=$(echo \"${MATRIX_VERSION}\" | jq -r --arg jv \"${COMPILE_JAVA_VERSION}\" '.[$jv][0]') && \\\n    COMPILE_JDBC_JAR_PATH=\"${DRIVERS_DIR}/snowflake-jdbc-${COMPILE_JDBC_VERSION}.jar\" && \\\n    \\\n    COMPILE_COMMAND=\"source \\\"${SDKMAN_DIR}/bin/sdkman-init.sh\\\" >/dev/null 2>&1 && \\\n                     sdk use java \\\"${COMPILE_JAVA_VERSION}\\\" >/dev/null 2>&1 && \\\n                     cd \\\"${PROBER_APP_DIR}\\\" && \\\n                     javac -cp \\\".:${COMPILE_JDBC_JAR_PATH}\\\" com/snowflake/client/jdbc/prober/Prober.java\" && \\\n    \\\n    bash -c \"${COMPILE_COMMAND}\"\n\n# Copy the entrypoint script into the image and make it executable\nCOPY entrypoint.sh entrypoint.sh\nRUN chmod +x entrypoint.sh\nRUN chmod +x /app/.sdkman/bin/sdkman-init.sh\n"
  },
  {
    "path": "prober/Jenkinsfile.groovy",
    "content": "pipeline {\n    agent { label 'regular-memory-node' }\n\n    options {\n        ansiColor('xterm')\n        timestamps()\n    }\n\n    environment {\n        VAULT_CREDENTIALS = credentials('vault-jenkins')\n        COMMIT_SHA_SHORT = sh(script: 'cd JDBC/prober && git rev-parse --short HEAD', returnStdout: true).trim()\n        IMAGE_NAME = 'snowdrivers/jdbc-driver-prober'\n        TEAM_NAME = 'Snow Drivers'\n        TEAM_JIRA_DL = 'triage-snow-drivers-warsaw-dl'\n        TEAM_JIRA_AREA = 'Developer Platform'\n        TEAM_JIRA_COMPONENT = 'JDBC Driver'\n    }\n\n    stages {\n        stage('Build Image') {\n            steps {\n                dir('./JDBC/prober') {\n                    sh \"\"\"\n                    ls -l\n                    docker build \\\n                    -t ${IMAGE_NAME}:${COMMIT_SHA_SHORT} \\\n                    --label \"org.opencontainers.image.revision=${COMMIT_SHA_SHORT}\" \\\n                    -f ./Dockerfile .\n                    \"\"\"\n                }\n            }\n        }\n\n        stage('Checkout Jenkins Push Scripts') {\n            steps {\n                dir('k8sc-jenkins_scripts') {\n                    git branch: 'master',\n                            credentialsId: 'jenkins-snowflake-github-app-3',\n                            url: 'https://github.com/snowflakedb/k8sc-jenkins_scripts.git'\n                }\n            }\n        }\n\n        stage('Push Image') {\n            steps {\n                sh \"\"\"\n                ./k8sc-jenkins_scripts/jenkins_push.sh \\\n                -r \"${VAULT_CREDENTIALS_USR}\" \\\n                -s \"${VAULT_CREDENTIALS_PSW}\" \\\n                -i \"${IMAGE_NAME}\" \\\n                -v \"${COMMIT_SHA_SHORT}\" \\\n                -n \"${TEAM_JIRA_DL}\" \\\n                -a \"${TEAM_JIRA_AREA}\" \\\n                -C \"${TEAM_JIRA_COMPONENT}\"\n                \"\"\"\n            }\n        }\n    }\n\n    post {\n        always {\n            cleanWs()\n        }\n    }\n}"
  },
  {
    "path": "prober/entrypoint.sh",
    "content": "#!/bin/bash\n\n# Exit immediately if a command exits with a non-zero status\nset -e\n\nJAVA_VERSION_ID=\"\"\nJDBC_VERSION=\"\"\n\nif [[ \"$1\" == \"--java_version\" && \"$3\" == \"--driver_version\" ]]; then\n    JAVA_VERSION_ID=\"$2\"\n    JDBC_VERSION=\"$4\"\nelse\n  echo \"Error: Missing required arguments.\"\n  echo \"Usage: $0 --java_version <java_version_id> --driver_version <jdbc_version> [prober_arguments...]\"\n  echo \"Example: $0 --java_version 17.0.10-tem --driver_version 3.24.2 --host localhost --port 8080 --user admin --password secret\"\n  exit 1\nfi\n\nPROBER_ARGS=(\"${@}\")\n\n# Define key directories\nSDKMAN_DIR=\"/app/.sdkman\"\nDRIVERS_DIR=\"/opt/jdbc_drivers\"\nPROBER_APP_DIR=\"/app\"\n\nsource \"${SDKMAN_DIR}/bin/sdkman-init.sh\" >/dev/null 2>&1\nsdk use java \"${JAVA_VERSION_ID}\" >/dev/null 2>&1\n\nJDBC_DRIVER_JAR_PATH=\"${DRIVERS_DIR}/snowflake-jdbc-${JDBC_VERSION}.jar\"\nCURRENT_CLASSPATH=\"${PROBER_APP_DIR}:${JDBC_DRIVER_JAR_PATH}\"\n\nexec java -cp \"${CURRENT_CLASSPATH}\" \\\n          --add-opens=java.base/java.nio=ALL-UNNAMED \\\n          com.snowflake.client.jdbc.prober.Prober \\\n          \"${PROBER_ARGS[@]}\"\n\n# This line will not be reached because 'exec' replaces the process.\n# It would only be reached if 'exec java' itself failed to start the Java process.\necho \"Error: Java application failed to start.\"\nexit 1 # Exit with an error if Java failed to start\n"
  },
  {
    "path": "prober/src/main/java/com/snowflake/client/jdbc/prober/Prober.java",
    "content": "package com.snowflake.client.jdbc.prober;\n\nimport net.snowflake.client.api.connection.SnowflakeConnection;\n\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.security.KeyFactory;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.interfaces.RSAPrivateCrtKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Random;\nimport java.util.StringJoiner;\nimport java.util.logging.LogManager;\nimport java.util.stream.Collectors;\n\npublic class Prober {\n  private static final String CHARACTERS = \"abcdefghijklmnopqrstuvwxyz\";\n  private static final Random random = new Random();\n  private static final String stageName = \"test_stage_\" + generateRandomString(10);\n  private static final String stageFilePath = \"test_file_\" + generateRandomString(10) + \".txt\";\n  private static final String tableName = \"test_table\" + generateRandomString(10);\n  private static String javaVersion;\n  private static String driverVersion;\n\n  enum Status {\n    SUCCESS(0),\n    FAILURE(1);\n\n    private final int code;\n\n    Status(int code) {\n      this.code = code;\n    }\n\n    public int getCode() {\n      return code;\n    }\n  }\n\n  enum Scope {\n    LOGIN,\n    PUT_FETCH_GET,\n    PUT_FETCH_GET_FAIL_CLOSED\n  }\n\n  public static void main(String[] args) throws Exception {\n    Map<String, String> arguments = parseArguments(args);\n\n    String url = \"jdbc:snowflake://\" + arguments.get(\"host\");\n    Properties props = new Properties();\n    for (Map.Entry<String, String> entry : arguments.entrySet()) {\n      props.setProperty(entry.getKey(), entry.getValue());\n    }\n    setPrivateKey(props);\n    setupLogging(props);\n\n    javaVersion = props.getProperty(\"java_version\");\n    driverVersion = props.getProperty(\"driver_version\");\n\n    if (Scope.LOGIN.name().toLowerCase().equals(props.getProperty(\"scope\"))) {\n      testLogin(url, props);\n    }\n    if (Scope.PUT_FETCH_GET.name().toLowerCase().equals(props.getProperty(\"scope\"))) {\n      testPutFetchGet(url, props);\n    }\n    if (Scope.PUT_FETCH_GET_FAIL_CLOSED.name().toLowerCase().equals(props.getProperty(\"scope\"))) {\n      testPutFetchGetFailClosed(url, props);\n    }\n  }\n\n  private static void testLogin(String url, Properties properties) {\n    boolean success;\n    try (Connection connection = DriverManager.getConnection(url, properties);\n         Statement statement = connection.createStatement();\n         ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n      resultSet.next();\n      int result = resultSet.getInt(1);\n      success = result == 1;\n    } catch (SQLException e) {\n      success = false;\n      System.err.println(e.getMessage());\n      logMetric(\"cloudprober_driver_java_perform_login\", Status.FAILURE);\n      System.exit(1);\n    }\n    logMetric(\"cloudprober_driver_java_perform_login\", success ? Status.SUCCESS : Status.FAILURE);\n  }\n\n  private static void testPutFetchGet(String url, Properties properties) {\n    try (Connection connection = DriverManager.getConnection(url, properties);\n         Statement statement = connection.createStatement()) {\n      SnowflakeConnection sfConnection = connection.unwrap(SnowflakeConnection.class);\n      List<String> csv = generateCsv(100);\n      String csvFile = csv.stream().collect(Collectors.joining(System.lineSeparator()));\n      createWarehouse(statement, properties, \"cloudprober_driver_java_create_warehouse\");\n      createDatabase(statement, properties, \"cloudprober_driver_java_create_database\");\n      createSchema(statement, properties, \"cloudprober_driver_java_create_schema\");\n      createDataTable(statement, \"cloudprober_driver_java_create_table\");\n      createDataStage(statement, \"cloudprober_driver_java_create_stage\");\n\n      uploadFile(sfConnection, csvFile, \"cloudprober_driver_java_perform_put\");\n      loadFileIntoTable(statement, \"cloudprober_driver_java_copy_data_from_stage_into_table\");\n      fetchAndVerifyRows(statement, \"cloudprober_driver_java_data_transferred_completely\");\n      downloadFile(sfConnection, \"cloudprober_driver_java_perform_get\");\n      compareFetchedDataAndFile(statement, csv, \"cloudprober_driver_java_data_integrity\");\n\n      cleanupResources(statement, \"cloudprober_driver_java_cleanup_resources\");\n      \n      csv.clear();\n      csvFile = null;\n    } catch (SQLException e) {\n      System.err.println(e.getMessage());\n      System.exit(1);\n    } finally {\n      System.gc();\n    }\n  }\n\n  private static void testPutFetchGetFailClosed(String url, Properties properties) {\n    Properties failClosedProperties = new Properties();\n    failClosedProperties.putAll(properties);\n    failClosedProperties.put(\"ocspFailOpen\", \"false\");\n    try (Connection connection = DriverManager.getConnection(url, failClosedProperties);\n         Statement statement = connection.createStatement()) {\n      SnowflakeConnection sfConnection = connection.unwrap(SnowflakeConnection.class);\n      List<String> csv = generateCsv(100);\n      String csvFile = csv.stream().collect(Collectors.joining(System.lineSeparator()));\n      createWarehouse(statement, failClosedProperties, \"cloudprober_driver_java_create_warehouse_fail_closed\");\n      createDatabase(statement, failClosedProperties, \"cloudprober_driver_java_create_database_fail_closed\");\n      createSchema(statement, failClosedProperties, \"cloudprober_driver_java_create_schema_fail_closed\");\n      createDataTable(statement, \"cloudprober_driver_java_create_table_fail_closed\");\n      createDataStage(statement, \"cloudprober_driver_java_create_stage_fail_closed\");\n\n      uploadFile(sfConnection, csvFile, \"cloudprober_driver_java_perform_put_fail_closed\");\n      loadFileIntoTable(statement, \"cloudprober_driver_java_copy_data_from_stage_into_table_fail_closed\");\n      fetchAndVerifyRows(statement, \"cloudprober_driver_java_data_transferred_completely_fail_closed\");\n      downloadFile(sfConnection, \"cloudprober_driver_java_perform_get_fail_closed\");\n      compareFetchedDataAndFile(statement, csv, \"cloudprober_driver_java_data_integrity_fail_closed\");\n\n      cleanupResources(statement, \"cloudprober_driver_java_cleanup_resources_fail_closed\");\n      \n      csv.clear();\n      csvFile = null;\n    } catch (SQLException e) {\n      System.err.println(e.getMessage());\n      System.exit(1);\n    } finally {\n      System.gc();\n    }\n  }\n\n\n  private static void createDatabase(Statement statement, Properties properties, String metricName) throws SQLException {\n    try {\n      String databaseName = properties.getProperty(\"database\", \"test_db\");\n      try (ResultSet rs1 = statement.executeQuery(\"CREATE DATABASE IF NOT EXISTS \" + databaseName);\n           ResultSet rs2 = statement.executeQuery(\"USE database \" + databaseName)) {\n      }\n      logMetric(metricName, Status.SUCCESS);\n    } catch (SQLException e) {\n      System.err.println(\"Error creating database: \" + e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void createSchema(Statement statement, Properties properties, String metricName) throws SQLException {\n    try {\n      String schemaName = properties.getProperty(\"schema\", \"test_schema\");\n      try (ResultSet rs1 = statement.executeQuery(\"CREATE SCHEMA IF NOT EXISTS \" + schemaName);\n           ResultSet rs2 = statement.executeQuery(\"USE SCHEMA \" + schemaName)) {\n      }\n      logMetric(metricName, Status.SUCCESS);\n    } catch (SQLException e) {\n      System.err.println(\"Error creating schema: \" + e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void createWarehouse(Statement statement, Properties properties, String metricName) throws SQLException {\n    try {\n      String warehouseName = properties.getProperty(\"warehouse\", \"test_wh\");\n      try (ResultSet rs1 = statement.executeQuery(\"CREATE WAREHOUSE IF NOT EXISTS \" + warehouseName + \" WAREHOUSE_SIZE='X-SMALL';\");\n           ResultSet rs2 = statement.executeQuery(\"USE WAREHOUSE \" + warehouseName)) {\n      }\n      logMetric(metricName, Status.SUCCESS);\n    } catch (SQLException e) {\n      System.err.println(\"Error creating warehouse: \" + e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void cleanupResources(Statement statement, String metricName) {\n    try {\n      try (ResultSet rs1 = statement.executeQuery(\"REMOVE @\" + stageName);\n           ResultSet rs2 = statement.executeQuery(\"DROP TABLE IF EXISTS \" + tableName)) {\n      }\n      logMetric(metricName, Status.SUCCESS);\n    } catch (SQLException e) {\n      System.err.println(\"Error during cleanup: \" + e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void compareFetchedDataAndFile(Statement statement, List<String> csv, String metricName) throws SQLException {\n    try (ResultSet resultSet = statement.executeQuery(\"select id,name,email from \" + tableName + \" order by id\")) {\n      for (int i = 1; i < csv.size(); i++) {\n        String csvRow = csv.get(i);\n        String[] csvValues = csvRow.split(\",\", 3);\n        int listId = Integer.parseInt(csvValues[0]);\n        String listName = csvValues[1];\n        String listEmail = csvValues[2];\n\n        if (!resultSet.next()) {\n          logMetric(metricName, Status.FAILURE);\n          return;\n        }\n        int dbId = resultSet.getInt(1);\n        String dbName = resultSet.getString(2);\n        String dbEmail = resultSet.getString(3);\n\n        boolean idMatch = (dbId == listId);\n        boolean nameMatch = dbName.equals(listName);\n        boolean emailMatch = dbEmail.equals(listEmail);\n        if (!(idMatch && nameMatch && emailMatch)) {\n          logMetric(metricName, Status.FAILURE);\n          return;\n        }\n      }\n      logMetric(metricName, Status.SUCCESS);\n    }\n  }\n\n  private static String downloadFile(SnowflakeConnection sfConnection, String metricName) throws SQLException {\n    try (InputStream downloadStream = sfConnection.downloadStream(\"@\" + stageName, stageFilePath, false);\n         BufferedReader reader = new BufferedReader(new InputStreamReader(downloadStream, StandardCharsets.UTF_8))) {\n      List<String> lines = reader.lines().collect(Collectors.toList());\n      if (lines.size() == 101) {\n        logMetric(metricName, Status.SUCCESS);\n      } else {\n        logMetric(metricName, Status.FAILURE);\n      }\n      return lines.stream().collect(Collectors.joining(System.lineSeparator()));\n    } catch (IOException e) {\n      logMetric(metricName, Status.FAILURE);\n      throw new SQLException(\"Error downloading file\", e);\n    }\n  }\n\n  private static void fetchAndVerifyRows(Statement statement, String metricName) throws SQLException {\n    try (ResultSet resultSet = statement.executeQuery(\"select count(*) from \" + tableName)) {\n      if (resultSet.next()) {\n        int rowCount = resultSet.getInt(1);\n        boolean success = rowCount == 100;\n        logMetric(metricName, success ? Status.SUCCESS : Status.FAILURE);\n      } else {\n        logMetric(metricName, Status.FAILURE);\n      }\n    }\n  }\n\n  private static void loadFileIntoTable(Statement statement, String metricName) throws SQLException {\n    try {\n      try (ResultSet rs = statement.executeQuery(\"copy into \" + tableName + \" from @\" + stageName + \"/\" + stageFilePath + \" FILE_FORMAT = (TYPE = CSV FIELD_OPTIONALLY_ENCLOSED_BY = '\\\"' SKIP_HEADER = 1);\")) {\n        // ResultSet automatically closed by try-with-resources\n      }\n      logMetric(metricName, Status.SUCCESS);\n    } catch (SQLException e) {\n      System.err.println(\"Error during copy into table: \" + e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void uploadFile(SnowflakeConnection sfConnection, String fileContent, String metricName) throws SQLException {\n    try {\n      sfConnection.uploadStream(\"@\" + stageName, \"\", new ByteArrayInputStream(fileContent.getBytes()), stageFilePath, false);\n      logMetric(metricName, Status.SUCCESS);\n    } catch (SQLException e) {\n      System.err.println(\"Error during file upload: \" + e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void createDataTable(Statement statement, String metricName) throws SQLException {\n    try {\n      try (ResultSet resultSet = statement.executeQuery(\"CREATE OR REPLACE TABLE \" + tableName + \" (id int, name text, email text)\")) {\n        if (resultSet.next()) {\n          boolean result = resultSet.getString(1).equals(\"Table \" + tableName.toUpperCase() + \" successfully created.\");\n          logMetric(metricName, result ? Status.SUCCESS : Status.FAILURE);\n        } else {\n          logMetric(metricName, Status.FAILURE);\n        }\n      }\n    } catch (SQLException e) {\n      System.err.println(e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void createDataStage(Statement statement, String metricName) throws SQLException {\n    try {\n      try (ResultSet createStageResult = statement.executeQuery(\"CREATE OR REPLACE STAGE \" + stageName)) {\n        if (createStageResult.next()) {\n          boolean result = createStageResult.getString(1).equals(\"Stage area \" + stageName.toUpperCase() + \" successfully created.\");\n          logMetric(metricName, result ? Status.SUCCESS : Status.FAILURE);\n        } else {\n          logMetric(metricName, Status.FAILURE);\n        }\n      }\n    } catch (SQLException e) {\n      System.err.println(e.getMessage());\n      logMetric(metricName, Status.FAILURE);\n      System.exit(1);\n    }\n  }\n\n  private static void setupLogging(Properties properties) throws IOException {\n    String loggingPropertiesString = \"handlers=java.util.logging.ConsoleHandler\\n.level=\" + properties.getProperty(\"log_level\");\n    properties.put(\"JAVA_LOGGING_CONSOLE_STD_OUT\", \"false\");\n    try (InputStream propertiesStream = new ByteArrayInputStream(\n        loggingPropertiesString.getBytes(StandardCharsets.UTF_8)\n    )) {\n      LogManager.getLogManager().readConfiguration(propertiesStream);\n    }\n  }\n\n  private static void logMetric(String metricName, Status status) {\n    System.out.println(metricName + \"{java_version=\\\"\" + javaVersion + \"\\\", driver_version=\\\"\" + driverVersion + \"\\\"} \" + status.getCode());\n  }\n\n  private static Map<String, String> parseArguments(String[] args) {\n    Map<String, String> parsedArgs = new HashMap<>();\n    for (int i = 0; i < args.length; i++) {\n      String currentArg = args[i];\n\n      if (currentArg.startsWith(\"--\")) {\n        String key = currentArg.substring(2); // Remove \"--\"\n\n        // Check if there is a next argument to be the value\n        if (i + 1 < args.length) {\n          String nextArg = args[i + 1];\n          // Ensure the next argument is not another key\n          if (!nextArg.startsWith(\"--\")) {\n            parsedArgs.put(key, nextArg);\n            i++; // Increment i to skip the value argument in the next iteration\n          }\n        }\n      }\n    }\n    return parsedArgs;\n  }\n\n  private static List<String> generateCsv(int numRows) {\n    String[] headers = {\"ID\", \"Name\", \"Email\"};\n    List<String> csvRows = new ArrayList<>();\n\n    csvRows.add(String.join(\",\", headers));\n\n    for (int i = 1; i <= numRows; i++) {\n      String firstName = generateRandomString(4 + random.nextInt(5));\n      String lastName = generateRandomString(5 + random.nextInt(6));\n\n      String fullName = firstName + \" \" + lastName;\n      String email = (firstName + \".\" + lastName + \"@example.com\").toLowerCase();\n\n      StringJoiner rowJoiner = new StringJoiner(\",\");\n      rowJoiner.add(String.valueOf(i));\n      rowJoiner.add(fullName);\n      rowJoiner.add(email);\n\n      csvRows.add(rowJoiner.toString());\n    }\n\n    return csvRows;\n  }\n\n  private static String generateRandomString(int length) {\n    if (length <= 0) {\n      return \"\";\n    }\n    StringBuilder builder = new StringBuilder(length);\n    for (int i = 0; i < length; i++) {\n      builder.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));\n    }\n    builder.setCharAt(0, Character.toUpperCase(builder.charAt(0)));\n    return builder.toString();\n  }\n\n  private static void setPrivateKey(Properties props) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {\n    String keyStr = new String(Files.readAllBytes(Paths.get(props.getProperty(\"private_key_file\"))), StandardCharsets.UTF_8).trim();\n    byte[] keyBytes = Base64.getUrlDecoder().decode(keyStr);\n\n    // Convert the DER bytes to a private key object\n    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);\n    KeyFactory keyFactory = KeyFactory.getInstance(\"RSA\");\n    RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyFactory.generatePrivate(keySpec);\n    props.put(\"privateKey\", privateKey);\n    // Remove the path from properties so the driver does not try to read it\n    props.remove(\"private_key_file\");\n  }\n}"
  },
  {
    "path": "public_pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>net.snowflake</groupId>\n    <artifactId>snowflake-jdbc</artifactId>\n    <version>1.0-SNAPSHOT</version>\n    <packaging>jar</packaging>\n    <name>Snowflake JDBC Driver</name>\n    <description>Snowflake JDBC Driver</description>\n    <url>https://www.snowflake.net/</url>\n\n    <licenses>\n      <license>\n        <name>The Apache Software License, Version 2.0</name>\n        <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n      </license>\n    </licenses>\n\n    <developers>\n      <developer>\n        <name>Snowflake Support Team</name>\n        <email>snowflake-java@snowflake.net</email>\n        <organization>Snowflake Computing</organization>\n        <organizationUrl>https://www.snowflake.net</organizationUrl>\n      </developer>\n    </developers>\n\n    <scm>\n      <connection>scm:git:git://github.com/snowflakedb/snowflake-jdbc</connection>\n      <url>http://github.com/snowflakedb/snowflake-jdbc/tree/master</url>\n    </scm>\n\n    <properties>\n      <jna.version>5.13.0</jna.version>\n    </properties>\n\n    <dependencies>\n      <dependency>\n        <groupId>net.java.dev.jna</groupId>\n        <artifactId>jna</artifactId>\n        <version>${jna.version}</version>\n        <optional>true</optional>\n      </dependency>\n      <dependency>\n        <groupId>net.java.dev.jna</groupId>\n        <artifactId>jna-platform</artifactId>\n        <version>${jna.version}</version>\n        <optional>true</optional>\n      </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "settings.json",
    "content": ""
  },
  {
    "path": "src/main/java/net/snowflake/client/api/auth/AuthenticatorType.java",
    "content": "package net.snowflake.client.api.auth;\n\n/**\n * Enumeration of authentication methods supported by the Snowflake JDBC driver.\n *\n * <p>This enum defines the various authentication mechanisms that can be used to establish a\n * connection to Snowflake. The authenticator type is specified via the connection property {@code\n * authenticator}.\n *\n * <h2 id=\"usage-example\">Usage Example</h2>\n *\n * <pre>{@code\n * Properties props = new Properties();\n * props.put(\"user\", \"myuser\");\n * props.put(\"authenticator\", \"EXTERNALBROWSER\");\n * Connection conn = DriverManager.getConnection(url, props);\n * }</pre>\n */\npublic enum AuthenticatorType {\n  /** Regular login with username and password via Snowflake, may or may not have MFA */\n  SNOWFLAKE,\n\n  /** Federated authentication with OKTA as identity provider */\n  OKTA,\n\n  /** Web-browser-based authenticator for SAML 2.0 compliant service/application */\n  EXTERNALBROWSER,\n\n  /** OAuth 2.0 authentication flow */\n  OAUTH,\n\n  /** Snowflake JWT token authentication using a private key */\n  SNOWFLAKE_JWT,\n\n  /** Internal authenticator to enable id_token for web browser based authentication */\n  ID_TOKEN,\n\n  /** Authenticator to enable token for regular login with MFA */\n  USERNAME_PASSWORD_MFA,\n\n  /** OAuth authorization code flow with browser popup */\n  OAUTH_AUTHORIZATION_CODE,\n\n  /** OAuth client credentials flow with clientId and clientSecret */\n  OAUTH_CLIENT_CREDENTIALS,\n\n  /** Programmatic Access Token (PAT) authentication created in Snowflake */\n  PROGRAMMATIC_ACCESS_TOKEN,\n\n  /**\n   * Workload identity authentication using existing AWS/GCP/Azure/OIDC workload identity\n   * credentials\n   */\n  WORKLOAD_IDENTITY\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/connection/DownloadStreamConfig.java",
    "content": "package net.snowflake.client.api.connection;\n\n/**\n * Optional configuration for downloading files from a Snowflake stage as a stream.\n *\n * <p>This class provides optional configuration for the {@link\n * SnowflakeConnection#downloadStream(String, String, DownloadStreamConfig)} method. Required\n * parameters (stageName, sourceFileName) are passed as method arguments, while optional settings\n * are configured here.\n *\n * <p><b>Example usage:</b>\n *\n * <pre>{@code\n * DownloadStreamConfig config = DownloadStreamConfig.builder()\n *     .setDecompress(true)\n *     .build();\n *\n * try (InputStream stream = connection.downloadStream(\"@my_stage\", \"data/file.csv.gz\", config)) {\n *   // Process the stream\n * }\n * }</pre>\n *\n * @see SnowflakeConnection#downloadStream(String, String, DownloadStreamConfig)\n */\npublic class DownloadStreamConfig {\n  private final boolean decompress;\n\n  /**\n   * Private constructor. Use {@link Builder} to create instances.\n   *\n   * @param builder the builder instance\n   */\n  private DownloadStreamConfig(Builder builder) {\n    this.decompress = builder.decompress;\n  }\n\n  /**\n   * Whether to decompress the file during download.\n   *\n   * @return true if the file should be decompressed, false otherwise\n   */\n  public boolean isDecompress() {\n    return decompress;\n  }\n\n  /**\n   * Creates a new builder instance.\n   *\n   * @return a new {@link Builder}\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder for creating {@link DownloadStreamConfig} instances.\n   *\n   * <p>This builder provides a fluent API for configuring optional download stream settings. All\n   * setter methods return the builder instance for method chaining.\n   *\n   * <p><b>Example:</b>\n   *\n   * <pre>{@code\n   * DownloadStreamConfig config = DownloadStreamConfig.builder()\n   *     .setDecompress(true)\n   *     .build();\n   * }</pre>\n   */\n  public static class Builder {\n    private boolean decompress = false;\n\n    /** Private constructor. Use {@link DownloadStreamConfig#builder()} instead. */\n    private Builder() {}\n\n    /**\n     * Sets whether to automatically decompress the file during download.\n     *\n     * <p>If set to {@code true}, the driver will automatically decompress files with recognized\n     * compression extensions (e.g., .gz, .bz2, .zip) during download. The returned stream will\n     * contain the decompressed data.\n     *\n     * <p>If set to {@code false}, the file is downloaded as-is without decompression.\n     *\n     * @param decompress true to decompress the file, false to download as-is\n     * @return this builder instance\n     */\n    public Builder setDecompress(boolean decompress) {\n      this.decompress = decompress;\n      return this;\n    }\n\n    /**\n     * Builds the {@link DownloadStreamConfig} instance.\n     *\n     * @return a new {@link DownloadStreamConfig} instance\n     */\n    public DownloadStreamConfig build() {\n      return new DownloadStreamConfig(this);\n    }\n  }\n\n  @Override\n  public String toString() {\n    return \"DownloadStreamConfig{\" + \"decompress=\" + decompress + '}';\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/connection/SnowflakeConnection.java",
    "content": "package net.snowflake.client.api.connection;\n\nimport java.io.InputStream;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\n\n/** This interface defines Snowflake specific APIs for Connection */\npublic interface SnowflakeConnection {\n  /**\n   * Upload data from a stream to a Snowflake stage with required parameters only.\n   *\n   * <p>This is a convenience method that uses default options (compress data, no destination\n   * prefix). For advanced configuration, use {@link #uploadStream(String, String, InputStream,\n   * UploadStreamConfig)}.\n   *\n   * <p>The caller is responsible for closing the input stream after upload completes.\n   *\n   * @param stageName the name of the stage (e.g., \"@my_stage\")\n   * @param destFileName the destination file name on the stage\n   * @param inputStream the input stream containing data to upload\n   * @throws SQLException if upload fails\n   */\n  void uploadStream(String stageName, String destFileName, InputStream inputStream)\n      throws SQLException;\n\n  /**\n   * Upload data from a stream to a Snowflake stage with optional configuration.\n   *\n   * <p>This method allows customization of upload behavior via {@link UploadStreamConfig}, such as\n   * setting a destination prefix or controlling compression.\n   *\n   * <p>The caller is responsible for closing the input stream after upload completes.\n   *\n   * @param stageName the name of the stage (e.g., \"@my_stage\")\n   * @param destFileName the destination file name on the stage\n   * @param inputStream the input stream containing data to upload\n   * @param config optional configuration for upload behavior\n   * @throws SQLException if upload fails\n   */\n  void uploadStream(\n      String stageName, String destFileName, InputStream inputStream, UploadStreamConfig config)\n      throws SQLException;\n\n  /**\n   * Download a file from a Snowflake stage as a stream with required parameters only.\n   *\n   * <p>This is a convenience method that uses default options (no decompression). For advanced\n   * configuration, use {@link #downloadStream(String, String, DownloadStreamConfig)}.\n   *\n   * <p>The caller is responsible for closing the returned input stream.\n   *\n   * @param stageName the name of the stage (e.g., \"@my_stage\")\n   * @param sourceFileName the path to the file within the stage\n   * @return an input stream containing the file data\n   * @throws SQLException if download fails\n   */\n  InputStream downloadStream(String stageName, String sourceFileName) throws SQLException;\n\n  /**\n   * Download a file from a Snowflake stage as a stream with optional configuration.\n   *\n   * <p>This method allows customization of download behavior via {@link DownloadStreamConfig}, such\n   * as automatic decompression.\n   *\n   * <p>The caller is responsible for closing the returned input stream.\n   *\n   * @param stageName the name of the stage (e.g., \"@my_stage\")\n   * @param sourceFileName the path to the file within the stage\n   * @param config optional configuration for download behavior\n   * @return an input stream containing the file data\n   * @throws SQLException if download fails\n   */\n  InputStream downloadStream(String stageName, String sourceFileName, DownloadStreamConfig config)\n      throws SQLException;\n\n  /**\n   * Return unique session ID from current session generated by making connection\n   *\n   * @return a unique alphanumeric value representing current session ID\n   * @throws SQLException if an error occurs\n   */\n  String getSessionID() throws SQLException;\n\n  /**\n   * Return the status of a query.\n   *\n   * @param queryID the query ID.\n   * @return the status of the query.\n   * @throws SQLException if an error occurs.\n   */\n  QueryStatus getQueryStatus(String queryID) throws SQLException;\n\n  /**\n   * Create a new instance of a ResultSet object based off query ID. ResultSet will contain results\n   * of corresponding query. Used when original ResultSet object is no longer available, such as\n   * when original connection has been closed.\n   *\n   * @param queryID the query ID\n   * @return ResultSet based off the query ID\n   * @throws SQLException if an error occurs\n   */\n  ResultSet createResultSet(String queryID) throws SQLException;\n\n  /**\n   * Return an array of child query IDs for the given query ID.\n   *\n   * <p>If the given query ID is for a multiple statements query, it returns an array of its child\n   * statements, otherwise, it returns an array to include the given query ID.\n   *\n   * @param queryID The given query ID\n   * @return An array of child query IDs\n   * @throws SQLException If the query is running or the corresponding query is FAILED.\n   */\n  String[] getChildQueryIds(String queryID) throws SQLException;\n\n  /**\n   * Get the major version of the Snowflake database.\n   *\n   * @return database major version\n   * @throws SQLException if an error occurs\n   */\n  int getDatabaseMajorVersion() throws SQLException;\n\n  /**\n   * Get the minor version of the Snowflake database.\n   *\n   * @return database minor version\n   * @throws SQLException if an error occurs\n   */\n  int getDatabaseMinorVersion() throws SQLException;\n\n  /**\n   * Get the full version string of the Snowflake database.\n   *\n   * @return database version string\n   * @throws SQLException if an error occurs\n   */\n  String getDatabaseVersion() throws SQLException;\n\n  /**\n   * Get the current role for the session.\n   *\n   * @return the current role name, or null if no role is set\n   * @throws SQLException if the connection is closed or an error occurs\n   */\n  String getRole() throws SQLException;\n\n  /**\n   * Get the current warehouse for the session.\n   *\n   * @return the current warehouse name, or null if no warehouse is set\n   * @throws SQLException if the connection is closed or an error occurs\n   */\n  String getWarehouse() throws SQLException;\n\n  /**\n   * Get the current database for the session.\n   *\n   * <p>This is equivalent to {@link java.sql.Connection#getCatalog()} but uses Snowflake-specific\n   * terminology for discoverability.\n   *\n   * @return the current database name, or null if no database is set\n   * @throws SQLException if the connection is closed or an error occurs\n   */\n  String getDatabase() throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/connection/SnowflakeDatabaseMetaData.java",
    "content": "package net.snowflake.client.api.connection;\n\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n/**\n * Snowflake-specific extension of {@link DatabaseMetaData}.\n *\n * <p>This interface extends the standard JDBC DatabaseMetaData interface with Snowflake-specific\n * metadata operations.\n */\npublic interface SnowflakeDatabaseMetaData extends DatabaseMetaData {\n\n  /**\n   * Retrieves a description of the streams available in the given catalog.\n   *\n   * <p>This is a Snowflake-specific extension for retrieving information about Snowflake streams.\n   *\n   * @param catalog a catalog name; must match the catalog name as it is stored in the database; \"\"\n   *     retrieves those without a catalog; null means that the catalog name should not be used to\n   *     narrow the search\n   * @param schemaPattern a schema name pattern; must match the schema name as it is stored in the\n   *     database; \"\" retrieves those without a schema; null means that the schema name should not\n   *     be used to narrow the search\n   * @param streamName a stream name pattern; must match the stream name as it is stored in the\n   *     database\n   * @return a ResultSet object in which each row is a stream description\n   * @throws SQLException if a database access error occurs\n   */\n  ResultSet getStreams(String catalog, String schemaPattern, String streamName) throws SQLException;\n\n  /**\n   * Retrieves a description of the table columns available in the specified catalog with extended\n   * metadata.\n   *\n   * <p>This is a Snowflake-specific overload of {@link DatabaseMetaData#getColumns} that allows\n   * retrieving extended column metadata.\n   *\n   * @param catalog a catalog name; must match the catalog name as it is stored in the database; \"\"\n   *     retrieves those without a catalog; null means that the catalog name should not be used to\n   *     narrow the search\n   * @param schemaPattern a schema name pattern; must match the schema name as it is stored in the\n   *     database; \"\" retrieves those without a schema; null means that the schema name should not\n   *     be used to narrow the search\n   * @param tableNamePattern a table name pattern; must match the table name as it is stored in the\n   *     database\n   * @param columnNamePattern a column name pattern; must match the column name as it is stored in\n   *     the database\n   * @param extendedSet if true, returns extended metadata including base type information\n   * @return ResultSet - each row is a column description\n   * @throws SQLException if a database access error occurs\n   */\n  ResultSet getColumns(\n      String catalog,\n      String schemaPattern,\n      String tableNamePattern,\n      String columnNamePattern,\n      boolean extendedSet)\n      throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/connection/UploadStreamConfig.java",
    "content": "package net.snowflake.client.api.connection;\n\n/**\n * Optional configuration for uploading data to a Snowflake stage from a stream.\n *\n * <p>This class provides optional configuration for the {@link\n * SnowflakeConnection#uploadStream(String, String, java.io.InputStream, UploadStreamConfig)}\n * method. Required parameters (stageName, destFileName, inputStream) are passed as method\n * arguments, while optional settings are configured here.\n *\n * <p><b>Example usage:</b>\n *\n * <pre>{@code\n * try (InputStream dataStream = new FileInputStream(\"data.csv\")) {\n *   UploadStreamConfig config = UploadStreamConfig.builder()\n *       .setDestPrefix(\"data/2024\")\n *       .setCompressData(true)\n *       .build();\n *\n *   connection.uploadStream(\"@my_stage\", \"uploaded_data.csv\", dataStream, config);\n * }\n * }</pre>\n *\n * @see SnowflakeConnection#uploadStream(String, String, java.io.InputStream, UploadStreamConfig)\n */\npublic class UploadStreamConfig {\n  private final String destPrefix;\n  private final boolean compressData;\n\n  /**\n   * Private constructor. Use {@link Builder} to create instances.\n   *\n   * @param builder the builder instance\n   */\n  private UploadStreamConfig(Builder builder) {\n    this.destPrefix = builder.destPrefix;\n    this.compressData = builder.compressData;\n  }\n\n  /**\n   * Gets the destination prefix (directory path within the stage).\n   *\n   * @return the destination prefix, or null if files should be uploaded to stage root\n   */\n  public String getDestPrefix() {\n    return destPrefix;\n  }\n\n  /**\n   * Whether to compress the data during upload.\n   *\n   * @return true if data should be compressed, false otherwise\n   */\n  public boolean isCompressData() {\n    return compressData;\n  }\n\n  /**\n   * Creates a new builder instance.\n   *\n   * @return a new {@link Builder}\n   */\n  public static Builder builder() {\n    return new Builder();\n  }\n\n  /**\n   * Builder for creating {@link UploadStreamConfig} instances.\n   *\n   * <p>This builder provides a fluent API for configuring optional upload stream settings. All\n   * setter methods return the builder instance for method chaining.\n   *\n   * <p><b>Example:</b>\n   *\n   * <pre>{@code\n   * UploadStreamConfig config = UploadStreamConfig.builder()\n   *     .setDestPrefix(\"data/2024\")\n   *     .setCompressData(true)\n   *     .build();\n   * }</pre>\n   */\n  public static class Builder {\n    private String destPrefix;\n    private boolean compressData = true;\n\n    /** Private constructor. Use {@link UploadStreamConfig#builder()} instead. */\n    private Builder() {}\n\n    /**\n     * Sets the destination prefix (directory path) within the stage.\n     *\n     * <p>This is optional. If not set, files will be uploaded to the root of the stage. Use forward\n     * slashes to separate directory levels.\n     *\n     * <p><b>Examples:</b>\n     *\n     * <ul>\n     *   <li>{@code \"data\"} - upload to data directory\n     *   <li>{@code \"data/2024/01\"} - upload to nested directories\n     *   <li>{@code null} or empty - upload to stage root (default)\n     * </ul>\n     *\n     * @param destPrefix the destination prefix/directory path (can be null or empty for stage root)\n     * @return this builder instance\n     */\n    public Builder setDestPrefix(String destPrefix) {\n      this.destPrefix =\n          (destPrefix == null || destPrefix.trim().isEmpty()) ? null : destPrefix.trim();\n      return this;\n    }\n\n    /**\n     * Sets whether to automatically compress the data during upload.\n     *\n     * <p>If set to {@code true} (default), the driver will compress the data using gzip compression\n     * before uploading. This reduces upload time and storage costs. The file will be stored with a\n     * .gz extension appended to the destination file name.\n     *\n     * <p>If set to {@code false}, the data is uploaded as-is without compression.\n     *\n     * @param compressData true to compress the data (default), false to upload uncompressed\n     * @return this builder instance\n     */\n    public Builder setCompressData(boolean compressData) {\n      this.compressData = compressData;\n      return this;\n    }\n\n    /**\n     * Builds the {@link UploadStreamConfig} instance.\n     *\n     * @return a new {@link UploadStreamConfig} instance\n     */\n    public UploadStreamConfig build() {\n      return new UploadStreamConfig(this);\n    }\n  }\n\n  @Override\n  public String toString() {\n    return \"UploadStreamConfig{\"\n        + \"destPrefix='\"\n        + destPrefix\n        + '\\''\n        + \", compressData=\"\n        + compressData\n        + '}';\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/datasource/SnowflakeDataSource.java",
    "content": "package net.snowflake.client.api.datasource;\n\nimport java.security.PrivateKey;\nimport java.util.List;\nimport java.util.Properties;\nimport javax.sql.DataSource;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\n\n/**\n * Snowflake-specific extension of {@link javax.sql.DataSource} that provides configuration methods\n * for Snowflake JDBC connections.\n *\n * <p>Use {@link SnowflakeDataSourceFactory} to create instances of this interface.\n *\n * <p><b>Example usage:</b>\n *\n * <pre>{@code\n * SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n * ds.setAccount(\"myaccount\");\n * ds.setUser(\"myuser\");\n * ds.setPassword(\"mypassword\");\n * ds.setDatabase(\"mydb\");\n * ds.setSchema(\"myschema\");\n * ds.setWarehouse(\"mywh\");\n *\n * try (Connection conn = ds.getConnection()) {\n *   // use connection\n * }\n * }</pre>\n *\n * @see SnowflakeDataSourceFactory\n */\npublic interface SnowflakeDataSource extends DataSource {\n\n  /** Sets the JDBC URL for the connection. */\n  void setUrl(String url);\n\n  /** Sets the database name. */\n  void setDatabaseName(String databaseName);\n\n  /** Sets the schema name. */\n  void setSchema(String schema);\n\n  /** Sets the warehouse name. */\n  void setWarehouse(String warehouse);\n\n  /** Sets the role name. */\n  void setRole(String role);\n\n  /** Sets the user name. */\n  void setUser(String user);\n\n  /** Sets the server name (hostname). */\n  void setServerName(String serverName);\n\n  /** Sets the password. */\n  void setPassword(String password);\n\n  /** Sets the port number. */\n  void setPortNumber(int portNumber);\n\n  /** Sets the account identifier. */\n  void setAccount(String account);\n\n  /** Sets whether to use SSL (default: true). */\n  void setSsl(boolean ssl);\n\n  /** Sets the authenticator type (e.g., \"snowflake\", \"externalbrowser\", \"oauth\"). */\n  void setAuthenticator(String authenticator);\n\n  /** Sets the token for OAuth/PAT authentication. */\n  void setToken(String token);\n\n  /** Gets the JDBC URL. */\n  String getUrl();\n\n  /** Sets the private key for key-pair authentication. */\n  void setPrivateKey(PrivateKey privateKey);\n\n  /** Sets the private key file location and optional password for key-pair authentication. */\n  void setPrivateKeyFile(String location, String password);\n\n  /** Sets the Base64-encoded private key and optional password for key-pair authentication. */\n  void setPrivateKeyBase64(String privateKeyBase64, String password);\n\n  /** Sets the tracing level. */\n  void setTracing(String tracing);\n\n  /** Gets the connection properties. */\n  Properties getProperties();\n\n  /** Sets whether to allow underscores in hostnames. */\n  void setAllowUnderscoresInHost(boolean allowUnderscoresInHost);\n\n  /** Sets whether to disable GCS default credentials. */\n  void setDisableGcsDefaultCredentials(boolean isGcsDefaultCredentialsDisabled);\n\n  /** Sets whether to disable SAML URL validation. */\n  void setDisableSamlURLCheck(boolean disableSamlURLCheck);\n\n  /** Sets the passcode for MFA authentication. */\n  void setPasscode(String passcode);\n\n  /** Sets whether the passcode is included in the password for MFA authentication. */\n  void setPasscodeInPassword(boolean isPasscodeInPassword);\n\n  /** Sets whether to disable SOCKS proxy. */\n  void setDisableSocksProxy(boolean ignoreJvmSocksProxy);\n\n  /** Sets non-proxy hosts pattern. */\n  void setNonProxyHosts(String nonProxyHosts);\n\n  /** Sets the proxy host. */\n  void setProxyHost(String proxyHost);\n\n  /** Sets the proxy password. */\n  void setProxyPassword(String proxyPassword);\n\n  /** Sets the proxy port. */\n  void setProxyPort(int proxyPort);\n\n  /** Sets the proxy protocol (e.g., \"http\", \"https\"). */\n  void setProxyProtocol(String proxyProtocol);\n\n  /** Sets the proxy user. */\n  void setProxyUser(String proxyUser);\n\n  /** Sets whether to use a proxy. */\n  void setUseProxy(boolean useProxy);\n\n  /** Sets the network timeout in seconds. */\n  void setNetworkTimeout(int networkTimeoutSeconds);\n\n  /** Sets the query timeout in seconds. */\n  void setQueryTimeout(int queryTimeoutSeconds);\n\n  /** Sets the application name. */\n  void setApplication(String application);\n\n  /** Sets the client configuration file path. */\n  void setClientConfigFile(String clientConfigFile);\n\n  /** Sets whether to enable pattern search in metadata queries. */\n  void setEnablePatternSearch(boolean enablePatternSearch);\n\n  /** Sets whether to enable PUT/GET commands. */\n  void setEnablePutGet(boolean enablePutGet);\n\n  /** Sets whether to treat Arrow DECIMAL columns as INT. */\n  void setArrowTreatDecimalAsInt(boolean treatDecimalAsInt);\n\n  /** Sets the maximum number of HTTP retries. */\n  void setMaxHttpRetries(int maxHttpRetries);\n\n  /** Sets whether OCSP checking should fail open. */\n  void setOcspFailOpen(boolean ocspFailOpen);\n\n  /** Sets the maximum number of PUT/GET retries. */\n  void setPutGetMaxRetries(int putGetMaxRetries);\n\n  /** Sets whether strings are quoted in column definitions. */\n  void setStringsQuotedForColumnDef(boolean stringsQuotedForColumnDef);\n\n  /** Sets whether to enable diagnostics. */\n  void setEnableDiagnostics(boolean enableDiagnostics);\n\n  /** Sets the diagnostics allowlist file path. */\n  void setDiagnosticsAllowlistFile(String diagnosticsAllowlistFile);\n\n  /** Sets the default date format with timezone for JDBC. */\n  void setJDBCDefaultFormatDateWithTimezone(Boolean jdbcDefaultFormatDateWithTimezone);\n\n  /** Sets whether getDate should use null timezone. */\n  void setGetDateUseNullTimezone(Boolean getDateUseNullTimezone);\n\n  /** Sets whether to enable client-side MFA token request. */\n  void setEnableClientRequestMfaToken(boolean enableClientRequestMfaToken);\n\n  /** Sets whether to enable client-side storage of temporary credentials. */\n  void setEnableClientStoreTemporaryCredential(boolean enableClientStoreTemporaryCredential);\n\n  /** Sets the browser response timeout in seconds for external browser authentication. */\n  void setBrowserResponseTimeout(int seconds);\n\n  /** Sets custom HTTP header customizers. */\n  void setHttpHeadersCustomizers(List<HttpHeadersCustomizer> httpHeadersCustomizers);\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/datasource/SnowflakeDataSourceFactory.java",
    "content": "package net.snowflake.client.api.datasource;\n\nimport net.snowflake.client.internal.api.implementation.datasource.SnowflakeBasicDataSource;\n\n/**\n * Factory for creating {@link SnowflakeDataSource} instances.\n *\n * <p>This factory provides methods to create different types of Snowflake DataSource\n * implementations. Use this factory instead of directly instantiating DataSource classes.\n *\n * <p><b>Example usage:</b>\n *\n * <pre>{@code\n * SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n * ds.setAccount(\"myaccount\");\n * ds.setUser(\"myuser\");\n * ds.setPassword(\"mypassword\");\n * ds.setDatabase(\"mydb\");\n * ds.setSchema(\"myschema\");\n * ds.setWarehouse(\"mywh\");\n *\n * try (Connection conn = ds.getConnection()) {\n *   // use connection\n * }\n * }</pre>\n *\n * @see SnowflakeDataSource\n */\npublic final class SnowflakeDataSourceFactory {\n\n  private SnowflakeDataSourceFactory() {\n    throw new AssertionError(\"SnowflakeDataSourceFactory cannot be instantiated\");\n  }\n\n  /**\n   * Creates a new non-pooled Snowflake DataSource.\n   *\n   * <p>This DataSource creates a new physical connection for each {@link\n   * javax.sql.DataSource#getConnection()} call. For applications that require connection pooling,\n   * consider using an external connection pool manager (e.g., HikariCP, Apache DBCP) with this\n   * DataSource.\n   *\n   * @return a new {@link SnowflakeDataSource} instance\n   */\n  public static SnowflakeDataSource createDataSource() {\n    return new SnowflakeBasicDataSource();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/driver/SnowflakeDriver.java",
    "content": "package net.snowflake.client.api.driver;\n\nimport java.sql.Connection;\nimport java.sql.Driver;\nimport java.sql.DriverManager;\nimport java.sql.DriverPropertyInfo;\nimport java.sql.SQLException;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.driver.AutoConfigurationHelper;\nimport net.snowflake.client.internal.driver.ConnectionFactory;\nimport net.snowflake.client.internal.driver.DriverInitializer;\nimport net.snowflake.client.internal.driver.DriverVersion;\nimport net.snowflake.client.internal.jdbc.SnowflakeConnectString;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.ResourceBundleManager;\n\n/**\n * JDBC Driver implementation for Snowflake.\n *\n * <p>To use this driver, specify the following URL formats:\n *\n * <ul>\n *   <li>{@code jdbc:snowflake://host:port} - Standard connection\n *   <li>{@code jdbc:snowflake:auto} - Auto-configuration from connections.toml\n * </ul>\n *\n * <p>The driver is automatically registered with {@link DriverManager} when loaded.\n *\n * @see java.sql.Driver\n */\npublic class SnowflakeDriver implements Driver {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeDriver.class);\n\n  public static final SnowflakeDriver INSTANCE;\n  public static final Properties EMPTY_PROPERTIES = new Properties();\n\n  private static final DriverVersion VERSION = DriverVersion.getInstance();\n\n  static final ResourceBundleManager versionResourceBundleManager =\n      ResourceBundleManager.getSingleton(\"net.snowflake.client.jdbc.version\");\n\n  static {\n    try {\n      DriverManager.registerDriver(INSTANCE = new SnowflakeDriver());\n      logger.debug(\"Snowflake JDBC Driver {} registered successfully\", VERSION.getFullVersion());\n    } catch (SQLException ex) {\n      throw new IllegalStateException(\"Unable to register \" + SnowflakeDriver.class.getName(), ex);\n    }\n\n    // Perform all driver initialization (Arrow, security, telemetry, etc.)\n    DriverInitializer.initialize();\n  }\n\n  /**\n   * Checks whether a given URL is in a valid Snowflake JDBC format.\n   *\n   * <p>Valid formats:\n   *\n   * <ul>\n   *   <li>{@code jdbc:snowflake://host[:port]} - Standard connection\n   *   <li>{@code jdbc:snowflake:auto} - Auto-configuration\n   * </ul>\n   *\n   * @param url the database URL\n   * @return true if the URL is valid and accepted by this driver\n   */\n  @Override\n  public boolean acceptsURL(String url) {\n    if (AutoConfigurationHelper.isAutoConfigurationUrl(url)) {\n      return true;\n    }\n    return SnowflakeConnectString.parse(url, EMPTY_PROPERTIES).isValid();\n  }\n\n  /**\n   * Establishes a connection to the Snowflake database.\n   *\n   * @param url the database URL\n   * @param info additional connection properties\n   * @return a Connection object, or null if the URL is not accepted\n   * @throws SQLException if a database access error occurs\n   */\n  @Override\n  public Connection connect(String url, Properties info) throws SQLException {\n    return ConnectionFactory.createConnection(url, info);\n  }\n\n  /**\n   * Establishes a connection using auto-configuration.\n   *\n   * @return a Connection object\n   * @throws SQLException if a database access error occurs\n   */\n  public Connection connect() throws SQLException {\n    return ConnectionFactory.createConnectionWithAutoConfig();\n  }\n\n  @Override\n  public int getMajorVersion() {\n    return VERSION.getMajor();\n  }\n\n  @Override\n  public int getMinorVersion() {\n    return VERSION.getMinor();\n  }\n\n  /**\n   * Gets the driver patch version number. This is not part of the standard JDBC Driver interface,\n   * but is needed for full version information.\n   *\n   * @return driver patch version number\n   */\n  public long getPatchVersion() {\n    return VERSION.getPatch();\n  }\n\n  /**\n   * Gets driver version information for telemetry and logging.\n   *\n   * @return full version string (e.g., \"4.0.0\")\n   */\n  public static String getImplementationVersion() {\n    return VERSION.getFullVersion();\n  }\n\n  @Override\n  public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {\n    if (url == null || url.isEmpty()) {\n      DriverPropertyInfo[] result = new DriverPropertyInfo[1];\n      result[0] = new DriverPropertyInfo(\"serverURL\", null);\n      result[0].description =\n          \"server URL in form of <protocol>://<host or domain>:<port number>/<path of resource>\";\n      return result;\n    }\n\n    try (SnowflakeConnectionImpl con = new SnowflakeConnectionImpl(url, info, true)) {\n      List<DriverPropertyInfo> missingProperties = con.returnMissingProperties();\n      return missingProperties.toArray(new DriverPropertyInfo[0]);\n    }\n  }\n\n  @Override\n  public boolean jdbcCompliant() {\n    return false;\n  }\n\n  @Override\n  public java.util.logging.Logger getParentLogger() {\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/exception/ErrorCode.java",
    "content": "package net.snowflake.client.api.exception;\n\nimport net.snowflake.common.core.SqlState;\n\n/** Internal JDBC driver error codes */\npublic enum ErrorCode {\n\n  /**\n   * Error codes partitioning:\n   *\n   * <p>0NNNNN: GS SQL error codes 1NNNNN: XP error codes 2NNNNN: JDBC driver error codes 3NNNNN: GS\n   * generic error codes 4NNNNN: Node.js driver error codes\n   *\n   * <p>N can be any digits from 0 to 9.\n   */\n  INTERNAL_ERROR(200001, SqlState.INTERNAL_ERROR),\n  CONNECTION_ERROR(200002, SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),\n  INTERRUPTED(200003, SqlState.QUERY_CANCELED),\n  COMPRESSION_TYPE_NOT_SUPPORTED(200004, SqlState.FEATURE_NOT_SUPPORTED),\n  QUERY_CANCELED(200005, SqlState.QUERY_CANCELED),\n  COMPRESSION_TYPE_NOT_KNOWN(200006, SqlState.FEATURE_NOT_SUPPORTED),\n  FAIL_LIST_FILES(200007, SqlState.DATA_EXCEPTION),\n  FILE_NOT_FOUND(200008, SqlState.DATA_EXCEPTION),\n  FILE_IS_DIRECTORY(200009, SqlState.DATA_EXCEPTION),\n  DUPLICATE_CONNECTION_PROPERTY_SPECIFIED(200010, SqlState.DATA_EXCEPTION),\n  MISSING_USERNAME(200011, SqlState.INVALID_AUTHORIZATION_SPECIFICATION),\n  MISSING_PASSWORD(200012, SqlState.INVALID_AUTHORIZATION_SPECIFICATION),\n  S3_OPERATION_ERROR(200013, SqlState.SYSTEM_ERROR),\n  MAX_RESULT_LIMIT_EXCEEDED(200014, SqlState.PROGRAM_LIMIT_EXCEEDED),\n  NETWORK_ERROR(200015, SqlState.IO_ERROR),\n  IO_ERROR(200016, SqlState.IO_ERROR),\n  PATH_NOT_DIRECTORY(200017, SqlState.DATA_EXCEPTION),\n  DATA_TYPE_NOT_SUPPORTED(200018, SqlState.FEATURE_NOT_SUPPORTED),\n  CLIENT_SIDE_SORTING_NOT_SUPPORTED(200019, SqlState.FEATURE_NOT_SUPPORTED),\n  AWS_CLIENT_ERROR(200020, SqlState.SYSTEM_ERROR),\n  INVALID_SQL(200021, SqlState.SQL_STATEMENT_NOT_YET_COMPLETE),\n  BAD_RESPONSE(200022, SqlState.INTERNAL_ERROR),\n  ARRAY_BIND_MIXED_TYPES_NOT_SUPPORTED(200023, SqlState.FEATURE_NOT_SUPPORTED),\n  STATEMENT_CLOSED(200024, SqlState.FEATURE_NOT_SUPPORTED),\n  STATEMENT_ALREADY_RUNNING_QUERY(200025, SqlState.FEATURE_NOT_SUPPORTED),\n  MISSING_SERVER_URL(200026, SqlState.INVALID_AUTHORIZATION_SPECIFICATION),\n  TOO_MANY_SESSION_PARAMETERS(200027, SqlState.FEATURE_NOT_SUPPORTED),\n  MISSING_CONNECTION_PROPERTY(200028, SqlState.INVALID_AUTHORIZATION_SPECIFICATION),\n  INVALID_CONNECTION_URL(200029, SqlState.INVALID_AUTHORIZATION_SPECIFICATION),\n  DUPLICATE_STATEMENT_PARAMETER_SPECIFIED(200030, SqlState.DATA_EXCEPTION),\n  TOO_MANY_STATEMENT_PARAMETERS(200031, SqlState.FEATURE_NOT_SUPPORTED),\n  COLUMN_DOES_NOT_EXIST(200032, SqlState.DATA_EXCEPTION),\n  INVALID_PARAMETER_TYPE(200033, SqlState.INVALID_PARAMETER_VALUE),\n  ROW_DOES_NOT_EXIST(200034, SqlState.DATA_EXCEPTION),\n  FEATURE_UNSUPPORTED(200035, SqlState.FEATURE_NOT_SUPPORTED),\n  INVALID_STATE(200036, SqlState.FEATURE_NOT_SUPPORTED),\n  RESULTSET_ALREADY_CLOSED(200037, SqlState.FEATURE_NOT_SUPPORTED),\n  INVALID_VALUE_CONVERT(200038, SqlState.FEATURE_NOT_SUPPORTED),\n  IDP_CONNECTION_ERROR(200039, SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),\n  IDP_INCORRECT_DESTINATION(200040, SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),\n  CONNECTION_ESTABLISHED_WITH_DIFFERENT_PROP(200041, SqlState.WARNING),\n  UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API(200042, SqlState.FEATURE_NOT_SUPPORTED),\n  STATEMENT_PREPARE_FAILURE(200043, SqlState.FEATURE_NOT_SUPPORTED),\n  AZURE_SERVICE_ERROR(200044, SqlState.SYSTEM_ERROR),\n\n  INVALID_OR_UNSUPPORTED_PRIVATE_KEY(200045, SqlState.SYNTAX_ERROR),\n  FAILED_TO_GENERATE_JWT(200046, SqlState.SYNTAX_ERROR),\n  INVALID_PARAMETER_VALUE(200047, SqlState.INVALID_PARAMETER_VALUE),\n  QUERY_FIRST_RESULT_NOT_RESULT_SET(200048, SqlState.WARNING),\n  UPDATE_FIRST_RESULT_NOT_UPDATE_COUNT(200049, SqlState.WARNING),\n  CHILD_RESULT_IDS_AND_TYPES_DIFFERENT_SIZES(200050, SqlState.INTERNAL_ERROR),\n  INVALID_PROXY_PROPERTIES(200051, SqlState.CONNECTION_EXCEPTION),\n  CONNECTION_CLOSED(200052, SqlState.CONNECTION_DOES_NOT_EXIST),\n  NON_FATAL_ERROR(200053, SqlState.WARNING),\n  NUMERIC_VALUE_OUT_OF_RANGE(200054, SqlState.NUMERIC_VALUE_OUT_OF_RANGE),\n  NO_VALID_DATA(200055, SqlState.NO_DATA),\n  INVALID_APP_NAME(200056, SqlState.INVALID_PARAMETER_VALUE),\n  EXECUTE_BATCH_INTEGER_OVERFLOW(200058, SqlState.NUMERIC_VALUE_OUT_OF_RANGE),\n  INVALID_CONNECT_STRING(200059, SqlState.CONNECTION_EXCEPTION),\n  INVALID_OKTA_USERNAME(200060, SqlState.CONNECTION_EXCEPTION),\n  GCP_SERVICE_ERROR(200061, SqlState.SYSTEM_ERROR),\n  AUTHENTICATOR_REQUEST_TIMEOUT(200062, SqlState.CONNECTION_EXCEPTION),\n  INVALID_STRUCT_DATA(200063, SqlState.DATA_EXCEPTION),\n  DISABLEOCSP_INSECUREMODE_VALUE_MISMATCH(200064, SqlState.INVALID_PARAMETER_VALUE),\n  TOO_MANY_FILES_TO_DOWNLOAD_AS_STREAM(200065, SqlState.DATA_EXCEPTION),\n  FILE_OPERATION_UPLOAD_ERROR(200066, SqlState.INTERNAL_ERROR),\n  FILE_OPERATION_DOWNLOAD_ERROR(200067, SqlState.INTERNAL_ERROR),\n  OAUTH_AUTHORIZATION_CODE_FLOW_ERROR(200068, SqlState.CONNECTION_EXCEPTION),\n  OAUTH_CLIENT_CREDENTIALS_FLOW_ERROR(200069, SqlState.CONNECTION_EXCEPTION),\n  OAUTH_REFRESH_TOKEN_FLOW_ERROR(200070, SqlState.CONNECTION_EXCEPTION),\n  WORKLOAD_IDENTITY_FLOW_ERROR(200071, SqlState.CONNECTION_EXCEPTION),\n  OKTA_MFA_NOT_SUPPORTED(200072, SqlState.FEATURE_NOT_SUPPORTED),\n  UNKNOWN_CERT_REVOCATION_CHECK_MODE(200073, SqlState.INVALID_PARAMETER_VALUE),\n  BOTH_OCSP_AND_CERT_REVOCATION_CHECK(200074, SqlState.FEATURE_NOT_SUPPORTED),\n  FILE_TRANSFER_ERROR(253000, SqlState.SYSTEM_ERROR),\n  DOWNLOAD_ERROR(253002, SqlState.SYSTEM_ERROR),\n  UPLOAD_ERROR(253003, SqlState.SYSTEM_ERROR),\n\n  OCSP_GENERAL_ERROR(254000, SqlState.INTERNAL_ERROR),\n  HTTP_GENERAL_ERROR(290000, SqlState.INTERNAL_ERROR);\n\n  public static final String errorMessageResource = \"net.snowflake.client.jdbc.jdbc_error_messages\";\n\n  /** Snowflake internal message associated to the error. */\n  private final Integer messageCode;\n\n  private final String sqlState;\n\n  /**\n   * Construct a new error code specification given Snowflake internal error code and SQL state\n   * error code.\n   *\n   * <p>\n   *\n   * @param messageCode Snowflake internal error code\n   * @param sqlState SQL state error code\n   */\n  ErrorCode(Integer messageCode, String sqlState) {\n    this.messageCode = messageCode;\n    this.sqlState = sqlState;\n  }\n\n  public Integer getMessageCode() {\n    return messageCode;\n  }\n\n  public String getSqlState() {\n    return sqlState;\n  }\n\n  @Override\n  public String toString() {\n    return \"ErrorCode{\" + \"messageCode=\" + messageCode + \", sqlState=\" + sqlState + '}';\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/exception/SnowflakeSQLException.java",
    "content": "package net.snowflake.client.api.exception;\n\nimport java.sql.SQLException;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.common.core.ResourceBundleManager;\n\npublic class SnowflakeSQLException extends SQLException {\n  private static final long serialVersionUID = 1L;\n\n  static final ResourceBundleManager errorResourceBundleManager =\n      ResourceBundleManager.getSingleton(ErrorCode.errorMessageResource);\n\n  private String queryId = \"unknown\";\n\n  /**\n   * This constructor should only be used for error from Global service. Since Global service has\n   * already built the error message, we use it as is. For any errors local to JDBC driver, we\n   * should use one of the constructors below to build the error message.\n   *\n   * @param queryId query id\n   * @param reason reason for which exception is created\n   * @param sqlState SQL state\n   * @param vendorCode vendor code\n   */\n  public SnowflakeSQLException(String queryId, String reason, String sqlState, int vendorCode) {\n    super(reason, sqlState, vendorCode);\n    this.queryId = queryId;\n  }\n\n  /**\n   * @param queryId the queryID\n   * @param reason exception reason\n   * @param sqlState the SQL state\n   */\n  public SnowflakeSQLException(String queryId, String reason, String sqlState) {\n    super(reason, sqlState);\n    this.queryId = queryId;\n  }\n\n  /**\n   * @param queryId query ID\n   * @param sqlState SQL state\n   * @param vendorCode vendor code\n   */\n  public SnowflakeSQLException(String queryId, String sqlState, int vendorCode) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(String.valueOf(vendorCode)),\n        sqlState,\n        vendorCode);\n    this.queryId = queryId;\n  }\n\n  /**\n   * @param sqlState the SQL state\n   * @param vendorCode the vendor code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(String sqlState, int vendorCode, Object... params) {\n    this((String) null, sqlState, vendorCode, params);\n  }\n\n  /**\n   * @param queryId query ID\n   * @param sqlState the SQL state\n   * @param vendorCode the vendor code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(String queryId, String sqlState, int vendorCode, Object... params) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(String.valueOf(vendorCode), params),\n        sqlState,\n        vendorCode);\n    this.queryId = queryId;\n  }\n\n  /**\n   * @param ex Throwable exception\n   * @param sqlState the SQL state\n   * @param vendorCode the vendor code\n   */\n  public SnowflakeSQLException(Throwable ex, String sqlState, int vendorCode) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(String.valueOf(vendorCode)),\n        sqlState,\n        vendorCode,\n        ex);\n  }\n\n  /**\n   * @param ex Throwable exception\n   * @param errorCode the error code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(Throwable ex, ErrorCode errorCode, Object... params) {\n    this(ex, errorCode.getSqlState(), errorCode.getMessageCode(), params);\n  }\n\n  /**\n   * @param ex Throwable exception\n   * @param sqlState the SQL state\n   * @param vendorCode the vendor code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(Throwable ex, String sqlState, int vendorCode, Object... params) {\n    this(null, ex, sqlState, vendorCode, params);\n  }\n\n  /**\n   * @param queryId query ID\n   * @param ex Throwable exception\n   * @param sqlState the SQL state\n   * @param vendorCode the vendor code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(\n      String queryId, Throwable ex, String sqlState, int vendorCode, Object... params) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(String.valueOf(vendorCode), params),\n        sqlState,\n        vendorCode,\n        ex);\n    this.queryId = queryId;\n  }\n\n  /**\n   * @param errorCode the error code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(ErrorCode errorCode, Object... params) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(\n            String.valueOf(errorCode.getMessageCode()), params),\n        errorCode.getSqlState(),\n        errorCode.getMessageCode());\n  }\n\n  /**\n   * @param queryId query ID\n   * @param errorCode error code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLException(String queryId, ErrorCode errorCode, Object... params) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(\n            String.valueOf(errorCode.getMessageCode()), params),\n        errorCode.getSqlState(),\n        errorCode.getMessageCode());\n    this.queryId = queryId;\n  }\n\n  /**\n   * @param e the SFException\n   */\n  public SnowflakeSQLException(SFException e) {\n    this(e.getQueryId(), e.getMessage(), e.getSqlState(), e.getVendorCode());\n  }\n\n  public SnowflakeSQLException(String reason) {\n    super(reason);\n  }\n\n  public SnowflakeSQLException(Throwable ex, String message) {\n    super(message, ex);\n  }\n\n  public String getQueryId() {\n    return queryId;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/http/HttpHeadersCustomizer.java",
    "content": "package net.snowflake.client.api.http;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Allows programmatic customization of HTTP headers for requests sent by the Snowflake JDBC driver.\n *\n * <p>Implementations can be registered with the driver (e.g., via {@link\n * net.snowflake.client.api.datasource.SnowflakeDataSource}) to dynamically add headers. They define\n * which requests to apply headers to, provide the headers, and specify if headers should regenerate\n * on retries (e.g., for dynamic tokens).\n */\npublic interface HttpHeadersCustomizer {\n  String HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY = \"net.snowflake.client.jdbc.HttpHeadersCustomizer\";\n\n  /**\n   * Determines if this customizer should be applied to the given request context.\n   *\n   * @param method The HTTP method (e.g., \"GET\", \"POST\").\n   * @param uri The target URI for the request.\n   * @param currentHeaders A read-only view of headers already present before this customizer runs.\n   * @return true if newHeaders() should be called for this request, false otherwise.\n   */\n  boolean applies(String method, String uri, Map<String, List<String>> currentHeaders);\n\n  /**\n   * Generates the custom headers to be added to the request.\n   *\n   * @return A Map where keys are header names and values are Lists of header values.\n   */\n  Map<String, List<String>> newHeaders();\n\n  /**\n   * Indicates if newHeaders() should be called only once for the initial request attempt (true), or\n   * if it should be called again before each retry attempt (false).\n   *\n   * @return true for static headers, false for dynamic headers needing regeneration.\n   */\n  boolean invokeOnce();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/loader/LoadResultListener.java",
    "content": "package net.snowflake.client.api.loader;\n\n/** Callback API for processing errors and statistics of upload operation */\npublic interface LoadResultListener {\n\n  /**\n   * @return this result listener needs to listen to the provided records\n   */\n  boolean needSuccessRecords();\n\n  /**\n   * @param op Operation requested\n   * @param record Data submitted for processing\n   */\n  void recordProvided(Operation op, Object[] record);\n\n  /**\n   * @param op Operation requested\n   * @param i number of rows that had been processed\n   */\n  void addProcessedRecordCount(Operation op, int i);\n\n  /**\n   * @param op Operation requested\n   * @param i number of rows that had been affected by a given operation\n   */\n  void addOperationRecordCount(Operation op, int i);\n\n  /**\n   * @return whether this result listener needs to listen to error records\n   */\n  boolean needErrors();\n\n  /**\n   * @param error information about error that was encountered\n   */\n  void addError(LoadingError error);\n\n  /**\n   * @return Whether to throw an exception upon encountering error\n   */\n  boolean throwOnError();\n\n  /**\n   * Method to add to the error count for a listener\n   *\n   * @param number the number of errors\n   */\n  void addErrorCount(int number);\n\n  /** Method to reset the error count back to zero */\n  void resetErrorCount();\n\n  /**\n   * method to get the total number of errors\n   *\n   * @return the number of errors\n   */\n  int getErrorCount();\n\n  /**\n   * Method to add to the error record count for a listener\n   *\n   * @param number the number of error records\n   */\n  void addErrorRecordCount(int number);\n\n  /** Method to reset the errorRecordCount back to zero */\n  void resetErrorRecordCount();\n\n  /**\n   * method to get the total number of error records\n   *\n   * @return the number of rows in errors\n   */\n  int getErrorRecordCount();\n\n  /** Resets submitted row count */\n  void resetSubmittedRowCount();\n\n  /**\n   * Adds the number of submitted rows\n   *\n   * @param number the number of submitted row\n   */\n  void addSubmittedRowCount(int number);\n\n  /**\n   * Gets the number of submitted row\n   *\n   * @return the number of submitted row\n   */\n  int getSubmittedRowCount();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/loader/Loader.java",
    "content": "package net.snowflake.client.api.loader;\n\n/**\n * Bulk loader for Snowflake.\n *\n * <p>This interface extends {@link AutoCloseable}, enabling try-with-resources pattern for\n * automatic resource management:\n *\n * <pre>{@code\n * try (Loader loader = LoaderFactory.createLoader(...)) {\n *     loader.start();\n *     loader.submitRow(data);\n *     loader.finish();\n * } // Connections closed automatically\n * }</pre>\n */\npublic interface Loader extends AutoCloseable {\n\n  // Configuration, see LoaderProperty\n  void setProperty(LoaderProperty property, Object value);\n\n  // Callback API\n  void setListener(LoadResultListener listener);\n\n  /** Initiates loading threads. Executes \"before\" statement */\n  void start();\n\n  /**\n   * Pass row data\n   *\n   * @param data, must match shape of the table (requested columns, in the order provided)\n   */\n  void submitRow(Object[] data);\n\n  /**\n   * If operation is changed, previous data is committed\n   *\n   * @param op operation will be reset\n   */\n  void resetOperation(Operation op);\n\n  /**\n   * Rollback uncommitted changes. If no transaction was initialized, indeterminate fraction of rows\n   * could have been committed.\n   */\n  void rollback();\n\n  /**\n   * Finishes processing and commits or rolls back. Will throw the exception that was the cause of\n   * an abort\n   *\n   * @throws Exception if that was the cause of an abort\n   */\n  void finish() throws Exception;\n\n  /**\n   * Close connections that have been provided upon initialization.\n   *\n   * <p>This method is called automatically when using try-with-resources. It is safe to call this\n   * method multiple times.\n   *\n   * @throws Exception if an error occurs while closing resources\n   */\n  @Override\n  void close() throws Exception;\n\n  // Raised for data conversion errors, if requested\n  class DataError extends RuntimeException {\n    private static final long serialVersionUID = 1L;\n\n    public DataError(String msg) {\n      super(msg);\n    }\n\n    public DataError(String msg, Throwable ex) {\n      super(msg, ex);\n    }\n\n    public DataError(Throwable ex) {\n      super(ex);\n    }\n  }\n\n  // Raised for connection and other system errors.\n  class ConnectionError extends RuntimeException {\n    private static final long serialVersionUID = 1L;\n\n    public ConnectionError(String msg) {\n      super(msg);\n    }\n\n    // SNOW-22336: pass cause to connector\n    public ConnectionError(String msg, Throwable ex) {\n      super(msg, ex);\n    }\n\n    public ConnectionError(Throwable ex) {\n      super(ex);\n    }\n  }\n\n  // get the listener instance used by this loader instance\n  LoadResultListener getListener();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/loader/LoaderFactory.java",
    "content": "package net.snowflake.client.api.loader;\n\nimport java.sql.Connection;\nimport java.util.Map;\nimport net.snowflake.client.internal.loader.StreamLoader;\n\npublic class LoaderFactory {\n\n  public static Loader createLoader(\n      Map<LoaderProperty, Object> properties,\n      Connection uploadConnection,\n      Connection processingConnection) {\n    return new StreamLoader(properties, uploadConnection, processingConnection);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/loader/LoaderProperty.java",
    "content": "package net.snowflake.client.api.loader;\n\n/** Configuration parameters for Loader */\npublic enum LoaderProperty {\n  tableName, // Target table name                                          String\n  schemaName, // Target table schema                                        String\n  databaseName, // Target table database                                      String\n  remoteStage, // Stage to use - \"~\" is default                              String\n  columns, // List of columns that will be uploaded                      List<String>\n  keys, // List of columns used as keys for updating                  List<String>\n  operation, // UPDATE, DELETE, MODIFY, UPSERT                             Enum Operation\n  startTransaction, // start transaction for the operation                        Boolean\n  oneBatch, // process all data in one batch                              Boolean\n  truncateTable, // delete all data from the table prior to run                Boolean\n  executeBefore, // SQL statement to execute before run                        String\n  executeAfter, // SQL statement to execute after run                         String\n  isFirstStartCall, // skip deleting data. Used in multiple calls of loader.start Boolean\n  isLastFinishCall, // skip commit. Used in multiple calls of loader.finish       Boolean\n  batchRowSize, // Batch row size. Flush queues when it reaches this          Long\n  onError, // on_error option                                            String\n  csvFileBucketSize, // File bucket size. 64 by default.                           Long\n  csvFileSize, // File size. 50MB by default.                                Long\n  preserveStageFile, // Preserve stage files if error occurs                       Boolean\n  useLocalTimezone, // Use local timezone in converting TIMESTAMP                 Boolean\n  compressFileByPut, // Compress file by PUT. false by default                     Boolean\n  compressDataBeforePut, // Compress data before PUT. true by default              Boolean\n  compressLevel, // Compress level: 1 (Speed) to 9 (Compression) for\n  // compressDataBeforePut option. No impact to\n  // compressFileByPut.  1 by default.                          Long\n\n  // compatibility parameters\n  mapTimeToTimestamp, // map TIME data type to TIMESTAMP. Informatica v1\n  // connector behavior.                                       Boolean\n\n  // deprecated. to be removed.\n  copyEmptyFieldAsEmpty, // EMPTY_FIELD_AS_NULL = true by default                  Boolean\n\n  // test parameters\n  testRemoteBadCSV // TEST: Inject bad CSV in the remote stage                   Boolean\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/loader/LoadingError.java",
    "content": "package net.snowflake.client.api.loader;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.internal.loader.BufferStage;\nimport net.snowflake.client.internal.loader.StreamLoader;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Wrapper for data format errors returned by the COPY/validate command.\n *\n * <p>This class encapsulates error information from failed data loading operations. It provides\n * details about what went wrong, where in the file the error occurred, and the rejected record\n * data.\n *\n * <h2 id=\"usage-example\">Usage Example</h2>\n *\n * <pre>{@code\n * LoadResultListener listener = new LoadResultListener() {\n *   public void addError(LoadingError error) {\n *     System.err.println(\"Error in file: \" + error.getFile());\n *     System.err.println(\"Line: \" + error.getProperty(ErrorProperty.LINE));\n *     System.err.println(\"Error: \" + error.getProperty(ErrorProperty.ERROR));\n *   }\n *   // ... implement other methods\n * };\n * }</pre>\n *\n * @see LoadResultListener\n * @see Loader\n */\npublic class LoadingError {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(LoadingError.class);\n\n  /**\n   * Properties that can be associated with a loading error.\n   *\n   * <p>These correspond to columns in the Snowflake COPY command validation results and provide\n   * detailed information about what went wrong during data loading.\n   */\n  public enum ErrorProperty {\n    /** The error message describing what went wrong */\n    ERROR,\n    /** The line number in the source file where the error occurred */\n    LINE,\n    /** The character position where the error occurred */\n    CHARACTER,\n    /** The byte offset in the file where the error occurred */\n    BYTE_OFFSET,\n    /** The error category (e.g., \"parsing\", \"data type mismatch\") */\n    CATEGORY,\n    /** The numeric error code */\n    CODE,\n    /** The SQL state code */\n    SQL_STATE,\n    /** The name of the column where the error occurred */\n    COLUMN_NAME,\n    /** The row number in the result set */\n    ROW_NUMBER,\n    /** The starting line number of the row */\n    ROW_START_LINE,\n    /** The rejected record data */\n    REJECTED_RECORD\n  }\n\n  private String _stage;\n\n  private String _prefix;\n\n  private String _file;\n\n  private final String _target;\n\n  private final Map<ErrorProperty, String> _properties = new HashMap<ErrorProperty, String>();\n\n  public static String UNKNOWN = \"unknown\";\n\n  /**\n   * Construct error from validation output\n   *\n   * @param rs result set\n   * @param bs buffer stage\n   * @param loader stream loader\n   */\n  public LoadingError(ResultSet rs, BufferStage bs, StreamLoader loader) {\n    _stage = bs.getRemoteLocation();\n\n    try {\n      String ffile = rs.getString(\"FILE\");\n      _file = ffile.substring(ffile.lastIndexOf(\"/\"));\n      _prefix = ffile.substring(0, ffile.lastIndexOf(\"/\"));\n    } catch (SQLException ex) {\n      _file = UNKNOWN;\n    }\n\n    _target = loader.getTable();\n\n    for (ErrorProperty p : ErrorProperty.values()) {\n      try {\n        _properties.put(p, rs.getString(p.name()));\n      } catch (SQLException ex) {\n        logger.error(\"Exception\", ex);\n      }\n    }\n  }\n\n  /**\n   * Gets the stage name where the error occurred.\n   *\n   * @return the stage name\n   */\n  public String getStage() {\n    return _stage;\n  }\n\n  /**\n   * Gets the file prefix within the stage.\n   *\n   * @return the file prefix path\n   */\n  public String getPrefix() {\n    return _prefix;\n  }\n\n  /**\n   * Gets the file name where the error occurred.\n   *\n   * @return the file name\n   */\n  public String getFile() {\n    return _file;\n  }\n\n  /**\n   * Gets the target table name.\n   *\n   * @return the target table name\n   */\n  public String getTarget() {\n    return _target;\n  }\n\n  /**\n   * Gets the value of a specific error property.\n   *\n   * @param p the error property to retrieve\n   * @return the value of the error property, or null if not set\n   */\n  public String getProperty(ErrorProperty p) {\n    return this._properties.get(p);\n  }\n\n  /**\n   * Sets the value of a specific error property.\n   *\n   * @param p the error property to set\n   * @param value the value to assign to the property\n   */\n  public void setProperty(ErrorProperty p, String value) {\n    this._properties.put(p, value);\n  }\n\n  public String toString() {\n    StringBuilder sb = new StringBuilder();\n\n    sb.append(\"{\");\n    String prefix = \"\";\n    for (ErrorProperty p : ErrorProperty.values()) {\n      sb.append(prefix);\n      sb.append(\"\\\"\").append(p.name()).append(\"\\\": \");\n      sb.append(\"\\\"\");\n      String value = String.valueOf(_properties.get(p));\n      sb.append(value.replaceAll(\"[\\\\s]+\", \" \").replace(\"\\\"\", \"\\\\\\\"\"));\n      sb.append(\"\\\"\");\n      prefix = \",\";\n    }\n    sb.append(\"}\");\n\n    return sb.toString();\n  }\n\n  /**\n   * Converts this loading error into a DataError exception.\n   *\n   * @return a DataError exception containing this error's details\n   */\n  public Loader.DataError getException() {\n    return new Loader.DataError(this.toString());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/loader/Operation.java",
    "content": "package net.snowflake.client.api.loader;\n\n/**\n * Operations supported by the Snowflake Loader API.\n *\n * <p>These operations define how data is loaded into the target table. The operation type is\n * specified via the {@link LoaderProperty#operation} property.\n *\n * @see Loader\n * @see LoaderFactory\n * @see LoaderProperty#operation\n */\npublic enum Operation {\n  /** Insert new rows into the target table */\n  INSERT,\n\n  /** Delete rows from the target table based on keys */\n  DELETE,\n\n  /** Modify existing rows in the target table */\n  MODIFY,\n\n  /** Insert new rows or update existing rows (update-or-insert) */\n  UPSERT\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/pooling/SnowflakeConnectionPoolDataSource.java",
    "content": "package net.snowflake.client.api.pooling;\n\nimport javax.sql.ConnectionPoolDataSource;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\n\n/**\n * SnowflakeConnectionPoolDataSource is the interface for a connection pool data source. Its\n * implementation is instantiated by {@link SnowflakeConnectionPoolDataSourceFactory}.\n */\npublic interface SnowflakeConnectionPoolDataSource\n    extends ConnectionPoolDataSource, SnowflakeDataSource {}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/pooling/SnowflakeConnectionPoolDataSourceFactory.java",
    "content": "package net.snowflake.client.api.pooling;\n\nimport net.snowflake.client.internal.api.implementation.pooling.SnowflakeConnectionPoolDataSourceImpl;\n\n/**\n * Factory for creating {@link SnowflakeConnectionPoolDataSourceImpl} instances.\n *\n * <p>This factory provides methods to create different types of Snowflake Connection Pool Data\n * Source implementations. Use this factory instead of directly instantiating Connection Pool Data\n * Source classes.\n *\n * <p><b>Example usage:</b>\n *\n * <pre>{@code\n * SnowflakeConnectionPoolDataSource ds = SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n * ds.setAccount(\"myaccount\");\n * ds.setUser(\"myuser\");\n * ds.setPassword(\"mypassword\");\n * ds.setDatabase(\"mydb\");\n * ds.setSchema(\"myschema\");\n * ds.setWarehouse(\"mywh\");\n *\n * try (Connection conn = ds.getConnection()) {\n *   // use connection\n * }\n * }</pre>\n *\n * @see SnowflakeConnectionPoolDataSourceImpl\n */\npublic class SnowflakeConnectionPoolDataSourceFactory {\n\n  private SnowflakeConnectionPoolDataSourceFactory() {\n    throw new AssertionError(\"SnowflakeConnectionPoolDataSourceFactory cannot be instantiated\");\n  }\n\n  /**\n   * Creates a new SnowflakeConnectionPoolDataSource instance.\n   *\n   * @return a new SnowflakeConnectionPoolDataSource instance\n   */\n  public static SnowflakeConnectionPoolDataSource createConnectionPoolDataSource() {\n    return new SnowflakeConnectionPoolDataSourceImpl();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/FieldMetadata.java",
    "content": "package net.snowflake.client.api.resultset;\n\nimport java.util.List;\n\n/**\n * Metadata describing a field in a structured type (OBJECT, ARRAY, MAP). This interface provides\n * read-only access to field information including name, type, precision, scale, and nested fields.\n */\npublic interface FieldMetadata {\n\n  /**\n   * Gets the name of the field.\n   *\n   * @return the field name\n   */\n  String getName();\n\n  /**\n   * Gets the type name of the field.\n   *\n   * @return the type name\n   */\n  String getTypeName();\n\n  /**\n   * Gets the SQL type code of the field.\n   *\n   * @return the SQL type code as defined in {@link java.sql.Types}\n   */\n  int getType();\n\n  /**\n   * Checks if the field is nullable.\n   *\n   * @return true if the field can contain null values, false otherwise\n   */\n  boolean isNullable();\n\n  /**\n   * Gets the byte length of the field.\n   *\n   * @return the byte length\n   */\n  int getByteLength();\n\n  /**\n   * Gets the precision of the field.\n   *\n   * @return the precision\n   */\n  int getPrecision();\n\n  /**\n   * Gets the scale of the field.\n   *\n   * @return the scale\n   */\n  int getScale();\n\n  /**\n   * Checks if the field has a fixed size.\n   *\n   * @return true if the field has a fixed size, false otherwise\n   */\n  boolean isFixed();\n\n  /**\n   * Gets the base Snowflake type of the field.\n   *\n   * @return the base {@link SnowflakeType}\n   */\n  SnowflakeType getBase();\n\n  /**\n   * Gets the nested field metadata for structured types (OBJECT, ARRAY, MAP).\n   *\n   * @return list of nested field metadata, or empty list if no nested fields\n   */\n  List<FieldMetadata> getFields();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/QueryStatus.java",
    "content": "package net.snowflake.client.api.resultset;\n\nimport java.util.Arrays;\n\n/**\n * Represents detailed status information for a query execution.\n *\n * <p>This class provides comprehensive metadata about a query's execution, including timing\n * information, warehouse details, and error information if applicable. Use this class to monitor\n * query progress and diagnose execution issues.\n *\n * <h2 id=\"usage-example\">Usage Example</h2>\n *\n * <pre>{@code\n * String queryId = statement.unwrap(SnowflakeStatement.class).getQueryID();\n * QueryStatus status = connection.unwrap(SnowflakeConnection.class)\n *     .getQueryStatus(queryId);\n *\n * System.out.println(\"Query Status: \" + status.getStatus());\n * System.out.println(\"Duration: \" + status.getTotalDuration() + \"ms\");\n * if (status.getErrorCode() != 0) {\n *     System.err.println(\"Error: \" + status.getErrorMessage());\n * }\n * }</pre>\n */\npublic final class QueryStatus {\n\n  public enum Status {\n    RUNNING(0, \"RUNNING\"),\n    ABORTING(1, \"ABORTING\"),\n    SUCCESS(2, \"SUCCESS\"),\n    FAILED_WITH_ERROR(3, \"FAILED_WITH_ERROR\"),\n    ABORTED(4, \"ABORTED\"),\n    QUEUED(5, \"QUEUED\"),\n    FAILED_WITH_INCIDENT(6, \"FAILED_WITH_INCIDENT\"),\n    DISCONNECTED(7, \"DISCONNECTED\"),\n    RESUMING_WAREHOUSE(8, \"RESUMING_WAREHOUSE\"),\n    QUEUED_REPAIRING_WAREHOUSE(9, \"QUEUED_REPARING_WAREHOUSE\"),\n    RESTARTED(10, \"RESTARTED\"),\n    BLOCKED(11, \"BLOCKED\"),\n    NO_DATA(12, \"NO_DATA\");\n\n    private final int value;\n    private final String description;\n\n    Status(int value, String description) {\n      this.value = value;\n      this.description = description;\n    }\n\n    public int getValue() {\n      return this.value;\n    }\n\n    public String getDescription() {\n      return this.description;\n    }\n\n    private static boolean isStillRunning(Status status) {\n      switch (status) {\n        case RUNNING:\n        case QUEUED:\n        case RESUMING_WAREHOUSE:\n        case QUEUED_REPAIRING_WAREHOUSE:\n        case BLOCKED:\n        case NO_DATA:\n          return true;\n        default:\n          return false;\n      }\n    }\n\n    private static boolean isAnError(Status status) {\n      switch (status) {\n        case ABORTING:\n        case FAILED_WITH_ERROR:\n        case ABORTED:\n        case FAILED_WITH_INCIDENT:\n        case DISCONNECTED:\n        case BLOCKED:\n          return true;\n        default:\n          return false;\n      }\n    }\n\n    private static Status getStatusFromString(String description) {\n      if (description != null) {\n        return Arrays.stream(Status.values())\n            .filter(st -> description.equalsIgnoreCase(st.getDescription()))\n            .findFirst()\n            .orElse(Status.NO_DATA);\n      }\n      // Is it correct? I think we should never reach this point, but maybe we should return NO_DATA\n      // instead?\n      return null;\n    }\n  }\n\n  private final long endTime;\n  private final int errorCode;\n  private final String errorMessage;\n  private final String id;\n  private final String name;\n  private final long sessionId;\n  private final String sqlText;\n  private final long startTime;\n  private final String state;\n  private final Status status;\n  private final int totalDuration;\n  private final String warehouseExternalSize;\n  private final int warehouseId;\n  private final String warehouseName;\n  private final String warehouseServerType;\n\n  /**\n   * Constructs a QueryStatus object with detailed query execution information.\n   *\n   * @param endTime the end time of the query in milliseconds since epoch\n   * @param errorCode the error code if query failed, 0 otherwise\n   * @param errorMessage the error message if query failed, empty otherwise\n   * @param id the unique query ID\n   * @param name the query status name\n   * @param sessionId the session ID that executed the query\n   * @param sqlText the SQL text of the query\n   * @param startTime the start time of the query in milliseconds since epoch\n   * @param state the internal state of the query\n   * @param totalDuration the total duration of query execution in milliseconds\n   * @param warehouseExternalSize the external size of the warehouse (e.g., \"X-Small\")\n   * @param warehouseId the warehouse ID\n   * @param warehouseName the warehouse name\n   * @param warehouseServerType the warehouse server type\n   */\n  public QueryStatus(\n      long endTime,\n      int errorCode,\n      String errorMessage,\n      String id,\n      String name,\n      long sessionId,\n      String sqlText,\n      long startTime,\n      String state,\n      int totalDuration,\n      String warehouseExternalSize,\n      int warehouseId,\n      String warehouseName,\n      String warehouseServerType) {\n    this.endTime = endTime;\n    this.errorCode = errorCode;\n    this.errorMessage = errorMessage;\n    this.id = id;\n    this.name = name;\n    this.sessionId = sessionId;\n    this.sqlText = sqlText;\n    this.startTime = startTime;\n    this.state = state;\n    this.status = Status.getStatusFromString(name);\n    this.totalDuration = totalDuration;\n    this.warehouseExternalSize = warehouseExternalSize;\n    this.warehouseId = warehouseId;\n    this.warehouseName = warehouseName;\n    this.warehouseServerType = warehouseServerType;\n  }\n\n  /**\n   * Creates an empty QueryStatus instance with default values.\n   *\n   * @return an empty QueryStatus object\n   */\n  public static QueryStatus empty() {\n    return new QueryStatus(0, 0, \"\", \"\", \"\", 0, \"\", 0, \"\", 0, \"\", 0, \"\", \"\");\n  }\n\n  /**\n   * Checks if this query status is empty (no data).\n   *\n   * @return true if the status name is empty\n   */\n  public boolean isEmpty() {\n    return name.isEmpty();\n  }\n\n  /**\n   * Checks if the query is still running.\n   *\n   * @return true if the query is in a running state\n   */\n  public boolean isStillRunning() {\n    return Status.isStillRunning(status);\n  }\n\n  /**\n   * Checks if the query completed successfully.\n   *\n   * @return true if the query status is SUCCESS\n   */\n  public boolean isSuccess() {\n    return status == Status.SUCCESS;\n  }\n\n  /**\n   * Checks if the query encountered an error.\n   *\n   * @return true if the query is in an error state\n   */\n  public boolean isAnError() {\n    return Status.isAnError(status);\n  }\n\n  /**\n   * Gets the end time of query execution.\n   *\n   * @return the end time in milliseconds since epoch\n   */\n  public long getEndTime() {\n    return endTime;\n  }\n\n  /**\n   * Gets the error code if the query failed.\n   *\n   * @return the error code, or 0 if no error occurred\n   */\n  public int getErrorCode() {\n    return errorCode;\n  }\n\n  /**\n   * Gets the error message if the query failed.\n   *\n   * @return the error message, or empty string if no error occurred\n   */\n  public String getErrorMessage() {\n    return errorMessage;\n  }\n\n  /**\n   * Gets the unique query ID.\n   *\n   * @return the query ID\n   */\n  public String getId() {\n    return id;\n  }\n\n  /**\n   * Gets the query status name.\n   *\n   * @return the status name (e.g., \"RUNNING\", \"SUCCESS\")\n   */\n  public String getName() {\n    return name;\n  }\n\n  /**\n   * Gets the session ID that executed the query.\n   *\n   * @return the session ID\n   */\n  public long getSessionId() {\n    return sessionId;\n  }\n\n  /**\n   * Gets the SQL text of the query.\n   *\n   * @return the SQL query text\n   */\n  public String getSqlText() {\n    return sqlText;\n  }\n\n  /**\n   * Gets the start time of query execution.\n   *\n   * @return the start time in milliseconds since epoch\n   */\n  public long getStartTime() {\n    return startTime;\n  }\n\n  /**\n   * Gets the internal state of the query.\n   *\n   * @return the internal query state\n   */\n  public String getState() {\n    return state;\n  }\n\n  /**\n   * Gets the total duration of query execution.\n   *\n   * @return the total duration in milliseconds\n   */\n  public int getTotalDuration() {\n    return totalDuration;\n  }\n\n  /**\n   * Gets the external size of the warehouse that executed the query.\n   *\n   * @return the warehouse size (e.g., \"X-Small\", \"Small\", \"Medium\")\n   */\n  public String getWarehouseExternalSize() {\n    return warehouseExternalSize;\n  }\n\n  /**\n   * Gets the warehouse ID that executed the query.\n   *\n   * @return the warehouse ID\n   */\n  public int getWarehouseId() {\n    return warehouseId;\n  }\n\n  /**\n   * Gets the warehouse name that executed the query.\n   *\n   * @return the warehouse name\n   */\n  public String getWarehouseName() {\n    return warehouseName;\n  }\n\n  /**\n   * Gets the warehouse server type that executed the query.\n   *\n   * @return the warehouse server type\n   */\n  public String getWarehouseServerType() {\n    return warehouseServerType;\n  }\n\n  /**\n   * Gets the status description.\n   *\n   * @return the status name\n   */\n  public String getDescription() {\n    return name;\n  }\n\n  /**\n   * Gets the query status enum value.\n   *\n   * @return the Status enum representing the current state\n   */\n  public Status getStatus() {\n    return status;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/SnowflakeAsyncResultSet.java",
    "content": "package net.snowflake.client.api.resultset;\n\nimport java.sql.SQLException;\n\n/** This interface defines Snowflake specific APIs for asynchronous ResultSet */\npublic interface SnowflakeAsyncResultSet extends SnowflakeResultSet {\n  /**\n   * This function retrieves the status of an asynchronous query. An empty ResultSet object has\n   * already been returned, but the query may still be running. This function can be used to query\n   * whether it is possible to retrieve results from the ResultSet already.\n   *\n   * <p><code>status.isSuccess()</code> means that results can be retrieved.\n   *\n   * @return an instance containing query metadata\n   * @throws SQLException if an error is encountered\n   */\n  QueryStatus getStatus() throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/SnowflakeResultSet.java",
    "content": "package net.snowflake.client.api.resultset;\n\nimport java.sql.SQLException;\nimport java.util.List;\nimport java.util.Map;\n\n/** This interface defines Snowflake specific APIs for ResultSet */\npublic interface SnowflakeResultSet {\n  /**\n   * @return the Snowflake query ID of the query which generated this result set\n   * @throws SQLException if an error is encountered\n   */\n  String getQueryID() throws SQLException;\n\n  /**\n   * Get a list of ResultSetSerializables for the ResultSet in order to parallel processing\n   *\n   * @param maxSizeInBytes The expected max data size wrapped in the ResultSetSerializables object.\n   *     NOTE: this parameter is intended to make the data size in each serializable object to be\n   *     less than it. But if user specifies a small value which may be smaller than the data size\n   *     of one result chunk. So the definition can't be guaranteed completely. For this special\n   *     case, one serializable object is used to wrap the data chunk.\n   * @return a list of ResultSetSerializables\n   * @throws SQLException if fails to get the ResultSetSerializable objects.\n   */\n  List<SnowflakeResultSetSerializable> getResultSetSerializables(long maxSizeInBytes)\n      throws SQLException;\n\n  /**\n   * Get an array of elements from a structured type (ARRAY) column.\n   *\n   * <p>This method is used to retrieve array elements with proper type conversion for Snowflake\n   * structured types.\n   *\n   * @param <T> the type of array elements\n   * @param columnIndex the column index (1-based)\n   * @param type the class of array elements\n   * @return an array of elements, or null if the value was SQL NULL\n   * @throws SQLException if the column is not a structured type or conversion fails\n   */\n  <T> T[] getArray(int columnIndex, Class<T> type) throws SQLException;\n\n  /**\n   * Get a list of elements from a structured type (ARRAY) column.\n   *\n   * <p>This method is used to retrieve array elements as a List with proper type conversion for\n   * Snowflake structured types.\n   *\n   * @param <T> the type of list elements\n   * @param columnIndex the column index (1-based)\n   * @param type the class of list elements\n   * @return a List of elements, or null if the value was SQL NULL\n   * @throws SQLException if the column is not a structured type or conversion fails\n   */\n  <T> List<T> getList(int columnIndex, Class<T> type) throws SQLException;\n\n  /**\n   * Get a map of key-value pairs from a structured type (MAP or OBJECT) column.\n   *\n   * <p>This method is used to retrieve map entries with proper type conversion for Snowflake\n   * structured types.\n   *\n   * @param <T> the type of map values\n   * @param columnIndex the column index (1-based)\n   * @param type the class of map values\n   * @return a Map of String keys to typed values, or null if the value was SQL NULL\n   * @throws SQLException if the column is not a structured type or conversion fails\n   */\n  <T> Map<String, T> getMap(int columnIndex, Class<T> type) throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/SnowflakeResultSetMetaData.java",
    "content": "package net.snowflake.client.api.resultset;\n\nimport java.sql.SQLException;\nimport java.util.List;\n\npublic interface SnowflakeResultSetMetaData {\n  String getQueryID() throws SQLException;\n\n  List<String> getColumnNames() throws SQLException;\n\n  int getColumnIndex(String columnName) throws SQLException;\n\n  int getInternalColumnType(int column) throws SQLException;\n\n  List<FieldMetadata> getColumnFields(int column) throws SQLException;\n\n  /**\n   * Get vector dimension\n   *\n   * @param column column index\n   * @return vector dimension when the column is vector type or 0 when it is not vector type\n   * @throws SQLException when cannot get column dimension\n   */\n  int getVectorDimension(int column) throws SQLException;\n\n  /**\n   * Get vector dimension\n   *\n   * @param columnName column name\n   * @return vector dimension when the column is vector type or 0 when it is not vector type\n   * @throws SQLException when cannot get column dimension\n   */\n  int getVectorDimension(String columnName) throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/SnowflakeResultSetSerializable.java",
    "content": "package net.snowflake.client.api.resultset;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Properties;\n\n/**\n * This interface defines Snowflake specific APIs to access the data wrapped in the result set\n * serializable object.\n */\npublic interface SnowflakeResultSetSerializable {\n  // This wraps the required info for retrieving ResultSet\n  class ResultSetRetrieveConfig {\n    private Properties proxyProperties;\n    private String sfFullURL;\n\n    public ResultSetRetrieveConfig(Builder builder) {\n      this.proxyProperties = builder.proxyProperties;\n      this.sfFullURL = builder.sfFullURL;\n    }\n\n    public Properties getProxyProperties() {\n      return proxyProperties;\n    }\n\n    public String getSfFullURL() {\n      return sfFullURL;\n    }\n\n    // The inner builder class for ResultSetRetrieveConfig\n    public static class Builder {\n      private Builder() {}\n\n      private Properties proxyProperties = null;\n      private String sfFullURL = null;\n\n      public static Builder newInstance() {\n        return new Builder();\n      }\n\n      public ResultSetRetrieveConfig build() throws IllegalArgumentException {\n        // The SFURL must include protocol like https or http\n        if (sfFullURL == null || !sfFullURL.toLowerCase().startsWith(\"http\")) {\n          throw new IllegalArgumentException(\n              \"The SF URL must include protocol. The invalid is: \" + sfFullURL);\n        }\n\n        return new ResultSetRetrieveConfig(this);\n      }\n\n      public Builder setProxyProperties(Properties proxyProperties) {\n        this.proxyProperties = proxyProperties;\n        return this;\n      }\n\n      public Builder setSfFullURL(String sfFullURL) {\n        this.sfFullURL = sfFullURL;\n        return this;\n      }\n    }\n  }\n\n  /**\n   * Get ResultSet from the ResultSet Serializable object so that the user can access the data.\n   *\n   * @param resultSetRetrieveConfig The extra info to retrieve the result set.\n   * @return a ResultSet which represents for the data wrapped in the object\n   * @throws SQLException if an error occurs\n   */\n  ResultSet getResultSet(ResultSetRetrieveConfig resultSetRetrieveConfig) throws SQLException;\n\n  /**\n   * Retrieve total row count included in the ResultSet Serializable object.\n   *\n   * @return the total row count from metadata\n   * @throws SQLException if an error occurs\n   */\n  long getRowCount() throws SQLException;\n\n  /**\n   * Retrieve compressed data size included in the ResultSet Serializable object.\n   *\n   * @return the total compressed data size in bytes from metadata\n   * @throws SQLException if an error occurs\n   */\n  long getCompressedDataSizeInBytes() throws SQLException;\n\n  /**\n   * Retrieve uncompressed data size included in the ResultSet Serializable object.\n   *\n   * @return the total uncompressed data size in bytes from metadata\n   * @throws SQLException if an error occurs\n   */\n  long getUncompressedDataSizeInBytes() throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/resultset/SnowflakeType.java",
    "content": "package net.snowflake.client.api.resultset;\n\n/**\n * Enumeration of Snowflake data types.\n *\n * <p>This enum represents the various data types supported by Snowflake. These type values are used\n * in metadata operations such as {@link FieldMetadata#getBase()} to describe the underlying type of\n * a field in structured types (OBJECT, ARRAY, MAP).\n *\n * <h2 id=\"usage-example\">Usage Example</h2>\n *\n * <pre>{@code\n * import net.snowflake.client.api.resultset.FieldMetadata;\n * import net.snowflake.client.api.resultset.SnowflakeType;\n *\n * // Get field metadata from a structured type column\n * FieldMetadata field = resultSetMetaData.getColumnFields(1).get(0);\n * SnowflakeType baseType = field.getBase();\n *\n * if (baseType == SnowflakeType.TEXT) {\n *     System.out.println(\"Field is a text type\");\n * } else if (baseType == SnowflakeType.INTEGER) {\n *     System.out.println(\"Field is an integer type\");\n * }\n * }</pre>\n *\n * Additionally, this enum includes JDBC type code extensions for Snowflake-specific data types not\n * covered by {@link java.sql.Types}.\n *\n * <p>These constants can be used with {@link java.sql.PreparedStatement#setObject(int, Object,\n * int)} to specify the target SQL type when binding parameters to Snowflake-specific types. *\n *\n * <p>Example usage:\n *\n * <pre>{@code\n * PreparedStatement pstmt = connection.prepareStatement(\"INSERT INTO t VALUES (?)\");\n * pstmt.setObject(1, myTimestamp, SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ);\n * }</pre>\n *\n * @since 4.0.0\n * @see FieldMetadata\n */\npublic enum SnowflakeType {\n  /** Represents an ANY type (unspecified/dynamic) */\n  ANY,\n  /** Represents an ARRAY type */\n  ARRAY,\n  /** Represents a BINARY type */\n  BINARY,\n  /** Represents a BOOLEAN type */\n  BOOLEAN,\n  /** Represents a CHAR type */\n  CHAR,\n  /** Represents a DATE type */\n  DATE,\n  /** Represents a DECFLOAT (decimal floating-point) type */\n  DECFLOAT,\n  /** Represents a FIXED-point numeric type (NUMBER with scale) */\n  FIXED,\n  /** Represents an INTEGER type */\n  INTEGER,\n  /** Represents an OBJECT type (structured) */\n  OBJECT,\n  /** Represents a MAP type */\n  MAP,\n  /** Represents a REAL (floating-point) type */\n  REAL,\n  /** Represents a TEXT/VARCHAR type */\n  TEXT,\n  /** Represents a TIME type */\n  TIME,\n  /** Represents a TIMESTAMP type (no timezone) */\n  TIMESTAMP,\n  /** Represents a TIMESTAMP with local timezone */\n  TIMESTAMP_LTZ,\n  /** Represents a TIMESTAMP with no timezone */\n  TIMESTAMP_NTZ,\n  /** Represents a TIMESTAMP with timezone */\n  TIMESTAMP_TZ,\n  /** Represents an INTERVAL YEAR TO MONTH type */\n  INTERVAL_YEAR_MONTH,\n  /** Represents an INTERVAL DAY TO TIME type */\n  INTERVAL_DAY_TIME,\n  /** Represents a VARIANT (semi-structured) type */\n  VARIANT,\n  /** Represents a GEOGRAPHY type */\n  GEOGRAPHY,\n  /** Represents a GEOMETRY type */\n  GEOMETRY,\n  /** Represents a VECTOR type */\n  VECTOR;\n\n  public static final int EXTRA_TYPES_TIMESTAMP_LTZ = 50000;\n  public static final int EXTRA_TYPES_TIMESTAMP_TZ = 50001;\n  public static final int EXTRA_TYPES_TIMESTAMP_NTZ = 50002;\n  public static final int EXTRA_TYPES_VECTOR = 50003;\n  public static final int EXTRA_TYPES_DECFLOAT = 50004;\n  public static final int EXTRA_TYPES_YEAR_MONTH_INTERVAL = 50005;\n  public static final int EXTRA_TYPES_DAY_TIME_INTERVAL = 50006;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/statement/SnowflakePreparedStatement.java",
    "content": "package net.snowflake.client.api.statement;\n\nimport java.math.BigInteger;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Map;\n\npublic interface SnowflakePreparedStatement {\n  /**\n   * @return the Snowflake query ID of the latest executed query\n   * @throws SQLException if an error occurs\n   */\n  String getQueryID() throws SQLException;\n\n  /**\n   * Execute a query asynchronously\n   *\n   * @return ResultSet containing results\n   * @throws SQLException if an error occurs\n   */\n  ResultSet executeAsyncQuery() throws SQLException;\n\n  /**\n   * Sets the designated parameter to the given BigInteger value.\n   *\n   * @param parameterIndex the parameter index\n   * @param x the BigInteger value\n   * @throws SQLException if an error occurs\n   */\n  void setBigInteger(int parameterIndex, BigInteger x) throws SQLException;\n\n  /**\n   * Sets the designated parameter to the given Map instance.\n   *\n   * @param parameterIndex the parameter index\n   * @param map the map instance\n   * @param type the type\n   * @param <T> generic type\n   * @throws SQLException if an error occurs\n   */\n  <T> void setMap(int parameterIndex, Map<String, T> map, int type) throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/api/statement/SnowflakeStatement.java",
    "content": "package net.snowflake.client.api.statement;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.List;\n\n/** This interface defines Snowflake specific APIs for Statement */\npublic interface SnowflakeStatement {\n  /**\n   * @return the Snowflake query ID of the latest executed query (even failed one) or null when the\n   *     last query ID is not available\n   * @throws SQLException if an error is encountered\n   */\n  String getQueryID() throws SQLException;\n\n  /**\n   * @return the Snowflake query IDs of the latest executed batch queries\n   * @throws SQLException if an error is encountered\n   */\n  List<String> getBatchQueryIDs() throws SQLException;\n\n  /**\n   * Set statement level parameter\n   *\n   * @param name parameter name\n   * @param value parameter value\n   * @throws SQLException if an error is encountered\n   */\n  void setParameter(String name, Object value) throws SQLException;\n\n  /**\n   * Set batch ID\n   *\n   * @param batchID the batch ID\n   */\n  void setBatchID(String batchID);\n\n  /**\n   * Execute SQL query asynchronously\n   *\n   * @param sql sql statement\n   * @return ResultSet\n   * @throws SQLException if @link{#executeQueryInternal(String, Map)} throws an exception\n   */\n  // Should we return AsyncResultSet here? It would have to extend ResultSet\n  ResultSet executeAsyncQuery(String sql) throws SQLException;\n\n  /**\n   * Sets the query timeout when running an async query.\n   *\n   * @param seconds The number of seconds until timeout.\n   * @throws SQLException if an error is encountered\n   */\n  void setAsyncQueryTimeout(int seconds) throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/connection/SnowflakeConnectionImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.connection;\n\nimport static net.snowflake.client.api.exception.ErrorCode.FEATURE_UNSUPPORTED;\nimport static net.snowflake.client.api.exception.ErrorCode.INVALID_CONNECT_STRING;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.recordIfExternal;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.CallableStatement;\nimport java.sql.ClientInfoStatus;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.DriverPropertyInfo;\nimport java.sql.JDBCType;\nimport java.sql.NClob;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLClientInfoException;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.SQLWarning;\nimport java.sql.SQLXML;\nimport java.sql.Savepoint;\nimport java.sql.Statement;\nimport java.sql.Struct;\nimport java.util.Collections;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.zip.GZIPInputStream;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.connection.UploadStreamConfig;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.internal.api.implementation.metadata.SnowflakeDatabaseMetaDataImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeCallableStatementImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SfSqlArray;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.DefaultSFConnectionHandler;\nimport net.snowflake.client.internal.jdbc.SFBaseFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SFConnectionHandler;\nimport net.snowflake.client.internal.jdbc.SnowflakeClob;\nimport net.snowflake.client.internal.jdbc.SnowflakeConnectString;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.log.SFLoggerUtil;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.common.core.SqlState;\n\n/** Snowflake connection implementation */\npublic class SnowflakeConnectionImpl implements Connection, SnowflakeConnection {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeConnectionImpl.class);\n\n  static {\n    SFLoggerUtil.initializeSnowflakeLogger();\n  }\n\n  /** Refer to all created and open statements from this connection */\n  private final Set<Statement> openStatements = ConcurrentHashMap.newKeySet();\n\n  // Injected delay for the purpose of connection timeout testing\n  // Any statement execution will sleep for the specified number of milliseconds\n  private final AtomicInteger _injectedDelay = new AtomicInteger(0);\n  private boolean isClosed;\n  private SQLWarning sqlWarnings = null;\n  private List<DriverPropertyInfo> missingProperties = null;\n\n  /**\n   * Amount of milliseconds a user is willing to tolerate for network related issues (e.g. HTTP\n   * 503/504) or database transient issues (e.g. GS not responding)\n   *\n   * <p>A value of 0 means no timeout\n   *\n   * <p>Default: 300 seconds\n   */\n  private int networkTimeoutInMilli = 0; // in milliseconds\n\n  /* this should be set to Connection.TRANSACTION_READ_COMMITTED\n   * There may not be many implications here since the call to\n   * setTransactionIsolation doesn't do anything.\n   */\n  private int transactionIsolation = Connection.TRANSACTION_NONE;\n  private SFBaseSession sfSession;\n\n  /** The SnowflakeConnectionImpl that provides the underlying physical-layer implementation */\n  private SFConnectionHandler sfConnectionHandler;\n\n  private boolean showStatementParameters;\n\n  private ObjectMapper objectMapper;\n\n  /**\n   * Instantiates a SnowflakeConnectionImpl with the passed-in SnowflakeConnectionImpl.\n   *\n   * @param sfConnectionHandler The SnowflakeConnectionImpl.\n   * @throws SQLException if failed to instantiate a SnowflakeConnectionImpl.\n   */\n  public SnowflakeConnectionImpl(SFConnectionHandler sfConnectionHandler) throws SQLException {\n    initConnectionWithImpl(sfConnectionHandler, null, null);\n  }\n\n  /**\n   * Instantiates a SnowflakeConnectionImpl with the passed-in SnowflakeConnectionImpl.\n   *\n   * @param sfConnectionHandler The SnowflakeConnectionImpl.\n   * @param url The URL string.\n   * @param info Connection properties.\n   * @throws SQLException if failed to instantiate connection.\n   */\n  public SnowflakeConnectionImpl(\n      SFConnectionHandler sfConnectionHandler, String url, Properties info) throws SQLException {\n    initConnectionWithImpl(sfConnectionHandler, url, info);\n  }\n\n  /**\n   * A connection will establish a session token from snowflake\n   *\n   * @param url server url used to create snowflake connection\n   * @param info properties about the snowflake connection\n   * @throws SQLException if failed to create a snowflake connection i.e. username or password not\n   *     specified\n   */\n  public SnowflakeConnectionImpl(String url, Properties info) throws SQLException {\n    SnowflakeConnectString conStr = SnowflakeConnectString.parse(url, info);\n    if (!conStr.isValid()) {\n      throw new SnowflakeSQLException(INVALID_CONNECT_STRING, url);\n    }\n\n    initConnectionWithImpl(new DefaultSFConnectionHandler(conStr), url, info);\n    appendWarnings(sfSession.getSqlWarnings());\n    isClosed = false;\n  }\n\n  public SnowflakeConnectionImpl(String url, Properties info, boolean fakeConnection)\n      throws SQLException {\n    SnowflakeConnectString conStr = SnowflakeConnectString.parse(url, info);\n    if (!conStr.isValid()) {\n      throw new SnowflakeSQLException(\n          SqlState.CONNECTION_EXCEPTION, INVALID_CONNECT_STRING.getMessageCode(), url);\n    }\n\n    initConnectionWithImpl(new DefaultSFConnectionHandler(conStr, true), url, info);\n    isClosed = false;\n  }\n\n  private void initConnectionWithImpl(\n      SFConnectionHandler sfConnectionHandler, String url, Properties info) throws SQLException {\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    logger.debug(\"Initializing new connection\");\n    this.sfConnectionHandler = sfConnectionHandler;\n    sfConnectionHandler.initializeConnection(url, info);\n    this.sfSession = sfConnectionHandler.getSFSession();\n    this.objectMapper = ObjectMapperFactory.getObjectMapperForSession(sfSession);\n    missingProperties = sfSession.checkProperties();\n    this.showStatementParameters = sfSession.getPreparedStatementLogging();\n    stopwatch.stop();\n    logger.debug(\n        \"Connection initialized successfully in {} ms. Session id: {}\",\n        stopwatch.elapsedMillis(),\n        sfSession.getSessionId());\n  }\n\n  public List<DriverPropertyInfo> returnMissingProperties() {\n    return missingProperties;\n  }\n\n  private void raiseSQLExceptionIfConnectionIsClosed() throws SQLException {\n    if (isClosed) {\n      throw new SnowflakeSQLException(ErrorCode.CONNECTION_CLOSED);\n    }\n  }\n\n  /**\n   * Execute a statement where the result isn't needed, and the statement is closed before this\n   * method returns\n   *\n   * @param stmtText text of the statement\n   * @throws SQLException exception thrown it the statement fails to execute\n   */\n  private void executeImmediate(String stmtText) throws SQLException {\n    // execute the statement and auto-close it as well\n    try (final Statement statement = this.createStatement()) {\n      statement.execute(stmtText);\n    }\n  }\n\n  /**\n   * Create a statement\n   *\n   * @return statement statement object\n   * @throws SQLException if failed to create a snowflake statement\n   */\n  @Override\n  public Statement createStatement() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement stmt = createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  /**\n   * Get an instance of a ResultSet object\n   *\n   * @param queryID the query ID\n   * @return ResultSet\n   * @throws SQLException if connection is closed\n   */\n  public ResultSet createResultSet(String queryID) throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    ResultSet rs = sfConnectionHandler.createResultSet(queryID, createStatement());\n    return rs;\n  }\n\n  /**\n   * Return an array of child query ID for the given query ID.\n   *\n   * <p>If the given query ID is for a multiple statements query, it returns an array of its child\n   * statements, otherwise, it returns an array to include the given query ID.\n   *\n   * @param queryID The given query ID\n   * @return An array of child query IDs\n   * @throws SQLException If the query is running or the corresponding query is FAILED.\n   */\n  @Override\n  public String[] getChildQueryIds(String queryID) throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    // execute the statement and auto-close it as well\n    try (final Statement statement = this.createStatement()) {\n      return statement.unwrap(SnowflakeStatementImpl.class).getChildQueryIds(queryID);\n    }\n  }\n\n  /**\n   * Close the connection\n   *\n   * @throws SQLException failed to close the connection\n   */\n  @Override\n  public void close() throws SQLException {\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    String sessionId = null;\n\n    if (sfSession != null) {\n      sessionId = sfSession.getSessionId();\n      logger.debug(\"Closing connection with session id: {}\", sessionId);\n    } else {\n      logger.debug(\"Closing connection without associated session\");\n    }\n\n    if (isClosed) {\n      logger.debug(\"Connection is already closed\");\n      // No exception is raised even if the connection is closed.\n      return;\n    }\n\n    isClosed = true;\n    try {\n      if (sfSession != null && sfSession.isSafeToClose()) {\n        sfSession.close(internalCallMarker());\n        sfSession = null;\n      }\n      // make sure to close all created statements\n      if (!openStatements.isEmpty()) {\n        logger.debug(\"Closing {} opened statements\", openStatements.size());\n      }\n      for (Statement stmt : openStatements) {\n        if (stmt != null && !stmt.isClosed()) {\n          if (stmt.isWrapperFor(SnowflakeStatementImpl.class)) {\n            stmt.unwrap(SnowflakeStatementImpl.class).close(false);\n          } else {\n            stmt.close();\n          }\n        }\n      }\n      if (!openStatements.isEmpty()) {\n        logger.debug(\"Statements closed successfully\");\n      }\n      openStatements.clear();\n\n    } catch (SFException ex) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession, ex.getSqlState(), ex.getVendorCode(), ex.getCause(), ex.getParams());\n    }\n    stopwatch.stop();\n    logger.debug(\n        \"Connection with session id: {} closed successfully in {} ms\",\n        sessionId,\n        stopwatch.elapsedMillis());\n  }\n\n  @Override\n  public String getSessionID() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getSessionId();\n  }\n\n  @Override\n  public QueryStatus getQueryStatus(String queryID) throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getQueryStatus(queryID);\n  }\n\n  @Override\n  public String getRole() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getRole();\n  }\n\n  @Override\n  public String getWarehouse() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getWarehouse();\n  }\n\n  @Override\n  public String getDatabase() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getDatabase();\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    logger.trace(\"boolean isClosed()\", false);\n\n    return isClosed;\n  }\n\n  /**\n   * Return the database metadata\n   *\n   * @return Database metadata\n   * @throws SQLException if any database error occurs\n   */\n  @Override\n  public DatabaseMetaData getMetaData() throws SQLException {\n    logger.trace(\"DatabaseMetaData getMetaData()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return new SnowflakeDatabaseMetaDataImpl(this);\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql) throws SQLException {\n    logger.trace(\"CallableStatement prepareCall(String sql)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    CallableStatement stmt = prepareCall(sql, false);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  public CallableStatement prepareCall(String sql, boolean skipParsing) throws SQLException {\n    logger.trace(\"CallableStatement prepareCall(String sql, boolean skipParsing)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    CallableStatement stmt =\n        new SnowflakeCallableStatementImpl(\n            this,\n            sql,\n            skipParsing,\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    logger.trace(\n        \"CallableStatement prepareCall(String sql,\" + \" int resultSetType,int resultSetConcurrency\",\n        false);\n    CallableStatement stmt =\n        prepareCall(sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public CallableStatement prepareCall(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    logger.trace(\"CallableStatement prepareCall(String sql, int \" + \"resultSetType,\", false);\n    CallableStatement stmt =\n        new SnowflakeCallableStatementImpl(\n            this, sql, false, resultSetType, resultSetConcurrency, resultSetHoldability);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public String nativeSQL(String sql) throws SQLException {\n    logger.trace(\"String nativeSQL(String sql)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sql;\n  }\n\n  @Override\n  public boolean getAutoCommit() throws SQLException {\n    logger.trace(\"boolean getAutoCommit()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getAutoCommit();\n  }\n\n  @Override\n  public void setAutoCommit(boolean isAutoCommit) throws SQLException {\n    logger.trace(\"void setAutoCommit(boolean isAutoCommit)\", false);\n    boolean currentAutoCommit = this.getAutoCommit();\n    if (isAutoCommit != currentAutoCommit) {\n      sfSession.setAutoCommit(isAutoCommit);\n      this.executeImmediate(\n          \"alter session /* JDBC:SnowflakeConnectionImpl.setAutoCommit*/ set autocommit=\"\n              + isAutoCommit);\n    }\n  }\n\n  @Override\n  public void commit() throws SQLException {\n    logger.trace(\"void commit()\", false);\n    this.executeImmediate(\"commit\");\n  }\n\n  @Override\n  public void rollback() throws SQLException {\n    logger.trace(\"void rollback()\", false);\n    this.executeImmediate(\"rollback\");\n  }\n\n  @Override\n  public void rollback(Savepoint savepoint) throws SQLException {\n    logger.trace(\"void rollback(Savepoint savepoint)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public boolean isReadOnly() throws SQLException {\n    logger.trace(\"boolean isReadOnly()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public void setReadOnly(boolean readOnly) throws SQLException {\n    logger.trace(\"void setReadOnly(boolean readOnly)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    if (readOnly) {\n      logger.debug(\"setReadOnly not supported.\", false);\n    }\n  }\n\n  @Override\n  public String getCatalog() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getDatabase();\n  }\n\n  @Override\n  public void setCatalog(String catalog) throws SQLException {\n    logger.trace(\"void setCatalog(String catalog)\", false);\n\n    // switch db by running \"use db\"\n    this.executeImmediate(\"use database \\\"\" + catalog + \"\\\"\");\n  }\n\n  @Override\n  public int getTransactionIsolation() throws SQLException {\n    logger.trace(\"int getTransactionIsolation()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return this.transactionIsolation;\n  }\n\n  /**\n   * Sets the transaction isolation level.\n   *\n   * @param level transaction level: TRANSACTION_NONE or TRANSACTION_READ_COMMITTED\n   * @throws SQLException if any SQL error occurs\n   */\n  @Override\n  public void setTransactionIsolation(int level) throws SQLException {\n    logger.trace(\"void setTransactionIsolation(int level), level = {}\", level);\n    raiseSQLExceptionIfConnectionIsClosed();\n    if (level == Connection.TRANSACTION_NONE || level == Connection.TRANSACTION_READ_COMMITTED) {\n      this.transactionIsolation = level;\n    } else {\n      throw new SQLFeatureNotSupportedException(\n          \"Transaction Isolation \" + level + \" not supported.\",\n          FEATURE_UNSUPPORTED.getSqlState(),\n          FEATURE_UNSUPPORTED.getMessageCode());\n    }\n  }\n\n  @Override\n  public SQLWarning getWarnings() throws SQLException {\n    logger.trace(\"SQLWarning getWarnings()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sqlWarnings;\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    logger.trace(\"void clearWarnings()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    sfSession.clearSqlWarnings();\n    sqlWarnings = null;\n  }\n\n  @Override\n  public Statement createStatement(int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    logger.trace(\n        \"Statement createStatement(int resultSetType, \" + \"int resultSetConcurrency)\", false);\n\n    Statement stmt =\n        createStatement(resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public Statement createStatement(\n      int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {\n    logger.trace(\n        \"Statement createStatement(int resultSetType, \"\n            + \"int resultSetConcurrency, int resultSetHoldability\",\n        false);\n\n    Statement stmt =\n        new SnowflakeStatementImpl(this, resultSetType, resultSetConcurrency, resultSetHoldability);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql) throws SQLException {\n    logger.trace(\"PreparedStatement prepareStatement(String sql)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    PreparedStatement stmt = prepareStatement(sql, false);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {\n    logger.trace(\n        \"PreparedStatement prepareStatement(String sql, \" + \"int autoGeneratedKeys)\", false);\n\n    if (autoGeneratedKeys == Statement.NO_GENERATED_KEYS) {\n      return prepareStatement(sql);\n    }\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {\n    logger.trace(\"PreparedStatement prepareStatement(String sql, \" + \"int[] columnIndexes)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {\n    logger.trace(\n        \"PreparedStatement prepareStatement(String sql, \" + \"String[] columnNames)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    logger.trace(\"PreparedStatement prepareStatement(String sql, \" + \"int resultSetType,\", false);\n\n    PreparedStatement stmt =\n        prepareStatement(\n            sql, resultSetType, resultSetConcurrency, ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    logger.trace(\"PreparedStatement prepareStatement(String sql, \" + \"int resultSetType,\", false);\n\n    PreparedStatement stmt =\n        new SnowflakePreparedStatementImpl(\n            this, sql, false, resultSetType, resultSetConcurrency, resultSetHoldability);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  public PreparedStatement prepareStatement(String sql, boolean skipParsing) throws SQLException {\n    logger.trace(\"PreparedStatement prepareStatement(String sql, boolean skipParsing)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    PreparedStatement stmt =\n        new SnowflakePreparedStatementImpl(\n            this,\n            sql,\n            skipParsing,\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    openStatements.add(stmt);\n    return stmt;\n  }\n\n  @Override\n  public Map<String, Class<?>> getTypeMap() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return Collections.emptyMap(); // nop\n  }\n\n  @Override\n  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public int getHoldability() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return ResultSet.CLOSE_CURSORS_AT_COMMIT; // nop\n  }\n\n  @Override\n  public void setHoldability(int holdability) throws SQLException {\n\n    raiseSQLExceptionIfConnectionIsClosed();\n    if ((holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT\n        && holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT)) {\n      throw new SQLException(\"The given parameter is not a ResultSet holdability constant.\");\n    }\n    // HOLD_CURSORS_OVER_COMMIT holdability is currently not supported.\n    // no-op if the holdability is CLOSE_CURSORS_AT_COMMIT\n    if (holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n    }\n  }\n\n  @Override\n  public Savepoint setSavepoint() throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public Savepoint setSavepoint(String name) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public void releaseSavepoint(Savepoint savepoint) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public Blob createBlob() throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public Clob createClob() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return new SnowflakeClob();\n  }\n\n  @Override\n  public NClob createNClob() throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public SQLXML createSQLXML() throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public boolean isValid(int timeout) throws SQLException {\n    if (timeout < 0) {\n      throw new SQLException(\"timeout is less than 0\");\n    } else if (isClosed) {\n      return false;\n    } else {\n      try {\n        sfSession.callHeartBeat(timeout);\n      } catch (SFException | Exception ex) {\n        return false;\n      }\n      return true;\n    }\n  }\n\n  @Override\n  public void setClientInfo(String name, String value) throws SQLClientInfoException {\n    Map<String, ClientInfoStatus> failedProps = new HashMap<>();\n    failedProps.put(name, ClientInfoStatus.REASON_UNKNOWN_PROPERTY);\n    raiseSetClientInfoException(failedProps);\n  }\n\n  private void raiseSetClientInfoException(Map<String, ClientInfoStatus> failedProps)\n      throws SQLClientInfoException {\n    if (isClosed) {\n      throw new SQLClientInfoException(\n          \"The connection is not opened.\",\n          ErrorCode.CONNECTION_CLOSED.getSqlState(),\n          ErrorCode.CONNECTION_CLOSED.getMessageCode(),\n          failedProps);\n    }\n\n    throw new SQLClientInfoException(\n        \"The client property cannot be set by setClientInfo.\",\n        ErrorCode.INVALID_PARAMETER_VALUE.getSqlState(),\n        ErrorCode.INVALID_PARAMETER_VALUE.getMessageCode(),\n        failedProps);\n  }\n\n  @Override\n  public Properties getClientInfo() throws SQLException {\n    logger.trace(\"Properties getClientInfo()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    // sfSession must not be null if the connection is not closed.\n    return sfSession.getClientInfo();\n  }\n\n  @Override\n  public void setClientInfo(Properties properties) throws SQLClientInfoException {\n    Map<String, ClientInfoStatus> failedProps = new HashMap<>();\n    Enumeration<?> propList = properties.propertyNames();\n    while (propList.hasMoreElements()) {\n      String name = (String) propList.nextElement();\n      failedProps.put(name, ClientInfoStatus.REASON_UNKNOWN_PROPERTY);\n    }\n    raiseSetClientInfoException(failedProps);\n  }\n\n  @Override\n  public String getClientInfo(String name) throws SQLException {\n    logger.trace(\"String getClientInfo(String name)\", false);\n\n    raiseSQLExceptionIfConnectionIsClosed();\n    // sfSession must not be null if the connection is not closed.\n    return sfSession.getClientInfo(name);\n  }\n\n  @Override\n  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {\n    logger.trace(\"Array createArrayOf(String typeName, Object[] \" + \"elements)\", false);\n    return new SfSqlArray(\n        JDBCType.valueOf(typeName.toUpperCase()).getVendorTypeNumber(),\n        elements,\n        sfSession,\n        objectMapper);\n  }\n\n  @Override\n  public Struct createStruct(String typeName, Object[] attributes) throws SQLException {\n    logger.trace(\"Struct createStruct(String typeName, Object[] \" + \"attributes)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(sfSession);\n  }\n\n  @Override\n  public String getSchema() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getSchema();\n  }\n\n  @Override\n  public void setSchema(String schema) throws SQLException {\n    logger.trace(\"void setSchema(String schema)\", false);\n\n    String databaseName = getCatalog();\n\n    // switch schema by running \"use db.schema\"\n    if (databaseName == null) {\n      this.executeImmediate(\"use schema \\\"\" + schema + \"\\\"\");\n    } else {\n      this.executeImmediate(\"use schema \\\"\" + databaseName + \"\\\".\\\"\" + schema + \"\\\"\");\n    }\n  }\n\n  @Override\n  public void abort(Executor executor) throws SQLException {\n    logger.trace(\"void abort(Executor executor)\", false);\n\n    close();\n  }\n\n  @Override\n  public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {\n    logger.trace(\"void setNetworkTimeout(Executor executor, int \" + \"milliseconds)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    networkTimeoutInMilli = milliseconds;\n  }\n\n  @Override\n  public int getNetworkTimeout() throws SQLException {\n    logger.trace(\"int getNetworkTimeout()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return networkTimeoutInMilli;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"boolean isWrapperFor(Class<?> iface)\", false);\n\n    return iface.isInstance(this);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"<T> T unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  @Override\n  public int getDatabaseMajorVersion() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getDatabaseMajorVersion();\n  }\n\n  @Override\n  public int getDatabaseMinorVersion() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getDatabaseMinorVersion();\n  }\n\n  @Override\n  public String getDatabaseVersion() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    return sfSession.getDatabaseVersion();\n  }\n\n  public SFConnectionHandler getHandler() {\n    return getHandler(null);\n  }\n\n  public SFConnectionHandler getHandler(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SnowflakeConnectionImpl\", \"getHandler\", internalCallMarker);\n    return sfConnectionHandler;\n  }\n\n  @Override\n  public void uploadStream(String stageName, String destFileName, InputStream inputStream)\n      throws SQLException {\n    uploadStream(stageName, destFileName, inputStream, UploadStreamConfig.builder().build());\n  }\n\n  @Override\n  public void uploadStream(\n      String stageName, String destFileName, InputStream inputStream, UploadStreamConfig config)\n      throws SQLException {\n    if (config == null) {\n      throw new IllegalArgumentException(\"UploadStreamConfig cannot be null\");\n    }\n    uploadStreamInternal(\n        stageName, config.getDestPrefix(), inputStream, destFileName, config.isCompressData());\n  }\n\n  /**\n   * Method to compress data from a stream and upload it at a stage location. The data will be\n   * uploaded as one file. No splitting is done in this method.\n   *\n   * <p>caller is responsible for releasing the inputStream after the method is called.\n   *\n   * <p>This method is deprecated\n   *\n   * @param stageName stage name: e.g. ~ or table name or stage name\n   * @param destPrefix path prefix under which the data should be uploaded on the stage\n   * @param inputStream input stream from which the data will be uploaded\n   * @param destFileName destination file name to use\n   * @throws SQLException failed to compress and put data from a stream at stage\n   */\n  @Deprecated\n  public void compressAndUploadStream(\n      String stageName, String destPrefix, InputStream inputStream, String destFileName)\n      throws SQLException {\n    uploadStreamInternal(stageName, destPrefix, inputStream, destFileName, true);\n  }\n\n  /**\n   * Method to put data from a stream at a stage location. The data will be uploaded as one file. No\n   * splitting is done in this method.\n   *\n   * <p>Stream size must match the total size of data in the input stream unless compressData\n   * parameter is set to true.\n   *\n   * <p>caller is responsible for passing the correct size for the data in the stream and releasing\n   * the inputStream after the method is called.\n   *\n   * @param stageName stage name: e.g. ~ or table name or stage name\n   * @param destPrefix path prefix under which the data should be uploaded on the stage\n   * @param inputStream input stream from which the data will be uploaded\n   * @param destFileName destination file name to use\n   * @param compressData whether compression is requested fore uploading data\n   * @throws SQLException raises if any error occurs\n   */\n  private void uploadStreamInternal(\n      String stageName,\n      String destPrefix,\n      InputStream inputStream,\n      String destFileName,\n      boolean compressData)\n      throws SQLException {\n    logger.debug(\n        \"Upload data from stream: stageName={}\" + \", destPrefix={}, destFileName={}\",\n        stageName,\n        destPrefix,\n        destFileName);\n\n    if (stageName == null) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"stage name is null\");\n    }\n\n    if (destFileName == null) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"stage name is null\");\n    }\n\n    SnowflakeStatementImpl stmt = this.createStatement().unwrap(SnowflakeStatementImpl.class);\n\n    StringBuilder destStage = new StringBuilder();\n\n    // add stage name\n    if (!(stageName.startsWith(\"@\") || stageName.startsWith(\"'@\") || stageName.startsWith(\"$$@\"))) {\n      destStage.append(\"@\");\n    }\n    destStage.append(stageName);\n\n    // add dest prefix\n    if (destPrefix != null) {\n      if (!destPrefix.startsWith(\"/\")) {\n        destStage.append(\"/\");\n      }\n      destStage.append(destPrefix);\n    }\n\n    StringBuilder putCommand = new StringBuilder();\n    // use a placeholder for source file\n    putCommand.append(\"put file:///tmp/placeholder \");\n    putCommand.append(destStage.toString());\n    putCommand.append(\" overwrite=true\");\n\n    SFBaseFileTransferAgent transferAgent =\n        sfConnectionHandler.getFileTransferAgent(putCommand.toString(), stmt.getSFBaseStatement());\n    transferAgent.setDestStagePath(destStage.toString());\n    transferAgent.setSourceStream(inputStream);\n    transferAgent.setDestFileNameForStreamSource(destFileName);\n    transferAgent.setCompressSourceFromStream(compressData);\n    transferAgent.execute();\n\n    stmt.close();\n  }\n\n  @Override\n  public InputStream downloadStream(String stageName, String sourceFileName) throws SQLException {\n    return downloadStream(stageName, sourceFileName, DownloadStreamConfig.builder().build());\n  }\n\n  @Override\n  public InputStream downloadStream(\n      String stageName, String sourceFileName, DownloadStreamConfig config) throws SQLException {\n    if (config == null) {\n      throw new IllegalArgumentException(\"DownloadStreamConfig cannot be null\");\n    }\n\n    boolean decompress = config.isDecompress();\n\n    logger.debug(\n        \"Download data to stream: stageName={}\" + \", sourceFileName={}\", stageName, sourceFileName);\n\n    if (isNullOrEmpty(stageName)) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"stage name is null or empty\");\n    }\n\n    if (isNullOrEmpty(sourceFileName)) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"source file name is null or empty\");\n    }\n\n    SnowflakeStatementImpl stmt =\n        new SnowflakeStatementImpl(\n            this,\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n\n    StringBuilder getCommand = new StringBuilder();\n\n    getCommand.append(\"get \");\n\n    if (!stageName.startsWith(\"@\")) {\n      getCommand.append(\"@\");\n    }\n\n    getCommand.append(stageName);\n\n    getCommand.append(\"/\");\n\n    if (sourceFileName.startsWith(\"/\")) {\n      sourceFileName = sourceFileName.substring(1);\n    }\n\n    getCommand.append(sourceFileName);\n\n    // special characters and spaces require single quotes around stage name.\n    boolean isSpecialChar = !sourceFileName.matches(\"^[a-zA-Z0-9_/.]*$\");\n    if (isSpecialChar) {\n      getCommand.insert(getCommand.indexOf(\"@\"), \"'\");\n      getCommand.append(\"'\");\n    }\n\n    // this is a fake path, used to form Get query and retrieve stage info,\n    // no file will be downloaded to this location\n    getCommand.append(\" file:///tmp/ /*jdbc download stream*/\");\n\n    SFBaseFileTransferAgent transferAgent =\n        sfConnectionHandler.getFileTransferAgent(getCommand.toString(), stmt.getSFBaseStatement());\n\n    InputStream stream = transferAgent.downloadStream(sourceFileName);\n\n    if (decompress) {\n      try {\n        return new GZIPInputStream(stream);\n      } catch (IOException ex) {\n        throw new SnowflakeSQLLoggedException(\n            sfSession,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            SqlState.INTERNAL_ERROR,\n            ex.getMessage());\n      }\n    } else {\n      return stream;\n    }\n  }\n\n  public void setInjectedDelay(int delay) throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    sfSession.setInjectedDelay(delay);\n  }\n\n  public void injectedDelay() throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    int d = _injectedDelay.get();\n\n    if (d != 0) {\n      _injectedDelay.set(0);\n      try {\n        logger.trace(\"delayed for {}\", d);\n\n        Thread.sleep(d);\n      } catch (InterruptedException ex) {\n      }\n    }\n  }\n\n  public void setInjectFileUploadFailure(String fileToFail) throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    sfSession.setInjectFileUploadFailure(fileToFail);\n  }\n\n  public SFBaseSession getSFBaseSession() {\n    return getSFBaseSession(null);\n  }\n\n  public SFBaseSession getSFBaseSession(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SnowflakeConnectionImpl\", \"getSFBaseSession\", internalCallMarker);\n    return sfSession;\n  }\n\n  // Convenience method to return an SFSession-typed SFBaseSession object, but\n  // performs the type-checking as necessary.\n  public SFSession getSfSession() throws SnowflakeSQLException {\n    return getSfSession(null);\n  }\n\n  public SFSession getSfSession(InternalCallMarker internalCallMarker)\n      throws SnowflakeSQLException {\n    recordIfExternal(\"SnowflakeConnectionImpl\", \"getSfSession\", internalCallMarker);\n    if (sfSession instanceof SFSession) {\n      return (SFSession) sfSession;\n    }\n\n    throw new SnowflakeSQLException(\"getSFSession() called with a different SFBaseSession type.\");\n  }\n\n  private void appendWarning(SQLWarning w) {\n    if (sqlWarnings == null) {\n      sqlWarnings = w;\n    } else {\n      sqlWarnings.setNextWarning(w);\n    }\n  }\n\n  private void appendWarnings(List<SFException> warnings) {\n    for (SFException e : warnings) {\n      appendWarning(new SQLWarning(e.getMessage(), e.getSqlState(), e.getVendorCode()));\n    }\n  }\n\n  public boolean getShowStatementParameters() {\n    return showStatementParameters;\n  }\n\n  public void removeClosedStatement(Statement stmt) {\n    openStatements.remove(stmt);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/datasource/SnowflakeBasicDataSource.java",
    "content": "package net.snowflake.client.internal.api.implementation.datasource;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.PrintWriter;\nimport java.io.Serializable;\nimport java.security.PrivateKey;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.logging.Logger;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Basic implementation of {@link SnowflakeDataSource} for Snowflake JDBC connections.\n *\n * <p>This class provides a simple, non-pooled DataSource implementation that creates new Snowflake\n * connections on demand. It is suitable for applications that do not require connection pooling or\n * for use with external connection pool managers.\n *\n * <p><b>Note:</b> This class is not intended for direct instantiation. Use {@link\n * net.snowflake.client.api.datasource.SnowflakeDataSourceFactory#createDataSource()} instead.\n */\npublic class SnowflakeBasicDataSource implements SnowflakeDataSource, Serializable {\n  private static final long serialVersionUID = 1L;\n  private static final String AUTHENTICATOR_SNOWFLAKE_JWT = \"SNOWFLAKE_JWT\";\n  private static final String AUTHENTICATOR_OAUTH = \"OAUTH\";\n\n  private static final String AUTHENTICATOR_EXTERNAL_BROWSER = \"EXTERNALBROWSER\";\n\n  private static final String AUTHENTICATOR_USERNAME_PASSWORD_MFA = \"USERNAME_PASSWORD_MFA\";\n\n  private String url;\n\n  private String serverName;\n\n  private String user;\n\n  private String password;\n\n  private int portNumber = 0;\n\n  private String authenticator;\n\n  private Properties properties = new Properties();\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeBasicDataSource.class);\n\n  static {\n    try {\n      Class.forName(\"net.snowflake.client.api.driver.SnowflakeDriver\");\n    } catch (ClassNotFoundException e) {\n      throw new IllegalStateException(\n          \"Unable to load \"\n              + \"net.snowflake.client.api.driver.SnowflakeDriver. \"\n              + \"Please check if you have proper Snowflake JDBC \"\n              + \"Driver jar on the classpath\",\n          e);\n    }\n  }\n\n  private void writeObjectHelper(ObjectOutputStream out) throws IOException {\n    out.writeObject(url);\n    out.writeObject(serverName);\n    out.writeObject(user);\n    out.writeObject(password);\n    out.writeObject(portNumber);\n    out.writeObject(authenticator);\n    out.writeObject(properties);\n  }\n\n  private void readObjectHelper(ObjectInputStream in) throws IOException, ClassNotFoundException {\n    url = (String) in.readObject();\n    serverName = (String) in.readObject();\n    user = (String) in.readObject();\n    password = (String) in.readObject();\n    portNumber = (int) in.readObject();\n    authenticator = (String) in.readObject();\n    properties = (Properties) in.readObject();\n  }\n\n  private void writeObject(ObjectOutputStream out) throws IOException {\n    writeObjectHelper(out);\n  }\n\n  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {\n    readObjectHelper(in);\n  }\n\n  @Override\n  public Connection getConnection() throws SQLException {\n    return getConnection(user, password);\n  }\n\n  @Override\n  public Connection getConnection(String username, String password) throws SQLException {\n    if (!AUTHENTICATOR_OAUTH.equalsIgnoreCase(\n        authenticator)) { // For OAuth, no username is required\n      if (username == null) {\n        throw new SnowflakeSQLException(\n            \"Cannot create connection because username is missing in DataSource properties.\");\n      }\n      properties.put(SFSessionProperty.USER.getPropertyKey(), username);\n    }\n\n    // The driver needs password for OAUTH as part of SNOW-533673 feature request.\n    if (!AUTHENTICATOR_SNOWFLAKE_JWT.equalsIgnoreCase(authenticator)\n        && !AUTHENTICATOR_EXTERNAL_BROWSER.equalsIgnoreCase(authenticator)) {\n      if (password == null) {\n        throw new SnowflakeSQLException(\n            \"Cannot create connection because password is missing in DataSource properties.\");\n      }\n      properties.put(SFSessionProperty.PASSWORD.getPropertyKey(), password);\n    }\n\n    try {\n      Connection con = SnowflakeDriver.INSTANCE.connect(getUrl(), properties);\n      logger.trace(\"Created a connection for {} at {}\", user, (ArgSupplier) this::getUrl);\n      return con;\n    } catch (SQLException e) {\n      logger.error(\"Failed to create a connection for {} at {}: {}\", user, getUrl(), e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PrintWriter getLogWriter() throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public void setLogWriter(PrintWriter out) throws SQLException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public int getLoginTimeout() {\n    try {\n      return Integer.parseInt(\n          properties.getProperty(SFSessionProperty.LOGIN_TIMEOUT.getPropertyKey()));\n    } catch (NumberFormatException e) {\n      return 0;\n    }\n  }\n\n  @Override\n  public void setLoginTimeout(int seconds) throws SQLException {\n    properties.put(SFSessionProperty.LOGIN_TIMEOUT.getPropertyKey(), Integer.toString(seconds));\n  }\n\n  @Override\n  public Logger getParentLogger() throws SQLFeatureNotSupportedException {\n    throw new SQLFeatureNotSupportedException();\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) {\n    return false;\n  }\n\n  @Override\n  public <T> T unwrap(Class<T> iface) {\n    return null;\n  }\n\n  @Override\n  public void setUrl(String url) {\n    this.url = url;\n  }\n\n  @Override\n  public void setDatabaseName(String databaseName) {\n    properties.put(SFSessionProperty.DATABASE.getPropertyKey(), databaseName);\n  }\n\n  @Override\n  public void setSchema(String schema) {\n    properties.put(SFSessionProperty.SCHEMA.getPropertyKey(), schema);\n  }\n\n  @Override\n  public void setWarehouse(String warehouse) {\n    properties.put(SFSessionProperty.WAREHOUSE.getPropertyKey(), warehouse);\n  }\n\n  @Override\n  public void setRole(String role) {\n    properties.put(SFSessionProperty.ROLE.getPropertyKey(), role);\n  }\n\n  @Override\n  public void setUser(String user) {\n    this.user = user;\n  }\n\n  @Override\n  public void setServerName(String serverName) {\n    this.serverName = serverName;\n  }\n\n  @Override\n  public void setPassword(String password) {\n    this.password = password;\n  }\n\n  @Override\n  public void setPortNumber(int portNumber) {\n    this.portNumber = portNumber;\n  }\n\n  @Override\n  public void setAccount(String account) {\n    this.properties.put(SFSessionProperty.ACCOUNT.getPropertyKey(), account);\n  }\n\n  @Override\n  public void setSsl(boolean ssl) {\n    this.properties.put(\"ssl\", String.valueOf(ssl));\n  }\n\n  @Override\n  public void setAuthenticator(String authenticator) {\n    this.authenticator = authenticator;\n    this.properties.put(SFSessionProperty.AUTHENTICATOR.getPropertyKey(), authenticator);\n  }\n\n  @Override\n  public void setToken(String token) {\n    this.properties.put(SFSessionProperty.TOKEN.getPropertyKey(), token);\n  }\n\n  @Override\n  public String getUrl() {\n    if (url != null) {\n      return url;\n    } else {\n      // generate url;\n      StringBuilder url = new StringBuilder(100);\n      url.append(\"jdbc:snowflake://\");\n      url.append(serverName);\n      if (portNumber != 0) {\n        url.append(\":\").append(portNumber);\n      }\n\n      return url.toString();\n    }\n  }\n\n  @Override\n  public void setPrivateKey(PrivateKey privateKey) {\n    this.setAuthenticator(AUTHENTICATOR_SNOWFLAKE_JWT);\n    this.properties.put(SFSessionProperty.PRIVATE_KEY.getPropertyKey(), privateKey);\n  }\n\n  @Override\n  public void setPrivateKeyFile(String location, String password) {\n    this.setAuthenticator(AUTHENTICATOR_SNOWFLAKE_JWT);\n    this.properties.put(SFSessionProperty.PRIVATE_KEY_FILE.getPropertyKey(), location);\n    if (!isNullOrEmpty(password)) {\n      this.properties.put(SFSessionProperty.PRIVATE_KEY_PWD.getPropertyKey(), password);\n    }\n  }\n\n  @Override\n  public void setPrivateKeyBase64(String privateKeyBase64, String password) {\n    this.setAuthenticator(AUTHENTICATOR_SNOWFLAKE_JWT);\n    this.properties.put(SFSessionProperty.PRIVATE_KEY_BASE64.getPropertyKey(), privateKeyBase64);\n    if (!isNullOrEmpty(password)) {\n      this.properties.put(SFSessionProperty.PRIVATE_KEY_PWD.getPropertyKey(), password);\n    }\n  }\n\n  @Override\n  public void setTracing(String tracing) {\n    this.properties.put(SFSessionProperty.TRACING.getPropertyKey(), tracing);\n  }\n\n  @Override\n  public Properties getProperties() {\n    return this.properties;\n  }\n\n  @Override\n  public void setAllowUnderscoresInHost(boolean allowUnderscoresInHost) {\n    this.properties.put(\n        SFSessionProperty.ALLOW_UNDERSCORES_IN_HOST.getPropertyKey(),\n        String.valueOf(allowUnderscoresInHost));\n  }\n\n  @Override\n  public void setDisableGcsDefaultCredentials(boolean isGcsDefaultCredentialsDisabled) {\n    this.properties.put(\n        SFSessionProperty.DISABLE_GCS_DEFAULT_CREDENTIALS.getPropertyKey(),\n        String.valueOf(isGcsDefaultCredentialsDisabled));\n  }\n\n  @Override\n  public void setDisableSamlURLCheck(boolean disableSamlURLCheck) {\n    this.properties.put(\n        SFSessionProperty.DISABLE_SAML_URL_CHECK.getPropertyKey(),\n        String.valueOf(disableSamlURLCheck));\n  }\n\n  @Override\n  public void setPasscode(String passcode) {\n    this.setAuthenticator(AUTHENTICATOR_USERNAME_PASSWORD_MFA);\n    this.properties.put(SFSessionProperty.PASSCODE.getPropertyKey(), passcode);\n  }\n\n  @Override\n  public void setPasscodeInPassword(boolean isPasscodeInPassword) {\n    this.properties.put(\n        SFSessionProperty.PASSCODE_IN_PASSWORD.getPropertyKey(),\n        String.valueOf(isPasscodeInPassword));\n    if (isPasscodeInPassword) {\n      this.setAuthenticator(AUTHENTICATOR_USERNAME_PASSWORD_MFA);\n    }\n  }\n\n  @Override\n  public void setDisableSocksProxy(boolean ignoreJvmSocksProxy) {\n    this.properties.put(\n        SFSessionProperty.DISABLE_SOCKS_PROXY.getPropertyKey(),\n        String.valueOf(ignoreJvmSocksProxy));\n  }\n\n  @Override\n  public void setNonProxyHosts(String nonProxyHosts) {\n    this.properties.put(SFSessionProperty.NON_PROXY_HOSTS.getPropertyKey(), nonProxyHosts);\n  }\n\n  @Override\n  public void setProxyHost(String proxyHost) {\n    this.properties.put(SFSessionProperty.PROXY_HOST.getPropertyKey(), proxyHost);\n  }\n\n  @Override\n  public void setProxyPassword(String proxyPassword) {\n    this.properties.put(SFSessionProperty.PROXY_PASSWORD.getPropertyKey(), proxyPassword);\n  }\n\n  @Override\n  public void setProxyPort(int proxyPort) {\n    this.properties.put(SFSessionProperty.PROXY_PORT.getPropertyKey(), Integer.toString(proxyPort));\n  }\n\n  @Override\n  public void setProxyProtocol(String proxyProtocol) {\n    this.properties.put(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey(), proxyProtocol);\n  }\n\n  @Override\n  public void setProxyUser(String proxyUser) {\n    this.properties.put(SFSessionProperty.PROXY_USER.getPropertyKey(), proxyUser);\n  }\n\n  @Override\n  public void setUseProxy(boolean useProxy) {\n    this.properties.put(SFSessionProperty.USE_PROXY.getPropertyKey(), String.valueOf(useProxy));\n  }\n\n  @Override\n  public void setNetworkTimeout(int networkTimeoutSeconds) {\n    this.properties.put(\n        SFSessionProperty.NETWORK_TIMEOUT.getPropertyKey(),\n        Integer.toString(networkTimeoutSeconds));\n  }\n\n  @Override\n  public void setQueryTimeout(int queryTimeoutSeconds) {\n    this.properties.put(\n        SFSessionProperty.QUERY_TIMEOUT.getPropertyKey(), Integer.toString(queryTimeoutSeconds));\n  }\n\n  @Override\n  public void setApplication(String application) {\n    this.properties.put(SFSessionProperty.APPLICATION.getPropertyKey(), application);\n  }\n\n  @Override\n  public void setClientConfigFile(String clientConfigFile) {\n    this.properties.put(SFSessionProperty.CLIENT_CONFIG_FILE.getPropertyKey(), clientConfigFile);\n  }\n\n  @Override\n  public void setEnablePatternSearch(boolean enablePatternSearch) {\n    this.properties.put(\n        SFSessionProperty.ENABLE_PATTERN_SEARCH.getPropertyKey(),\n        String.valueOf(enablePatternSearch));\n  }\n\n  @Override\n  public void setEnablePutGet(boolean enablePutGet) {\n    this.properties.put(\n        SFSessionProperty.ENABLE_PUT_GET.getPropertyKey(), String.valueOf(enablePutGet));\n  }\n\n  @Override\n  public void setArrowTreatDecimalAsInt(boolean treatDecimalAsInt) {\n    this.properties.put(\n        SFSessionProperty.JDBC_ARROW_TREAT_DECIMAL_AS_INT.getPropertyKey(),\n        String.valueOf(treatDecimalAsInt));\n  }\n\n  @Override\n  public void setMaxHttpRetries(int maxHttpRetries) {\n    this.properties.put(\n        SFSessionProperty.MAX_HTTP_RETRIES.getPropertyKey(), Integer.toString(maxHttpRetries));\n  }\n\n  @Override\n  public void setOcspFailOpen(boolean ocspFailOpen) {\n    this.properties.put(\n        SFSessionProperty.OCSP_FAIL_OPEN.getPropertyKey(), String.valueOf(ocspFailOpen));\n  }\n\n  @Override\n  public void setPutGetMaxRetries(int putGetMaxRetries) {\n    this.properties.put(\n        SFSessionProperty.PUT_GET_MAX_RETRIES.getPropertyKey(), Integer.toString(putGetMaxRetries));\n  }\n\n  @Override\n  public void setStringsQuotedForColumnDef(boolean stringsQuotedForColumnDef) {\n    this.properties.put(\n        SFSessionProperty.STRINGS_QUOTED.getPropertyKey(),\n        String.valueOf(stringsQuotedForColumnDef));\n  }\n\n  @Override\n  public void setEnableDiagnostics(boolean enableDiagnostics) {\n    this.properties.put(\n        SFSessionProperty.ENABLE_DIAGNOSTICS.getPropertyKey(), String.valueOf(enableDiagnostics));\n  }\n\n  @Override\n  public void setDiagnosticsAllowlistFile(String diagnosticsAllowlistFile) {\n    this.properties.put(\n        SFSessionProperty.DIAGNOSTICS_ALLOWLIST_FILE.getPropertyKey(), diagnosticsAllowlistFile);\n  }\n\n  @Override\n  public void setJDBCDefaultFormatDateWithTimezone(Boolean jdbcDefaultFormatDateWithTimezone) {\n    this.properties.put(\n        \"JDBC_DEFAULT_FORMAT_DATE_WITH_TIMEZONE\", jdbcDefaultFormatDateWithTimezone);\n  }\n\n  @Override\n  public void setGetDateUseNullTimezone(Boolean getDateUseNullTimezone) {\n    this.properties.put(\"JDBC_GET_DATE_USE_NULL_TIMEZONE\", getDateUseNullTimezone);\n  }\n\n  @Override\n  public void setEnableClientRequestMfaToken(boolean enableClientRequestMfaToken) {\n    this.setAuthenticator(AUTHENTICATOR_USERNAME_PASSWORD_MFA);\n    this.properties.put(\n        SFSessionProperty.ENABLE_CLIENT_REQUEST_MFA_TOKEN.getPropertyKey(),\n        enableClientRequestMfaToken);\n  }\n\n  @Override\n  public void setEnableClientStoreTemporaryCredential(\n      boolean enableClientStoreTemporaryCredential) {\n    this.setAuthenticator(AUTHENTICATOR_EXTERNAL_BROWSER);\n    this.properties.put(\n        SFSessionProperty.ENABLE_CLIENT_STORE_TEMPORARY_CREDENTIAL.getPropertyKey(),\n        enableClientStoreTemporaryCredential);\n  }\n\n  @Override\n  public void setBrowserResponseTimeout(int seconds) {\n    this.setAuthenticator(AUTHENTICATOR_EXTERNAL_BROWSER);\n    this.properties.put(\"BROWSER_RESPONSE_TIMEOUT\", Integer.toString(seconds));\n  }\n\n  @Override\n  public void setHttpHeadersCustomizers(List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    this.properties.put(\n        HttpHeadersCustomizer.HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, httpHeadersCustomizers);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/metadata/SnowflakeDatabaseMetaDataImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.metadata;\n\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_CATALOGS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_COLUMNS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_COLUMNS_EXTENDED_SET;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_FOREIGN_KEYS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_FUNCTIONS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_FUNCTION_COLUMNS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_PRIMARY_KEYS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_PROCEDURES;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_PROCEDURE_COLUMNS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_SCHEMAS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_STREAMS;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_TABLES;\nimport static net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata.GET_TABLE_PRIVILEGES;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\nimport static net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper.convertStringToType;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.RowIdLifetime;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.api.connection.SnowflakeDatabaseMetaData;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.jdbc.DBMetadataResultSetMetadata;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumnMetadata;\nimport net.snowflake.client.internal.jdbc.SnowflakeDatabaseMetaDataQueryResultSet;\nimport net.snowflake.client.internal.jdbc.SnowflakeDatabaseMetaDataResultSet;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport net.snowflake.common.util.Wildcard;\n\npublic class SnowflakeDatabaseMetaDataImpl implements SnowflakeDatabaseMetaData {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeDatabaseMetaDataImpl.class);\n\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  private static final String DatabaseProductName = \"Snowflake\";\n\n  private static final String DriverName = \"Snowflake\";\n\n  private static final char SEARCH_STRING_ESCAPE = '\\\\';\n\n  private static final String JDBCVersion = \"4.2\";\n  // Open Group CLI Functions\n  // LOG10 is not supported\n  public static final String NumericFunctionsSupported =\n      \"ABS,ACOS,ASIN,ATAN,ATAN2,CBRT,CEILING,COS,COT,DEGREES,EXP,FACTORIAL,\"\n          + \"FLOOR,HAVERSINE,LN,LOG,MOD,PI,POWER,RADIANS,RAND,\"\n          + \"ROUND,SIGN,SIN,SQRT,SQUARE,TAN,TRUNCATE\";\n  // DIFFERENCE and SOUNDEX are not supported\n  public static final String StringFunctionsSupported =\n      \"ASCII,BIT_LENGTH,CHAR,CONCAT,INSERT,LCASE,LEFT,LENGTH,LPAD,\"\n          + \"LOCATE,LTRIM,OCTET_LENGTH,PARSE_IP,PARSE_URL,REPEAT,REVERSE,\"\n          + \"REPLACE,RPAD,RTRIMMED_LENGTH,SPACE,SPLIT,SPLIT_PART,\"\n          + \"SPLIT_TO_TABLE,STRTOK,STRTOK_TO_ARRAY,STRTOK_SPLIT_TO_TABLE,\"\n          + \"TRANSLATE,TRIM,UNICODE,UUID_STRING,INITCAP,LOWER,UPPER,REGEXP,\"\n          + \"REGEXP_COUNT,REGEXP_INSTR,REGEXP_LIKE,REGEXP_REPLACE,\"\n          + \"REGEXP_SUBSTR,RLIKE,CHARINDEX,CONTAINS,EDITDISTANCE,ENDSWITH,\"\n          + \"ILIKE,ILIKE ANY,LIKE,LIKE ALL,LIKE ANY,POSITION,REPLACE,RIGHT,\"\n          + \"STARTSWITH,SUBSTRING,COMPRESS,DECOMPRESS_BINARY,DECOMPRESS_STRING,\"\n          + \"BASE64_DECODE_BINARY,BASE64_DECODE_STRING,BASE64_ENCODE,\"\n          + \"HEX_DECODE_BINARY,HEX_DECODE_STRING,HEX_ENCODE,\"\n          + \"TRY_BASE64_DECODE_BINARY,TRY_BASE64_DECODE_STRING,\"\n          + \"TRY_HEX_DECODE_BINARY,TRY_HEX_DECODE_STRING,MD_5,MD5_HEX,\"\n          + \"MD5_BINARY,SHA1,SHA1_HEX,SHA2,SHA1_BINARY,SHA2_HEX,SHA2_BINARY,\"\n          + \" HASH,HASH_AGG,COLLATE,COLLATION\";\n  private static final String DateAndTimeFunctionsSupported =\n      \"CURDATE,\"\n          + \"CURTIME,DAYNAME,DAYOFMONTH,DAYOFWEEK,DAYOFYEAR,HOUR,MINUTE,MONTH,\"\n          + \"MONTHNAME,NOW,QUARTER,SECOND,TIMESTAMPADD,TIMESTAMPDIFF,WEEK,YEAR\";\n  public static final String SystemFunctionsSupported = \"DATABASE,IFNULL,USER\";\n\n  // These are keywords not in SQL2003 standard\n  private static final String notSQL2003Keywords =\n      String.join(\n          \",\",\n          \"ACCOUNT\",\n          \"ASOF\",\n          \"BIT\",\n          \"BYTEINT\",\n          \"CONNECTION\",\n          \"DATABASE\",\n          \"DATETIME\",\n          \"DATE_PART\",\n          \"FIXED\",\n          \"FOLLOWING\",\n          \"GSCLUSTER\",\n          \"GSPACKAGE\",\n          \"IDENTIFIER\",\n          \"ILIKE\",\n          \"INCREMENT\",\n          \"ISSUE\",\n          \"LONG\",\n          \"MAP\",\n          \"MATCH_CONDITION\",\n          \"MINUS\",\n          \"NUMBER\",\n          \"OBJECT\",\n          \"ORGANIZATION\",\n          \"QUALIFY\",\n          \"REFERENCE\",\n          \"REGEXP\",\n          \"RLIKE\",\n          \"SAMPLE\",\n          \"SCHEMA\",\n          \"STRING\",\n          \"TEXT\",\n          \"TIMESTAMPLTZ\",\n          \"TIMESTAMPNTZ\",\n          \"TIMESTAMPTZ\",\n          \"TIMESTAMP_LTZ\",\n          \"TIMESTAMP_NTZ\",\n          \"TIMESTAMP_TZ\",\n          \"TINYINT\",\n          \"TRANSIT\",\n          \"TRY_CAST\",\n          \"VARIANT\",\n          \"VECTOR\",\n          \"VIEW\");\n\n  private static final String MAX_VARCHAR_BINARY_SIZE_PARAM_NAME =\n      \"VARCHAR_AND_BINARY_MAX_SIZE_IN_RESULT\";\n\n  // Defaults to 16MB\n  private static final int DEFAULT_MAX_LOB_SIZE = 16777216;\n\n  private final Connection connection;\n\n  private final SFBaseSession session;\n\n  private Telemetry ibInstance;\n\n  private final boolean metadataRequestUseConnectionCtx;\n\n  private boolean useSessionSchema = false;\n\n  private final boolean metadataRequestUseSessionDatabase;\n\n  private boolean stringsQuoted = false;\n\n  // The number of columns for the result set returned from the current procedure. A value of -1\n  // means the procedure doesn't return a result set\n  private int procedureResultsetColumnNum;\n\n  // Indicates if pattern matching is allowed for all parameters.\n  private boolean isPatternMatchingEnabled = true;\n  private boolean exactSchemaSearchEnabled;\n  private boolean enableWildcardsInShowMetadataCommands;\n\n  public SnowflakeDatabaseMetaDataImpl(Connection connection) throws SQLException {\n    logger.trace(\"SnowflakeDatabaseMetaDataImpl(SnowflakeConnection connection)\", false);\n\n    this.connection = connection;\n    this.session =\n        connection.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession(internalCallMarker());\n    this.metadataRequestUseConnectionCtx = session.getMetadataRequestUseConnectionCtx();\n    this.metadataRequestUseSessionDatabase = session.getMetadataRequestUseSessionDatabase();\n    this.stringsQuoted = session.isStringQuoted();\n    this.ibInstance = session.getTelemetryClient(internalCallMarker());\n    this.procedureResultsetColumnNum = -1;\n    this.isPatternMatchingEnabled = session.getEnablePatternSearch();\n    this.exactSchemaSearchEnabled = session.getEnableExactSchemaSearch();\n    this.enableWildcardsInShowMetadataCommands = session.getEnableWildcardsInShowMetadataCommands();\n  }\n\n  private void raiseSQLExceptionIfConnectionIsClosed() throws SQLException {\n    if (connection.isClosed()) {\n      throw new SnowflakeSQLException(ErrorCode.CONNECTION_CLOSED);\n    }\n  }\n\n  /**\n   * Function to send in-band telemetry data about DatabaseMetadata get API calls and their\n   * associated SHOW commands\n   *\n   * @param resultSet The ResultSet generated from the SHOW command in the function call. Can be of\n   *     type SnowflakeResultSet or SnowflakeDatabaseMetaDataResultSet\n   * @param functionName name of DatabaseMetadata API function call\n   * @param catalog database\n   * @param schema schema\n   * @param generalNamePattern name of table, function, etc\n   * @param specificNamePattern name of table column, function parameter name, etc\n   */\n  private void sendInBandTelemetryMetadataMetrics(\n      ResultSet resultSet,\n      String functionName,\n      String catalog,\n      String schema,\n      String generalNamePattern,\n      String specificNamePattern) {\n    String queryId = \"\";\n    try {\n      if (resultSet.isWrapperFor(SnowflakeResultSet.class)) {\n        queryId = resultSet.unwrap(SnowflakeResultSet.class).getQueryID();\n      } else if (resultSet.isWrapperFor(SnowflakeDatabaseMetaDataResultSet.class)) {\n        queryId = resultSet.unwrap(SnowflakeDatabaseMetaDataResultSet.class).getQueryID();\n      }\n    } catch (SQLException e) {\n      // This should never be reached because resultSet should always be one of the 2 types\n      // unwrapped above.\n      // In case we get here, do nothing; just don't include query ID\n    }\n    ObjectNode ibValue = mapper.createObjectNode();\n    ibValue.put(\"type\", TelemetryField.METADATA_METRICS.toString());\n    ibValue.put(\"query_id\", queryId);\n    ibValue.put(\"function_name\", functionName);\n    ibValue.with(\"function_parameters\").put(\"catalog\", catalog);\n    ibValue.with(\"function_parameters\").put(\"schema\", schema);\n    ibValue.with(\"function_parameters\").put(\"general_name_pattern\", generalNamePattern);\n    ibValue.with(\"function_parameters\").put(\"specific_name_pattern\", specificNamePattern);\n    ibValue.put(\"use_connection_context\", metadataRequestUseConnectionCtx ? \"true\" : \"false\");\n    ibValue.put(\"session_database_name\", session.getDatabase());\n    ibValue.put(\"session_schema_name\", session.getSchema());\n    TelemetryData data = TelemetryUtil.buildJobData(ibValue);\n    ibInstance.addLogToBatch(data);\n  }\n\n  // used to get convert string back to normal after its special characters have been escaped to\n  // send it through Wildcard regex\n  private String unescapeChars(String escapedString) {\n    String unescapedString = escapedString.replace(\"\\\\_\", \"_\");\n    unescapedString = unescapedString.replace(\"\\\\%\", \"%\");\n    unescapedString = unescapedString.replace(\"\\\\\\\\\", \"\\\\\");\n    unescapedString = escapeSqlQuotes(unescapedString);\n    return unescapedString;\n  }\n\n  // In SQL, double quotes must be escaped with an additional pair of double quotes. Add additional\n  // quotes to avoid syntax errors with SQL queries.\n  private String escapeSqlQuotes(String originalString) {\n    return originalString.replace(\"\\\"\", \"\\\"\\\"\");\n  }\n\n  /**\n   * This guards against SQL injections by ensuring that any single quote is escaped properly.\n   *\n   * @param arg the original schema\n   * @return\n   */\n  private String escapeSingleQuoteForLikeCommand(String arg) {\n    if (arg == null) {\n      return null;\n    }\n    int i = 0;\n    int index = arg.indexOf(\"'\", i);\n    while (index != -1) {\n      if (index == 0 || (index > 0 && arg.charAt(index - 1) != '\\\\')) {\n        arg = arg.replace(\"'\", \"\\\\'\");\n        i = index + 2;\n      } else {\n        i = index + 1;\n      }\n      index = i < arg.length() ? arg.indexOf(\"'\", i) : -1;\n    }\n    return arg;\n  }\n\n  private boolean isSchemaNameWildcardPattern(String inputString) {\n    // if schema contains wildcard, don't treat it as wildcard; treat as just a schema name if\n    // session schema or wildcards in identifiers in show metadata queries disabled\n    return (useSessionSchema || !enableWildcardsInShowMetadataCommands)\n        ? false\n        : Wildcard.isWildcardPatternStr(inputString);\n  }\n\n  @Override\n  public boolean allProceduresAreCallable() throws SQLException {\n    logger.trace(\"boolean allProceduresAreCallable()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean allTablesAreSelectable() throws SQLException {\n    logger.trace(\"boolean allTablesAreSelectable()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public String getURL() throws SQLException {\n    logger.trace(\"String getURL()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    String url = session.getUrl();\n    return url.startsWith(\"http://\")\n        ? url.replace(\"http://\", \"jdbc:snowflake://\")\n        : url.replace(\"https://\", \"jdbc:snowflake://\");\n  }\n\n  @Override\n  public String getUserName() throws SQLException {\n    logger.trace(\"String getUserName()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return session.getUser();\n  }\n\n  @Override\n  public boolean isReadOnly() throws SQLException {\n    logger.trace(\"boolean isReadOnly()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    // no read only mode is supported.\n    return false;\n  }\n\n  @Override\n  public boolean nullsAreSortedHigh() throws SQLException {\n    logger.trace(\"boolean nullsAreSortedHigh()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean nullsAreSortedLow() throws SQLException {\n    logger.trace(\"boolean nullsAreSortedLow()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean nullsAreSortedAtStart() throws SQLException {\n    logger.trace(\"boolean nullsAreSortedAtStart()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean nullsAreSortedAtEnd() throws SQLException {\n    logger.trace(\"boolean nullsAreSortedAtEnd()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public String getDatabaseProductName() throws SQLException {\n    logger.trace(\"String getDatabaseProductName()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return DatabaseProductName;\n  }\n\n  @Override\n  public String getDatabaseProductVersion() throws SQLException {\n    logger.trace(\"String getDatabaseProductVersion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return connection.unwrap(SnowflakeConnectionImpl.class).getDatabaseVersion();\n  }\n\n  @Override\n  public String getDriverName() throws SQLException {\n    logger.trace(\"String getDriverName()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return DriverName;\n  }\n\n  @Override\n  public String getDriverVersion() throws SQLException {\n    logger.trace(\"String getDriverVersion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return SnowflakeDriver.INSTANCE.getMajorVersion()\n        + \".\"\n        + SnowflakeDriver.INSTANCE.getMinorVersion()\n        + \".\"\n        + SnowflakeDriver.INSTANCE.getPatchVersion();\n  }\n\n  @Override\n  public int getDriverMajorVersion() {\n    logger.trace(\"int getDriverMajorVersion()\", false);\n    return SnowflakeDriver.INSTANCE.getMajorVersion();\n  }\n\n  @Override\n  public int getDriverMinorVersion() {\n    logger.trace(\"int getDriverMinorVersion()\", false);\n    return SnowflakeDriver.INSTANCE.getMinorVersion();\n  }\n\n  @Override\n  public boolean usesLocalFiles() throws SQLException {\n    logger.trace(\"boolean usesLocalFiles()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean usesLocalFilePerTable() throws SQLException {\n    logger.trace(\"boolean usesLocalFilePerTable()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsMixedCaseIdentifiers() throws SQLException {\n    logger.trace(\"boolean supportsMixedCaseIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean storesUpperCaseIdentifiers() throws SQLException {\n    logger.trace(\"boolean storesUpperCaseIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean storesLowerCaseIdentifiers() throws SQLException {\n    logger.trace(\"boolean storesLowerCaseIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean storesMixedCaseIdentifiers() throws SQLException {\n    logger.trace(\"boolean storesMixedCaseIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {\n    logger.trace(\"boolean supportsMixedCaseQuotedIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {\n    logger.trace(\"boolean storesUpperCaseQuotedIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {\n    logger.trace(\"boolean storesLowerCaseQuotedIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {\n    logger.trace(\"boolean storesMixedCaseQuotedIdentifiers()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public String getIdentifierQuoteString() throws SQLException {\n    logger.trace(\"String getIdentifierQuoteString()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return \"\\\"\";\n  }\n\n  @Override\n  public String getSQLKeywords() throws SQLException {\n    logger.trace(\"String getSQLKeywords()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return notSQL2003Keywords;\n  }\n\n  @Override\n  public String getNumericFunctions() throws SQLException {\n    logger.trace(\"String getNumericFunctions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return NumericFunctionsSupported;\n  }\n\n  @Override\n  public String getStringFunctions() throws SQLException {\n    logger.trace(\"String getStringFunctions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return StringFunctionsSupported;\n  }\n\n  @Override\n  public String getSystemFunctions() throws SQLException {\n    logger.trace(\"String getSystemFunctions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return SystemFunctionsSupported;\n  }\n\n  @Override\n  public String getTimeDateFunctions() throws SQLException {\n    logger.trace(\"String getTimeDateFunctions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return DateAndTimeFunctionsSupported;\n  }\n\n  @Override\n  public String getSearchStringEscape() throws SQLException {\n    logger.trace(\"String getSearchStringEscape()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return Character.toString(SEARCH_STRING_ESCAPE);\n  }\n\n  @Override\n  public String getExtraNameCharacters() throws SQLException {\n    logger.trace(\"String getExtraNameCharacters()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return \"$\";\n  }\n\n  @Override\n  public boolean supportsAlterTableWithAddColumn() throws SQLException {\n    logger.trace(\"boolean supportsAlterTableWithAddColumn()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsAlterTableWithDropColumn() throws SQLException {\n    logger.trace(\"boolean supportsAlterTableWithDropColumn()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsColumnAliasing() throws SQLException {\n    logger.trace(\"boolean supportsColumnAliasing()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean nullPlusNonNullIsNull() throws SQLException {\n    logger.trace(\"boolean nullPlusNonNullIsNull()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsConvert() throws SQLException {\n    logger.trace(\"boolean supportsConvert()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsConvert(int fromType, int toType) throws SQLException {\n    logger.trace(\"boolean supportsConvert(int fromType, int toType)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsTableCorrelationNames() throws SQLException {\n    logger.trace(\"boolean supportsTableCorrelationNames()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsDifferentTableCorrelationNames() throws SQLException {\n    logger.trace(\"boolean supportsDifferentTableCorrelationNames()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsExpressionsInOrderBy() throws SQLException {\n    logger.trace(\"boolean supportsExpressionsInOrderBy()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsOrderByUnrelated() throws SQLException {\n    logger.trace(\"boolean supportsOrderByUnrelated()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsGroupBy() throws SQLException {\n    logger.trace(\"boolean supportsGroupBy()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsGroupByUnrelated() throws SQLException {\n    logger.trace(\"boolean supportsGroupByUnrelated()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsGroupByBeyondSelect() throws SQLException {\n    logger.trace(\"boolean supportsGroupByBeyondSelect()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsLikeEscapeClause() throws SQLException {\n    logger.trace(\"boolean supportsLikeEscapeClause()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsMultipleResultSets() throws SQLException {\n    logger.trace(\"boolean supportsMultipleResultSets()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsMultipleTransactions() throws SQLException {\n    logger.trace(\"boolean supportsMultipleTransactions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsNonNullableColumns() throws SQLException {\n    logger.trace(\"boolean supportsNonNullableColumns()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsMinimumSQLGrammar() throws SQLException {\n    logger.trace(\"boolean supportsMinimumSQLGrammar()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsCoreSQLGrammar() throws SQLException {\n    logger.trace(\"boolean supportsCoreSQLGrammar()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsExtendedSQLGrammar() throws SQLException {\n    logger.trace(\"boolean supportsExtendedSQLGrammar()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsANSI92EntryLevelSQL() throws SQLException {\n    logger.trace(\"boolean supportsANSI92EntryLevelSQL()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsANSI92IntermediateSQL() throws SQLException {\n    logger.trace(\"boolean supportsANSI92IntermediateSQL()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsANSI92FullSQL() throws SQLException {\n    logger.trace(\"boolean supportsANSI92FullSQL()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsIntegrityEnhancementFacility() throws SQLException {\n    logger.trace(\"boolean supportsIntegrityEnhancementFacility()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsOuterJoins() throws SQLException {\n    logger.trace(\"boolean supportsOuterJoins()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsFullOuterJoins() throws SQLException {\n    logger.trace(\"boolean supportsFullOuterJoins()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsLimitedOuterJoins() throws SQLException {\n    logger.trace(\"boolean supportsLimitedOuterJoins()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public String getSchemaTerm() throws SQLException {\n    logger.trace(\"String getSchemaTerm()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return \"schema\";\n  }\n\n  @Override\n  public String getProcedureTerm() throws SQLException {\n    logger.trace(\"String getProcedureTerm()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return \"procedure\";\n  }\n\n  @Override\n  public String getCatalogTerm() throws SQLException {\n    logger.trace(\"String getCatalogTerm()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return \"database\";\n  }\n\n  @Override\n  public boolean isCatalogAtStart() throws SQLException {\n    logger.trace(\"boolean isCatalogAtStart()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public String getCatalogSeparator() throws SQLException {\n    logger.trace(\"String getCatalogSeparator()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return \".\";\n  }\n\n  @Override\n  public boolean supportsSchemasInDataManipulation() throws SQLException {\n    logger.trace(\"boolean supportsSchemasInDataManipulation()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsSchemasInProcedureCalls() throws SQLException {\n    logger.trace(\"boolean supportsSchemasInProcedureCalls()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsSchemasInTableDefinitions() throws SQLException {\n    logger.trace(\"boolean supportsSchemasInTableDefinitions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsSchemasInIndexDefinitions() throws SQLException {\n    logger.trace(\"boolean supportsSchemasInIndexDefinitions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {\n    logger.trace(\"boolean supportsSchemasInPrivilegeDefinitions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInDataManipulation() throws SQLException {\n    logger.trace(\"boolean supportsCatalogsInDataManipulation()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsCatalogsInProcedureCalls() throws SQLException {\n    logger.trace(\"boolean supportsCatalogsInProcedureCalls()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInTableDefinitions() throws SQLException {\n    logger.trace(\"boolean supportsCatalogsInTableDefinitions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsCatalogsInIndexDefinitions() throws SQLException {\n    logger.trace(\"boolean supportsCatalogsInIndexDefinitions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {\n    logger.trace(\"boolean supportsCatalogsInPrivilegeDefinitions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsPositionedDelete() throws SQLException {\n    logger.trace(\"boolean supportsPositionedDelete()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsPositionedUpdate() throws SQLException {\n    logger.trace(\"boolean supportsPositionedUpdate()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsSelectForUpdate() throws SQLException {\n    logger.trace(\"boolean supportsSelectForUpdate()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsStoredProcedures() throws SQLException {\n    logger.trace(\"boolean supportsStoredProcedures()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInComparisons() throws SQLException {\n    logger.trace(\"boolean supportsSubqueriesInComparisons()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInExists() throws SQLException {\n    logger.trace(\"boolean supportsSubqueriesInExists()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInIns() throws SQLException {\n    logger.trace(\"boolean supportsSubqueriesInIns()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsSubqueriesInQuantifieds() throws SQLException {\n    logger.trace(\"boolean supportsSubqueriesInQuantifieds()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsCorrelatedSubqueries() throws SQLException {\n    logger.trace(\"boolean supportsCorrelatedSubqueries()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsUnion() throws SQLException {\n    logger.trace(\"boolean supportsUnion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsUnionAll() throws SQLException {\n    logger.trace(\"boolean supportsUnionAll()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsOpenCursorsAcrossCommit() throws SQLException {\n    logger.trace(\"boolean supportsOpenCursorsAcrossCommit()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenCursorsAcrossRollback() throws SQLException {\n    logger.trace(\"boolean supportsOpenCursorsAcrossRollback()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenStatementsAcrossCommit() throws SQLException {\n    logger.trace(\"boolean supportsOpenStatementsAcrossCommit()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsOpenStatementsAcrossRollback() throws SQLException {\n    logger.trace(\"boolean supportsOpenStatementsAcrossRollback()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public int getMaxBinaryLiteralLength() throws SQLException {\n    logger.trace(\"int getMaxBinaryLiteralLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return getMaxCharLiteralLength() / 2; // hex instead of octal, thus divided by 2\n  }\n\n  @Override\n  public int getMaxCharLiteralLength() throws SQLException {\n    logger.trace(\"int getMaxCharLiteralLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Optional<Integer> maxLiteralLengthFromSession =\n        Optional.ofNullable(\n            (Integer) session.getOtherParameter(MAX_VARCHAR_BINARY_SIZE_PARAM_NAME));\n    return maxLiteralLengthFromSession.orElse(DEFAULT_MAX_LOB_SIZE);\n  }\n\n  @Override\n  public int getMaxColumnNameLength() throws SQLException {\n    logger.trace(\"int getMaxColumnNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 255;\n  }\n\n  @Override\n  public int getMaxColumnsInGroupBy() throws SQLException {\n    logger.trace(\"int getMaxColumnsInGroupBy()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInIndex() throws SQLException {\n    logger.trace(\"int getMaxColumnsInIndex()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInOrderBy() throws SQLException {\n    logger.trace(\"int getMaxColumnsInOrderBy()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInSelect() throws SQLException {\n    logger.trace(\"int getMaxColumnsInSelect()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxColumnsInTable() throws SQLException {\n    logger.trace(\"int getMaxColumnsInTable()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxConnections() throws SQLException {\n    logger.trace(\"int getMaxConnections()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxCursorNameLength() throws SQLException {\n    logger.trace(\"int getMaxCursorNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxIndexLength() throws SQLException {\n    logger.trace(\"int getMaxIndexLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxSchemaNameLength() throws SQLException {\n    logger.trace(\"int getMaxSchemaNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 255;\n  }\n\n  @Override\n  public int getMaxProcedureNameLength() throws SQLException {\n    logger.trace(\"int getMaxProcedureNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxCatalogNameLength() throws SQLException {\n    logger.trace(\"int getMaxCatalogNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 255;\n  }\n\n  @Override\n  public int getMaxRowSize() throws SQLException {\n    logger.trace(\"int getMaxRowSize()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {\n    logger.trace(\"boolean doesMaxRowSizeIncludeBlobs()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public int getMaxStatementLength() throws SQLException {\n    logger.trace(\"int getMaxStatementLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxStatements() throws SQLException {\n    logger.trace(\"int getMaxStatements()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxTableNameLength() throws SQLException {\n    logger.trace(\"int getMaxTableNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 255;\n  }\n\n  @Override\n  public int getMaxTablesInSelect() throws SQLException {\n    logger.trace(\"int getMaxTablesInSelect()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 0;\n  }\n\n  @Override\n  public int getMaxUserNameLength() throws SQLException {\n    logger.trace(\"int getMaxUserNameLength()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return 255;\n  }\n\n  @Override\n  public int getDefaultTransactionIsolation() throws SQLException {\n    logger.trace(\"int getDefaultTransactionIsolation()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return Connection.TRANSACTION_READ_COMMITTED;\n  }\n\n  @Override\n  public boolean supportsTransactions() throws SQLException {\n    logger.trace(\"boolean supportsTransactions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsTransactionIsolationLevel(int level) throws SQLException {\n    logger.trace(\"boolean supportsTransactionIsolationLevel(int level)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return (level == Connection.TRANSACTION_NONE)\n        || (level == Connection.TRANSACTION_READ_COMMITTED);\n  }\n\n  @Override\n  public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {\n    logger.trace(\"boolean supportsDataDefinitionAndDataManipulationTransactions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean supportsDataManipulationTransactionsOnly() throws SQLException {\n    logger.trace(\"boolean supportsDataManipulationTransactionsOnly()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean dataDefinitionCausesTransactionCommit() throws SQLException {\n    logger.trace(\"boolean dataDefinitionCausesTransactionCommit()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean dataDefinitionIgnoredInTransactions() throws SQLException {\n    logger.trace(\"boolean dataDefinitionIgnoredInTransactions()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public ResultSet getProcedures(\n      final String originalCatalog,\n      final String originalSchemaPattern,\n      final String procedureNamePattern)\n      throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n    logger.trace(\n        \"public ResultSet getProcedures(String originalCatalog, \"\n            + \"String originalSchemaPattern,String procedureNamePattern)\",\n        false);\n\n    String showProcedureCommand =\n        getFirstResultSetCommand(\n            originalCatalog, originalSchemaPattern, procedureNamePattern, \"procedures\");\n\n    if (showProcedureCommand.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_PROCEDURES, statement);\n    }\n\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchemaPattern);\n    String catalog = result.database();\n    String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n    final Pattern compiledProcedurePattern = Wildcard.toRegexPattern(procedureNamePattern, true);\n\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(statement, showProcedureCommand, GET_PROCEDURES);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet, \"getProcedures\", catalog, schemaPattern, procedureNamePattern, \"none\");\n\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_PROCEDURES, resultSet, statement) {\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        // iterate throw the show table result until we find an entry\n        // that matches the table name\n        while (showObjectResultSet.next()) {\n          String catalogName = showObjectResultSet.getString(\"catalog_name\");\n          String schemaName = showObjectResultSet.getString(\"schema_name\");\n          String procedureName = showObjectResultSet.getString(\"name\");\n          String remarks = showObjectResultSet.getString(\"description\");\n          String specificName = showObjectResultSet.getString(\"arguments\");\n          short procedureType = procedureReturnsResult;\n          if ((compiledProcedurePattern == null\n                  || compiledProcedurePattern.matcher(procedureName).matches())\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.matcher(schemaName).matches()\n                  || isExactSchema && schemaPattern.equals(schemaPattern))) {\n            logger.trace(\"Found a matched function:\" + schemaName + \".\" + procedureName);\n\n            nextRow[0] = catalogName;\n            nextRow[1] = schemaName;\n            nextRow[2] = procedureName;\n            nextRow[3] = remarks;\n            nextRow[4] = procedureType;\n            nextRow[5] = specificName;\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  @Override\n  public ResultSet getProcedureColumns(\n      final String catalog,\n      final String schemaPattern,\n      final String procedureNamePattern,\n      final String columnNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getProcedureColumns(String catalog, \"\n            + \"String schemaPattern,String procedureNamePattern,\"\n            + \"String columnNamePattern)\",\n        false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n    boolean addAllRows = false;\n\n    String showProcedureCommand =\n        getFirstResultSetCommand(catalog, schemaPattern, procedureNamePattern, \"procedures\");\n    if (showProcedureCommand.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_PROCEDURE_COLUMNS, statement);\n    }\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n    final Pattern compiledProcedurePattern = Wildcard.toRegexPattern(procedureNamePattern, true);\n\n    if (columnNamePattern == null\n        || columnNamePattern.isEmpty()\n        || columnNamePattern.trim().equals(\"%\")\n        || columnNamePattern.trim().equals(\".*\")) {\n      addAllRows = true;\n    }\n\n    ResultSet resultSetStepOne =\n        executeAndReturnEmptyResultIfNotFound(\n            statement, showProcedureCommand, GET_PROCEDURE_COLUMNS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSetStepOne,\n        \"getProcedureColumns\",\n        catalog,\n        schemaPattern,\n        procedureNamePattern,\n        columnNamePattern);\n    ArrayList<Object[]> rows = new ArrayList<Object[]>();\n    while (resultSetStepOne.next()) {\n      String procedureNameUnparsed = resultSetStepOne.getString(\"arguments\").trim();\n      String procedureNameNoArgs = resultSetStepOne.getString(\"name\");\n      String schemaName = resultSetStepOne.getString(\"schema_name\");\n      // Check that schema name match the original input\n      // And check special case - schema with special name in quotes\n      boolean isSchemaNameMatch =\n          compiledSchemaPattern != null\n              && (compiledSchemaPattern.matcher(schemaName).matches()\n                  || (schemaName.startsWith(\"\\\"\")\n                      && schemaName.endsWith(\"\\\"\")\n                      && compiledSchemaPattern\n                          .matcher(schemaName)\n                          .region(1, schemaName.length() - 1)\n                          .matches()));\n\n      // Check that procedure name and schema name match the original input in case wildcards have\n      // been used.\n      // Procedure name column check must occur later when columns are parsed.\n      if ((compiledProcedurePattern != null\n              && !compiledProcedurePattern.matcher(procedureNameNoArgs).matches())\n          || (compiledSchemaPattern != null && !isSchemaNameMatch)) {\n        continue;\n      }\n      String catalogName = resultSetStepOne.getString(\"catalog_name\");\n      String showProcedureColCommand =\n          getSecondResultSetCommand(catalogName, schemaName, procedureNameUnparsed, \"procedure\");\n\n      ResultSet resultSetStepTwo =\n          executeAndReturnEmptyResultIfNotFound(\n              statement, showProcedureColCommand, GET_PROCEDURE_COLUMNS);\n      if (resultSetStepTwo.next() == false) {\n        continue;\n      }\n      // Retrieve the procedure arguments and procedure return values.\n      String args = resultSetStepTwo.getString(\"value\");\n      resultSetStepTwo.next();\n      String res = resultSetStepTwo.getString(\"value\");\n      // parse procedure arguments and return values into a list of columns\n      // result value(s) will be at the top of the list, followed by any arguments\n      List<String> procedureCols = parseColumns(res, args);\n      String paramNames[] = new String[procedureCols.size() / 2];\n      String paramTypes[] = new String[procedureCols.size() / 2];\n      if (procedureCols.size() > 1) {\n        for (int i = 0; i < procedureCols.size(); i++) {\n          if (i % 2 == 0) {\n            paramNames[i / 2] = procedureCols.get(i);\n          } else {\n            paramTypes[i / 2] = procedureCols.get(i);\n          }\n        }\n      }\n      for (int i = 0; i < paramNames.length; i++) {\n        // if it's the 1st in for loop, it's the result\n        if (i == 0 || paramNames[i].equalsIgnoreCase(columnNamePattern) || addAllRows) {\n          Object[] nextRow = new Object[20];\n          // add a row to resultSet\n          nextRow[0] = catalog; // catalog. Can be null.\n          nextRow[1] = schemaName; // schema. Can be null.\n          nextRow[2] = procedureNameNoArgs; // procedure name\n          nextRow[3] = paramNames[i]; // column/parameter name\n          // column type\n          if (i == 0 && procedureResultsetColumnNum < 0) {\n            nextRow[4] = procedureColumnReturn;\n          } else if (procedureResultsetColumnNum >= 0 && i < procedureResultsetColumnNum) {\n            nextRow[4] = procedureColumnResult;\n          } else {\n            nextRow[4] = procedureColumnIn; // kind of column/parameter\n          }\n          String typeName = paramTypes[i];\n          String typeNameTrimmed = typeName;\n          // don't include nullability in type name, such as NUMBER NOT NULL. Just include NUMBER.\n          if (typeName.contains(\" NOT NULL\")) {\n            typeNameTrimmed = typeName.substring(0, typeName.indexOf(' '));\n          }\n          // don't include column size in type name\n          if (typeNameTrimmed.contains(\"(\") && typeNameTrimmed.contains(\")\")) {\n            typeNameTrimmed = typeNameTrimmed.substring(0, typeNameTrimmed.indexOf('('));\n          }\n          int type = convertStringToType(typeName);\n          nextRow[5] = type; // data type\n          nextRow[6] = typeNameTrimmed; // type name\n          // precision and scale. Values only exist for numbers\n          int precision = 38;\n          short scale = 0;\n          if (type < 10) {\n            if (typeName.contains(\"(\") && typeName.contains(\")\")) {\n              precision =\n                  Integer.parseInt(\n                      typeName.substring(typeName.indexOf('(') + 1, typeName.indexOf(',')));\n              scale =\n                  Short.parseShort(\n                      typeName.substring(typeName.indexOf(',') + 1, typeName.indexOf(')')));\n              nextRow[7] = precision;\n              nextRow[9] = scale;\n            } else {\n              nextRow[7] = precision;\n              nextRow[9] = scale;\n            }\n          } else {\n            nextRow[7] = 0;\n            nextRow[9] = null;\n          }\n          nextRow[8] = 0; // length in bytes. not supported\n          nextRow[10] = 10; // radix. Probably 10 is default, but unknown.\n          // if type specifies \"not null\", no null values are allowed.\n          if (typeName.toLowerCase().contains(\"not null\")) {\n            nextRow[11] = procedureNoNulls;\n            nextRow[18] = \"NO\";\n          }\n          // if the type is a return value (only when i = 0), it can always be specified as \"not\n          // null.\" The fact that\n          // this isn't specified means it has nullable return values.\n          else if (i == 0) {\n            nextRow[11] = procedureNullable;\n            nextRow[18] = \"YES\";\n          }\n          // if the row is for an input parameter, it's impossible to know from the description\n          // whether the values\n          // are allowed to be null or not. Nullability is unknown.\n          else {\n            nextRow[11] =\n                procedureNullableUnknown; // nullable. We don't know from current function info.\n            nextRow[18] = \"\";\n          }\n          nextRow[12] = resultSetStepOne.getString(\"description\").trim(); // remarks\n          nextRow[13] = null; // default value for column. Not supported\n          nextRow[14] = 0; // Sql data type: reserved for future use\n          nextRow[15] = 0; // sql datetime sub: reserved for future use\n          // char octet length\n          if (type == Types.BINARY\n              || type == Types.VARBINARY\n              || type == Types.CHAR\n              || type == Types.VARCHAR) {\n            if (typeName.contains(\"(\") && typeName.contains(\")\")) {\n              int char_octet_len =\n                  Integer.parseInt(\n                      typeName.substring(typeName.indexOf('(') + 1, typeName.indexOf(')')));\n              nextRow[16] = char_octet_len;\n            } else if (type == Types.CHAR || type == Types.VARCHAR) {\n              nextRow[16] = getMaxCharLiteralLength();\n            } else if (type == Types.BINARY || type == Types.VARBINARY) {\n              nextRow[16] = getMaxBinaryLiteralLength();\n            }\n          } else {\n            nextRow[16] = null;\n          }\n          // the ordinal position is 0 for a return value.\n          // for result set columns, the ordinal position is of the column in the result set\n          // starting at 1\n          if (procedureResultsetColumnNum >= 0) {\n            if (i < procedureResultsetColumnNum) {\n              nextRow[17] = i + 1;\n            } else {\n              nextRow[17] = i - procedureResultsetColumnNum + 1;\n            }\n          } else {\n            nextRow[17] = i; // ordinal position.\n          }\n          nextRow[19] = procedureNameUnparsed; // specific name\n          rows.add(nextRow);\n        }\n      }\n    }\n    Object[][] resultRows = new Object[rows.size()][20];\n    for (int i = 0; i < resultRows.length; i++) {\n      resultRows[i] = rows.get(i);\n    }\n    return new SnowflakeDatabaseMetaDataResultSet(GET_PROCEDURE_COLUMNS, resultRows, statement);\n  }\n\n  // apply session context when catalog is unspecified\n  private ContextAwareMetadataSearch applySessionContext(String catalog, String schemaPattern) {\n    if (metadataRequestUseConnectionCtx) {\n      // CLIENT_METADATA_USE_SESSION_DATABASE = TRUE\n      if (catalog == null) {\n        catalog = session.getDatabase();\n      }\n      if (schemaPattern == null) {\n        schemaPattern = session.getSchema();\n        useSessionSchema = true;\n      }\n    } else {\n      if (metadataRequestUseSessionDatabase) {\n        if (catalog == null) {\n          catalog = session.getDatabase();\n        }\n      }\n    }\n    return new ContextAwareMetadataSearch(\n        catalog,\n        schemaPattern,\n        (exactSchemaSearchEnabled && useSessionSchema) || !enableWildcardsInShowMetadataCommands);\n  }\n\n  /* helper function for getProcedures, getFunctionColumns, etc. Returns sql command to show some type of result such\n  as procedures or udfs */\n  private String getFirstResultSetCommand(\n      String catalog, String schemaPattern, String name, String type) {\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result = applySessionContext(catalog, schemaPattern);\n    catalog = result.database();\n    schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    String showProcedureCommand = \"show /* JDBC:DatabaseMetaData.getProcedures() */ \" + type;\n\n    if (name != null && !name.isEmpty() && !name.trim().equals(\"%\") && !name.trim().equals(\".*\")) {\n      showProcedureCommand += \" like '\" + escapeSingleQuoteForLikeCommand(name) + \"'\";\n    }\n\n    if (catalog == null) {\n      showProcedureCommand += \" in account\";\n    } else if (catalog.isEmpty()) {\n      return \"\";\n    } else {\n      String catalogEscaped = escapeSqlQuotes(catalog);\n      if (!isExactSchema && (schemaPattern == null || isSchemaNameWildcardPattern(schemaPattern))) {\n        showProcedureCommand += \" in database \\\"\" + catalogEscaped + \"\\\"\";\n      } else if (schemaPattern.isEmpty()) {\n        return \"\";\n      } else {\n        schemaPattern = unescapeChars(schemaPattern);\n        showProcedureCommand += \" in schema \\\"\" + catalogEscaped + \"\\\".\\\"\" + schemaPattern + \"\\\"\";\n      }\n    }\n    logger.debug(\"Sql command to get column metadata: {}\", showProcedureCommand);\n\n    return showProcedureCommand;\n  }\n\n  /* another helper function for getProcedures, getFunctionColumns, etc. Returns sql command that describes\n  procedures or functions */\n  private String getSecondResultSetCommand(\n      String catalog, String schemaPattern, String name, String type) {\n    if (isNullOrEmpty(name)) {\n      return \"\";\n    }\n    String procedureCols = name.substring(name.indexOf(\"(\"), name.indexOf(\" RETURN\"));\n    String quotedName = \"\\\"\" + name.substring(0, name.indexOf(\"(\")) + \"\\\"\";\n    String procedureName = quotedName + procedureCols;\n    String showProcedureColCommand;\n    if (!isNullOrEmpty(catalog) && !isNullOrEmpty(schemaPattern)) {\n      showProcedureColCommand =\n          \"desc \" + type + \" \" + catalog + \".\" + schemaPattern + \".\" + procedureName;\n    } else if (!isNullOrEmpty(schemaPattern)) {\n      showProcedureColCommand = \"desc \" + type + \" \" + schemaPattern + \".\" + procedureName;\n    } else {\n      showProcedureColCommand = \"desc \" + type + \" \" + procedureName;\n    }\n    return showProcedureColCommand;\n  }\n\n  @Override\n  public ResultSet getTables(\n      String originalCatalog,\n      String originalSchemaPattern,\n      final String tableNamePattern,\n      final String[] types)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getTables(String catalog={}, String \"\n            + \"schemaPattern={}, String tableNamePattern={}, String[] types={})\",\n        originalCatalog,\n        originalSchemaPattern,\n        tableNamePattern,\n        (ArgSupplier) () -> Arrays.toString(types));\n\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    Set<String> supportedTableTypes = new HashSet<>();\n    ResultSet resultSet = getTableTypes();\n    while (resultSet.next()) {\n      supportedTableTypes.add(resultSet.getString(\"TABLE_TYPE\"));\n    }\n    resultSet.close();\n\n    List<String> inputValidTableTypes = new ArrayList<>();\n    // then filter on the input table types;\n    if (types != null) {\n      for (String t : types) {\n        if (supportedTableTypes.contains(t)) {\n          inputValidTableTypes.add(t);\n        }\n      }\n    } else {\n      inputValidTableTypes = new ArrayList<String>(supportedTableTypes);\n    }\n\n    // if the input table types don't have types supported by Snowflake,\n    // then return an empty result set directly\n    Statement statement = connection.createStatement();\n    if (inputValidTableTypes.size() == 0) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_TABLES, statement);\n    }\n\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchemaPattern);\n    String catalog = result.database();\n    String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n    final Pattern compiledTablePattern = Wildcard.toRegexPattern(tableNamePattern, true);\n\n    String showTablesCommand = null;\n    final boolean viewOnly =\n        inputValidTableTypes.size() == 1 && \"VIEW\".equalsIgnoreCase(inputValidTableTypes.get(0));\n    final boolean tableOnly =\n        inputValidTableTypes.size() == 1 && \"TABLE\".equalsIgnoreCase(inputValidTableTypes.get(0));\n    if (viewOnly) {\n      showTablesCommand = \"show /* JDBC:DatabaseMetaData.getTables() */ views\";\n    } else if (tableOnly) {\n      showTablesCommand = \"show /* JDBC:DatabaseMetaData.getTables() */ tables\";\n    } else {\n      showTablesCommand = \"show /* JDBC:DatabaseMetaData.getTables() */ objects\";\n    }\n\n    // only add pattern if it is not empty and not matching all character.\n    if (tableNamePattern != null\n        && !tableNamePattern.isEmpty()\n        && !tableNamePattern.trim().equals(\"%\")\n        && !tableNamePattern.trim().equals(\".*\")) {\n      showTablesCommand += \" like '\" + escapeSingleQuoteForLikeCommand(tableNamePattern) + \"'\";\n    }\n\n    if (catalog == null) {\n      showTablesCommand += \" in account\";\n    } else if (catalog.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_TABLES, statement);\n    } else {\n      String catalogEscaped = escapeSqlQuotes(catalog);\n      // if the schema pattern is a deterministic identifier, specify schema\n      // in the show command. This is necessary for us to see any tables in\n      // a schema if the current schema a user is connected to is different\n      // given that we don't support show tables without a known schema.\n      if (schemaPattern == null || isSchemaNameWildcardPattern(schemaPattern)) {\n        showTablesCommand += \" in database \\\"\" + catalogEscaped + \"\\\"\";\n      } else if (schemaPattern.isEmpty()) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_TABLES, statement);\n      } else {\n        String schemaUnescaped = isExactSchema ? schemaPattern : unescapeChars(schemaPattern);\n        showTablesCommand += \" in schema \\\"\" + catalogEscaped + \"\\\".\\\"\" + schemaUnescaped + \"\\\"\";\n      }\n    }\n\n    logger.debug(\"Sql command to get table metadata: {}\", showTablesCommand);\n\n    resultSet = executeAndReturnEmptyResultIfNotFound(statement, showTablesCommand, GET_TABLES);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet,\n        \"getTables\",\n        originalCatalog,\n        originalSchemaPattern,\n        tableNamePattern,\n        Arrays.toString(types));\n\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_TABLES, resultSet, statement) {\n      @Override\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        // iterate throw the show table result until we find an entry\n        // that matches the table name\n        while (showObjectResultSet.next()) {\n          String tableName = showObjectResultSet.getString(2);\n\n          String dbName;\n          String schemaName;\n          String kind;\n          String comment;\n\n          if (viewOnly) {\n            dbName = showObjectResultSet.getString(4);\n            schemaName = showObjectResultSet.getString(5);\n            kind = \"VIEW\";\n            comment = showObjectResultSet.getString(7);\n          } else {\n            dbName = showObjectResultSet.getString(3);\n            schemaName = showObjectResultSet.getString(4);\n            kind = showObjectResultSet.getString(5);\n            comment = showObjectResultSet.getString(6);\n          }\n\n          if ((compiledTablePattern == null || compiledTablePattern.matcher(tableName).matches())\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.matcher(schemaName).matches())) {\n            nextRow[0] = dbName;\n            nextRow[1] = schemaName;\n            nextRow[2] = tableName;\n            nextRow[3] = kind;\n            nextRow[4] = comment;\n            nextRow[5] = null;\n            nextRow[6] = null;\n            nextRow[7] = null;\n            nextRow[8] = null;\n            nextRow[9] = null;\n            return true;\n          }\n        }\n\n        close();\n        return false;\n      }\n    };\n  }\n\n  @Override\n  public ResultSet getSchemas() throws SQLException {\n    logger.trace(\"ResultSet getSchemas()\", false);\n\n    return getSchemas(null, null);\n  }\n\n  @Override\n  public ResultSet getCatalogs() throws SQLException {\n    logger.trace(\"ResultSet getCatalogs()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    String showDB = \"show /* JDBC:DatabaseMetaData.getCatalogs() */ databases in account\";\n\n    Statement statement = connection.createStatement();\n    return new SnowflakeDatabaseMetaDataQueryResultSet(\n        GET_CATALOGS, statement.executeQuery(showDB), statement) {\n      @Override\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        // iterate throw the show databases result\n        if (showObjectResultSet.next()) {\n          String dbName = showObjectResultSet.getString(2);\n\n          nextRow[0] = dbName;\n          return true;\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  @Override\n  public ResultSet getTableTypes() throws SQLException {\n    logger.trace(\"ResultSet getTableTypes()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n\n    // TODO: We should really get the list of table types from GS\n    return new SnowflakeDatabaseMetaDataResultSet(\n        Collections.singletonList(\"TABLE_TYPE\"),\n        Collections.singletonList(\"TEXT\"),\n        Collections.singletonList(Types.VARCHAR),\n        new Object[][] {{\"TABLE\"}, {\"VIEW\"}},\n        statement);\n  }\n\n  @Override\n  public ResultSet getColumns(\n      String catalog,\n      String schemaPattern,\n      final String tableNamePattern,\n      final String columnNamePattern)\n      throws SQLException {\n    return getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern, false);\n  }\n\n  public ResultSet getColumns(\n      String originalCatalog,\n      String originalSchemaPattern,\n      final String tableNamePattern,\n      final String columnNamePattern,\n      final boolean extendedSet)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getColumns(String catalog={}, String schemaPattern={}, \"\n            + \"String tableNamePattern={}, String columnNamePattern={}, boolean extendedSet={}\",\n        originalCatalog,\n        originalSchemaPattern,\n        tableNamePattern,\n        columnNamePattern,\n        extendedSet);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchemaPattern);\n    String catalog = result.database();\n    String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n    final Pattern compiledTablePattern = Wildcard.toRegexPattern(tableNamePattern, true);\n    final Pattern compiledColumnPattern = Wildcard.toRegexPattern(columnNamePattern, true);\n\n    String showColumnsCommand = \"show /* JDBC:DatabaseMetaData.getColumns() */ columns\";\n\n    if (columnNamePattern != null\n        && !columnNamePattern.isEmpty()\n        && !columnNamePattern.trim().equals(\"%\")\n        && !columnNamePattern.trim().equals(\".*\")) {\n      showColumnsCommand += \" like '\" + escapeSingleQuoteForLikeCommand(columnNamePattern) + \"'\";\n    }\n\n    if (catalog == null) {\n      showColumnsCommand += \" in account\";\n    } else if (catalog.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(\n          extendedSet ? GET_COLUMNS_EXTENDED_SET : GET_COLUMNS, statement);\n    } else {\n      String catalogEscaped = escapeSqlQuotes(catalog);\n      if (schemaPattern == null || isSchemaNameWildcardPattern(schemaPattern)) {\n        showColumnsCommand += \" in database \\\"\" + catalogEscaped + \"\\\"\";\n      } else if (schemaPattern.isEmpty()) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(\n            extendedSet ? GET_COLUMNS_EXTENDED_SET : GET_COLUMNS, statement);\n      } else {\n        String schemaUnescaped = isExactSchema ? schemaPattern : unescapeChars(schemaPattern);\n        if (tableNamePattern == null\n            || (Wildcard.isWildcardPatternStr(tableNamePattern)\n                && enableWildcardsInShowMetadataCommands)) {\n          showColumnsCommand += \" in schema \\\"\" + catalogEscaped + \"\\\".\\\"\" + schemaUnescaped + \"\\\"\";\n        } else if (tableNamePattern.isEmpty()) {\n          return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(\n              extendedSet ? GET_COLUMNS_EXTENDED_SET : GET_COLUMNS, statement);\n        } else {\n          String tableNameUnescaped = unescapeChars(tableNamePattern);\n          showColumnsCommand +=\n              \" in table \\\"\"\n                  + catalogEscaped\n                  + \"\\\".\\\"\"\n                  + schemaUnescaped\n                  + \"\\\".\\\"\"\n                  + tableNameUnescaped\n                  + \"\\\"\";\n        }\n      }\n    }\n\n    logger.debug(\"Sql command to get column metadata: {}\", showColumnsCommand);\n\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(\n            statement, showColumnsCommand, extendedSet ? GET_COLUMNS_EXTENDED_SET : GET_COLUMNS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet,\n        \"getColumns\",\n        originalCatalog,\n        originalSchemaPattern,\n        tableNamePattern,\n        columnNamePattern);\n\n    return new SnowflakeDatabaseMetaDataQueryResultSet(\n        extendedSet ? GET_COLUMNS_EXTENDED_SET : GET_COLUMNS, resultSet, statement) {\n      int ordinalPosition = 0;\n\n      String currentTableName = null;\n\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        // iterate throw the show table result until we find an entry\n        // that matches the table name\n        while (showObjectResultSet.next()) {\n          String tableName = showObjectResultSet.getString(1);\n          String schemaName = showObjectResultSet.getString(2);\n          String columnName = showObjectResultSet.getString(3);\n          String dataTypeStr = showObjectResultSet.getString(4);\n          String defaultValue = showObjectResultSet.getString(6);\n          defaultValue.trim();\n          if (defaultValue.isEmpty()) {\n            defaultValue = null;\n          } else if (!stringsQuoted) {\n            if (defaultValue.startsWith(\"\\'\") && defaultValue.endsWith(\"\\'\")) {\n              // remove extra set of single quotes\n              defaultValue = defaultValue.substring(1, defaultValue.length() - 1);\n              // scan for 2 single quotes in a row and remove one of them\n              defaultValue = defaultValue.replace(\"''\", \"'\");\n            }\n          }\n          String comment = showObjectResultSet.getString(9);\n          String catalogName = showObjectResultSet.getString(10);\n          String autoIncrement = showObjectResultSet.getString(11);\n\n          if ((compiledTablePattern == null || compiledTablePattern.matcher(tableName).matches())\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.matcher(schemaName).matches())\n              && (compiledColumnPattern == null\n                  || compiledColumnPattern.matcher(columnName).matches())) {\n            logger.debug(\"Found a matched column:\" + tableName + \".\" + columnName);\n\n            // reset ordinal position for new table\n            if (!tableName.equals(currentTableName)) {\n              ordinalPosition = 1;\n              currentTableName = tableName;\n            } else {\n              ordinalPosition++;\n            }\n\n            JsonNode jsonNode;\n            try {\n              jsonNode = mapper.readTree(dataTypeStr);\n            } catch (Exception ex) {\n              logger.error(\"Exception when parsing column\" + \" result\", ex);\n\n              throw new SnowflakeSQLException(\n                  SqlState.INTERNAL_ERROR,\n                  ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                  \"error parsing data type: \" + dataTypeStr);\n            }\n\n            logger.debug(\"Data type string: {}\", dataTypeStr);\n\n            SnowflakeColumnMetadata columnMetadata =\n                new SnowflakeColumnMetadata(jsonNode, session.isJdbcTreatDecimalAsInt(), session);\n\n            logger.debug(\"Nullable: {}\", columnMetadata.isNullable());\n\n            // SNOW-16881: add catalog name\n            nextRow[0] = catalogName;\n            nextRow[1] = schemaName;\n            nextRow[2] = tableName;\n            nextRow[3] = columnName;\n\n            int internalColumnType = columnMetadata.getType();\n            int externalColumnType = internalColumnType;\n\n            if (internalColumnType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ) {\n              externalColumnType = Types.TIMESTAMP;\n            }\n            if (internalColumnType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ) {\n\n              externalColumnType =\n                  session == null\n                      ? Types.TIMESTAMP_WITH_TIMEZONE\n                      : session.getEnableReturnTimestampWithTimeZone()\n                          ? Types.TIMESTAMP_WITH_TIMEZONE\n                          : Types.TIMESTAMP;\n            }\n\n            nextRow[4] = externalColumnType;\n            nextRow[5] = columnMetadata.getTypeName();\n\n            nextRow[6] = getColumnSize(columnMetadata);\n            nextRow[7] = null;\n            nextRow[8] = columnMetadata.getScale();\n            nextRow[9] = null;\n            nextRow[10] = (columnMetadata.isNullable() ? columnNullable : columnNoNulls);\n\n            logger.debug(\"Returning nullable: {}\", nextRow[10]);\n\n            nextRow[11] = comment;\n            nextRow[12] = defaultValue;\n            // snow-10597: sql data type is integer instead of string\n            nextRow[13] = externalColumnType;\n            nextRow[14] = null;\n            nextRow[15] =\n                (columnMetadata.getType() == Types.VARCHAR\n                        || columnMetadata.getType() == Types.CHAR)\n                    ? columnMetadata.getLength()\n                    : null;\n            nextRow[16] = ordinalPosition;\n\n            nextRow[17] = (columnMetadata.isNullable() ? \"YES\" : \"NO\");\n            nextRow[18] = null;\n            nextRow[19] = null;\n            nextRow[20] = null;\n            nextRow[21] = null;\n            nextRow[22] = \"\".equals(autoIncrement) ? \"NO\" : \"YES\";\n            nextRow[23] = \"NO\";\n            if (extendedSet) {\n              nextRow[24] = columnMetadata.getBase().name();\n            }\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  static Integer getColumnSize(SnowflakeColumnMetadata columnMetadata) {\n    // The COLUMN_SIZE column specifies the column size for the given column. For numeric data, this\n    // is the maximum precision. For character data, this is the length in characters. For datetime\n    // datatypes, this is the length in characters of the String representation (assuming the\n    // maximum allowed precision of the fractional seconds component). For binary data, this is the\n    // length in bytes. For the ROWID datatype, this is the length in bytes. Null is returned for\n    // data types where the column size is not applicable.\n    switch (columnMetadata.getType()) {\n        // Character data types\n      case Types.CHAR:\n      case Types.VARCHAR:\n        return columnMetadata.getLength();\n        // Binary data types\n      case Types.BINARY:\n      case Types.VARBINARY:\n        return columnMetadata.getLength();\n        // All numeric and datetime types - getPrecision() handles both correctly\n      case Types.DECIMAL:\n      case Types.NUMERIC:\n      case Types.BIGINT:\n      case Types.INTEGER:\n      case Types.SMALLINT:\n      case Types.TINYINT:\n      case Types.FLOAT:\n      case Types.DOUBLE:\n      case Types.REAL:\n      case Types.DATE:\n      case Types.TIME:\n      case Types.TIMESTAMP:\n      case Types.TIMESTAMP_WITH_TIMEZONE:\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ:\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ:\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ:\n      case SnowflakeType.EXTRA_TYPES_DECFLOAT:\n        return columnMetadata.getPrecision();\n        // For VECTOR Snowflake type we consider dimension as the column size\n      case SnowflakeType.EXTRA_TYPES_VECTOR:\n        return columnMetadata.getDimension();\n        // For all other types (BOOLEAN, ARRAY, OBJECT, etc.) return null as per JDBC spec\n        // requirement for non-applicable types\n      default:\n        return null;\n    }\n  }\n\n  @Override\n  public ResultSet getColumnPrivileges(\n      String catalog, String schema, String table, String columnNamePattern) throws SQLException {\n    logger.trace(\n        \"public ResultSet getColumnPrivileges(String catalog, \"\n            + \"String schema,String table, String columnNamePattern)\",\n        false);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    Statement statement = connection.createStatement();\n    return new SnowflakeDatabaseMetaDataResultSet(\n        Arrays.asList(\n            \"TABLE_CAT\",\n            \"TABLE_SCHEM\",\n            \"TABLE_NAME\",\n            \"COLUMN_NAME\",\n            \"GRANTOR\",\n            \"GRANTEE\",\n            \"PRIVILEGE\",\n            \"IS_GRANTABLE\"),\n        Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\"),\n        Arrays.asList(\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR),\n        new Object[][] {},\n        statement);\n  }\n\n  @Override\n  public ResultSet getTablePrivileges(\n      String originalCatalog, String originalSchemaPattern, final String tableNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getTablePrivileges(String catalog, \"\n            + \"String schemaPattern,String tableNamePattern)\",\n        false);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    Statement statement = connection.createStatement();\n\n    if (tableNamePattern == null) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_TABLE_PRIVILEGES, statement);\n    }\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchemaPattern);\n    String catalog = result.database();\n    String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    String showView = \"select * from \";\n\n    if (catalog != null\n        && !catalog.isEmpty()\n        && !catalog.trim().equals(\"%\")\n        && !catalog.trim().equals(\".*\")) {\n      showView += \"\\\"\" + escapeSqlQuotes(catalog) + \"\\\".\";\n    }\n    showView += \"information_schema.table_privileges\";\n\n    if (tableNamePattern != null\n        && !tableNamePattern.isEmpty()\n        && !tableNamePattern.trim().equals(\"%\")\n        && !tableNamePattern.trim().equals(\".*\")) {\n      showView += \" where table_name = '\" + tableNamePattern + \"'\";\n    }\n\n    if (schemaPattern != null\n        && !schemaPattern.isEmpty()\n        && !schemaPattern.trim().equals(\"%\")\n        && !schemaPattern.trim().equals(\".*\")) {\n      String unescapedSchema = isExactSchema ? schemaPattern : unescapeChars(schemaPattern);\n      if (showView.contains(\"where table_name\")) {\n        showView += \" and table_schema = '\" + unescapedSchema + \"'\";\n      } else {\n        showView += \" where table_schema = '\" + unescapedSchema + \"'\";\n      }\n    }\n    showView += \" order by table_catalog, table_schema, table_name, privilege_type\";\n\n    final String catalogIn = catalog;\n    final String schemaIn = schemaPattern;\n    final String tableIn = tableNamePattern;\n\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(statement, showView, GET_TABLE_PRIVILEGES);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet,\n        \"getTablePrivileges\",\n        originalCatalog,\n        originalSchemaPattern,\n        tableNamePattern,\n        \"none\");\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_TABLE_PRIVILEGES, resultSet, statement) {\n      @Override\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        while (showObjectResultSet.next()) {\n          String table_cat = showObjectResultSet.getString(\"TABLE_CATALOG\");\n          String table_schema = showObjectResultSet.getString(\"TABLE_SCHEMA\");\n          String table_name = showObjectResultSet.getString(\"TABLE_NAME\");\n          String grantor = showObjectResultSet.getString(\"GRANTOR\");\n          String grantee = showObjectResultSet.getString(\"GRANTEE\");\n          String privilege = showObjectResultSet.getString(\"PRIVILEGE_TYPE\");\n          String is_grantable = showObjectResultSet.getString(\"IS_GRANTABLE\");\n\n          if ((catalogIn == null\n                  || catalogIn.trim().equals(\"%\")\n                  || catalogIn.trim().equals(table_cat))\n              && (schemaIn == null\n                  || schemaIn.trim().equals(\"%\")\n                  || schemaIn.trim().equals(table_schema))\n              && (tableIn.trim().equals(table_name) || tableIn.trim().equals(\"%\"))) {\n            nextRow[0] = table_cat;\n            nextRow[1] = table_schema;\n            nextRow[2] = table_name;\n            nextRow[3] = grantor;\n            nextRow[4] = grantee;\n            nextRow[5] = privilege;\n            nextRow[6] = is_grantable;\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  @Override\n  public ResultSet getBestRowIdentifier(\n      String catalog, String schema, String table, int scope, boolean nullable)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getBestRowIdentifier(String catalog, \"\n            + \"String schema,String table, int scope,boolean nullable)\",\n        false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getVersionColumns(String catalog, String schema, String table)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getVersionColumns(String catalog, \" + \"String schema, String table)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getPrimaryKeys(String originalCatalog, String originalSchema, final String table)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getPrimaryKeys(String catalog={}, \"\n            + \"String schema={}, String table={})\",\n        originalCatalog,\n        originalSchema,\n        table);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n    String showPKCommand = \"show /* JDBC:DatabaseMetaData.getPrimaryKeys() */ primary keys in \";\n\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchema);\n    String catalog = result.database();\n    String schema = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    // These Patterns will only be used if the connection property enablePatternSearch=true\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schema, true);\n    final Pattern compiledTablePattern = Wildcard.toRegexPattern(table, true);\n\n    if (catalog == null) {\n      showPKCommand += \"account\";\n    } else if (catalog.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_PRIMARY_KEYS, statement);\n    } else {\n      String catalogUnescaped = escapeSqlQuotes(catalog);\n      if (schema == null || (isPatternMatchingEnabled && isSchemaNameWildcardPattern(schema))) {\n        showPKCommand += \"database \\\"\" + catalogUnescaped + \"\\\"\";\n      } else if (schema.isEmpty()) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_PRIMARY_KEYS, statement);\n      } else {\n        String schemaUnescaped = isExactSchema ? schema : unescapeChars(schema);\n        if (table == null) {\n          showPKCommand += \"schema \\\"\" + catalogUnescaped + \"\\\".\\\"\" + schemaUnescaped + \"\\\"\";\n        } else if (table.isEmpty()) {\n          return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_PRIMARY_KEYS, statement);\n        } else {\n          String tableUnescaped = unescapeChars(table);\n          showPKCommand +=\n              \"table \\\"\"\n                  + catalogUnescaped\n                  + \"\\\".\\\"\"\n                  + schemaUnescaped\n                  + \"\\\".\\\"\"\n                  + tableUnescaped\n                  + \"\\\"\";\n        }\n      }\n    }\n\n    final String catalogIn = catalog;\n    // These values for Schema and Table will only be used to filter results if the connection\n    // property\n    // enablePatternSearch=false\n    final String schemaIn = schema;\n    final String tableIn = table;\n\n    logger.debug(\"Sql command to get primary key metadata: {}\", showPKCommand);\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(statement, showPKCommand, GET_PRIMARY_KEYS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet, \"getPrimaryKeys\", originalCatalog, originalSchema, table, \"none\");\n    // Return empty result set since we don't have primary keys yet\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_PRIMARY_KEYS, resultSet, statement) {\n      @Override\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        while (showObjectResultSet.next()) {\n          // Get the values for each field to display\n          String table_cat = showObjectResultSet.getString(2);\n          String table_schem = showObjectResultSet.getString(3);\n          String table_name = showObjectResultSet.getString(4);\n          String column_name = showObjectResultSet.getString(5);\n          int key_seq = showObjectResultSet.getInt(6);\n          String pk_name = showObjectResultSet.getString(7);\n          boolean isMatch = false;\n\n          // Post filter based on the input\n          if (isPatternMatchingEnabled) {\n            isMatch =\n                isPrimaryKeyPatternSearch(\n                    table_cat, table_schem, table_name, column_name, key_seq, pk_name);\n          } else {\n            isMatch =\n                isPrimaryKeyExactSearch(\n                    table_cat, table_schem, table_name, column_name, key_seq, pk_name);\n          }\n          if (isMatch) {\n            createPrimaryKeyRow(table_cat, table_schem, table_name, column_name, key_seq, pk_name);\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n\n      private boolean isPrimaryKeyExactSearch(\n          String table_cat,\n          String table_schem,\n          String table_name,\n          String column_name,\n          int key_seq,\n          String pk_name) {\n        if ((catalogIn == null || catalogIn.equals(table_cat))\n            && (schemaIn == null || schemaIn.equals(table_schem))\n            && (tableIn == null || tableIn.equals(table_name))) {\n          return true;\n        }\n        return false;\n      }\n\n      private boolean isPrimaryKeyPatternSearch(\n          String table_cat,\n          String table_schem,\n          String table_name,\n          String column_name,\n          int key_seq,\n          String pk_name) {\n        if ((catalogIn == null || catalogIn.equals(table_cat))\n            && (compiledSchemaPattern == null\n                || compiledSchemaPattern.equals(table_schem)\n                || compiledSchemaPattern.matcher(table_schem).matches())\n            && (compiledTablePattern == null\n                || compiledTablePattern.equals(table_name)\n                || compiledTablePattern.matcher(table_name).matches())) {\n          return true;\n        }\n        return false;\n      }\n\n      private void createPrimaryKeyRow(\n          String table_cat,\n          String table_schem,\n          String table_name,\n          String column_name,\n          int key_seq,\n          String pk_name) {\n        nextRow[0] = table_cat;\n        nextRow[1] = table_schem;\n        nextRow[2] = table_name;\n        nextRow[3] = column_name;\n        nextRow[4] = key_seq;\n        nextRow[5] = pk_name;\n      }\n    };\n  }\n\n  /**\n   * Retrieves the foreign keys\n   *\n   * @param client type of foreign key\n   * @param originalParentCatalog database name\n   * @param originalParentSchema schema name\n   * @param parentTable table name\n   * @param foreignCatalog other database name\n   * @param foreignSchema other schema name\n   * @param foreignTable other table name\n   * @return foreign key columns in result set\n   */\n  private ResultSet getForeignKeys(\n      final String client,\n      String originalParentCatalog,\n      String originalParentSchema,\n      final String parentTable,\n      final String foreignCatalog,\n      final String foreignSchema,\n      final String foreignTable)\n      throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n    StringBuilder commandBuilder = new StringBuilder();\n\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result =\n        applySessionContext(originalParentCatalog, originalParentSchema);\n    String parentCatalog = result.database();\n    String parentSchema = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    // These Patterns will only be used to filter results if the connection property\n    // enablePatternSearch=true\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(parentSchema, true);\n    final Pattern compiledParentTablePattern = Wildcard.toRegexPattern(parentTable, true);\n    final Pattern compiledForeignSchemaPattern = Wildcard.toRegexPattern(foreignSchema, true);\n    final Pattern compiledForeignTablePattern = Wildcard.toRegexPattern(foreignTable, true);\n\n    if (client.equals(\"export\") || client.equals(\"cross\")) {\n      commandBuilder.append(\n          \"show /* JDBC:DatabaseMetaData.getForeignKeys() */ \" + \"exported keys in \");\n    } else if (client.equals(\"import\")) {\n      commandBuilder.append(\n          \"show /* JDBC:DatabaseMetaData.getForeignKeys() */ \" + \"imported keys in \");\n    }\n\n    if (parentCatalog == null) {\n      commandBuilder.append(\"account\");\n    } else if (parentCatalog.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_FOREIGN_KEYS, statement);\n    } else {\n      String unescapedParentCatalog = escapeSqlQuotes(parentCatalog);\n      if (parentSchema == null\n          || (isPatternMatchingEnabled && isSchemaNameWildcardPattern(parentSchema))) {\n        commandBuilder.append(\"database \\\"\" + unescapedParentCatalog + \"\\\"\");\n      } else if (parentSchema.isEmpty()) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_FOREIGN_KEYS, statement);\n      } else {\n        String unescapedParentSchema = isExactSchema ? parentSchema : unescapeChars(parentSchema);\n        if (parentTable == null) {\n          commandBuilder.append(\n              \"schema \\\"\" + unescapedParentCatalog + \"\\\".\\\"\" + unescapedParentSchema + \"\\\"\");\n        } else if (parentTable.isEmpty()) {\n          return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_FOREIGN_KEYS, statement);\n        } else {\n          String unescapedParentTable = unescapeChars(parentTable);\n          commandBuilder.append(\n              \"table \\\"\"\n                  + unescapedParentCatalog\n                  + \"\\\".\\\"\"\n                  + unescapedParentSchema\n                  + \"\\\".\\\"\"\n                  + unescapedParentTable\n                  + \"\\\"\");\n        }\n      }\n    }\n\n    final String finalParentCatalog = parentCatalog;\n    final String finalForeignCatalog = foreignCatalog;\n    // These values will only be used to filter results if the connection property\n    // enablePatternSearch=true\n    final String finalParentSchema = parentSchema;\n    final String finalParentTable = parentTable;\n    final String finalForeignSchema = foreignSchema;\n    final String finalForeignTable = foreignTable;\n\n    String command = commandBuilder.toString();\n\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(statement, command, GET_FOREIGN_KEYS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet,\n        \"getForeignKeys\",\n        originalParentCatalog,\n        originalParentSchema,\n        parentTable,\n        \"none\");\n\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_FOREIGN_KEYS, resultSet, statement) {\n      @Override\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        while (showObjectResultSet.next()) {\n          // Get the value for each field to display\n          String pktable_cat = showObjectResultSet.getString(2);\n          String pktable_schem = showObjectResultSet.getString(3);\n          String pktable_name = showObjectResultSet.getString(4);\n          String pkcolumn_name = showObjectResultSet.getString(5);\n          String fktable_cat = showObjectResultSet.getString(6);\n          String fktable_schem = showObjectResultSet.getString(7);\n          String fktable_name = showObjectResultSet.getString(8);\n          String fkcolumn_name = showObjectResultSet.getString(9);\n          int key_seq = showObjectResultSet.getInt(10);\n          short updateRule =\n              getForeignKeyConstraintProperty(\"update\", showObjectResultSet.getString(11));\n          short deleteRule =\n              getForeignKeyConstraintProperty(\"delete\", showObjectResultSet.getString(12));\n          String fk_name = showObjectResultSet.getString(13);\n          String pk_name = showObjectResultSet.getString(14);\n          short deferrability =\n              getForeignKeyConstraintProperty(\"deferrability\", showObjectResultSet.getString(15));\n\n          boolean passedFilter = false;\n\n          if (isPatternMatchingEnabled) {\n            passedFilter =\n                isForeignKeyPatternMatch(\n                    fktable_cat,\n                    fktable_schem,\n                    fktable_name,\n                    passedFilter,\n                    pktable_cat,\n                    pktable_schem,\n                    pktable_name);\n          } else {\n            passedFilter =\n                isForeignKeyExactMatch(\n                    fktable_cat,\n                    fktable_schem,\n                    fktable_name,\n                    passedFilter,\n                    pktable_cat,\n                    pktable_schem,\n                    pktable_name);\n          }\n\n          if (passedFilter) {\n            createForeinKeyRow(\n                pktable_cat,\n                pktable_schem,\n                pktable_name,\n                pkcolumn_name,\n                fktable_cat,\n                fktable_schem,\n                fktable_name,\n                fkcolumn_name,\n                key_seq,\n                updateRule,\n                deleteRule,\n                fk_name,\n                pk_name,\n                deferrability);\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n\n      private void createForeinKeyRow(\n          String pktable_cat,\n          String pktable_schem,\n          String pktable_name,\n          String pkcolumn_name,\n          String fktable_cat,\n          String fktable_schem,\n          String fktable_name,\n          String fkcolumn_name,\n          int key_seq,\n          short updateRule,\n          short deleteRule,\n          String fk_name,\n          String pk_name,\n          short deferrability) {\n        nextRow[0] = pktable_cat;\n        nextRow[1] = pktable_schem;\n        nextRow[2] = pktable_name;\n        nextRow[3] = pkcolumn_name;\n        nextRow[4] = fktable_cat;\n        nextRow[5] = fktable_schem;\n        nextRow[6] = fktable_name;\n        nextRow[7] = fkcolumn_name;\n        nextRow[8] = key_seq;\n        nextRow[9] = updateRule;\n        nextRow[10] = deleteRule;\n        nextRow[11] = fk_name;\n        nextRow[12] = pk_name;\n        nextRow[13] = deferrability;\n      }\n\n      private boolean isForeignKeyExactMatch(\n          String fktable_cat,\n          String fktable_schem,\n          String fktable_name,\n          boolean passedFilter,\n          String pktable_cat,\n          String pktable_schem,\n          String pktable_name) {\n        // Post filter the results based on the client type\n        if (client.equals(\"import\")) {\n          // For imported keys, filter on the foreign key table\n          if ((finalParentCatalog == null || finalParentCatalog.equals(fktable_cat))\n              && (finalParentSchema == null || finalParentSchema.equals(fktable_schem))\n              && (finalParentTable == null || finalParentTable.equals(fktable_name))) {\n            passedFilter = true;\n          }\n        } else if (client.equals(\"export\")) {\n          // For exported keys, filter on the primary key table\n          if ((finalParentCatalog == null || finalParentCatalog.equals(pktable_cat))\n              && (finalParentSchema == null || finalParentSchema.equals(pktable_schem))\n              && (finalParentTable == null || finalParentTable.equals(pktable_name))) {\n            passedFilter = true;\n          }\n        } else if (client.equals(\"cross\")) {\n          // For cross references, filter on both the primary key and foreign\n          // key table\n          if ((finalParentCatalog == null || finalParentCatalog.equals(pktable_cat))\n              && (finalParentSchema == null || finalParentSchema.equals(pktable_schem))\n              && (finalParentTable == null || finalParentTable.equals(pktable_name))\n              && (finalForeignCatalog == null || finalForeignCatalog.equals(fktable_cat))\n              && (finalForeignSchema == null || finalForeignSchema.equals(fktable_schem))\n              && (finalForeignTable == null || finalForeignTable.equals(fktable_name))) {\n            passedFilter = true;\n          }\n        }\n        return passedFilter;\n      }\n\n      private boolean isForeignKeyPatternMatch(\n          String fktable_cat,\n          String fktable_schem,\n          String fktable_name,\n          boolean passedFilter,\n          String pktable_cat,\n          String pktable_schem,\n          String pktable_name) {\n        // Post filter the results based on the client type\n        if (client.equals(\"import\")) {\n          // For imported keys, filter on the foreign key table\n          if ((finalParentCatalog == null || finalParentCatalog.equals(fktable_cat))\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.equals(fktable_schem)\n                  || compiledSchemaPattern.matcher(fktable_schem).matches())\n              && (compiledParentTablePattern == null\n                  || compiledParentTablePattern.equals(fktable_name)\n                  || compiledParentTablePattern.matcher(fktable_name).matches())) {\n            passedFilter = true;\n          }\n        } else if (client.equals(\"export\")) {\n          // For exported keys, filter on the primary key table\n          if ((finalParentCatalog == null || finalParentCatalog.equals(pktable_cat))\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.equals(pktable_schem)\n                  || compiledSchemaPattern.matcher(pktable_schem).matches())\n              && (compiledParentTablePattern == null\n                  || compiledParentTablePattern.equals(pktable_name)\n                  || compiledParentTablePattern.matcher(pktable_name).matches())) {\n            passedFilter = true;\n          }\n        } else if (client.equals(\"cross\")) {\n          // For cross references, filter on both the primary key and foreign\n          // key table\n          if ((finalParentCatalog == null || finalParentCatalog.equals(pktable_cat))\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.equals(pktable_schem)\n                  || compiledSchemaPattern.matcher(pktable_schem).matches())\n              && (compiledParentTablePattern == null\n                  || compiledParentTablePattern.equals(pktable_name)\n                  || compiledParentTablePattern.matcher(pktable_name).matches())\n              && (foreignCatalog == null || foreignCatalog.equals(fktable_cat))\n              && (compiledForeignSchemaPattern == null\n                  || compiledForeignSchemaPattern.equals(fktable_schem)\n                  || compiledForeignSchemaPattern.matcher(fktable_schem).matches())\n              && (compiledForeignTablePattern == null\n                  || compiledForeignTablePattern.equals(fktable_name)\n                  || compiledForeignTablePattern.matcher(fktable_name).matches())) {\n            passedFilter = true;\n          }\n        }\n        return passedFilter;\n      }\n    };\n  }\n\n  /**\n   * Returns the JDBC standard property string for the property string used in our show constraint\n   * commands\n   *\n   * @param property_name operation type\n   * @param property property value\n   * @return metadata property value\n   */\n  private short getForeignKeyConstraintProperty(String property_name, String property) {\n    short result = 0;\n    switch (property_name) {\n      case \"update\":\n      case \"delete\":\n        switch (property) {\n          case \"NO ACTION\":\n            result = importedKeyNoAction;\n            break;\n          case \"CASCADE\":\n            result = importedKeyCascade;\n            break;\n          case \"SET NULL\":\n            result = importedKeySetNull;\n            break;\n          case \"SET DEFAULT\":\n            result = importedKeySetDefault;\n            break;\n          case \"RESTRICT\":\n            result = importedKeyRestrict;\n            break;\n        }\n        break;\n      case \"deferrability\":\n        switch (property) {\n          case \"INITIALLY DEFERRED\":\n            result = importedKeyInitiallyDeferred;\n            break;\n          case \"INITIALLY IMMEDIATE\":\n            result = importedKeyInitiallyImmediate;\n            break;\n          case \"NOT DEFERRABLE\":\n            result = importedKeyNotDeferrable;\n            break;\n        }\n        break;\n    }\n    return result;\n  }\n\n  @Override\n  public ResultSet getImportedKeys(String originalCatalog, String originalSchema, String table)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getImportedKeys(String catalog={}, \"\n            + \"String schema={}, String table={})\",\n        originalCatalog,\n        originalSchema,\n        table);\n\n    return getForeignKeys(\"import\", originalCatalog, originalSchema, table, null, null, null);\n  }\n\n  @Override\n  public ResultSet getExportedKeys(String catalog, String schema, String table)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getExportedKeys(String catalog={}, \"\n            + \"String schema={}, String table={})\",\n        catalog,\n        schema,\n        table);\n\n    return getForeignKeys(\"export\", catalog, schema, table, null, null, null);\n  }\n\n  @Override\n  public ResultSet getCrossReference(\n      String parentCatalog,\n      String parentSchema,\n      String parentTable,\n      String foreignCatalog,\n      String foreignSchema,\n      String foreignTable)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getCrossReference(String parentCatalog={}, \"\n            + \"String parentSchema={}, String parentTable={}, \"\n            + \"String foreignCatalog={}, String foreignSchema={}, \"\n            + \"String foreignTable={})\",\n        parentCatalog,\n        parentSchema,\n        parentTable,\n        foreignCatalog,\n        foreignSchema,\n        foreignTable);\n    return getForeignKeys(\n        \"cross\",\n        parentCatalog,\n        parentSchema,\n        parentTable,\n        foreignCatalog,\n        foreignSchema,\n        foreignTable);\n  }\n\n  @Override\n  public ResultSet getTypeInfo() throws SQLException {\n    logger.trace(\"ResultSet getTypeInfo()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    Statement statement = connection.createStatement();\n\n    // Return empty result set since we don't have primary keys yet\n    return new SnowflakeDatabaseMetaDataResultSet(\n        Arrays.asList(\n            \"TYPE_NAME\",\n            \"DATA_TYPE\",\n            \"PRECISION\",\n            \"LITERAL_PREFIX\",\n            \"LITERAL_SUFFIX\",\n            \"CREATE_PARAMS\",\n            \"NULLABLE\",\n            \"CASE_SENSITIVE\",\n            \"SEARCHABLE\",\n            \"UNSIGNED_ATTRIBUTE\",\n            \"FIXED_PREC_SCALE\",\n            \"AUTO_INCREMENT\",\n            \"LOCAL_TYPE_NAME\",\n            \"MINIMUM_SCALE\",\n            \"MAXIMUM_SCALE\",\n            \"SQL_DATA_TYPE\",\n            \"SQL_DATETIME_SUB\",\n            \"NUM_PREC_RADIX\"),\n        Arrays.asList(\n            \"TEXT\", \"INTEGER\", \"INTEGER\", \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"BOOLEAN\", \"SHORT\",\n            \"BOOLEAN\", \"BOOLEAN\", \"BOOLEAN\", \"TEXT\", \"SHORT\", \"SHORT\", \"INTEGER\", \"INTEGER\",\n            \"INTEGER\"),\n        Arrays.asList(\n            Types.VARCHAR,\n            Types.INTEGER,\n            Types.INTEGER,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.SMALLINT,\n            Types.BOOLEAN,\n            Types.SMALLINT,\n            Types.BOOLEAN,\n            Types.BOOLEAN,\n            Types.BOOLEAN,\n            Types.VARCHAR,\n            Types.SMALLINT,\n            Types.SMALLINT,\n            Types.INTEGER,\n            Types.INTEGER,\n            Types.INTEGER),\n        new Object[][] {\n          {\n            \"NUMBER\",\n            Types.DECIMAL,\n            38,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            0,\n            37,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"INTEGER\",\n            Types.INTEGER,\n            38,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            0,\n            0,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"DOUBLE\",\n            Types.DOUBLE,\n            38,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            0,\n            37,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"VARCHAR\",\n            Types.VARCHAR,\n            -1,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            -1,\n            -1,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"DATE\",\n            Types.DATE,\n            -1,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            -1,\n            -1,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"TIME\",\n            Types.TIME,\n            -1,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            -1,\n            -1,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"TIMESTAMP\",\n            Types.TIMESTAMP,\n            -1,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            -1,\n            -1,\n            -1,\n            -1,\n            -1\n          },\n          {\n            \"BOOLEAN\",\n            Types.BOOLEAN,\n            -1,\n            null,\n            null,\n            null,\n            typeNullable,\n            false,\n            typeSearchable,\n            false,\n            true,\n            true,\n            null,\n            -1,\n            -1,\n            -1,\n            -1,\n            -1\n          }\n        },\n        statement);\n  }\n\n  /**\n   * Function to return a list of streams\n   *\n   * @param originalCatalog catalog name\n   * @param originalSchemaPattern schema name pattern\n   * @param streamName stream name\n   * @return a result set\n   * @throws SQLException if any SQL error occurs.\n   */\n  public ResultSet getStreams(\n      String originalCatalog, String originalSchemaPattern, String streamName) throws SQLException {\n    logger.trace(\n        \"public ResultSet getStreams(String catalog={}, String schemaPattern={}\"\n            + \"String streamName={}\",\n        originalCatalog,\n        originalSchemaPattern,\n        streamName);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchemaPattern);\n    String catalog = result.database();\n    String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n    final Pattern compiledStreamNamePattern = Wildcard.toRegexPattern(streamName, true);\n\n    String showStreamsCommand = \"show streams\";\n\n    if (streamName != null\n        && !streamName.isEmpty()\n        && !streamName.trim().equals(\"%\")\n        && !streamName.trim().equals(\".*\")) {\n      showStreamsCommand += \" like '\" + escapeSingleQuoteForLikeCommand(streamName) + \"'\";\n    }\n\n    if (catalog == null) {\n      showStreamsCommand += \" in account\";\n    } else if (catalog.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_STREAMS, statement);\n    } else {\n      String catalogEscaped = escapeSqlQuotes(catalog);\n      if (schemaPattern == null || isSchemaNameWildcardPattern(schemaPattern)) {\n        showStreamsCommand += \" in database \\\"\" + catalogEscaped + \"\\\"\";\n      } else if (schemaPattern.isEmpty()) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_STREAMS, statement);\n      } else {\n        String schemaUnescaped = isExactSchema ? schemaPattern : unescapeChars(schemaPattern);\n        showStreamsCommand += \" in schema \\\"\" + catalogEscaped + \"\\\".\\\"\" + schemaUnescaped + \"\\\"\";\n      }\n    }\n\n    logger.debug(\"Sql command to get stream metadata: {}\", showStreamsCommand);\n\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(statement, showStreamsCommand, GET_STREAMS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet, \"getStreams\", originalCatalog, originalSchemaPattern, streamName, \"none\");\n\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_STREAMS, resultSet, statement) {\n      @Override\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\");\n        incrementRow();\n\n        // iterate throw the show streams result until we find an entry\n        // that matches the stream name\n        while (showObjectResultSet.next()) {\n          String name = showObjectResultSet.getString(\"name\");\n          String databaseName = showObjectResultSet.getString(\"database_name\");\n          String schemaName = showObjectResultSet.getString(\"schema_name\");\n          String owner = showObjectResultSet.getString(\"owner\");\n          String comment = showObjectResultSet.getString(\"comment\");\n          String tableName = showObjectResultSet.getString(\"table_name\");\n          String sourceType = showObjectResultSet.getString(\"source_type\");\n          String baseTables = showObjectResultSet.getString(\"base_tables\");\n          String type = showObjectResultSet.getString(\"type\");\n          String stale = showObjectResultSet.getString(\"stale\");\n          String mode = showObjectResultSet.getString(\"mode\");\n\n          if ((compiledStreamNamePattern == null\n                  || compiledStreamNamePattern.matcher(name).matches())\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.matcher(schemaName).matches())) {\n            logger.debug(\"Found a matched stream:\" + schemaName + \".\" + name);\n            nextRow[0] = name;\n            nextRow[1] = databaseName;\n            nextRow[2] = schemaName;\n            nextRow[3] = owner;\n            nextRow[4] = comment;\n            nextRow[5] = tableName;\n            nextRow[6] = sourceType;\n            nextRow[7] = baseTables;\n            nextRow[8] = type;\n            nextRow[9] = stale;\n            nextRow[10] = mode;\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  @Override\n  public ResultSet getIndexInfo(\n      String catalog, String schema, String table, boolean unique, boolean approximate)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getIndexInfo(String catalog, String schema, \"\n            + \"String table,boolean unique, boolean approximate)\",\n        false);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    Statement statement = connection.createStatement();\n\n    // Return empty result set since we don't have primary keys yet\n    return new SnowflakeDatabaseMetaDataResultSet(\n        Arrays.asList(\n            \"TABLE_CAT\",\n            \"TABLE_SCHEM\",\n            \"TABLE_NAME\",\n            \"NON_UNIQUE\",\n            \"INDEX_QUALIFIER\",\n            \"INDEX_NAME\",\n            \"TYPE\",\n            \"ORDINAL_POSITION\",\n            \"COLUMN_NAME\",\n            \"ASC_OR_DESC\",\n            \"CARDINALITY\",\n            \"PAGES\",\n            \"FILTER_CONDITION\"),\n        Arrays.asList(\n            \"TEXT\", \"TEXT\", \"TEXT\", \"BOOLEAN\", \"TEXT\", \"TEXT\", \"SHORT\", \"SHORT\", \"TEXT\", \"TEXT\",\n            \"INTEGER\", \"INTEGER\", \"TEXT\"),\n        Arrays.asList(\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.BOOLEAN,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.SMALLINT,\n            Types.SMALLINT,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.INTEGER,\n            Types.INTEGER,\n            Types.VARCHAR),\n        new Object[][] {},\n        statement);\n  }\n\n  @Override\n  public boolean supportsResultSetType(int type) throws SQLException {\n    logger.trace(\"boolean supportsResultSetType(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return (type == ResultSet.TYPE_FORWARD_ONLY);\n  }\n\n  @Override\n  public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {\n    logger.trace(\n        \"public boolean supportsResultSetConcurrency(int type, \" + \"int concurrency)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return (type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY);\n  }\n\n  @Override\n  public boolean ownUpdatesAreVisible(int type) throws SQLException {\n    logger.trace(\"boolean ownUpdatesAreVisible(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean ownDeletesAreVisible(int type) throws SQLException {\n    logger.trace(\"boolean ownDeletesAreVisible(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean ownInsertsAreVisible(int type) throws SQLException {\n    logger.trace(\"boolean ownInsertsAreVisible(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean othersUpdatesAreVisible(int type) throws SQLException {\n    logger.trace(\"boolean othersUpdatesAreVisible(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean othersDeletesAreVisible(int type) throws SQLException {\n    logger.trace(\"boolean othersDeletesAreVisible(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean othersInsertsAreVisible(int type) throws SQLException {\n    logger.trace(\"boolean othersInsertsAreVisible(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean updatesAreDetected(int type) throws SQLException {\n    logger.trace(\"boolean updatesAreDetected(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean deletesAreDetected(int type) throws SQLException {\n    logger.trace(\"boolean deletesAreDetected(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean insertsAreDetected(int type) throws SQLException {\n    logger.trace(\"boolean insertsAreDetected(int type)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsBatchUpdates() throws SQLException {\n    logger.trace(\"boolean supportsBatchUpdates()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public ResultSet getUDTs(\n      String catalog, String schemaPattern, String typeNamePattern, int[] types)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getUDTs(String catalog, \"\n            + \"String schemaPattern,String typeNamePattern, int[] types)\",\n        false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    // We don't user-defined types, so return an empty result set\n    Statement statement = connection.createStatement();\n    return new SnowflakeDatabaseMetaDataResultSet(\n        Arrays.asList(\n            \"TYPE_CAT\",\n            \"TYPE_SCHEM\",\n            \"TYPE_NAME\",\n            \"CLASS_NAME\",\n            \"DATA_TYPE\",\n            \"REMARKS\",\n            \"BASE_TYPE\"),\n        Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"INTEGER\", \"TEXT\", \"SHORT\"),\n        Arrays.asList(\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.VARCHAR,\n            Types.INTEGER,\n            Types.VARCHAR,\n            Types.SMALLINT),\n        new Object[][] {},\n        statement);\n  }\n\n  @Override\n  public Connection getConnection() throws SQLException {\n    logger.trace(\"Connection getConnection()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return connection;\n  }\n\n  @Override\n  public boolean supportsSavepoints() throws SQLException {\n    logger.trace(\"boolean supportsSavepoints()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsNamedParameters() throws SQLException {\n    logger.trace(\"boolean supportsNamedParameters()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsMultipleOpenResults() throws SQLException {\n    logger.trace(\"boolean supportsMultipleOpenResults()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public boolean supportsGetGeneratedKeys() throws SQLException {\n    logger.trace(\"boolean supportsGetGeneratedKeys()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getSuperTypes(String catalog, \"\n            + \"String schemaPattern,String typeNamePattern)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getSuperTables(String catalog, \"\n            + \"String schemaPattern,String tableNamePattern)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getAttributes(\n      String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getAttributes(String catalog, String \"\n            + \"schemaPattern,\"\n            + \"String typeNamePattern,String attributeNamePattern)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean supportsResultSetHoldability(int holdability) throws SQLException {\n    logger.trace(\"boolean supportsResultSetHoldability(int holdability)\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT;\n  }\n\n  @Override\n  public int getResultSetHoldability() throws SQLException {\n    logger.trace(\"int getResultSetHoldability()\", false);\n    return ResultSet.CLOSE_CURSORS_AT_COMMIT;\n  }\n\n  @Override\n  public int getDatabaseMajorVersion() throws SQLException {\n    logger.trace(\"int getDatabaseMajorVersion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return connection.unwrap(SnowflakeConnectionImpl.class).getDatabaseMajorVersion();\n  }\n\n  @Override\n  public int getDatabaseMinorVersion() throws SQLException {\n    logger.trace(\"int getDatabaseMinorVersion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return connection.unwrap(SnowflakeConnectionImpl.class).getDatabaseMinorVersion();\n  }\n\n  @Override\n  public int getJDBCMajorVersion() throws SQLException {\n    logger.trace(\"int getJDBCMajorVersion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return Integer.parseInt(JDBCVersion.split(\"\\\\.\", 2)[0]);\n  }\n\n  @Override\n  public int getJDBCMinorVersion() throws SQLException {\n    logger.trace(\"int getJDBCMinorVersion()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return Integer.parseInt(JDBCVersion.split(\"\\\\.\", 2)[1]);\n  }\n\n  @Override\n  public int getSQLStateType() throws SQLException {\n    logger.trace(\"int getSQLStateType()\", false);\n    return sqlStateSQL;\n  }\n\n  @Override\n  public boolean locatorsUpdateCopy() {\n    logger.trace(\"boolean locatorsUpdateCopy()\", false);\n\n    return false;\n  }\n\n  @Override\n  public boolean supportsStatementPooling() throws SQLException {\n    logger.trace(\"boolean supportsStatementPooling()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return false;\n  }\n\n  @Override\n  public RowIdLifetime getRowIdLifetime() throws SQLException {\n    logger.trace(\"RowIdLifetime getRowIdLifetime()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getSchemas(String originalCatalog, String originalSchema) throws SQLException {\n    logger.trace(\n        \"public ResultSet getSchemas(String catalog={}, String \" + \"schemaPattern={})\",\n        originalCatalog,\n        originalSchema);\n    raiseSQLExceptionIfConnectionIsClosed();\n\n    // apply session context when catalog is unspecified\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchema);\n    final String catalog = result.database();\n    final String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n\n    StringBuilder showSchemas =\n        new StringBuilder(\"show /* JDBC:DatabaseMetaData.getSchemas() */ schemas\");\n\n    Statement statement = connection.createStatement();\n    if (isExactSchema && enableWildcardsInShowMetadataCommands) {\n      String escapedSchema =\n          schemaPattern.replaceAll(\"_\", \"\\\\\\\\\\\\\\\\_\").replaceAll(\"%\", \"\\\\\\\\\\\\\\\\%\");\n      showSchemas.append(\" like '\").append(escapedSchema).append(\"'\");\n    } else if (schemaPattern != null\n        && !schemaPattern.isEmpty()\n        && !schemaPattern.trim().equals(\"%\")\n        && !schemaPattern.trim().equals(\".*\")) {\n      // only add pattern if it is not empty and not matching all character.\n      showSchemas\n          .append(\" like '\")\n          .append(escapeSingleQuoteForLikeCommand(schemaPattern))\n          .append(\"'\");\n    }\n\n    if (catalog == null) {\n      showSchemas.append(\" in account\");\n    } else if (catalog.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_SCHEMAS, statement);\n    } else {\n      showSchemas.append(\" in database \\\"\").append(escapeSqlQuotes(catalog)).append(\"\\\"\");\n    }\n\n    String sqlQuery = showSchemas.toString();\n    logger.debug(\"Sql command to get schemas metadata: {}\", sqlQuery);\n\n    ResultSet resultSet = executeAndReturnEmptyResultIfNotFound(statement, sqlQuery, GET_SCHEMAS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet, \"getSchemas\", originalCatalog, originalSchema, \"none\", \"none\");\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_SCHEMAS, resultSet, statement) {\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        // iterate throw the show table result until we find an entry\n        // that matches the table name\n        while (showObjectResultSet.next()) {\n          String schemaName = showObjectResultSet.getString(2);\n          String dbName = showObjectResultSet.getString(5);\n\n          if (compiledSchemaPattern == null\n              || compiledSchemaPattern.matcher(schemaName).matches()\n              || isExactSchema && schemaPattern.equals(schemaPattern)) {\n            nextRow[0] = schemaName;\n            nextRow[1] = dbName;\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  @Override\n  public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {\n    logger.trace(\"boolean supportsStoredFunctionsUsingCallSyntax()\", false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    return true;\n  }\n\n  @Override\n  public boolean autoCommitFailureClosesAllResultSets() throws SQLException {\n    logger.trace(\"boolean autoCommitFailureClosesAllResultSets()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getClientInfoProperties() throws SQLException {\n    logger.trace(\"ResultSet getClientInfoProperties()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSet getFunctions(\n      final String originalCatalog,\n      final String originalSchemaPattern,\n      final String functionNamePattern)\n      throws SQLException {\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n    logger.trace(\n        \"public ResultSet getFunctions(String catalog={}, String schemaPattern={}, \"\n            + \"String functionNamePattern={}\",\n        originalCatalog,\n        originalSchemaPattern,\n        functionNamePattern);\n\n    String showFunctionCommand =\n        getFirstResultSetCommand(\n            originalCatalog, originalSchemaPattern, functionNamePattern, \"functions\");\n    if (showFunctionCommand.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_FUNCTIONS, statement);\n    }\n    ContextAwareMetadataSearch result = applySessionContext(originalCatalog, originalSchemaPattern);\n    String catalog = result.database();\n    String schemaPattern = result.schema();\n    boolean isExactSchema = result.isExactSchema();\n\n    final Pattern compiledSchemaPattern = Wildcard.toRegexPattern(schemaPattern, true);\n    final Pattern compiledFunctionPattern = Wildcard.toRegexPattern(functionNamePattern, true);\n\n    ResultSet resultSet =\n        executeAndReturnEmptyResultIfNotFound(statement, showFunctionCommand, GET_FUNCTIONS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSet, \"getFunctions\", catalog, schemaPattern, functionNamePattern, \"none\");\n\n    return new SnowflakeDatabaseMetaDataQueryResultSet(GET_FUNCTIONS, resultSet, statement) {\n      public boolean next() throws SQLException {\n        logger.trace(\"boolean next()\", false);\n        incrementRow();\n\n        // iterate throw the show table result until we find an entry\n        // that matches the table name\n        while (showObjectResultSet.next()) {\n          String catalogName = showObjectResultSet.getString(11);\n          String schemaName = showObjectResultSet.getString(3);\n          String functionName = showObjectResultSet.getString(2);\n          String remarks = showObjectResultSet.getString(10);\n          int functionType =\n              (\"Y\".equals(showObjectResultSet.getString(12))\n                  ? functionReturnsTable\n                  : functionNoTable);\n          String specificName = functionName;\n          if ((compiledFunctionPattern == null\n                  || compiledFunctionPattern.matcher(functionName).matches())\n              && (compiledSchemaPattern == null\n                  || compiledSchemaPattern.matcher(schemaName).matches()\n                  || isExactSchema && schemaPattern.equals(schemaPattern))) {\n            logger.debug(\"Found a matched function:\" + schemaName + \".\" + functionName);\n\n            nextRow[0] = catalogName;\n            nextRow[1] = schemaName;\n            nextRow[2] = functionName;\n            nextRow[3] = remarks;\n            nextRow[4] = functionType;\n            nextRow[5] = specificName;\n            return true;\n          }\n        }\n        close();\n        return false;\n      }\n    };\n  }\n\n  /**\n   * This is a function that takes in a string of return types and a string of parameter names and\n   * types. It splits both strings in a list of column names and column types. The names will be\n   * every odd index and the types will be every even index.\n   */\n  private List<String> parseColumns(String retType, String args) {\n    List<String> columns = new ArrayList<>();\n    if (retType.substring(0, 5).equalsIgnoreCase(\"table\")) {\n      // if return type is a table there will be a result set\n      String typeStr = retType.substring(retType.indexOf('(') + 1, retType.lastIndexOf(')'));\n      String[] types = typeStr.split(\"\\\\s+|, \");\n      if (types.length != 1) {\n        for (int i = 0; i < types.length; i++) {\n          columns.add(types[i]);\n        }\n        procedureResultsetColumnNum = columns.size() / 2;\n      }\n    } else {\n      // otherwise it will be a return value\n      columns.add(\"\"); // there is no name for this column\n      columns.add(retType);\n      procedureResultsetColumnNum = -1;\n    }\n    String argStr = args.substring(args.indexOf('(') + 1, args.lastIndexOf(')'));\n    String arguments[] = argStr.split(\"\\\\s+|, \");\n    if (arguments.length != 1) {\n      for (int i = 0; i < arguments.length; i++) {\n        columns.add(arguments[i]);\n      }\n    }\n    return columns;\n  }\n\n  @Override\n  public ResultSet getFunctionColumns(\n      String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getFunctionColumns(String catalog, \"\n            + \"String schemaPattern,String functionNamePattern,\"\n            + \"String columnNamePattern)\",\n        false);\n    raiseSQLExceptionIfConnectionIsClosed();\n    Statement statement = connection.createStatement();\n    boolean addAllRows = false;\n    String showFunctionCommand =\n        getFirstResultSetCommand(catalog, schemaPattern, functionNamePattern, \"functions\");\n\n    if (showFunctionCommand.isEmpty()) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(GET_FUNCTION_COLUMNS, statement);\n    }\n\n    if (columnNamePattern == null\n        || columnNamePattern.isEmpty()\n        || columnNamePattern.trim().equals(\"%\")\n        || columnNamePattern.trim().equals(\".*\")) {\n      addAllRows = true;\n    }\n\n    ResultSet resultSetStepOne =\n        executeAndReturnEmptyResultIfNotFound(statement, showFunctionCommand, GET_FUNCTION_COLUMNS);\n    sendInBandTelemetryMetadataMetrics(\n        resultSetStepOne,\n        \"getFunctionColumns\",\n        catalog,\n        schemaPattern,\n        functionNamePattern,\n        columnNamePattern);\n    ArrayList<Object[]> rows = new ArrayList<Object[]>();\n    while (resultSetStepOne.next()) {\n      String functionNameUnparsed = resultSetStepOne.getString(\"arguments\").trim();\n      String functionNameNoArgs = resultSetStepOne.getString(\"name\");\n      String realSchema = resultSetStepOne.getString(\"schema_name\");\n      String realDatabase = resultSetStepOne.getString(\"catalog_name\");\n      String showFunctionColCommand =\n          getSecondResultSetCommand(realDatabase, realSchema, functionNameUnparsed, \"function\");\n      ResultSet resultSetStepTwo =\n          executeAndReturnEmptyResultIfNotFound(\n              statement, showFunctionColCommand, GET_FUNCTION_COLUMNS);\n      if (resultSetStepTwo.next() == false) {\n        continue;\n      }\n      // Retrieve the function arguments and function return values.\n      String args = resultSetStepTwo.getString(\"value\");\n      resultSetStepTwo.next();\n      String res = resultSetStepTwo.getString(\"value\");\n      // parse function arguments and return values into a list of columns\n      // result value(s) will be at the top of the list, followed by any arguments\n      List<String> functionCols = parseColumns(res, args);\n      String paramNames[] = new String[functionCols.size() / 2];\n      String paramTypes[] = new String[functionCols.size() / 2];\n      if (functionCols.size() > 1) {\n        for (int i = 0; i < functionCols.size(); i++) {\n          if (i % 2 == 0) {\n            paramNames[i / 2] = functionCols.get(i);\n          } else {\n            paramTypes[i / 2] = functionCols.get(i);\n          }\n        }\n      }\n      for (int i = 0; i < paramNames.length; i++) {\n        // if it's the 1st in for loop, it's the result\n        if (i == 0 || paramNames[i].equalsIgnoreCase(columnNamePattern) || addAllRows) {\n          Object[] nextRow = new Object[17];\n          // add a row to resultSet\n          nextRow[0] = catalog; // function catalog. Can be null.\n          nextRow[1] = schemaPattern; // function schema. Can be null.\n          nextRow[2] = functionNameNoArgs; // function name\n          nextRow[3] = paramNames[i]; // column/parameter name\n          if (i == 0 && procedureResultsetColumnNum < 0) {\n            nextRow[4] = functionReturn;\n          } else if (procedureResultsetColumnNum >= 0 && i < procedureResultsetColumnNum) {\n            nextRow[4] = functionColumnResult;\n          } else {\n            nextRow[4] = functionColumnIn; // kind of column/parameter\n          }\n          String typeName = paramTypes[i];\n          int type = convertStringToType(typeName);\n          nextRow[5] = type; // data type\n          nextRow[6] = typeName; // type name\n          // precision and scale. Values only exist for numbers\n          int precision = 38;\n          short scale = 0;\n          if (type < 10) {\n            if (typeName.contains(\"(\") && typeName.contains(\")\")) {\n              precision =\n                  Integer.parseInt(\n                      typeName.substring(typeName.indexOf('(') + 1, typeName.indexOf(',')));\n              scale =\n                  Short.parseShort(\n                      typeName.substring(typeName.indexOf(',') + 1, typeName.indexOf(')')));\n              nextRow[7] = precision;\n              nextRow[9] = scale;\n            } else if (type == Types.FLOAT) {\n              nextRow[7] = 0;\n              nextRow[9] = null;\n            } else {\n              nextRow[7] = precision;\n              nextRow[9] = scale;\n            }\n          } else {\n            nextRow[7] = 0;\n            nextRow[9] = null;\n          }\n          nextRow[8] = 0; // length in bytes. not supported\n          nextRow[10] = 10; // radix. Probably 10 is default, but unknown.\n          nextRow[11] =\n              functionNullableUnknown; // nullable. We don't know from current function info.\n          nextRow[12] = resultSetStepOne.getString(\"description\").trim(); // remarks\n          if (type == Types.BINARY\n              || type == Types.VARBINARY\n              || type == Types.CHAR\n              || type == Types.VARCHAR) {\n            if (typeName.contains(\"(\") && typeName.contains(\")\")) {\n              int char_octet_len =\n                  Integer.parseInt(\n                      typeName.substring(typeName.indexOf('(') + 1, typeName.indexOf(')')));\n              nextRow[13] = char_octet_len;\n            } else if (type == Types.CHAR || type == Types.VARCHAR) {\n              nextRow[13] = getMaxCharLiteralLength();\n            } else if (type == Types.BINARY || type == Types.VARBINARY) {\n              nextRow[13] = getMaxBinaryLiteralLength();\n            }\n          } else {\n            nextRow[13] = null;\n          }\n          // the ordinal position is 0 for a return value.\n          // for result set columns, the ordinal position is of the column in the result set\n          // starting at 1\n          if (procedureResultsetColumnNum >= 0) {\n            if (i < procedureResultsetColumnNum) {\n              nextRow[14] = i + 1;\n            } else {\n              nextRow[14] = i - procedureResultsetColumnNum + 1;\n            }\n          } else {\n            nextRow[14] = i; // ordinal position.\n          }\n          nextRow[15] = \"\"; // nullability again. Not supported.\n          nextRow[16] = functionNameUnparsed;\n          rows.add(nextRow);\n        }\n      }\n    }\n    Object[][] resultRows = new Object[rows.size()][17];\n    for (int i = 0; i < resultRows.length; i++) {\n      resultRows[i] = rows.get(i);\n    }\n    return new SnowflakeDatabaseMetaDataResultSet(GET_FUNCTION_COLUMNS, resultRows, statement);\n  }\n\n  // @Override\n  public ResultSet getPseudoColumns(\n      String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)\n      throws SQLException {\n    logger.trace(\n        \"public ResultSet getPseudoColumns(String catalog, \"\n            + \"String schemaPattern,String tableNamePattern,\"\n            + \"String columnNamePattern)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  // @Override\n  public boolean generatedKeyAlwaysReturned() throws SQLException {\n    logger.trace(\"boolean generatedKeyAlwaysReturned()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  // unchecked\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"<T> T unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"boolean isWrapperFor(Class<?> iface)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  /**\n   * A small helper function to execute show command to get metadata, And if object does not exist,\n   * return an empty result set instead of throwing a SnowflakeSQLException\n   */\n  private ResultSet executeAndReturnEmptyResultIfNotFound(\n      Statement statement, String sql, DBMetadataResultSetMetadata metadataType)\n      throws SQLException {\n    ResultSet resultSet;\n    if (isNullOrEmpty(sql)) {\n      return SnowflakeDatabaseMetaDataResultSet.getEmptyResultSet(metadataType, statement);\n    }\n    try {\n      resultSet = statement.executeQuery(sql);\n    } catch (SnowflakeSQLException e) {\n      if (e.getSQLState().equals(SqlState.NO_DATA)\n          || e.getSQLState().equals(SqlState.BASE_TABLE_OR_VIEW_NOT_FOUND)\n          || e.getMessage().contains(\"Operation is not supported in reader account\")) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResult(\n            metadataType, statement, e.getQueryId());\n      }\n      // When using this helper function for \"desc function\" calls, there are some built-in\n      // functions with unusual argument syntax that throw an error when attempting to call\n      // desc function on them. For example, AS_TIMESTAMP_LTZ([,VARIANT]) throws an exception.\n      // Skip these built-in functions.\n      else if (sql.contains(\"desc function\")\n          && e.getSQLState().equals(SqlState.SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION)) {\n        return SnowflakeDatabaseMetaDataResultSet.getEmptyResult(\n            metadataType, statement, e.getQueryId());\n      } else {\n        throw e;\n      }\n    }\n    return resultSet;\n  }\n\n  private static class ContextAwareMetadataSearch {\n    private final String database;\n    private final String schema;\n    private final boolean isExactSchema;\n\n    public ContextAwareMetadataSearch(String database, String schema, boolean isExactSchema) {\n      this.database = database;\n      this.schema = schema;\n      this.isExactSchema = isExactSchema;\n    }\n\n    public String database() {\n      return database;\n    }\n\n    public String schema() {\n      return schema;\n    }\n\n    public boolean isExactSchema() {\n      return isExactSchema;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/pooling/LogicalConnection.java",
    "content": "package net.snowflake.client.internal.api.implementation.pooling;\n\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.CallableStatement;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.NClob;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLClientInfoException;\nimport java.sql.SQLException;\nimport java.sql.SQLWarning;\nimport java.sql.SQLXML;\nimport java.sql.Savepoint;\nimport java.sql.Statement;\nimport java.sql.Struct;\nimport java.util.Properties;\nimport java.util.concurrent.Executor;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Logical connection is wrapper class on top of SnowflakeConnectionImpl Every method call will be\n * delegated to SnowflakeConnectionImpl except for close method\n */\nclass LogicalConnection implements Connection {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(LogicalConnection.class);\n\n  /** physical connection to snowflake, instance SnowflakeConnectionImpl */\n  private final Connection physicalConnection;\n\n  /** Pooled connection object that create this logical connection */\n  private final SnowflakePooledConnection pooledConnection;\n\n  /**\n   * flags indicating whether this logical connection is closed or not Note: This is different from\n   * physical connection's state of whether closed or not\n   */\n  private boolean isClosed;\n\n  LogicalConnection(SnowflakePooledConnection pooledConnection) throws SQLException {\n    this.physicalConnection = pooledConnection.getPhysicalConnection();\n    this.pooledConnection = pooledConnection;\n    this.isClosed = physicalConnection.isClosed();\n  }\n\n  @Override\n  public Statement createStatement() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createStatement();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareStatement(sql);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareCall(sql);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public String nativeSQL(String sql) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.nativeSQL(sql);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setAutoCommit(boolean autoCommit) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setAutoCommit(autoCommit);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public boolean getAutoCommit() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getAutoCommit();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void commit() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.commit();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void rollback() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.rollback();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  /** Logical connection will not close physical connection, but fire events */\n  @Override\n  public void close() throws SQLException {\n    if (isClosed) {\n      return;\n    }\n    SnowflakeConnectionImpl sfConnection = physicalConnection.unwrap(SnowflakeConnectionImpl.class);\n    logger.debug(\"Closing logical connection with session id: {}\", sfConnection.getSessionID());\n    pooledConnection.fireConnectionCloseEvent();\n    isClosed = true;\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    return isClosed;\n  }\n\n  @Override\n  public DatabaseMetaData getMetaData() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getMetaData();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setReadOnly(boolean readOnly) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setReadOnly(readOnly);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public boolean isReadOnly() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.isReadOnly();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setCatalog(String catalog) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setCatalog(catalog);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public String getCatalog() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getCatalog();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setTransactionIsolation(int level) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setTransactionIsolation(level);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public int getTransactionIsolation() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getTransactionIsolation();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public SQLWarning getWarnings() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getWarnings();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.clearWarnings();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Statement createStatement(int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createStatement(resultSetType, resultSetConcurrency);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareStatement(sql, resultSetType, resultSetConcurrency);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency)\n      throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareCall(sql, resultSetType, resultSetConcurrency);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public java.util.Map<String, Class<?>> getTypeMap() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getTypeMap();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setTypeMap(java.util.Map<String, Class<?>> map) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setTypeMap(map);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setHoldability(int holdability) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setHoldability(holdability);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public int getHoldability() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getHoldability();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Savepoint setSavepoint() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.setSavepoint();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Savepoint setSavepoint(String name) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.setSavepoint(name);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void rollback(Savepoint savepoint) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.rollback(savepoint);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void releaseSavepoint(Savepoint savepoint) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.releaseSavepoint(savepoint);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Statement createStatement(\n      int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createStatement(\n          resultSetType, resultSetConcurrency, resultSetHoldability);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareStatement(\n          sql, resultSetType, resultSetConcurrency, resultSetHoldability);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public CallableStatement prepareCall(\n      String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)\n      throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareCall(\n          sql, resultSetType, resultSetConcurrency, resultSetHoldability);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareStatement(sql, autoGeneratedKeys);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareStatement(sql, columnIndexes);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.prepareStatement(sql, columnNames);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Clob createClob() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createClob();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Blob createBlob() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createBlob();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public NClob createNClob() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createNClob();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public SQLXML createSQLXML() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createSQLXML();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public boolean isValid(int timeout) throws SQLException {\n    try {\n      return !isClosed && physicalConnection.isValid(timeout);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setClientInfo(String name, String value) throws SQLClientInfoException {\n    try {\n      physicalConnection.setClientInfo(name, value);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setClientInfo(Properties properties) throws SQLClientInfoException {\n    try {\n      physicalConnection.setClientInfo(properties);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public String getClientInfo(String name) throws SQLException {\n    try {\n      return physicalConnection.getClientInfo(name);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Properties getClientInfo() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getClientInfo();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createArrayOf(typeName, elements);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public Struct createStruct(String typeName, Object[] attributes) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.createStruct(typeName, attributes);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setSchema(String schema) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setSchema(schema);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public String getSchema() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getSchema();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void abort(Executor executor) throws SQLException {\n    try {\n      physicalConnection.abort(executor);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      physicalConnection.setNetworkTimeout(executor, milliseconds);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public int getNetworkTimeout() throws SQLException {\n    throwExceptionIfClosed();\n\n    try {\n      return physicalConnection.getNetworkTimeout();\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    try {\n      return physicalConnection.isWrapperFor(iface);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    try {\n      return physicalConnection.unwrap(iface);\n    } catch (SQLException e) {\n      pooledConnection.fireConnectionErrorEvent(e);\n      throw e;\n    }\n  }\n\n  private void throwExceptionIfClosed() throws SQLException {\n    if (isClosed) {\n      throw new SnowflakeSQLException(ErrorCode.CONNECTION_CLOSED);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/pooling/SnowflakeConnectionPoolDataSourceImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.pooling;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport javax.sql.PooledConnection;\nimport net.snowflake.client.api.pooling.SnowflakeConnectionPoolDataSource;\nimport net.snowflake.client.internal.api.implementation.datasource.SnowflakeBasicDataSource;\n\npublic class SnowflakeConnectionPoolDataSourceImpl extends SnowflakeBasicDataSource\n    implements SnowflakeConnectionPoolDataSource {\n  @Override\n  public PooledConnection getPooledConnection() throws SQLException {\n    Connection connection = super.getConnection();\n    return new SnowflakePooledConnection(connection);\n  }\n\n  @Override\n  public PooledConnection getPooledConnection(String user, String password) throws SQLException {\n    Connection connection = super.getConnection(user, password);\n    return new SnowflakePooledConnection(connection);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/pooling/SnowflakePooledConnection.java",
    "content": "package net.snowflake.client.internal.api.implementation.pooling;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.HashSet;\nimport java.util.Set;\nimport javax.sql.ConnectionEvent;\nimport javax.sql.ConnectionEventListener;\nimport javax.sql.PooledConnection;\nimport javax.sql.StatementEventListener;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Snowflake implementation of pooled connection */\npublic class SnowflakePooledConnection implements PooledConnection {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakePooledConnection.class);\n\n  /** physical connection, an instance of SnowflakeConnectionImpl class */\n  private Connection physicalConnection;\n\n  /** list of event listener registered to listen for connection event */\n  private final Set<ConnectionEventListener> eventListeners;\n\n  public SnowflakePooledConnection(Connection physicalConnection) throws SQLException {\n    this.physicalConnection = physicalConnection;\n\n    SnowflakeConnectionImpl sfConnection = physicalConnection.unwrap(SnowflakeConnectionImpl.class);\n    logger.debug(\"Creating new pooled connection with session id: {}\", sfConnection.getSessionID());\n\n    this.eventListeners = new HashSet<>();\n  }\n\n  @Override\n  public Connection getConnection() throws SQLException {\n    SnowflakeConnectionImpl sfConnection = physicalConnection.unwrap(SnowflakeConnectionImpl.class);\n    logger.debug(\n        \"Creating new Logical Connection based on pooled connection with session id: {}\",\n        sfConnection.getSessionID());\n    return new LogicalConnection(this);\n  }\n\n  Connection getPhysicalConnection() {\n    return physicalConnection;\n  }\n\n  /** Fire a connection has been closed event to event listener */\n  void fireConnectionCloseEvent() {\n    for (ConnectionEventListener connectionEventListener : eventListeners) {\n      connectionEventListener.connectionClosed(new ConnectionEvent(this));\n    }\n  }\n\n  void fireConnectionErrorEvent(SQLException e) {\n    for (ConnectionEventListener connectionEventListener : eventListeners) {\n      connectionEventListener.connectionErrorOccurred(new ConnectionEvent(this, e));\n    }\n  }\n\n  @Override\n  public void addConnectionEventListener(ConnectionEventListener eventListener) {\n    this.eventListeners.add(eventListener);\n  }\n\n  @Override\n  public void close() throws SQLException {\n    if (this.physicalConnection != null) {\n      SnowflakeConnectionImpl sfConnection =\n          physicalConnection.unwrap(SnowflakeConnectionImpl.class);\n      logger.debug(\"Closing pooled connection with session id: {}\", sfConnection.getSessionID());\n      this.physicalConnection.close();\n      this.physicalConnection = null;\n    }\n\n    eventListeners.clear();\n  }\n\n  @Override\n  public void removeConnectionEventListener(ConnectionEventListener eventListener) {\n    this.eventListeners.remove(eventListener);\n  }\n\n  @Override\n  public void addStatementEventListener(StatementEventListener eventListener) {\n    // do nothing for now\n  }\n\n  @Override\n  public void removeStatementEventListener(StatementEventListener eventListener) {\n    // do nothing for now\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/resultset/FieldMetadataImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.resultset;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\n/** Implementation of {@link FieldMetadata} for structured type field information. */\npublic class FieldMetadataImpl implements FieldMetadata {\n\n  private String name;\n  private String typeName;\n  private int type;\n  private boolean nullable;\n\n  private int byteLength;\n\n  private int precision;\n  private int scale;\n  private boolean fixed;\n  private SnowflakeType base;\n  private List<FieldMetadata> fields;\n\n  public FieldMetadataImpl(\n      String name,\n      String typeName,\n      int type,\n      boolean nullable,\n      int byteLength,\n      int precision,\n      int scale,\n      boolean fixed,\n      SnowflakeType base,\n      List<FieldMetadata> fields) {\n    this.name = name;\n    this.typeName = typeName;\n    this.type = type;\n    this.nullable = nullable;\n    this.byteLength = byteLength;\n    this.precision = precision;\n    this.scale = scale;\n    this.fixed = fixed;\n    this.base = base;\n    this.fields = fields;\n  }\n\n  public FieldMetadataImpl() {\n    this.fields = new ArrayList<>();\n  }\n\n  @Override\n  public String getName() {\n    return name;\n  }\n\n  public void setName(String name) {\n    this.name = name;\n  }\n\n  @Override\n  public String getTypeName() {\n    return typeName;\n  }\n\n  public void setTypeName(String typeName) {\n    this.typeName = typeName;\n  }\n\n  @Override\n  public int getType() {\n    return type;\n  }\n\n  public void setType(int type) {\n    this.type = type;\n  }\n\n  @Override\n  public boolean isNullable() {\n    return nullable;\n  }\n\n  public void setNullable(boolean nullable) {\n    this.nullable = nullable;\n  }\n\n  @Override\n  public int getByteLength() {\n    return byteLength;\n  }\n\n  public void setByteLength(int byteLength) {\n    this.byteLength = byteLength;\n  }\n\n  @Override\n  public int getPrecision() {\n    return precision;\n  }\n\n  public void setPrecision(int precision) {\n    this.precision = precision;\n  }\n\n  @Override\n  public int getScale() {\n    return scale;\n  }\n\n  public void setScale(int scale) {\n    this.scale = scale;\n  }\n\n  @Override\n  public boolean isFixed() {\n    return fixed;\n  }\n\n  public void setFixed(boolean fixed) {\n    this.fixed = fixed;\n  }\n\n  @Override\n  public SnowflakeType getBase() {\n    return base;\n  }\n\n  public void setBase(SnowflakeType base) {\n    this.base = base;\n  }\n\n  @Override\n  public List<FieldMetadata> getFields() {\n    return fields;\n  }\n\n  public void setFields(List<FieldMetadata> fields) {\n    this.fields = fields;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/resultset/SnowflakeBaseResultSet.java",
    "content": "package net.snowflake.client.internal.api.implementation.resultset;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.mapSFExceptionToSQLException;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.Date;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.RowId;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLWarning;\nimport java.sql.SQLXML;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.ColumnTypeHelper;\nimport net.snowflake.client.internal.core.JsonSqlInput;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.StructObjectWrapper;\nimport net.snowflake.client.internal.core.structs.SQLDataCreationHelper;\nimport net.snowflake.client.internal.jdbc.SnowflakeClob;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetMetaDataV1;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\n/** Base class for query result set and metadata result set */\npublic abstract class SnowflakeBaseResultSet implements ResultSet, SnowflakeResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeBaseResultSet.class);\n  private final int resultSetType;\n  private final int resultSetConcurrency;\n  private final int resultSetHoldability;\n  protected SFBaseResultSet sfBaseResultSet;\n  // Snowflake supports sessionless result set. For this case, there is no\n  // statement for this result set.\n  protected final Statement statement;\n  protected SnowflakeResultSetMetaDataV1 resultSetMetaData = null;\n  protected Map<String, Object> parameters = new HashMap<>();\n  private int fetchSize = 0;\n  protected SFBaseSession session;\n  private final SnowflakeResultSetSerializableV1 serializable;\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n\n  public SnowflakeBaseResultSet(Statement statement) throws SQLException {\n    this.statement = statement;\n    this.resultSetType = statement.getResultSetType();\n    this.resultSetConcurrency = statement.getResultSetConcurrency();\n    this.resultSetHoldability = statement.getResultSetHoldability();\n    this.session = maybeGetSession(statement);\n    this.serializable = null;\n  }\n\n  private static SFBaseSession maybeGetSession(Statement statement) {\n    try {\n      return ((SnowflakeConnectionImpl) statement.getConnection())\n          .getSFBaseSession(internalCallMarker());\n    } catch (SQLException e) {\n      // This exception shouldn't be hit. Statement class should be able to be unwrapped.\n      logger.error(\n          \"Unable to unwrap SnowflakeStatementImpl class to retrieve session. Session is null.\",\n          false);\n      return null;\n    }\n  }\n\n  /**\n   * Create an sessionless result set, there is no statement and session for the result set.\n   *\n   * @param resultSetSerializable The result set serializable object which includes all metadata to\n   *     create the result set\n   * @throws SQLException if an error occurs\n   */\n  public SnowflakeBaseResultSet(SnowflakeResultSetSerializableV1 resultSetSerializable)\n      throws SQLException {\n    // This is a sessionless result set, so there is no actual statement for it.\n    this.statement = new SnowflakeStatementImpl.NoOpSnowflakeStatementImpl();\n    this.resultSetType = resultSetSerializable.getResultSetType();\n    this.resultSetConcurrency = resultSetSerializable.getResultSetConcurrency();\n    this.resultSetHoldability = resultSetSerializable.getResultSetHoldability();\n    this.session = null;\n    this.serializable = resultSetSerializable;\n  }\n\n  /**\n   * This should never be used. Simply needed this for SFAsynchronousResult subclass\n   *\n   * @throws SQLException if an error occurs\n   */\n  protected SnowflakeBaseResultSet() throws SQLException {\n    this.resultSetType = 0;\n    this.resultSetConcurrency = 0;\n    this.resultSetHoldability = 0;\n    this.statement = new SnowflakeStatementImpl.NoOpSnowflakeStatementImpl();\n    this.session = null;\n    this.serializable = null;\n  }\n\n  @Override\n  public abstract boolean next() throws SQLException;\n\n  @Override\n  public abstract boolean isClosed() throws SQLException;\n\n  /**\n   * Raises SQLException if the result set is closed\n   *\n   * @throws SQLException if the result set is closed.\n   */\n  protected void raiseSQLExceptionIfResultSetIsClosed() throws SQLException {\n    if (isClosed()) {\n      throw new SnowflakeSQLException(ErrorCode.RESULTSET_ALREADY_CLOSED);\n    }\n  }\n\n  @Override\n  public abstract byte[] getBytes(int columnIndex) throws SQLException;\n\n  /**\n   * Get Date value\n   *\n   * @param columnIndex column index\n   * @param tz timezone\n   * @return Date value at column index\n   * @throws SQLException if data at column index is incompatible with Date type\n   */\n  public abstract Date getDate(int columnIndex, TimeZone tz) throws SQLException;\n\n  private boolean getGetDateUseNullTimezone() {\n    if (this.session != null) {\n      return this.session.getGetDateUseNullTimezone();\n    }\n\n    if (this.serializable != null) {\n      return this.serializable.getGetDateUseNullTimezone();\n    }\n\n    return false;\n  }\n\n  @Override\n  public Date getDate(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return getDate(columnIndex, getGetDateUseNullTimezone() ? null : TimeZone.getDefault());\n  }\n\n  @Override\n  public abstract Time getTime(int columnIndex) throws SQLException;\n\n  @Override\n  public Timestamp getTimestamp(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return getTimestamp(columnIndex, (TimeZone) null);\n  }\n\n  /**\n   * Get timestamp value\n   *\n   * @param columnIndex column index\n   * @param tz timezone\n   * @return timestamp value at column index\n   * @throws SQLException if data at column index is incompatible with timestamp\n   */\n  public abstract Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SQLException;\n\n  @Override\n  public InputStream getAsciiStream(int columnIndex) throws SQLException {\n    logger.trace(\"InputStream getAsciiStream(int columnIndex)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  /**\n   * @deprecated\n   */\n  @Deprecated\n  @Override\n  public InputStream getUnicodeStream(int columnIndex) throws SQLException {\n    logger.trace(\"InputStream getUnicodeStream(int columnIndex)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public InputStream getBinaryStream(int columnIndex) throws SQLException {\n    logger.trace(\"InputStream getBinaryStream(int columnIndex)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public String getString(String columnLabel) throws SQLException {\n    logger.trace(\"String getString(String columnLabel)\", false);\n\n    return getString(findColumn(columnLabel));\n  }\n\n  @Override\n  public boolean getBoolean(String columnLabel) throws SQLException {\n    logger.trace(\"boolean getBoolean(String columnLabel)\", false);\n\n    return getBoolean(findColumn(columnLabel));\n  }\n\n  @Override\n  public byte getByte(String columnLabel) throws SQLException {\n    logger.trace(\"byte getByte(String columnLabel)\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    return getByte(findColumn(columnLabel));\n  }\n\n  @Override\n  public short getShort(String columnLabel) throws SQLException {\n    logger.trace(\"short getShort(String columnLabel)\", false);\n\n    return getShort(findColumn(columnLabel));\n  }\n\n  @Override\n  public int getInt(String columnLabel) throws SQLException {\n    logger.trace(\"int getInt(String columnLabel)\", false);\n\n    return getInt(findColumn(columnLabel));\n  }\n\n  @Override\n  public long getLong(String columnLabel) throws SQLException {\n    logger.trace(\"long getLong(String columnLabel)\", false);\n\n    return getLong(findColumn(columnLabel));\n  }\n\n  @Override\n  public float getFloat(String columnLabel) throws SQLException {\n    logger.trace(\"float getFloat(String columnLabel)\", false);\n\n    return getFloat(findColumn(columnLabel));\n  }\n\n  @Override\n  public double getDouble(String columnLabel) throws SQLException {\n    logger.trace(\"double getDouble(String columnLabel)\", false);\n\n    return getDouble(findColumn(columnLabel));\n  }\n\n  /**\n   * @deprecated\n   */\n  @Deprecated\n  @Override\n  public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {\n    logger.trace(\"BigDecimal getBigDecimal(String columnLabel, \" + \"int scale)\", false);\n\n    return getBigDecimal(findColumn(columnLabel), scale);\n  }\n\n  @Override\n  public byte[] getBytes(String columnLabel) throws SQLException {\n    logger.trace(\"byte[] getBytes(String columnLabel)\", false);\n\n    return getBytes(findColumn(columnLabel));\n  }\n\n  @Override\n  public Date getDate(String columnLabel) throws SQLException {\n    logger.trace(\"Date getDate(String columnLabel)\", false);\n\n    return getDate(findColumn(columnLabel));\n  }\n\n  @Override\n  public Time getTime(String columnLabel) throws SQLException {\n    logger.trace(\"Time getTime(String columnLabel)\", false);\n\n    return getTime(findColumn(columnLabel));\n  }\n\n  @Override\n  public Timestamp getTimestamp(String columnLabel) throws SQLException {\n    logger.trace(\"Timestamp getTimestamp(String columnLabel)\", false);\n\n    return getTimestamp(findColumn(columnLabel));\n  }\n\n  @Override\n  public InputStream getAsciiStream(String columnLabel) throws SQLException {\n    logger.trace(\"InputStream getAsciiStream(String columnLabel)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  /**\n   * @deprecated\n   */\n  @Deprecated\n  @Override\n  public InputStream getUnicodeStream(String columnLabel) throws SQLException {\n    logger.trace(\"InputStream getUnicodeStream(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public InputStream getBinaryStream(String columnLabel) throws SQLException {\n    logger.trace(\"InputStream getBinaryStream(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public SQLWarning getWarnings() throws SQLException {\n    logger.trace(\"SQLWarning getWarnings()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return null;\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    logger.trace(\"void clearWarnings()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n  }\n\n  @Override\n  public String getCursorName() throws SQLException {\n    logger.trace(\"String getCursorName()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public ResultSetMetaData getMetaData() throws SQLException {\n    logger.trace(\"ResultSetMetaData getMetaData()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetMetaData;\n  }\n\n  @Override\n  public Object getObject(String columnLabel) throws SQLException {\n    logger.trace(\"Object getObject(String columnLabel)\", false);\n\n    return getObject(findColumn(columnLabel));\n  }\n\n  @Override\n  public int findColumn(String columnLabel) throws SQLException {\n    logger.trace(\"int findColumn(String columnLabel)\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    int columnIndex = resultSetMetaData.getColumnIndex(columnLabel);\n\n    if (columnIndex == -1) {\n      throw new SQLException(\"Column not found: \" + columnLabel, SqlState.UNDEFINED_COLUMN);\n    } else {\n      return ++columnIndex;\n    }\n  }\n\n  @Override\n  public Reader getCharacterStream(int columnIndex) throws SQLException {\n    logger.trace(\"Reader getCharacterStream(int columnIndex)\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    String streamData = getString(columnIndex);\n    return (streamData == null) ? null : new StringReader(streamData);\n  }\n\n  @Override\n  public Reader getCharacterStream(String columnLabel) throws SQLException {\n    logger.trace(\"Reader getCharacterStream(String columnLabel)\", false);\n    return getCharacterStream(findColumn(columnLabel));\n  }\n\n  @Override\n  public BigDecimal getBigDecimal(String columnLabel) throws SQLException {\n    logger.trace(\"BigDecimal getBigDecimal(String columnLabel)\", false);\n\n    return getBigDecimal(findColumn(columnLabel));\n  }\n\n  @Override\n  public void beforeFirst() throws SQLException {\n    logger.trace(\"void beforeFirst()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void afterLast() throws SQLException {\n    logger.trace(\"void afterLast()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean first() throws SQLException {\n    logger.trace(\"boolean first()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean last() throws SQLException {\n    logger.trace(\"boolean last()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean absolute(int row) throws SQLException {\n    logger.trace(\"boolean absolute(int row)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean relative(int rows) throws SQLException {\n    logger.trace(\"boolean relative(int rows)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean previous() throws SQLException {\n    logger.trace(\"boolean previous()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public int getFetchDirection() throws SQLException {\n    logger.trace(\"int getFetchDirection()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return ResultSet.FETCH_FORWARD;\n  }\n\n  @Override\n  public void setFetchDirection(int direction) throws SQLException {\n    logger.trace(\"void setFetchDirection(int direction)\", false);\n\n    raiseSQLExceptionIfResultSetIsClosed();\n    if (direction != ResultSet.FETCH_FORWARD) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(session);\n    }\n  }\n\n  @Override\n  public int getFetchSize() throws SQLException {\n    logger.trace(\"int getFetchSize()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return this.fetchSize;\n  }\n\n  @Override\n  public void setFetchSize(int rows) throws SQLException {\n    logger.trace(\"void setFetchSize(int rows)\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    this.fetchSize = rows;\n  }\n\n  @Override\n  public int getType() throws SQLException {\n    logger.trace(\"int getType()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetType;\n  }\n\n  @Override\n  public int getConcurrency() throws SQLException {\n    logger.trace(\"int getConcurrency()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetConcurrency;\n  }\n\n  @Override\n  public boolean rowUpdated() throws SQLException {\n    logger.trace(\"boolean rowUpdated()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean rowInserted() throws SQLException {\n    logger.trace(\"boolean rowInserted()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean rowDeleted() throws SQLException {\n    logger.trace(\"boolean rowDeleted()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNull(int columnIndex) throws SQLException {\n    logger.trace(\"void updateNull(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBoolean(int columnIndex, boolean x) throws SQLException {\n    logger.trace(\"void updateBoolean(int columnIndex, boolean x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateByte(int columnIndex, byte x) throws SQLException {\n    logger.trace(\"void updateByte(int columnIndex, byte x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateShort(int columnIndex, short x) throws SQLException {\n    logger.trace(\"void updateShort(int columnIndex, short x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateInt(int columnIndex, int x) throws SQLException {\n    logger.trace(\"void updateInt(int columnIndex, int x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateLong(int columnIndex, long x) throws SQLException {\n    logger.trace(\"void updateLong(int columnIndex, long x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateFloat(int columnIndex, float x) throws SQLException {\n    logger.trace(\"void updateFloat(int columnIndex, float x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateDouble(int columnIndex, double x) throws SQLException {\n    logger.trace(\"void updateDouble(int columnIndex, double x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {\n    logger.trace(\"void updateBigDecimal(int columnIndex, BigDecimal x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateString(int columnIndex, String x) throws SQLException {\n    logger.trace(\"void updateString(int columnIndex, String x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBytes(int columnIndex, byte[] x) throws SQLException {\n    logger.trace(\"void updateBytes(int columnIndex, byte[] x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateDate(int columnIndex, Date x) throws SQLException {\n    logger.trace(\"void updateDate(int columnIndex, Date x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateTime(int columnIndex, Time x) throws SQLException {\n    logger.trace(\"void updateTime(int columnIndex, Time x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {\n    logger.trace(\"void updateTimestamp(int columnIndex, Timestamp x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {\n    logger.trace(\n        \"public void updateAsciiStream(int columnIndex, \" + \"InputStream x, int length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {\n    logger.trace(\n        \"public void updateBinaryStream(int columnIndex, \" + \"InputStream x, int length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {\n    logger.trace(\n        \"public void updateCharacterStream(int columnIndex, \" + \"Reader x, int length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {\n    logger.trace(\n        \"public void updateObject(int columnIndex, Object x, \" + \"int scaleOrLength)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateObject(int columnIndex, Object x) throws SQLException {\n    logger.trace(\"void updateObject(int columnIndex, Object x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNull(String columnLabel) throws SQLException {\n    logger.trace(\"void updateNull(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBoolean(String columnLabel, boolean x) throws SQLException {\n    logger.trace(\"void updateBoolean(String columnLabel, boolean x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateByte(String columnLabel, byte x) throws SQLException {\n    logger.trace(\"void updateByte(String columnLabel, byte x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateShort(String columnLabel, short x) throws SQLException {\n    logger.trace(\"void updateShort(String columnLabel, short x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateInt(String columnLabel, int x) throws SQLException {\n    logger.trace(\"void updateInt(String columnLabel, int x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateLong(String columnLabel, long x) throws SQLException {\n    logger.trace(\"void updateLong(String columnLabel, long x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateFloat(String columnLabel, float x) throws SQLException {\n    logger.trace(\"void updateFloat(String columnLabel, float x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateDouble(String columnLabel, double x) throws SQLException {\n    logger.trace(\"void updateDouble(String columnLabel, double x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {\n    logger.trace(\"void updateBigDecimal(String columnLabel, \" + \"BigDecimal x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateString(String columnLabel, String x) throws SQLException {\n    logger.trace(\"void updateString(String columnLabel, String x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBytes(String columnLabel, byte[] x) throws SQLException {\n    logger.trace(\"void updateBytes(String columnLabel, byte[] x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateDate(String columnLabel, Date x) throws SQLException {\n    logger.trace(\"void updateDate(String columnLabel, Date x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateTime(String columnLabel, Time x) throws SQLException {\n    logger.trace(\"void updateTime(String columnLabel, Time x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {\n    logger.trace(\"void updateTimestamp(String columnLabel, Timestamp x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {\n    logger.trace(\n        \"public void updateAsciiStream(String columnLabel, \" + \"InputStream x, int length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBinaryStream(String columnLabel, InputStream x, int length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateBinaryStream(String columnLabel, \" + \"InputStream x, int length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateCharacterStream(String columnLabel, Reader reader, int length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateCharacterStream(String columnLabel, \" + \"Reader reader,int length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {\n    logger.trace(\n        \"public void updateObject(String columnLabel, Object x, \" + \"int scaleOrLength)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateObject(String columnLabel, Object x) throws SQLException {\n    logger.trace(\"void updateObject(String columnLabel, Object x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void insertRow() throws SQLException {\n    logger.trace(\"void insertRow()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateRow() throws SQLException {\n    logger.trace(\"void updateRow()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void deleteRow() throws SQLException {\n    logger.trace(\"void deleteRow()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void refreshRow() throws SQLException {\n    logger.trace(\"void refreshRow()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void cancelRowUpdates() throws SQLException {\n    logger.trace(\"void cancelRowUpdates()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void moveToInsertRow() throws SQLException {\n    logger.trace(\"void moveToInsertRow()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void moveToCurrentRow() throws SQLException {\n    logger.trace(\"void moveToCurrentRow()\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Statement getStatement() throws SQLException {\n    logger.trace(\"Statement getStatement()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return statement;\n  }\n\n  @Override\n  public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {\n    logger.trace(\"Object getObject(int columnIndex, Map<String, \" + \"Class<?>> map)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Ref getRef(int columnIndex) throws SQLException {\n    logger.trace(\"Ref getRef(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Blob getBlob(int columnIndex) throws SQLException {\n    logger.trace(\"Blob getBlob(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Clob getClob(int columnIndex) throws SQLException {\n    logger.trace(\"Clob getClob(int columnIndex)\", false);\n    String columnValue = getString(columnIndex);\n\n    return columnValue == null ? null : new SnowflakeClob(columnValue);\n  }\n\n  @Override\n  public Array getArray(int columnIndex) throws SQLException {\n    logger.trace(\"Array getArray(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {\n    logger.trace(\n        \"public Object getObject(String columnLabel, \" + \"Map<String, Class<?>> map)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Ref getRef(String columnLabel) throws SQLException {\n    logger.trace(\"Ref getRef(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Blob getBlob(String columnLabel) throws SQLException {\n    logger.trace(\"Blob getBlob(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Clob getClob(String columnLabel) throws SQLException {\n    logger.trace(\"Clob getClob(String columnLabel)\", false);\n    String columnValue = getString(columnLabel);\n\n    return columnValue == null ? null : new SnowflakeClob(columnValue);\n  }\n\n  @Override\n  public Array getArray(String columnLabel) throws SQLException {\n    logger.trace(\"Array getArray(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Date getDate(int columnIndex, Calendar cal) throws SQLException {\n    logger.trace(\"Date getDate(int columnIndex, Calendar cal)\", false);\n    return getDate(columnIndex, cal.getTimeZone());\n  }\n\n  @Override\n  public Date getDate(String columnLabel, Calendar cal) throws SQLException {\n    logger.trace(\"Date getDate(String columnLabel, Calendar cal)\", false);\n\n    return getDate(findColumn(columnLabel), cal.getTimeZone());\n  }\n\n  @Override\n  public Time getTime(int columnIndex, Calendar cal) throws SQLException {\n    logger.trace(\"Time getTime(int columnIndex, Calendar cal)\", false);\n\n    return getTime(columnIndex);\n  }\n\n  @Override\n  public Time getTime(String columnLabel, Calendar cal) throws SQLException {\n    logger.trace(\"Time getTime(String columnLabel, Calendar cal)\", false);\n\n    return getTime(columnLabel);\n  }\n\n  @Override\n  public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {\n    logger.trace(\"Timestamp getTimestamp(int columnIndex, Calendar cal)\", false);\n\n    return getTimestamp(columnIndex, cal.getTimeZone());\n  }\n\n  @Override\n  public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {\n    logger.trace(\"Timestamp getTimestamp(String columnLabel, \" + \"Calendar cal)\", false);\n\n    return getTimestamp(findColumn(columnLabel), cal.getTimeZone());\n  }\n\n  @Override\n  public URL getURL(int columnIndex) throws SQLException {\n    logger.trace(\"URL getURL(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public URL getURL(String columnLabel) throws SQLException {\n    logger.trace(\"URL getURL(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateRef(int columnIndex, Ref x) throws SQLException {\n    logger.trace(\"void updateRef(int columnIndex, Ref x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateRef(String columnLabel, Ref x) throws SQLException {\n    logger.trace(\"void updateRef(String columnLabel, Ref x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBlob(int columnIndex, Blob x) throws SQLException {\n    logger.trace(\"void updateBlob(int columnIndex, Blob x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBlob(String columnLabel, Blob x) throws SQLException {\n    logger.trace(\"void updateBlob(String columnLabel, Blob x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateClob(int columnIndex, Clob x) throws SQLException {\n    logger.trace(\"void updateClob(int columnIndex, Clob x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateClob(String columnLabel, Clob x) throws SQLException {\n    logger.trace(\"void updateClob(String columnLabel, Clob x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateArray(int columnIndex, Array x) throws SQLException {\n    logger.trace(\"void updateArray(int columnIndex, Array x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateArray(String columnLabel, Array x) throws SQLException {\n    logger.trace(\"void updateArray(String columnLabel, Array x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public RowId getRowId(int columnIndex) throws SQLException {\n    logger.trace(\"RowId getRowId(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public RowId getRowId(String columnLabel) throws SQLException {\n    logger.trace(\"RowId getRowId(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateRowId(int columnIndex, RowId x) throws SQLException {\n    logger.trace(\"void updateRowId(int columnIndex, RowId x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateRowId(String columnLabel, RowId x) throws SQLException {\n    logger.trace(\"void updateRowId(String columnLabel, RowId x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public int getHoldability() throws SQLException {\n    logger.trace(\"int getHoldability()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetHoldability;\n  }\n\n  @Override\n  public void updateNString(int columnIndex, String nString) throws SQLException {\n    logger.trace(\"void updateNString(int columnIndex, String nString)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNString(String columnLabel, String nString) throws SQLException {\n    logger.trace(\"void updateNString(String columnLabel, String nString)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNClob(int columnIndex, NClob nClob) throws SQLException {\n    logger.trace(\"void updateNClob(int columnIndex, NClob nClob)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNClob(String columnLabel, NClob nClob) throws SQLException {\n    logger.trace(\"void updateNClob(String columnLabel, NClob nClob)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public NClob getNClob(int columnIndex) throws SQLException {\n    logger.trace(\"NClob getNClob(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public NClob getNClob(String columnLabel) throws SQLException {\n    logger.trace(\"NClob getNClob(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public SQLXML getSQLXML(int columnIndex) throws SQLException {\n    logger.trace(\"SQLXML getSQLXML(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public SQLXML getSQLXML(String columnLabel) throws SQLException {\n    logger.trace(\"SQLXML getSQLXML(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {\n    logger.trace(\"void updateSQLXML(int columnIndex, SQLXML xmlObject)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {\n    logger.trace(\"void updateSQLXML(String columnLabel, SQLXML xmlObject)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public String getNString(int columnIndex) throws SQLException {\n    logger.trace(\"String getNString(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public String getNString(String columnLabel) throws SQLException {\n    logger.trace(\"String getNString(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Reader getNCharacterStream(int columnIndex) throws SQLException {\n    logger.trace(\"Reader getNCharacterStream(int columnIndex)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public Reader getNCharacterStream(String columnLabel) throws SQLException {\n    logger.trace(\"Reader getNCharacterStream(String columnLabel)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {\n    logger.trace(\n        \"public void updateNCharacterStream(int columnIndex, \" + \"Reader x, long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNCharacterStream(String columnLabel, Reader reader, long length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateNCharacterStream(String columnLabel, \" + \"Reader reader,long length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {\n    logger.trace(\n        \"public void updateAsciiStream(int columnIndex, \" + \"InputStream x, long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {\n    logger.trace(\n        \"public void updateBinaryStream(int columnIndex, \" + \"InputStream x, long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {\n    logger.trace(\n        \"public void updateCharacterStream(int columnIndex, Reader x, \" + \"long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateAsciiStream(String columnLabel, InputStream x, long length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateAsciiStream(String columnLabel, \" + \"InputStream x, long length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBinaryStream(String columnLabel, InputStream x, long length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateBinaryStream(String columnLabel, \" + \"InputStream x, long length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateCharacterStream(String columnLabel, Reader reader, long length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateCharacterStream(String columnLabel, \" + \"Reader reader,long length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBlob(int columnIndex, InputStream inputStream, long length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateBlob(int columnIndex, InputStream \" + \"inputStream, long length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBlob(String columnLabel, InputStream inputStream, long length)\n      throws SQLException {\n    logger.trace(\n        \"public void updateBlob(String columnLabel, \" + \"InputStream inputStream,long length)\",\n        false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {\n    logger.trace(\"void updateClob(int columnIndex, Reader reader, \" + \"long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {\n    logger.trace(\n        \"public void updateClob(String columnLabel, Reader reader, \" + \"long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {\n    logger.trace(\n        \"public void updateNClob(int columnIndex, Reader reader, \" + \"long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {\n    logger.trace(\n        \"public void updateNClob(String columnLabel, Reader reader, \" + \"long length)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {\n    logger.trace(\"void updateNCharacterStream(int columnIndex, Reader x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {\n    logger.trace(\n        \"public void updateNCharacterStream(String columnLabel, \" + \"Reader reader)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {\n    logger.trace(\"void updateAsciiStream(int columnIndex, InputStream x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {\n    logger.trace(\"void updateBinaryStream(int columnIndex, InputStream x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {\n    logger.trace(\"void updateCharacterStream(int columnIndex, Reader x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {\n    logger.trace(\"void updateAsciiStream(String columnLabel, InputStream x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {\n    logger.trace(\"void updateBinaryStream(String columnLabel, InputStream x)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {\n    logger.trace(\n        \"public void updateCharacterStream(String columnLabel, \" + \"Reader reader)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {\n    logger.trace(\"void updateBlob(int columnIndex, InputStream inputStream)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {\n    logger.trace(\"void updateBlob(String columnLabel, InputStream \" + \"inputStream)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateClob(int columnIndex, Reader reader) throws SQLException {\n    logger.trace(\"void updateClob(int columnIndex, Reader reader)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateClob(String columnLabel, Reader reader) throws SQLException {\n    logger.trace(\"void updateClob(String columnLabel, Reader reader)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNClob(int columnIndex, Reader reader) throws SQLException {\n    logger.trace(\"void updateNClob(int columnIndex, Reader reader)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void updateNClob(String columnLabel, Reader reader) throws SQLException {\n    logger.trace(\"void updateNClob(String columnLabel, Reader reader)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {\n    logger.trace(\"<T> T getObject(int columnIndex,Class<T> type)\", false);\n    if (resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n      if (SQLData.class.isAssignableFrom(type)) {\n        SQLInput sqlInput =\n            SnowflakeUtil.mapSFExceptionToSQLException(\n                () -> {\n                  StructObjectWrapper structObjectWrapper =\n                      (StructObjectWrapper) sfBaseResultSet.getObjectWithoutString(columnIndex);\n                  if (structObjectWrapper == null) {\n                    return null;\n                  }\n                  return (SQLInput) structObjectWrapper.getObject();\n                });\n        if (sqlInput == null) {\n          return null;\n        } else {\n          SQLData instance = (SQLData) SQLDataCreationHelper.create(type);\n          instance.readSQL(sqlInput, null);\n          return (T) instance;\n        }\n      } else if (Map.class.isAssignableFrom(type)) {\n        Object object = getObject(columnIndex);\n        if (object == null) {\n          return null;\n        } else if (object instanceof Map) {\n          throw new SQLException(\n              \"Arrow native struct couldn't be converted to String. To map to SqlData the method getObject(int columnIndex, Class type) should be used\");\n        } else {\n          try {\n            return (T)\n                OBJECT_MAPPER.readValue(\n                    (String) object, new TypeReference<Map<Object, Object>>() {});\n          } catch (JsonProcessingException e) {\n            throw new SQLException(\"Value couldn't be converted to Map\");\n          }\n        }\n      }\n    }\n    if (String.class.isAssignableFrom(type)) {\n      return (T) getString(columnIndex);\n    } else if (Boolean.class.isAssignableFrom(type)) {\n      return (T) (Boolean) getBoolean(columnIndex);\n    } else if (Byte.class.isAssignableFrom(type)) {\n      return (T) (Byte) getByte(columnIndex);\n    } else if (Short.class.isAssignableFrom(type)) {\n      return (T) (Short) getShort(columnIndex);\n    } else if (Integer.class.isAssignableFrom(type)) {\n      return (T) (Integer) getInt(columnIndex);\n    } else if (Long.class.isAssignableFrom(type)) {\n      return (T) (Long) getLong(columnIndex);\n    } else if (Float.class.isAssignableFrom(type)) {\n      return (T) (Float) getFloat(columnIndex);\n    } else if (Double.class.isAssignableFrom(type)) {\n      return (T) (Double) getDouble(columnIndex);\n    } else if (Date.class.isAssignableFrom(type)) {\n      return (T) getDate(columnIndex);\n    } else if (Time.class.isAssignableFrom(type)) {\n      return (T) getTime(columnIndex);\n    } else if (Timestamp.class.isAssignableFrom(type)) {\n      return (T) getTimestamp(columnIndex);\n    } else if (BigDecimal.class.isAssignableFrom(type)) {\n      return (T) getBigDecimal(columnIndex);\n    } else if (Period.class.isAssignableFrom(type)) {\n      try {\n        return (T) sfBaseResultSet.getPeriod(columnIndex);\n      } catch (SFException e) {\n        throw new SQLException(\n            \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n                + type.getName());\n      }\n    } else if (Duration.class.isAssignableFrom(type)) {\n      try {\n        return (T) sfBaseResultSet.getDuration(columnIndex);\n      } catch (SFException e) {\n        throw new SQLException(\n            \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n                + type.getName());\n      }\n    } else {\n      logger.debug(\n          \"Unsupported type passed to getObject(int columnIndex,Class<T> type): \" + type.getName());\n      throw new SQLException(\n          \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n              + type.getName());\n    }\n  }\n\n  public <T> List<T> getList(int columnIndex, Class<T> type) throws SQLException {\n    logger.trace(\"<T> List<T> getList(int columnIndex, Class<T> type)\", false);\n    if (!resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(session);\n    }\n    T[] sqlInputs = getArray(columnIndex, type);\n    return Arrays.asList(sqlInputs);\n  }\n\n  public <T> T[] getArray(int columnIndex, Class<T> type) throws SQLException {\n    logger.trace(\"<T> T[] getArray(int columnIndex, Class<T> type)\", false);\n    if (!resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(session);\n    }\n    List<FieldMetadata> fieldMetadataList = resultSetMetaData.getColumnFields(columnIndex);\n    if (fieldMetadataList.size() != 1) {\n      throw new SQLException(\"Wrong size of fields for array type \" + fieldMetadataList.size());\n    }\n    FieldMetadata fieldMetadata = fieldMetadataList.get(0);\n    int columnSubType = fieldMetadata.getType();\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    int scale = fieldMetadata.getScale();\n    TimeZone tz = sfBaseResultSet.getSessionTimeZone();\n    Array array = getArray(columnIndex);\n    if (array == null) {\n      return null;\n    }\n    Object[] objects = (Object[]) array.getArray();\n    T[] arr = (T[]) java.lang.reflect.Array.newInstance(type, objects.length);\n    int counter = 0;\n    for (Object value : objects) {\n      if (value == null) {\n        arr[counter++] = null;\n      } else if (type.isAssignableFrom(value.getClass())) {\n        arr[counter++] = (T) value;\n      } else {\n        if (SQLData.class.isAssignableFrom(type)) {\n          SQLData instance = (SQLData) SQLDataCreationHelper.create(type);\n          SQLInput sqlInput =\n              sfBaseResultSet.createSqlInputForColumn(\n                  value, objects.getClass(), columnIndex, session, fieldMetadata.getFields());\n          instance.readSQL(sqlInput, null);\n          arr[counter++] = (T) instance;\n        } else if (String.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          sfBaseResultSet\n                              .getConverters()\n                              .getStringConverter()\n                              .getString(value, columnType, columnSubType, scale));\n        } else if (Boolean.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          sfBaseResultSet\n                              .getConverters()\n                              .getBooleanConverter()\n                              .getBoolean(value, columnType));\n        } else if (Byte.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          sfBaseResultSet\n                              .getConverters()\n                              .getBytesConverter()\n                              .getBytes(value, columnType, columnSubType, scale));\n        } else if (Short.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          Short.valueOf(\n                              sfBaseResultSet\n                                  .getConverters()\n                                  .getNumberConverter()\n                                  .getShort(value, columnType)));\n        } else if (Integer.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          Integer.valueOf(\n                              sfBaseResultSet\n                                  .getConverters()\n                                  .getNumberConverter()\n                                  .getInt(value, columnType)));\n        } else if (Long.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          Long.valueOf(\n                              sfBaseResultSet\n                                  .getConverters()\n                                  .getNumberConverter()\n                                  .getLong(value, columnType)));\n        } else if (Float.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          Float.valueOf(\n                              sfBaseResultSet\n                                  .getConverters()\n                                  .getNumberConverter()\n                                  .getFloat(value, columnType)));\n        } else if (Double.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          Double.valueOf(\n                              sfBaseResultSet\n                                  .getConverters()\n                                  .getNumberConverter()\n                                  .getDouble(value, columnType)));\n        } else if (Date.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          sfBaseResultSet\n                              .getConverters()\n                              .dateStringConverter(session)\n                              .convert((String) value));\n        } else if (Time.class.isAssignableFrom(type)) {\n          arr[counter++] =\n              mapSFExceptionToSQLException(\n                  () ->\n                      (T)\n                          sfBaseResultSet\n                              .getConverters()\n                              .timeFromStringConverter(session)\n                              .convert((String) value));\n        } else if (Timestamp.class.isAssignableFrom(type)) {\n          mapSFExceptionToSQLException(\n              () ->\n                  (T)\n                      sfBaseResultSet\n                          .getConverters()\n                          .timestampFromStringConverter(\n                              columnSubType, columnType, scale, session, null, tz)\n                          .convert((String) value));\n        } else if (BigDecimal.class.isAssignableFrom(type)) {\n          arr[counter++] = (T) getBigDecimal(columnIndex);\n        } else {\n          logger.debug(\n              \"Unsupported type passed to getArray(int columnIndex, Class<T> type): \"\n                  + type.getName());\n          throw new SQLException(\n              \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n                  + type.getName());\n        }\n      }\n    }\n    return arr;\n  }\n\n  public <T> Map<String, T> getMap(int columnIndex, Class<T> type) throws SQLException {\n    logger.trace(\"<T> Map<String, T> getMap(int columnIndex, Class<T> type)\", false);\n    if (!resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(session);\n    }\n    List<FieldMetadata> fieldMetadataList = resultSetMetaData.getColumnFields(columnIndex);\n    if (fieldMetadataList.size() != 2) {\n      throw new SQLException(\n          \"Wrong size of fields metadata for map type \" + fieldMetadataList.size());\n    }\n    FieldMetadata valueFieldMetadata = fieldMetadataList.get(1);\n    int columnSubType = valueFieldMetadata.getType();\n    int columnType = ColumnTypeHelper.getColumnType(valueFieldMetadata.getType(), session);\n    int scale = valueFieldMetadata.getScale();\n    TimeZone tz = sfBaseResultSet.getSessionTimeZone();\n    StructObjectWrapper structObjectWrapper =\n        (StructObjectWrapper)\n            SnowflakeUtil.mapSFExceptionToSQLException(\n                () -> sfBaseResultSet.getObjectWithoutString(columnIndex));\n    if (structObjectWrapper == null || structObjectWrapper.getObject() == null) {\n      return null;\n    }\n    Map<String, Object> map =\n        mapSFExceptionToSQLException(\n            () -> prepareMapWithValues(structObjectWrapper.getObject(), type));\n    Map<String, T> resultMap = new HashMap<>();\n    for (Map.Entry<String, Object> entry : map.entrySet()) {\n      if (SQLData.class.isAssignableFrom(type)) {\n        SQLData instance = (SQLData) SQLDataCreationHelper.create(type);\n        SQLInput sqlInput =\n            sfBaseResultSet.createSqlInputForColumn(\n                entry.getValue(),\n                structObjectWrapper.getObject().getClass(),\n                columnIndex,\n                session,\n                valueFieldMetadata.getFields());\n        instance.readSQL(sqlInput, null);\n        resultMap.put(entry.getKey(), (T) instance);\n      } else if (String.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        sfBaseResultSet\n                            .getConverters()\n                            .getStringConverter()\n                            .getString(entry.getValue(), columnType, columnSubType, scale)));\n      } else if (Boolean.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        sfBaseResultSet\n                            .getConverters()\n                            .getBooleanConverter()\n                            .getBoolean(entry.getValue(), columnType)));\n      } else if (Byte.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        sfBaseResultSet\n                            .getConverters()\n                            .getBytesConverter()\n                            .getBytes(entry.getValue(), columnType, columnSubType, scale)));\n      } else if (Short.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        (Short)\n                            sfBaseResultSet\n                                .getConverters()\n                                .getNumberConverter()\n                                .getShort(entry.getValue(), columnType)));\n      } else if (Integer.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        (Integer)\n                            sfBaseResultSet\n                                .getConverters()\n                                .getNumberConverter()\n                                .getInt(entry.getValue(), columnType)));\n      } else if (Long.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        (Long)\n                            sfBaseResultSet\n                                .getConverters()\n                                .getNumberConverter()\n                                .getLong(entry.getValue(), columnType)));\n      } else if (Float.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        (Float)\n                            sfBaseResultSet\n                                .getConverters()\n                                .getNumberConverter()\n                                .getFloat(entry.getValue(), columnType)));\n      } else if (Double.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        (Double)\n                            sfBaseResultSet\n                                .getConverters()\n                                .getNumberConverter()\n                                .getDouble(entry.getValue(), columnType)));\n      } else if (BigDecimal.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        sfBaseResultSet\n                            .getConverters()\n                            .getNumberConverter()\n                            .getBigDecimal(entry.getValue(), columnType)));\n      } else if (Date.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () -> (T) sfBaseResultSet.convertToDate(entry.getValue(), tz)));\n      } else if (Time.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () -> (T) sfBaseResultSet.convertToTime(entry.getValue(), scale)));\n\n      } else if (Timestamp.class.isAssignableFrom(type)) {\n        resultMap.put(\n            entry.getKey(),\n            mapSFExceptionToSQLException(\n                () ->\n                    (T)\n                        sfBaseResultSet.convertToTimestamp(\n                            entry.getValue(), columnType, columnSubType, tz, scale)));\n\n      } else {\n        logger.debug(\n            \"Unsupported type passed to getObject(int columnIndex,Class<T> type): \"\n                + type.getName());\n        throw new SQLException(\n            \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n                + type.getName());\n      }\n    }\n\n    return resultMap;\n  }\n\n  @Override\n  public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {\n    logger.trace(\"<T> T getObject(String columnLabel,Class<T> type)\", false);\n    return getObject(findColumn(columnLabel), type);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"<T> T unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"boolean isWrapperFor(Class<?> iface)\", false);\n\n    return iface.isInstance(this);\n  }\n\n  private <T> Map<String, Object> prepareMapWithValues(Object object, Class<T> type)\n      throws SFException {\n    if (object instanceof JsonSqlInput) {\n      Map map = new HashMap<>();\n      JsonNode jsonNode = ((JsonSqlInput) object).getInput();\n      for (Iterator<String> it = jsonNode.fieldNames(); it.hasNext(); ) {\n        String name = it.next();\n        map.put(\n            name,\n            SQLData.class.isAssignableFrom(type)\n                ? jsonNode.get(name)\n                : SnowflakeUtil.getJsonNodeStringValue(jsonNode.get(name)));\n      }\n      return map;\n    } else if (object instanceof Map) {\n      return (Map<String, Object>) object;\n    } else {\n      throw new SFException(ErrorCode.INVALID_STRUCT_DATA, \"Object couldn't be converted to map\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/statement/SnowflakeCallableStatementImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.statement;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.CallableStatement;\nimport java.sql.Clob;\nimport java.sql.Date;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.RowId;\nimport java.sql.SQLException;\nimport java.sql.SQLXML;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.Calendar;\nimport java.util.Map;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic final class SnowflakeCallableStatementImpl extends SnowflakePreparedStatementImpl\n    implements CallableStatement {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeCallableStatementImpl.class);\n\n  /**\n   * Construct SnowflakePreparedStatementImpl\n   *\n   * @param connection connection object\n   * @param sql sql\n   * @param skipParsing true if the applications want to skip parsing to get metadata. false by\n   *     default.\n   * @param resultSetType result set type: ResultSet.TYPE_FORWARD_ONLY.\n   * @param resultSetConcurrency result set concurrency: ResultSet.CONCUR_READ_ONLY.\n   * @param resultSetHoldability result set holdability: ResultSet.CLOSE_CURSORS_AT_COMMIT\n   * @throws SQLException if any SQL error occurs.\n   */\n  public SnowflakeCallableStatementImpl(\n      SnowflakeConnectionImpl connection,\n      String sql,\n      boolean skipParsing,\n      int resultSetType,\n      int resultSetConcurrency,\n      int resultSetHoldability)\n      throws SQLException {\n    super(\n        connection,\n        parseSqlEscapeSyntax(sql),\n        skipParsing,\n        resultSetType,\n        resultSetConcurrency,\n        resultSetHoldability);\n  }\n\n  /**\n   * Helper function to remove curly brackets for CallableStatement procedure calls, since GS parser\n   * does not support escape syntax for curly brackets\n   *\n   * @param originalSql original SQL text, possibly with curly brackets\n   * @return a string of SQL text with curly brackets removed\n   */\n  public static String parseSqlEscapeSyntax(String originalSql) {\n    originalSql = originalSql.trim();\n    if (originalSql.startsWith(\"{\") && originalSql.endsWith(\"}\")) {\n      logger.debug(\"Curly brackets {} removed before sending sql to server.\", false);\n      return originalSql.substring(1, originalSql.length() - 1);\n    }\n    return originalSql;\n  }\n\n  /*\n   The Snowflake database does not accept OUT or INOUT parameters, so the registerOutParameter functions and the get\n    functions (which get values of OUT parameters) will remain not implemented)\n  */\n\n  @Override\n  public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void registerOutParameter(int parameterIndex, int sqlType, String typeName)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void registerOutParameter(String parameterName, int sqlType) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void registerOutParameter(String parameterName, int sqlType, int scale)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void registerOutParameter(String parameterName, int sqlType, String typeName)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean wasNull() throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public String getString(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public String getString(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean getBoolean(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean getBoolean(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public byte getByte(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public byte getByte(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public short getShort(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public short getShort(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public int getInt(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public int getInt(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public long getLong(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public long getLong(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public float getFloat(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public float getFloat(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public double getDouble(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public double getDouble(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  @Deprecated\n  public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  @Deprecated\n  public BigDecimal getBigDecimal(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  @Deprecated\n  public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public byte[] getBytes(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public byte[] getBytes(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Date getDate(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Date getDate(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Time getTime(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Time getTime(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Time getTime(String parameterName, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Timestamp getTimestamp(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Timestamp getTimestamp(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Object getObject(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Object getObject(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Object getObject(String parameterName, Map<String, Class<?>> map) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Ref getRef(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Ref getRef(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Blob getBlob(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Blob getBlob(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Clob getClob(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Clob getClob(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Array getArray(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Array getArray(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Date getDate(int parameterIndex, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Date getDate(String parameterName, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Time getTime(int parameterIndex, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public URL getURL(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public URL getURL(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public RowId getRowId(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public RowId getRowId(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public SQLXML getSQLXML(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public SQLXML getSQLXML(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public String getNString(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public String getNString(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Reader getNCharacterStream(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Reader getNCharacterStream(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Reader getCharacterStream(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Reader getCharacterStream(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  /*\n   JDBC is not currently set up to store the parameter names, just the parameter index. Callable Statement can set\n   parameters using the functions in PreparedStatement based on parameter index.\n  */\n\n  @Override\n  public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setRowId(String parameterName, RowId x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNString(String parameterName, String value) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNCharacterStream(String parameterName, Reader value) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNCharacterStream(String parameterName, Reader value, long length)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNClob(String parameterName, NClob value) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setClob(String parameterName, Reader reader, long length) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setClob(String parameterName, Reader reader) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setClob(String parameterName, Clob x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBlob(String parameterName, InputStream inputStream, long length)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBlob(String parameterName, InputStream inputStream) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNClob(String parameterName, Reader reader) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public <T> T getObject(int parameterIndex, Class<T> type) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public <T> T getObject(String parameterName, Class<T> type) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBlob(String parameterName, Blob x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNClob(String parameterName, Reader reader, long length) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public NClob getNClob(int parameterIndex) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public NClob getNClob(String parameterName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setURL(String parameterName, URL val) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNull(String parameterName, int sqlType) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBoolean(String parameterName, boolean x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setByte(String parameterName, byte x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setShort(String parameterName, short x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setInt(String parameterName, int x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setLong(String parameterName, long x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setFloat(String parameterName, float x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setDouble(String parameterName, double x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setString(String parameterName, String x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBytes(String parameterName, byte[] x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setDate(String parameterName, Date x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setTime(String parameterName, Time x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setTimestamp(String parameterName, Timestamp x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setAsciiStream(String parameterName, InputStream x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBinaryStream(String parameterName, InputStream x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBinaryStream(String parameterName, InputStream x, long length)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setObject(String parameterName, Object x, int targetSqlType, int scale)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setObject(String parameterName, Object x) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setCharacterStream(String parameterName, Reader reader, int length)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setCharacterStream(String parameterName, Reader reader, long length)\n      throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setCharacterStream(String parameterName, Reader reader) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setDate(String parameterName, Date x, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setTime(String parameterName, Time x, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/statement/SnowflakePreparedStatementImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.statement;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.net.URL;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.Date;\nimport java.sql.NClob;\nimport java.sql.ParameterMetaData;\nimport java.sql.PreparedStatement;\nimport java.sql.Ref;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.RowId;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLXML;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.api.statement.SnowflakePreparedStatement;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.core.FieldSchemaCreator;\nimport net.snowflake.client.internal.core.JsonSqlOutput;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFPreparedStatementMetaData;\nimport net.snowflake.client.internal.core.SfSqlArray;\nimport net.snowflake.client.internal.core.SfTimestampUtil;\nimport net.snowflake.client.internal.core.StmtUtil;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.BindingParameterMetadata;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeParameterMetadata;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetMetaDataV1;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.VariableTypeArray;\nimport net.snowflake.common.core.SqlState;\n\npublic class SnowflakePreparedStatementImpl extends SnowflakeStatementImpl\n    implements PreparedStatement, SnowflakePreparedStatement {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakePreparedStatementImpl.class);\n\n  /** Error code returned when describing a statement that is binding table name */\n  private static final Integer ERROR_CODE_TABLE_BIND_VARIABLE_NOT_SET = 2128;\n\n  /** Error code when preparing statement with binding object names */\n  private static final Integer ERROR_CODE_OBJECT_BIND_NOT_SET = 2129;\n\n  /** Error code returned when describing a ddl command */\n  private static final Integer ERROR_CODE_STATEMENT_CANNOT_BE_PREPARED = 7;\n\n  /** snow-44393 Workaround for compiler cannot prepare to_timestamp(?, 3) */\n  private static final Integer ERROR_CODE_FORMAT_ARGUMENT_NOT_STRING = 1026;\n\n  /** A hash set that contains the error code that will not lead to exception in describe mode */\n  private static final Set<Integer> errorCodesIgnoredInDescribeMode =\n      new HashSet<>(\n          Arrays.asList(\n              ERROR_CODE_TABLE_BIND_VARIABLE_NOT_SET,\n              ERROR_CODE_STATEMENT_CANNOT_BE_PREPARED,\n              ERROR_CODE_OBJECT_BIND_NOT_SET,\n              ERROR_CODE_FORMAT_ARGUMENT_NOT_STRING));\n\n  private final String sql;\n\n  private SFPreparedStatementMetaData preparedStatementMetaData;\n\n  /** statement and result metadata from describe phase */\n  private boolean showStatementParameters;\n\n  /**\n   * map of bind name to bind values for single query execution\n   *\n   * <p>Currently, bind name is just value index\n   */\n  private Map<String, ParameterBindingDTO> parameterBindings = new HashMap<>();\n\n  /** map of bind values for batch query executions */\n  private Map<String, ParameterBindingDTO> batchParameterBindings = new HashMap<>();\n\n  private Map<String, Boolean> wasPrevValueNull = new HashMap<>();\n\n  /** Counter for batch size if we are executing a statement with array bind supported */\n  private int batchSize = 0;\n\n  private boolean alreadyDescribed = false;\n  private final ObjectMapper objectMapper;\n\n  /**\n   * Construct SnowflakePreparedStatementImpl\n   *\n   * @param connection connection object\n   * @param sql sql\n   * @param skipParsing true if the applications want to skip parsing to get metadata. false by\n   *     default.\n   * @param resultSetType result set type: ResultSet.TYPE_FORWARD_ONLY.\n   * @param resultSetConcurrency result set concurrency: ResultSet.CONCUR_READ_ONLY.\n   * @param resultSetHoldability result set holdability: ResultSet.CLOSE_CURSORS_AT_COMMIT\n   * @throws SQLException if any SQL error occurs.\n   */\n  public SnowflakePreparedStatementImpl(\n      SnowflakeConnectionImpl connection,\n      String sql,\n      boolean skipParsing,\n      int resultSetType,\n      int resultSetConcurrency,\n      int resultSetHoldability)\n      throws SQLException {\n    super(connection, resultSetType, resultSetConcurrency, resultSetHoldability);\n    this.sql = sql;\n    this.preparedStatementMetaData = SFPreparedStatementMetaData.emptyMetaData();\n    showStatementParameters = connection.getShowStatementParameters();\n    objectMapper =\n        ObjectMapperFactory.getObjectMapperForSession(\n            connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  /**\n   * This method will check alreadyDescribed flag. And if it is false, it will try to issue a\n   * describe request to server. If true, it will skip describe request.\n   *\n   * @throws SQLException\n   */\n  private void describeSqlIfNotTried() throws SQLException {\n    if (!alreadyDescribed) {\n      try {\n        this.preparedStatementMetaData = sfBaseStatement.describe(sql);\n        if (preparedStatementMetaData != null\n            && !preparedStatementMetaData.isArrayBindSupported()) {\n          logger.debug(\n              \"Array bind is not supported - each batch entry will be executed as a single request for query: {}\",\n              sql);\n        }\n      } catch (SFException e) {\n        throw new SnowflakeSQLLoggedException(connection.getSFBaseSession(internalCallMarker()), e);\n      } catch (SnowflakeSQLException e) {\n        if (!errorCodesIgnoredInDescribeMode.contains(e.getErrorCode())) {\n          throw e;\n        } else {\n          preparedStatementMetaData = SFPreparedStatementMetaData.emptyMetaData();\n        }\n      }\n      alreadyDescribed = true;\n    }\n  }\n\n  @Override\n  public ResultSet executeQuery() throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\"ResultSet PreparedStatement.executeQuery(String)\", this.batchID);\n    if (showStatementParameters) {\n      logger.info(\"executeQuery()\", false);\n    } else {\n      logger.trace(\"executeQuery()\", false);\n    }\n    ResultSet rs = executeQueryInternal(sql, false, parameterBindings, execTimeData);\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", execTimeData);\n    return rs;\n  }\n\n  /**\n   * Execute a query asynchronously\n   *\n   * @return ResultSet containing results\n   * @throws SQLException\n   */\n  public ResultSet executeAsyncQuery() throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\n            \"ResultSet PreparedStatement.executeAsyncQuery(String)\", this.batchID);\n    if (showStatementParameters) {\n      logger.info(\"executeAsyncQuery()\", false);\n    } else {\n      logger.trace(\"executeAsyncQuery()\", false);\n    }\n    ResultSet rs = executeQueryInternal(sql, true, parameterBindings, execTimeData);\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", execTimeData);\n    return rs;\n  }\n\n  @Override\n  public long executeLargeUpdate() throws SQLException {\n    ExecTimeTelemetryData execTimeTelemetryData =\n        new ExecTimeTelemetryData(\"long PreparedStatement.executeLargeUpdate()\", this.batchID);\n    logger.trace(\"executeLargeUpdate()\", false);\n    long updates = executeUpdateInternal(sql, parameterBindings, true, execTimeTelemetryData);\n    execTimeTelemetryData.setQueryEnd();\n    execTimeTelemetryData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", execTimeTelemetryData);\n    return updates;\n  }\n\n  @Override\n  public int executeUpdate() throws SQLException {\n    logger.trace(\"executeUpdate()\", false);\n    return (int) executeLargeUpdate();\n  }\n\n  @Override\n  public void setNull(int parameterIndex, int sqlType) throws SQLException {\n    logger.trace(\n        \"setNull(parameterIndex: {}, sqlType: {})\",\n        parameterIndex,\n        SnowflakeTypeHelper.JavaSQLType.find(sqlType));\n    raiseSQLExceptionIfStatementIsClosed();\n\n    ParameterBindingDTO binding = new ParameterBindingDTO(SnowflakeType.ANY.toString(), null);\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setBoolean(int parameterIndex, boolean x) throws SQLException {\n    logger.trace(\"setBoolean(parameterIndex: {}, boolean x)\", parameterIndex);\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.BOOLEAN, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setByte(int parameterIndex, byte x) throws SQLException {\n    logger.trace(\"setByte(parameterIndex: {}, byte x)\", parameterIndex);\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.TINYINT, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setShort(int parameterIndex, short x) throws SQLException {\n    logger.trace(\"setShort(parameterIndex: {}, short x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.SMALLINT, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setInt(int parameterIndex, int x) throws SQLException {\n    logger.trace(\"setInt(parameterIndex: {}, int x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.INTEGER, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setLong(int parameterIndex, long x) throws SQLException {\n    logger.trace(\"setLong(parameterIndex: {}, long x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.BIGINT, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setBigInteger(int parameterIndex, BigInteger x) throws SQLException {\n    logger.trace(\"setBigInteger(parameterIndex: {}, BigInteger x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.BIGINT, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setFloat(int parameterIndex, float x) throws SQLException {\n    logger.trace(\"setFloat(parameterIndex: {}, float x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.FLOAT, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setDouble(int parameterIndex, double x) throws SQLException {\n    logger.trace(\"setDouble(parameterIndex: {}, double x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.DOUBLE, connection.getSFBaseSession(internalCallMarker())),\n            String.valueOf(x));\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {\n    logger.trace(\"setBigDecimal(parameterIndex: {}, BigDecimal x)\", parameterIndex);\n\n    if (x == null) {\n      setNull(parameterIndex, Types.DECIMAL);\n    } else {\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.DECIMAL, connection.getSFBaseSession(internalCallMarker())),\n              String.valueOf(x));\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  @Override\n  public void setString(int parameterIndex, String x) throws SQLException {\n    logger.trace(\"setString(parameterIndex: {}, String x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.VARCHAR, connection.getSFBaseSession(internalCallMarker())),\n            x);\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setBytes(int parameterIndex, byte[] x) throws SQLException {\n    logger.trace(\"setBytes(parameterIndex: {}, byte[] x)\", parameterIndex);\n\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.BINARY, connection.getSFBaseSession(internalCallMarker())),\n            new SFBinary(x).toHex());\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  private void setObjectInternal(int parameterIndex, SQLData sqlData) throws SQLException {\n    logger.debug(\"setObjectInternal(parameterIndex: {}, SqlData sqlData)\", parameterIndex);\n\n    JsonSqlOutput stream =\n        new JsonSqlOutput(sqlData, connection.getSFBaseSession(internalCallMarker()));\n    sqlData.writeSQL(stream);\n    ParameterBindingDTO binding =\n        new ParameterBindingDTO(\n            \"json\",\n            SnowflakeUtil.javaTypeToSFTypeString(\n                Types.STRUCT, connection.getSFBaseSession(internalCallMarker())),\n            stream.getJsonString(),\n            stream.getSchema());\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setDate(int parameterIndex, Date x) throws SQLException {\n    logger.trace(\"setDate(parameterIndex: {}, Date x)\", parameterIndex);\n\n    if (x == null) {\n      setNull(parameterIndex, Types.DATE);\n    } else {\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.DATE, connection.getSFBaseSession(internalCallMarker())),\n              String.valueOf(\n                  x.getTime()\n                      + TimeZone.getDefault().getOffset(x.getTime())\n                      - ResultUtil.msDiffJulianToGregorian(x)));\n\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  @Override\n  public void setTime(int parameterIndex, Time x) throws SQLException {\n    logger.trace(\"setTime(parameterIndex: {}, Time x)\", parameterIndex);\n\n    if (x == null) {\n      setNull(parameterIndex, Types.TIME);\n    } else {\n      String value;\n      if (connection.getSFBaseSession(internalCallMarker()).getTreatTimeAsWallClockTime()) {\n        value = String.valueOf(x.toLocalTime().toNanoOfDay());\n      } else {\n        value = String.valueOf(SfTimestampUtil.getTimeInNanoseconds(x));\n      }\n\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.TIME, connection.getSFBaseSession(internalCallMarker())),\n              value);\n\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  @Override\n  public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {\n    logger.trace(\"setTimestamp(parameterIndex: {}, Timestamp x)\", parameterIndex);\n\n    setTimestampWithType(parameterIndex, x, Types.TIMESTAMP);\n  }\n\n  private void setTimestampWithType(int parameterIndex, Timestamp x, int snowflakeType)\n      throws SQLException {\n    // convert the timestamp from being in local time zone to be in UTC timezone\n    String value =\n        x == null\n            ? null\n            : String.valueOf(\n                BigDecimal.valueOf((x.getTime() - ResultUtil.msDiffJulianToGregorian(x)) / 1000)\n                    .scaleByPowerOfTen(9)\n                    .add(BigDecimal.valueOf(x.getNanos())));\n    String bindingTypeName;\n    switch (snowflakeType) {\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ:\n        bindingTypeName = SnowflakeType.TIMESTAMP_LTZ.name();\n        break;\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ:\n        bindingTypeName = SnowflakeType.TIMESTAMP_NTZ.name();\n        break;\n      default:\n        bindingTypeName =\n            connection.getSFBaseSession(internalCallMarker()).getTimestampMappedType().name();\n        break;\n    }\n\n    ParameterBindingDTO binding = new ParameterBindingDTO(bindingTypeName, value);\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  @Deprecated\n  public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void clearParameters() throws SQLException {\n    parameterBindings.clear();\n  }\n\n  @Override\n  public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {\n    if (x == null) {\n      setNull(parameterIndex, targetSqlType);\n    } else if (targetSqlType == Types.DATE) {\n      setDate(parameterIndex, (Date) x);\n    } else if (targetSqlType == Types.TIME) {\n      setTime(parameterIndex, (Time) x);\n    } else if (targetSqlType == Types.TIMESTAMP) {\n      setTimestamp(parameterIndex, (Timestamp) x);\n    } else if (targetSqlType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ\n        || targetSqlType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ) {\n      setTimestampWithType(parameterIndex, (Timestamp) x, targetSqlType);\n    } else if (targetSqlType == SnowflakeType.EXTRA_TYPES_DECFLOAT) {\n      setDecfloat(parameterIndex, (BigDecimal) x);\n    } else if (targetSqlType == SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL) {\n      setYearMonthInterval(parameterIndex, (String) x);\n    } else if (targetSqlType == SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL) {\n      setDayTimeInterval(parameterIndex, (String) x);\n    } else {\n      logger.trace(\n          \"setObject(parameterIndex: {}, Object x, sqlType: {})\",\n          parameterIndex,\n          SnowflakeTypeHelper.JavaSQLType.find(targetSqlType));\n\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  targetSqlType, connection.getSFBaseSession(internalCallMarker())),\n              String.valueOf(x));\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  private void setYearMonthInterval(int parameterIndex, String x) throws SQLException {\n    logger.trace(\"setYearMonthInterval(parameterIndex: {}, String x)\", parameterIndex);\n\n    if (x == null) {\n      setNull(parameterIndex, SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL);\n    } else {\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(SnowflakeType.INTERVAL_YEAR_MONTH.name(), x);\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  private void setDayTimeInterval(int parameterIndex, String x) throws SQLException {\n    logger.trace(\"setDayTimeInterval(parameterIndex: {}, String x)\", parameterIndex);\n\n    if (x == null) {\n      setNull(parameterIndex, SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n    } else {\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(SnowflakeType.INTERVAL_DAY_TIME.name(), x);\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  private void setDecfloat(int parameterIndex, BigDecimal x) throws SQLException {\n    logger.trace(\"setDecfloat(parameterIndex: {}, BigDecimal x)\", parameterIndex);\n\n    if (x == null) {\n      setNull(parameterIndex, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n    } else {\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(SnowflakeType.DECFLOAT.name(), String.valueOf(x));\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  @Override\n  public void setObject(int parameterIndex, Object x) throws SQLException {\n    if (x == null) {\n      setNull(parameterIndex, Types.NULL);\n    } else if (x instanceof String) {\n      setString(parameterIndex, (String) x);\n    } else if (x instanceof BigDecimal) {\n      setBigDecimal(parameterIndex, (BigDecimal) x);\n    } else if (x instanceof Short) {\n      setShort(parameterIndex, (Short) x);\n    } else if (x instanceof Integer) {\n      setInt(parameterIndex, (Integer) x);\n    } else if (x instanceof Long) {\n      setLong(parameterIndex, (Long) x);\n    } else if (x instanceof BigInteger) {\n      setBigInteger(parameterIndex, (BigInteger) x);\n    } else if (x instanceof Float) {\n      setFloat(parameterIndex, (Float) x);\n    } else if (x instanceof Double) {\n      setDouble(parameterIndex, (Double) x);\n    } else if (x instanceof Date) {\n      setDate(parameterIndex, (Date) x);\n    } else if (x instanceof Time) {\n      setTime(parameterIndex, (Time) x);\n    } else if (x instanceof Timestamp) {\n      setTimestamp(parameterIndex, (Timestamp) x);\n    } else if (x instanceof Boolean) {\n      setBoolean(parameterIndex, (Boolean) x);\n    } else if (x instanceof byte[]) {\n      setBytes(parameterIndex, (byte[]) x);\n    } else if (x instanceof SQLData) {\n      setObjectInternal(parameterIndex, (SQLData) x);\n    } else {\n      throw new SnowflakeSQLLoggedException(\n          connection.getSFBaseSession(internalCallMarker()),\n          ErrorCode.DATA_TYPE_NOT_SUPPORTED.getMessageCode(),\n          SqlState.FEATURE_NOT_SUPPORTED,\n          \"Object type: \" + x.getClass());\n    }\n  }\n\n  @Override\n  public boolean execute() throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\"boolean PreparedStatement.execute(String)\", this.batchID);\n    logger.debug(\"Execute: {}\", sql);\n    boolean success = executeInternal(sql, parameterBindings, execTimeData);\n\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", execTimeData);\n    return success;\n  }\n\n  @Override\n  public void addBatch() throws SQLException {\n    logger.trace(\"addBatch()\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n\n    describeSqlIfNotTried();\n    if (preparedStatementMetaData.isArrayBindSupported()) {\n      for (Map.Entry<String, ParameterBindingDTO> binding : parameterBindings.entrySet()) {\n        // get the entry for the bind variable in the batch binding map\n        ParameterBindingDTO bindingValueAndType = batchParameterBindings.get(binding.getKey());\n\n        List<String> values;\n\n        Object newValue = binding.getValue().getValue();\n        // create binding value and type for the first time\n        if (bindingValueAndType == null) {\n          // create the value list\n          values = new ArrayList<>();\n\n          bindingValueAndType = new ParameterBindingDTO(binding.getValue().getType(), values);\n\n          // put the new map into the batch\n          batchParameterBindings.put(binding.getKey(), bindingValueAndType);\n\n          wasPrevValueNull.put(binding.getKey(), binding.getValue().getValue() == null);\n        } else {\n          // make sure type matches except for null values\n          String prevType = bindingValueAndType.getType();\n          String newType = binding.getValue().getType();\n\n          if (wasPrevValueNull.get(binding.getKey()) && newValue != null) {\n            // if previous value is null and the current value is not null\n            // override the data type.\n            bindingValueAndType = batchParameterBindings.remove(binding.getKey());\n            bindingValueAndType.setType(newType);\n            batchParameterBindings.put(binding.getKey(), bindingValueAndType);\n            prevType = newType;\n            wasPrevValueNull.put(binding.getKey(), false);\n          }\n\n          // if previous type is null, replace it with new type\n          if (SnowflakeType.ANY.name().equalsIgnoreCase(prevType)\n              && !SnowflakeType.ANY.name().equalsIgnoreCase(newType)) {\n            bindingValueAndType.setType(newType);\n          } else if (binding.getValue().getValue() != null && !prevType.equalsIgnoreCase(newType)) {\n            String row = \"Unknown\";\n            if (bindingValueAndType.getValue() instanceof Collection) {\n              final List<String> typeCheckedList = (List<String>) bindingValueAndType.getValue();\n              values = typeCheckedList;\n              row = Integer.toString(values.size() + 1);\n            }\n            throw new SnowflakeSQLLoggedException(\n                connection.getSFBaseSession(internalCallMarker()),\n                ErrorCode.ARRAY_BIND_MIXED_TYPES_NOT_SUPPORTED.getMessageCode(),\n                SqlState.FEATURE_NOT_SUPPORTED,\n                SnowflakeTypeUtil.getJavaType(SnowflakeTypeUtil.fromString(prevType), false).name(),\n                SnowflakeTypeUtil.getJavaType(SnowflakeTypeUtil.fromString(newType), false).name(),\n                binding.getKey(),\n                row);\n          }\n\n          // found the existing map so just get the value list\n          final List<String> typeCheckedList = (List<String>) bindingValueAndType.getValue();\n          values = typeCheckedList;\n        }\n\n        // add the value to the list of values in batch binding map\n        values.add((String) newValue);\n        bindingValueAndType.setValue(values);\n      }\n      batchSize++;\n    } else {\n      batch.add(new BatchEntry(this.sql, parameterBindings));\n      parameterBindings = new HashMap<>();\n    }\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, Reader reader, int length)\n      throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setRef(int parameterIndex, Ref x) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBlob(int parameterIndex, Blob x) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setClob(int parameterIndex, Clob x) throws SQLException {\n    setString(parameterIndex, x == null ? null : x.toString());\n  }\n\n  @Override\n  public void setArray(int parameterIndex, Array array) throws SQLException {\n    if (array instanceof SfSqlArray) {\n      SfSqlArray sfArray = (SfSqlArray) array;\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              \"json\",\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.ARRAY, connection.getSFBaseSession(internalCallMarker())),\n              sfArray.getJsonString(),\n              sfArray.getSchema());\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    } else {\n      SfSqlArray sfArray =\n          new SfSqlArray(\n              Types.INTEGER,\n              array,\n              connection.getSFBaseSession(internalCallMarker()),\n              objectMapper);\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              \"json\",\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.ARRAY, connection.getSFBaseSession(internalCallMarker())),\n              sfArray.getJsonString(),\n              sfArray.getSchema());\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  @Override\n  public <T> void setMap(int parameterIndex, Map<String, T> map, int type) throws SQLException {\n    BindingParameterMetadata valueTypeSchema;\n    if (Types.STRUCT == type) {\n      SQLData sqlData = (SQLData) map.values().stream().findFirst().orElse(null);\n      JsonSqlOutput stream =\n          new JsonSqlOutput(sqlData, connection.getSFBaseSession(internalCallMarker()));\n      sqlData.writeSQL(stream);\n      valueTypeSchema = stream.getSchema();\n    } else {\n      valueTypeSchema = FieldSchemaCreator.buildBindingSchemaForType(type, false);\n    }\n\n    BindingParameterMetadata schema =\n        BindingParameterMetadata.BindingParameterMetadataBuilder.bindingParameterMetadata()\n            .withType(\"map\")\n            .withFields(\n                Arrays.asList(\n                    FieldSchemaCreator.buildBindingSchemaForType(Types.VARCHAR, false),\n                    valueTypeSchema))\n            .build();\n    ParameterBindingDTO binding = null;\n    try {\n      binding =\n          new ParameterBindingDTO(\n              \"json\",\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.STRUCT, connection.getSFBaseSession(internalCallMarker())),\n              SnowflakeUtil.mapJson(map),\n              schema);\n    } catch (JsonProcessingException e) {\n      throw new RuntimeException(e);\n    }\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public ResultSetMetaData getMetaData() throws SQLException {\n    logger.trace(\"getMetaData()\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n\n    describeSqlIfNotTried();\n    return new SnowflakeResultSetMetaDataV1(this.preparedStatementMetaData.getResultSetMetaData());\n  }\n\n  @Override\n  public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {\n    logger.trace(\"setDate(int parameterIndex, Date x, Calendar cal)\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n    if (x == null) {\n      setNull(parameterIndex, Types.DATE);\n    } else {\n      // convert the date from to be in local time zone to be in UTC\n      String value =\n          String.valueOf(\n              x.getTime()\n                  + cal.getTimeZone().getOffset(x.getTime())\n                  - ResultUtil.msDiffJulianToGregorian(x));\n\n      ParameterBindingDTO binding =\n          new ParameterBindingDTO(\n              SnowflakeUtil.javaTypeToSFTypeString(\n                  Types.DATE, connection.getSFBaseSession(internalCallMarker())),\n              value);\n      parameterBindings.put(String.valueOf(parameterIndex), binding);\n    }\n  }\n\n  @Override\n  public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {\n    logger.trace(\"setTime(int parameterIndex, Time x, Calendar cal)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    setTime(parameterIndex, x);\n  }\n\n  @Override\n  public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {\n    logger.trace(\"setTimestamp(int parameterIndex, Timestamp x, Calendar cal)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n\n    // convert the time from being in UTC to be in local time zone\n    String value = null;\n    SnowflakeType sfType =\n        SnowflakeUtil.javaTypeToSFType(\n            Types.TIMESTAMP, connection.getSFBaseSession(internalCallMarker()));\n    if (x != null) {\n      long milliSecSinceEpoch = x.getTime();\n\n      if (sfType == SnowflakeType.TIMESTAMP) {\n        sfType = connection.getSFBaseSession(internalCallMarker()).getTimestampMappedType();\n      }\n      // if type is timestamp_tz, keep the offset and the time value separate.\n      // store the offset, in minutes, as amount it's off from UTC\n      if (sfType == SnowflakeType.TIMESTAMP_TZ) {\n        value =\n            String.valueOf(\n                BigDecimal.valueOf(milliSecSinceEpoch / 1000)\n                    .scaleByPowerOfTen(9)\n                    .add(BigDecimal.valueOf(x.getNanos())));\n\n        int offset = cal.getTimeZone().getOffset(milliSecSinceEpoch) / 60000 + 1440;\n        value += \" \" + offset;\n      } else {\n        milliSecSinceEpoch = milliSecSinceEpoch + cal.getTimeZone().getOffset(milliSecSinceEpoch);\n\n        value =\n            String.valueOf(\n                BigDecimal.valueOf(milliSecSinceEpoch / 1000)\n                    .scaleByPowerOfTen(9)\n                    .add(BigDecimal.valueOf(x.getNanos())));\n      }\n    }\n\n    ParameterBindingDTO binding = new ParameterBindingDTO(sfType.name(), value);\n    parameterBindings.put(String.valueOf(parameterIndex), binding);\n  }\n\n  @Override\n  public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {\n    logger.trace(\"setNull(int parameterIndex, int sqlType, String typeName)\", false);\n\n    setNull(parameterIndex, sqlType);\n  }\n\n  @Override\n  public void setURL(int parameterIndex, URL x) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public ParameterMetaData getParameterMetaData() throws SQLException {\n    describeSqlIfNotTried();\n    return new SnowflakeParameterMetadata(\n        preparedStatementMetaData, connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setRowId(int parameterIndex, RowId x) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNString(int parameterIndex, String value) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNCharacterStream(int parameterIndex, Reader value, long length)\n      throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNClob(int parameterIndex, NClob value) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBlob(int parameterIndex, InputStream inputStream, long length)\n      throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)\n      throws SQLException {\n    logger.trace(\n        \"setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n    if (x == null) {\n      setNull(parameterIndex, targetSqlType);\n    } else if (targetSqlType == Types.DECIMAL || targetSqlType == Types.NUMERIC) {\n      BigDecimal decimalObj = new BigDecimal(String.valueOf(x));\n      decimalObj.setScale(scaleOrLength);\n      setBigDecimal(parameterIndex, decimalObj);\n    } else {\n      setObject(parameterIndex, x, targetSqlType);\n    }\n  }\n\n  @Override\n  public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, Reader reader, long length)\n      throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setClob(int parameterIndex, Reader reader) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setNClob(int parameterIndex, Reader reader) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public int executeUpdate(String sql) throws SQLException {\n    logger.debug(\"executeUpdate(String sql)\", false);\n\n    throw new SnowflakeSQLException(\n        ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API, StmtUtil.truncateSQL(sql));\n  }\n\n  @Override\n  public boolean execute(String sql) throws SQLException {\n    logger.debug(\"execute(String sql)\", false);\n\n    throw new SnowflakeSQLException(\n        ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API, StmtUtil.truncateSQL(sql));\n  }\n\n  @Override\n  public void addBatch(String sql) throws SQLException {\n    throw new SnowflakeSQLException(\n        ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API, StmtUtil.truncateSQL(sql));\n  }\n\n  @Override\n  public void clearBatch() throws SQLException {\n    super.clearBatch();\n    batchParameterBindings.clear();\n    parameterBindings.clear();\n    wasPrevValueNull.clear();\n    batchSize = 0;\n  }\n\n  @Override\n  public int[] executeBatch() throws SQLException {\n    logger.trace(\"executeBatch()\", false);\n    return executeBatchInternalWithArrayBind(false).intArr;\n  }\n\n  @Override\n  public long[] executeLargeBatch() throws SQLException {\n    logger.trace(\"executeLargeBatch()\", false);\n    return executeBatchInternalWithArrayBind(true).longArr;\n  }\n\n  VariableTypeArray executeBatchInternalWithArrayBind(boolean isLong) throws SQLException {\n    raiseSQLExceptionIfStatementIsClosed();\n\n    describeSqlIfNotTried();\n\n    if (this.preparedStatementMetaData.getStatementType().isGenerateResultSet()) {\n      throw new SnowflakeSQLException(\n          ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API, StmtUtil.truncateSQL(sql));\n    }\n\n    VariableTypeArray updateCounts;\n    if (isLong) {\n      long[] arr = new long[batch.size()];\n      updateCounts = new VariableTypeArray(null, arr);\n    } else {\n      int size = batch.size();\n      int[] arr = new int[size];\n      updateCounts = new VariableTypeArray(arr, null);\n    }\n\n    try {\n      if (this.preparedStatementMetaData.isArrayBindSupported()) {\n        if (batchSize <= 0) {\n          if (isLong) {\n            logger.debug(\n                \"executeLargeBatch() using array bind with no batch data. Return long[0] directly\",\n                false);\n            return new VariableTypeArray(null, new long[0]);\n          } else {\n            logger.debug(\n                \"executeBatch() using array bind with no batch data. Return int[0] directly\",\n                false);\n            return new VariableTypeArray(new int[0], null);\n          }\n        }\n\n        int updateCount =\n            (int)\n                executeUpdateInternal(\n                    this.sql, batchParameterBindings, false, new ExecTimeTelemetryData());\n\n        // when update count is the same as the number of bindings in the batch,\n        // expand the update count into an array (SNOW-14034)\n        if (updateCount == batchSize) {\n          if (isLong) {\n            updateCounts = new VariableTypeArray(null, new long[updateCount]);\n            for (int idx = 0; idx < updateCount; idx++) {\n              updateCounts.longArr[idx] = 1;\n            }\n          } else {\n            updateCounts = new VariableTypeArray(new int[updateCount], null);\n            for (int idx = 0; idx < updateCount; idx++) {\n              updateCounts.intArr[idx] = 1;\n            }\n          }\n        } else {\n          if (isLong) {\n            updateCounts.longArr = new long[] {updateCount};\n          } else {\n            updateCounts.intArr = new int[] {updateCount};\n          }\n        }\n      } else {\n        // Array binding is not supported\n        if (isLong) {\n          updateCounts.longArr = executeBatchInternal(true).longArr;\n        } else {\n          updateCounts.intArr = executeBatchInternal(false).intArr;\n        }\n      }\n      if (this.getSFBaseStatement()\n          .getSFBaseSession(internalCallMarker())\n          .getClearBatchOnlyAfterSuccessfulExecution()) {\n        clearBatch();\n      }\n    } finally {\n      if (!this.getSFBaseStatement()\n          .getSFBaseSession(internalCallMarker())\n          .getClearBatchOnlyAfterSuccessfulExecution()) {\n        clearBatch();\n      }\n    }\n\n    return updateCounts;\n  }\n\n  @Override\n  public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public int executeUpdate(String sql, String[] columnNames) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean execute(String sql, int[] columnIndexes) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean execute(String sql, String[] columnNames) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  // For testing use only\n  public Map<String, ParameterBindingDTO> getBatchParameterBindings() {\n    return batchParameterBindings;\n  }\n\n  // package private for testing purpose only\n  public Map<String, ParameterBindingDTO> getParameterBindings() {\n    return parameterBindings;\n  }\n\n  // For testing use only\n  public boolean isAlreadyDescribed() {\n    return this.alreadyDescribed;\n  }\n\n  // For testing use only\n  public boolean isArrayBindSupported() {\n    return this.preparedStatementMetaData.isArrayBindSupported();\n  }\n\n  @Override\n  public void resultSetMetadataHandler(SFBaseResultSet resultSet) throws SQLException {\n    if (!this.preparedStatementMetaData.isValidMetaData()) {\n      this.preparedStatementMetaData =\n          new SFPreparedStatementMetaData(\n              resultSet.getMetaData(),\n              resultSet.getStatementType(),\n              resultSet.getNumberOfBinds(),\n              resultSet.isArrayBindSupported(),\n              resultSet.getMetaDataOfBinds(),\n              true);\n\n      alreadyDescribed = true;\n    }\n  }\n\n  public String toString() {\n    return (this.sql != null) ? this.sql + \" - Query ID: \" + this.getQueryID() : super.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/api/implementation/statement/SnowflakeStatementImpl.java",
    "content": "package net.snowflake.client.internal.api.implementation.statement;\n\nimport static net.snowflake.client.api.exception.ErrorCode.FEATURE_UNSUPPORTED;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport java.sql.BatchUpdateException;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.SQLWarning;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.CancellationReason;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.core.SFStatementType;\nimport net.snowflake.client.internal.core.StmtUtil;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.QueryIdValidator;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetV1;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.VariableTypeArray;\nimport net.snowflake.common.core.SqlState;\n\n/** Snowflake statement */\npublic class SnowflakeStatementImpl implements Statement, SnowflakeStatement {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeStatementImpl.class);\n\n  private static final String NOOP_MESSAGE =\n      \"This is a dummy SnowflakeStatement, \" + \"no member function should be called for it.\";\n  private static final long NO_UPDATES = -1;\n\n  public final SnowflakeConnectionImpl connection;\n\n  protected final int resultSetType;\n  protected final int resultSetConcurrency;\n  protected final int resultSetHoldability;\n  protected String batchID = \"\";\n\n  /*\n   * The maximum number of rows this statement ( should return (0 => all rows).\n   */\n  private int maxRows = 0;\n\n  // Refer to all open resultSets from this statement\n  private final Set<ResultSet> openResultSets = ConcurrentHashMap.newKeySet();\n\n  // result set currently in use\n  private ResultSet resultSet = null;\n\n  private int fetchSize = 50;\n\n  private Boolean isClosed = false;\n\n  private long updateCount = NO_UPDATES;\n\n  // timeout in seconds\n  private int queryTimeout = 0;\n\n  SFBaseStatement sfBaseStatement;\n\n  private boolean poolable;\n\n  /** Snowflake query ID from the latest executed query */\n  private String queryID;\n\n  /** Snowflake query IDs from the latest executed batch */\n  private List<String> batchQueryIDs = new LinkedList<>();\n\n  /** batch of sql strings added by addBatch */\n  protected final List<BatchEntry> batch = new ArrayList<>();\n\n  private SQLWarning sqlWarnings;\n\n  /**\n   * Construct SnowflakeStatementImpl\n   *\n   * @param connection connection object\n   * @param resultSetType result set type: ResultSet.TYPE_FORWARD_ONLY.\n   * @param resultSetConcurrency result set concurrency: ResultSet.CONCUR_READ_ONLY.\n   * @param resultSetHoldability result set holdability: ResultSet.CLOSE_CURSORS_AT_COMMIT\n   * @throws SQLException if any SQL error occurs.\n   */\n  public SnowflakeStatementImpl(\n      SnowflakeConnectionImpl connection,\n      int resultSetType,\n      int resultSetConcurrency,\n      int resultSetHoldability)\n      throws SQLException {\n    logger.trace(\"SnowflakeStatement(SnowflakeConnectionImpl conn)\", false);\n\n    this.connection = connection;\n\n    if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {\n      throw new SQLFeatureNotSupportedException(\n          String.format(\"ResultSet type %d is not supported.\", resultSetType),\n          FEATURE_UNSUPPORTED.getSqlState(),\n          FEATURE_UNSUPPORTED.getMessageCode());\n    }\n\n    if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) {\n      throw new SQLFeatureNotSupportedException(\n          String.format(\"ResultSet concurrency %d is not supported.\", resultSetConcurrency),\n          FEATURE_UNSUPPORTED.getSqlState(),\n          FEATURE_UNSUPPORTED.getMessageCode());\n    }\n\n    if (resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {\n      throw new SQLFeatureNotSupportedException(\n          String.format(\"ResultSet holdability %d is not supported.\", resultSetHoldability),\n          FEATURE_UNSUPPORTED.getSqlState(),\n          FEATURE_UNSUPPORTED.getMessageCode());\n    }\n\n    this.resultSetType = resultSetType;\n    this.resultSetConcurrency = resultSetConcurrency;\n    this.resultSetHoldability = resultSetHoldability;\n\n    sfBaseStatement =\n        (connection != null) ? connection.getHandler(internalCallMarker()).getSFStatement() : null;\n  }\n\n  protected void raiseSQLExceptionIfStatementIsClosed() throws SQLException {\n    if (isClosed) {\n      throw new SnowflakeSQLException(ErrorCode.STATEMENT_CLOSED);\n    }\n  }\n\n  /**\n   * Execute SQL query\n   *\n   * @param sql sql statement\n   * @return ResultSet\n   * @throws SQLException if @link{#executeQueryInternal(String, Map)} throws an exception\n   */\n  @Override\n  public ResultSet executeQuery(String sql) throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\"ResultSet Statement.executeQuery(String)\", this.batchID);\n\n    raiseSQLExceptionIfStatementIsClosed();\n    ResultSet rs = executeQueryInternal(sql, false, null, execTimeData);\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", execTimeData);\n    return rs;\n  }\n\n  /**\n   * Execute SQL query asynchronously\n   *\n   * @param sql sql statement\n   * @return ResultSet\n   * @throws SQLException if @link{#executeQueryInternal(String, Map)} throws an exception\n   */\n  public ResultSet executeAsyncQuery(String sql) throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\"ResultSet Statement.executeAsyncQuery(String)\", this.batchID);\n    raiseSQLExceptionIfStatementIsClosed();\n    ResultSet rs = executeQueryInternal(sql, true, null, execTimeData);\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", queryID, execTimeData);\n    return rs;\n  }\n\n  /**\n   * Hook for subclasses to process result set metadata after query execution. This method is called\n   * internally after executing a query to allow subclasses to extract and cache metadata from the\n   * result set.\n   *\n   * <p>The default implementation does nothing. Subclasses (like PreparedStatementImpl) can\n   * override this to capture metadata such as parameter metadata.\n   *\n   * @param resultSet the internal result set containing metadata\n   * @throws SQLException if an error occurs while processing metadata\n   */\n  protected void resultSetMetadataHandler(SFBaseResultSet resultSet) throws SQLException {\n    // No-Op - subclasses can override\n  }\n\n  /**\n   * Execute an update statement\n   *\n   * @param sql sql statement\n   * @return number of rows updated\n   * @throws SQLException if @link{#executeUpdateInternal(String, Map)} throws exception\n   */\n  @Override\n  public int executeUpdate(String sql) throws SQLException {\n    return (int) this.executeLargeUpdate(sql);\n  }\n\n  /**\n   * Execute an update statement returning the number of affected rows in long\n   *\n   * @param sql sql statement\n   * @return number of rows updated in long\n   * @throws SQLException if @link{#executeUpdateInternal(String, Map)} throws exception\n   */\n  @Override\n  public long executeLargeUpdate(String sql) throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\"ResultSet Statement.executeLargeUpdate(String)\", this.batchID);\n    long res = executeUpdateInternal(sql, null, true, execTimeData);\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", queryID, execTimeData);\n    return res;\n  }\n\n  public long executeUpdateInternal(\n      String sql,\n      Map<String, ParameterBindingDTO> parameterBindings,\n      boolean updateQueryRequired,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException {\n    execTimeData.setQueryText(sql);\n    execTimeData.setSessionId(connection.getSessionID());\n    raiseSQLExceptionIfStatementIsClosed();\n\n    /* If sql command is a staging command that has parameter binding, throw an exception because parameter binding\n    is not supported for staging commands. */\n    if (StmtUtil.checkStageManageCommand(sql) != null && parameterBindings != null) {\n      throw new SnowflakeSQLLoggedException(\n          connection.getSFBaseSession(internalCallMarker()),\n          ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API,\n          StmtUtil.truncateSQL(sql));\n    }\n    SFBaseResultSet sfResultSet;\n    try {\n      sfResultSet =\n          sfBaseStatement.execute(\n              sql, parameterBindings, SFBaseStatement.CallingMethod.EXECUTE_UPDATE, execTimeData);\n      sfResultSet.setSession(this.connection.getSFBaseSession(internalCallMarker()));\n      updateCount = ResultUtil.calculateUpdateCount(sfResultSet);\n      queryID = sfResultSet.getQueryId();\n      resultSetMetadataHandler(sfResultSet);\n    } catch (SnowflakeSQLException ex) {\n      setQueryIdWhenValidOrNull(ex.getQueryId());\n      throw ex;\n    } catch (SFException ex) {\n      setQueryIdWhenValidOrNull(ex.getQueryId());\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    } finally {\n      if (resultSet != null && !resultSet.isClosed()) {\n        openResultSets.add(resultSet);\n      }\n      resultSet = null;\n    }\n\n    if (updateCount == NO_UPDATES && updateQueryRequired) {\n      throw new SnowflakeSQLLoggedException(\n          connection.getSFBaseSession(internalCallMarker()),\n          ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API,\n          StmtUtil.truncateSQL(sql));\n    }\n\n    return updateCount;\n  }\n\n  private void setQueryIdWhenValidOrNull(String queryId) {\n    if (QueryIdValidator.isValid(queryId)) {\n      this.queryID = queryId;\n    } else {\n      this.queryID = null;\n    }\n  }\n\n  /**\n   * Internal method for executing a query with bindings accepted.\n   *\n   * @param sql sql statement\n   * @param asyncExec execute query asynchronously\n   * @param parameterBindings parameters bindings\n   * @return query result set\n   * @throws SQLException if @link{SFStatement.execute(String)} throws exception\n   */\n  ResultSet executeQueryInternal(\n      String sql,\n      boolean asyncExec,\n      Map<String, ParameterBindingDTO> parameterBindings,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException {\n    execTimeData.setQueryText(sql);\n    execTimeData.setSessionId(connection.getSessionID());\n    SFBaseResultSet sfResultSet;\n    try {\n      if (asyncExec) {\n        if (!connection.getHandler(internalCallMarker()).supportsAsyncQuery()) {\n          throw new SQLFeatureNotSupportedException(\n              \"Async execution not supported in current context.\");\n        }\n        sfResultSet =\n            sfBaseStatement.asyncExecute(\n                sql, parameterBindings, SFBaseStatement.CallingMethod.EXECUTE_QUERY, execTimeData);\n      } else {\n        sfResultSet =\n            sfBaseStatement.execute(\n                sql, parameterBindings, SFBaseStatement.CallingMethod.EXECUTE_QUERY, execTimeData);\n        resultSetMetadataHandler(sfResultSet);\n      }\n      sfResultSet.setSession(this.connection.getSFBaseSession(internalCallMarker()));\n      queryID = sfResultSet.getQueryId();\n\n    } catch (SnowflakeSQLException ex) {\n      setQueryIdWhenValidOrNull(ex.getQueryId());\n      throw ex;\n    } catch (SFException ex) {\n      setQueryIdWhenValidOrNull(ex.getQueryId());\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n\n    if (resultSet != null && !resultSet.isClosed()) {\n      openResultSets.add(resultSet);\n    }\n\n    if (asyncExec) {\n      resultSet =\n          connection.getHandler(internalCallMarker()).createAsyncResultSet(sfResultSet, this);\n    } else {\n      resultSet = connection.getHandler(internalCallMarker()).createResultSet(sfResultSet, this);\n    }\n    return getResultSet();\n  }\n\n  /**\n   * Execute sql\n   *\n   * @param sql sql statement\n   * @param parameterBindings a map of binds to use for this query\n   * @return whether there is result set or not\n   * @throws SQLException if @link{#executeQuery(String)} throws exception\n   */\n  boolean executeInternal(\n      String sql,\n      Map<String, ParameterBindingDTO> parameterBindings,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException {\n    raiseSQLExceptionIfStatementIsClosed();\n    execTimeData.setQueryText(sql);\n    execTimeData.setSessionId(connection.getSessionID());\n    connection.injectedDelay();\n\n    logger.debug(\"Execute: {}\", sql);\n\n    String trimmedSql = sql.trim();\n\n    if (trimmedSql.length() >= 20 && trimmedSql.toLowerCase().startsWith(\"set-sf-property\")) {\n      // deprecated: sfsql\n      executeSetProperty(sql);\n      return false;\n    }\n\n    SFBaseResultSet sfResultSet;\n    try {\n      sfResultSet =\n          sfBaseStatement.execute(\n              sql, parameterBindings, SFBaseStatement.CallingMethod.EXECUTE, execTimeData);\n      sfResultSet.setSession(this.connection.getSFBaseSession(internalCallMarker()));\n      resultSetMetadataHandler(sfResultSet);\n      if (resultSet != null && !resultSet.isClosed()) {\n        openResultSets.add(resultSet);\n      }\n      resultSet = connection.getHandler(internalCallMarker()).createResultSet(sfResultSet, this);\n      queryID = sfResultSet.getQueryId();\n\n      // Legacy behavior treats update counts as result sets for single-\n      // statement execute, so we only treat update counts as update counts\n      // if CLIENT_SFSQL is not set, or if a statement\n      // is multi-statement\n      boolean copyResultSetEnabled =\n          sfResultSet.getStatementType() == SFStatementType.COPY\n              && connection.getSFBaseSession(internalCallMarker()).isEnableCopyResultSet();\n\n      if (!copyResultSetEnabled\n          && !sfResultSet.getStatementType().isGenerateResultSet()\n          && (!connection.getSFBaseSession(internalCallMarker()).isSfSQLMode()\n              || sfBaseStatement.hasChildren())) {\n        updateCount = ResultUtil.calculateUpdateCount(sfResultSet);\n        if (resultSet != null && !resultSet.isClosed()) {\n          openResultSets.add(resultSet);\n        }\n        resultSet = null;\n        return false;\n      }\n\n      updateCount = NO_UPDATES;\n      return true;\n    } catch (SnowflakeSQLException ex) {\n      setQueryIdWhenValidOrNull(ex.getQueryId());\n      throw ex;\n    } catch (SFException ex) {\n      setQueryIdWhenValidOrNull(ex.getQueryId());\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  /**\n   * @return the query ID of the latest executed query\n   */\n  public String getQueryID() {\n    return queryID;\n  }\n\n  /**\n   * @return the query IDs of the latest executed batch queries\n   */\n  public List<String> getBatchQueryIDs() {\n    return Collections.unmodifiableList(batchQueryIDs);\n  }\n\n  /**\n   * @return the child query IDs for the multiple statements query.\n   */\n  public String[] getChildQueryIds(String queryID) throws SQLException {\n    return sfBaseStatement.getChildQueryIds(queryID);\n  }\n\n  /**\n   * @return the open resultSets from this statement\n   */\n  public Set<ResultSet> getOpenResultSets() {\n    return openResultSets;\n  }\n\n  /**\n   * Execute sql\n   *\n   * @param sql sql statement\n   * @return whether there is result set or not\n   * @throws SQLException if @link{#executeQuery(String)} throws exception\n   */\n  @Override\n  public boolean execute(String sql) throws SQLException {\n    ExecTimeTelemetryData execTimeData =\n        new ExecTimeTelemetryData(\"ResultSet Statement.execute(String)\", this.batchID);\n    boolean res = executeInternal(sql, null, execTimeData);\n    execTimeData.setQueryEnd();\n    execTimeData.generateTelemetry();\n    logger.debug(\"Query completed. {}\", queryID, execTimeData);\n    return res;\n  }\n\n  @Override\n  public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {\n    logger.trace(\"execute(String sql, int autoGeneratedKeys)\", false);\n\n    if (autoGeneratedKeys == Statement.NO_GENERATED_KEYS) {\n      return execute(sql);\n    } else {\n      throw new SnowflakeLoggedFeatureNotSupportedException(\n          connection.getSFBaseSession(internalCallMarker()));\n    }\n  }\n\n  @Override\n  public boolean execute(String sql, int[] columnIndexes) throws SQLException {\n    logger.trace(\"execute(String sql, int[] columnIndexes)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean execute(String sql, String[] columnNames) throws SQLException {\n    logger.trace(\"execute(String sql, String[] columnNames)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  /**\n   * Batch Execute. If one of the commands in the batch failed, JDBC will continuing processing and\n   * throw BatchUpdateException after all commands are processed.\n   *\n   * @return an array of update counts\n   * @throws SQLException if any error occurs.\n   */\n  @Override\n  public int[] executeBatch() throws SQLException {\n    logger.trace(\"int[] executeBatch()\", false);\n    return executeBatchInternal(false).intArr;\n  }\n\n  /**\n   * Batch Execute. If one of the commands in the batch failed, JDBC will continuing processing and\n   * throw BatchUpdateException after all commands are processed.\n   *\n   * @return an array of update counts\n   * @throws SQLException if any error occurs.\n   */\n  @Override\n  public long[] executeLargeBatch() throws SQLException {\n    logger.trace(\"executeBatch()\", false);\n    return executeBatchInternal(true).longArr;\n  }\n\n  /**\n   * This method will iterate through batch and provide sql and bindings to underlying SFStatement\n   * to get result set.\n   *\n   * <p>Note, array binds use a different code path since only one network roundtrip in the array\n   * bind execution case.\n   *\n   * @return the number of updated rows\n   * @throws SQLException raises if statement is closed or any db error occurs\n   */\n  VariableTypeArray executeBatchInternal(boolean isLong) throws SQLException {\n    raiseSQLExceptionIfStatementIsClosed();\n\n    SQLException exceptionReturned = null;\n    VariableTypeArray updateCounts;\n    if (isLong) {\n      long[] arr = new long[batch.size()];\n      updateCounts = new VariableTypeArray(null, arr);\n    } else {\n      int size = batch.size();\n      int[] arr = new int[size];\n      updateCounts = new VariableTypeArray(arr, null);\n    }\n    batchQueryIDs.clear();\n    for (int i = 0; i < batch.size(); i++) {\n      BatchEntry b = batch.get(i);\n      try {\n        long cnt =\n            this.executeUpdateInternal(\n                b.getSql(), b.getParameterBindings(), false, new ExecTimeTelemetryData());\n        if (cnt == NO_UPDATES) {\n          // in executeBatch we set updateCount to SUCCESS_NO_INFO\n          // for successful query with no updates\n          cnt = SUCCESS_NO_INFO;\n        }\n        if (isLong) {\n          updateCounts.longArr[i] = cnt;\n        } else if (cnt <= Integer.MAX_VALUE) {\n          updateCounts.intArr[i] = (int) cnt;\n        } else {\n          throw new SnowflakeSQLLoggedException(\n              connection.getSFBaseSession(internalCallMarker()),\n              ErrorCode.EXECUTE_BATCH_INTEGER_OVERFLOW.getMessageCode(),\n              SqlState.NUMERIC_VALUE_OUT_OF_RANGE,\n              i);\n        }\n        batchQueryIDs.add(queryID);\n      } catch (SQLException e) {\n        exceptionReturned = exceptionReturned == null ? e : exceptionReturned;\n        if (isLong) {\n          updateCounts.longArr[i] = (long) EXECUTE_FAILED;\n        } else {\n          updateCounts.intArr[i] = EXECUTE_FAILED;\n        }\n      }\n    }\n\n    if (exceptionReturned != null && isLong) {\n      throw new BatchUpdateException(\n          exceptionReturned.getLocalizedMessage(),\n          exceptionReturned.getSQLState(),\n          exceptionReturned.getErrorCode(),\n          updateCounts.longArr,\n          exceptionReturned);\n    } else if (exceptionReturned != null) {\n      throw new BatchUpdateException(\n          exceptionReturned.getLocalizedMessage(),\n          exceptionReturned.getSQLState(),\n          exceptionReturned.getErrorCode(),\n          updateCounts.intArr,\n          exceptionReturned);\n    }\n    if (this.getSFBaseStatement()\n        .getSFBaseSession(internalCallMarker())\n        .getClearBatchOnlyAfterSuccessfulExecution()) {\n      clearBatch();\n    }\n    return updateCounts;\n  }\n\n  @Override\n  public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {\n    logger.trace(\"executeUpdate(String sql, int autoGeneratedKeys)\", false);\n\n    return (int) this.executeLargeUpdate(sql, autoGeneratedKeys);\n  }\n\n  @Override\n  public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {\n    logger.trace(\"executeUpdate(String sql, int autoGeneratedKeys)\", false);\n\n    if (autoGeneratedKeys == Statement.NO_GENERATED_KEYS) {\n      return executeLargeUpdate(sql);\n    } else {\n      throw new SnowflakeLoggedFeatureNotSupportedException(\n          connection.getSFBaseSession(internalCallMarker()));\n    }\n  }\n\n  @Override\n  public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {\n    logger.trace(\"executeUpdate(String sql, int[] columnIndexes)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {\n    logger.trace(\"executeLargeUpdate(String sql, int[] columnIndexes)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public int executeUpdate(String sql, String[] columnNames) throws SQLException {\n    logger.trace(\"executeUpdate(String sql, String[] columnNames)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {\n    logger.trace(\"executeUpdate(String sql, String[] columnNames)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public Connection getConnection() throws SQLException {\n    logger.trace(\"getConnection()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return connection;\n  }\n\n  @Override\n  public int getFetchDirection() throws SQLException {\n    logger.trace(\"getFetchDirection()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return ResultSet.FETCH_FORWARD;\n  }\n\n  @Override\n  public int getFetchSize() throws SQLException {\n    logger.trace(\"getFetchSize()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return fetchSize;\n  }\n\n  @Override\n  public ResultSet getGeneratedKeys() throws SQLException {\n    logger.trace(\"getGeneratedKeys()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return new SnowflakeResultSetV1.EmptyResultSet();\n  }\n\n  @Override\n  public int getMaxFieldSize() throws SQLException {\n    logger.trace(\"getMaxFieldSize()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return connection.getMetaData().getMaxCharLiteralLength();\n  }\n\n  @Override\n  public int getMaxRows() throws SQLException {\n    logger.trace(\"getMaxRows()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return maxRows;\n  }\n\n  @Override\n  public boolean getMoreResults() throws SQLException {\n    logger.trace(\"getMoreResults()\", false);\n\n    return getMoreResults(Statement.CLOSE_CURRENT_RESULT);\n  }\n\n  @Override\n  public boolean getMoreResults(int current) throws SQLException {\n    logger.trace(\"getMoreResults(int current)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n\n    // clean up the current result set, if it exists\n    if (resultSet != null\n        && (current == Statement.CLOSE_CURRENT_RESULT || current == Statement.CLOSE_ALL_RESULTS)) {\n      resultSet.close();\n    }\n\n    boolean hasResultSet = sfBaseStatement.getMoreResults(current);\n    SFBaseResultSet sfResultSet = sfBaseStatement.getResultSet();\n\n    if (hasResultSet) // result set returned\n    {\n      sfResultSet.setSession(this.connection.getSFBaseSession(internalCallMarker()));\n      if (resultSet != null && !resultSet.isClosed()) {\n        openResultSets.add(resultSet);\n      }\n      resultSet = connection.getHandler(internalCallMarker()).createResultSet(sfResultSet, this);\n      updateCount = NO_UPDATES;\n      return true;\n    } else if (sfResultSet != null) // update count returned\n    {\n      if (resultSet != null && !resultSet.isClosed()) {\n        openResultSets.add(resultSet);\n      }\n      resultSet = null;\n      try {\n        updateCount = ResultUtil.calculateUpdateCount(sfResultSet);\n      } catch (SFException ex) {\n        throw new SnowflakeSQLLoggedException(\n            connection.getSFBaseSession(internalCallMarker()), ex);\n      }\n      // Multi statement queries should return true while there are still statements to iterate\n      // through.\n      if (queryID != null\n          && sfBaseStatement.hasChildren()\n          && sfBaseStatement.getChildQueryIds(queryID).length > 0) {\n        return true;\n      }\n      return false;\n    } else // no more results\n    {\n      updateCount = NO_UPDATES;\n      return false;\n    }\n  }\n\n  @Override\n  public int getQueryTimeout() throws SQLException {\n    logger.trace(\"getQueryTimeout()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return this.queryTimeout;\n  }\n\n  @Override\n  public ResultSet getResultSet() throws SQLException {\n    logger.trace(\"getResultSet()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return resultSet;\n  }\n\n  @Override\n  public int getResultSetConcurrency() throws SQLException {\n    logger.trace(\"getResultSetConcurrency()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return resultSetConcurrency;\n  }\n\n  @Override\n  public int getResultSetHoldability() throws SQLException {\n    logger.trace(\"getResultSetHoldability()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return resultSetHoldability;\n  }\n\n  @Override\n  public int getResultSetType() throws SQLException {\n    logger.trace(\"getResultSetType()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return this.resultSetType;\n  }\n\n  @Override\n  public int getUpdateCount() throws SQLException {\n    logger.trace(\"getUpdateCount()\", false);\n    return (int) getUpdateCountIfDML();\n  }\n\n  @Override\n  public long getLargeUpdateCount() throws SQLException {\n    logger.trace(\"getLargeUpdateCount()\", false);\n    return getUpdateCountIfDML();\n  }\n\n  private long getUpdateCountIfDML() throws SQLException {\n    raiseSQLExceptionIfStatementIsClosed();\n    return updateCount;\n  }\n\n  @Override\n  public SQLWarning getWarnings() throws SQLException {\n    logger.trace(\"getWarnings()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return sqlWarnings;\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    logger.trace(\"isClosed()\", false);\n    return isClosed; // no exception\n  }\n\n  @Override\n  public boolean isPoolable() throws SQLException {\n    logger.trace(\"isPoolable()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    return poolable;\n  }\n\n  @Override\n  public void setCursorName(String name) throws SQLException {\n    logger.trace(\"setCursorName(String name)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setEscapeProcessing(boolean enable) throws SQLException {\n    logger.trace(\"setEscapeProcessing(boolean enable)\", false);\n    // NOTE: We could raise an exception here, because not implemented\n    // but it may break the existing applications. For now returning nothing.\n    // we should revisit.\n    raiseSQLExceptionIfStatementIsClosed();\n  }\n\n  @Override\n  public void setFetchDirection(int direction) throws SQLException {\n    logger.trace(\"setFetchDirection(int direction)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    if (direction != ResultSet.FETCH_FORWARD) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(\n          connection.getSFBaseSession(internalCallMarker()));\n    }\n  }\n\n  @Override\n  public void setFetchSize(int rows) throws SQLException {\n    logger.trace(\"setFetchSize(int rows), rows={}\", rows);\n    raiseSQLExceptionIfStatementIsClosed();\n    fetchSize = rows;\n  }\n\n  @Override\n  public void setMaxFieldSize(int max) throws SQLException {\n    logger.trace(\"setMaxFieldSize(int max)\", false);\n\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void setMaxRows(int max) throws SQLException {\n    logger.trace(\"setMaxRows(int max)\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n\n    this.maxRows = max;\n    try {\n      if (this.sfBaseStatement != null) {\n        this.sfBaseStatement.addProperty(\"rows_per_resultset\", max);\n      }\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Override\n  public void setPoolable(boolean poolable) throws SQLException {\n    logger.trace(\"setPoolable(boolean poolable)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n\n    if (poolable) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(\n          connection.getSFBaseSession(internalCallMarker()));\n    }\n    this.poolable = poolable;\n  }\n\n  /**\n   * Sets a parameter at the statement level.\n   *\n   * @param name parameter name.\n   * @param value parameter value.\n   * @throws SQLException if any SQL error occurs.\n   */\n  public void setParameter(String name, Object value) throws SQLException {\n    logger.trace(\"setParameter\", false);\n\n    try {\n      if (this.sfBaseStatement != null) {\n        this.sfBaseStatement.addProperty(name, value);\n      }\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(ex);\n    }\n  }\n\n  @Override\n  public void setBatchID(String batchID) {\n    this.batchID = batchID;\n  }\n\n  @Override\n  public void setQueryTimeout(int seconds) throws SQLException {\n    logger.trace(\"setQueryTimeout(int seconds)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n\n    this.queryTimeout = seconds;\n    try {\n      if (this.sfBaseStatement != null) {\n        this.sfBaseStatement.addProperty(\"query_timeout\", seconds);\n      }\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Override\n  public void setAsyncQueryTimeout(int seconds) throws SQLException {\n    logger.trace(\"setAsyncQueryTimeout(int seconds)\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n\n    try {\n      if (this.sfBaseStatement != null) {\n        this.sfBaseStatement.addProperty(\"STATEMENT_TIMEOUT_IN_SECONDS\", seconds);\n        // disable statement level query timeout to avoid override by connection parameter\n        this.setQueryTimeout(0);\n      }\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          null, ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"isWrapperFor(Class<?> iface)\", false);\n\n    return iface.isInstance(this);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  @Override\n  public void closeOnCompletion() throws SQLException {\n    logger.trace(\"closeOnCompletion()\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public boolean isCloseOnCompletion() throws SQLException {\n    logger.trace(\"isCloseOnCompletion()\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(\n        connection.getSFBaseSession(internalCallMarker()));\n  }\n\n  @Override\n  public void close() throws SQLException {\n    close(true);\n  }\n\n  public void close(boolean removeClosedStatementFromConnection) throws SQLException {\n    logger.trace(\"close()\", false);\n\n    // No exception is raised even if the statement is closed.\n    if (resultSet != null) {\n      resultSet.close();\n      resultSet = null;\n    }\n    isClosed = true;\n    batch.clear();\n\n    // also make sure to close all created resultSets from this statement\n    for (ResultSet rs : openResultSets) {\n      if (rs != null && !rs.isClosed()) {\n        if (rs.isWrapperFor(SnowflakeResultSetV1.class)) {\n          rs.unwrap(SnowflakeResultSetV1.class).close(false);\n        } else {\n          rs.close();\n        }\n      }\n    }\n    openResultSets.clear();\n    sfBaseStatement.close();\n    if (removeClosedStatementFromConnection) {\n      connection.removeClosedStatement(this);\n    }\n  }\n\n  @Override\n  public void cancel() throws SQLException {\n    logger.trace(\"cancel()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n\n    try {\n      sfBaseStatement.cancel(CancellationReason.CLIENT_REQUESTED);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(ex, ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Override\n  public void clearWarnings() throws SQLException {\n    logger.trace(\"clearWarnings()\", false);\n    raiseSQLExceptionIfStatementIsClosed();\n    sqlWarnings = null;\n  }\n\n  @Override\n  public void addBatch(String sql) throws SQLException {\n    logger.trace(\"addBatch(String sql)\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n\n    batch.add(new BatchEntry(sql, null));\n  }\n\n  @Override\n  public void clearBatch() throws SQLException {\n    logger.trace(\"clearBatch()\", false);\n\n    raiseSQLExceptionIfStatementIsClosed();\n\n    batch.clear();\n  }\n\n  private void executeSetProperty(final String sql) {\n    logger.trace(\"setting property\", false);\n\n    // tokenize the sql\n    String[] tokens = sql.split(\"\\\\s+\");\n\n    if (tokens.length < 2) {\n      return;\n    }\n\n    if (\"tracing\".equalsIgnoreCase(tokens[1])) {\n      if (tokens.length >= 3) {\n        /*connection.tracingLevel = Level.parse(tokens[2].toUpperCase());\n        if (connection.tracingLevel != null)\n        {\n          Logger snowflakeLogger = Logger.getLogger(\"net.snowflake\");\n          snowflakeLogger.setLevel(connection.tracingLevel);\n        }*/\n      }\n    } else {\n      this.sfBaseStatement.executeSetProperty(sql);\n    }\n  }\n\n  public SFBaseStatement getSFBaseStatement() throws SQLException {\n    return sfBaseStatement;\n  }\n\n  // Convenience method to return an SFStatement-typed SFStatementInterface object, but\n  // performs the type-checking as necessary.\n  public SFStatement getSfStatement() throws SnowflakeSQLException {\n    if (sfBaseStatement instanceof SFStatement) {\n      return (SFStatement) sfBaseStatement;\n    }\n\n    throw new SnowflakeSQLException(\n        \"getSfStatement() called with a different SFStatementInterface type.\");\n  }\n\n  public void removeClosedResultSet(ResultSet rs) {\n    openResultSets.remove(rs);\n  }\n\n  final class BatchEntry {\n    private final String sql;\n\n    private final Map<String, ParameterBindingDTO> parameterBindings;\n\n    BatchEntry(String sql, Map<String, ParameterBindingDTO> parameterBindings) {\n      this.sql = sql;\n      this.parameterBindings = parameterBindings;\n    }\n\n    public String getSql() {\n      return sql;\n    }\n\n    public Map<String, ParameterBindingDTO> getParameterBindings() {\n      return parameterBindings;\n    }\n  }\n\n  /**\n   * This is a No Operation Statement to avoid null pointer exception for sessionless result set.\n   */\n  public static class NoOpSnowflakeStatementImpl extends SnowflakeStatementImpl {\n    public NoOpSnowflakeStatementImpl() throws SQLException {\n      super(\n          null,\n          ResultSet.TYPE_FORWARD_ONLY,\n          ResultSet.CONCUR_READ_ONLY,\n          ResultSet.CLOSE_CURSORS_AT_COMMIT);\n    }\n\n    @Override\n    public ResultSet executeQuery(String sql) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int executeUpdate(String sql) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public long executeLargeUpdate(String sql) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public String getQueryID() {\n      return \"invalid_query_id\";\n    }\n\n    @Override\n    public List<String> getBatchQueryIDs() {\n      return new ArrayList<>();\n    }\n\n    @Override\n    public boolean execute(String sql) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean execute(String sql, int[] columnIndexes) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean execute(String sql, String[] columnNames) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int[] executeBatch() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public long[] executeLargeBatch() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int executeUpdate(String sql, String[] columnNames) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public Connection getConnection() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getFetchDirection() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getFetchSize() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public ResultSet getGeneratedKeys() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getMaxFieldSize() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getMaxRows() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean getMoreResults() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean getMoreResults(int current) throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getQueryTimeout() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public ResultSet getResultSet() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getResultSetConcurrency() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getResultSetHoldability() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getResultSetType() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public int getUpdateCount() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public long getLargeUpdateCount() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public SQLWarning getWarnings() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean isClosed() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public boolean isPoolable() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public void setCursorName(String name) throws SQLException {}\n\n    @Override\n    public void setEscapeProcessing(boolean enable) throws SQLException {}\n\n    @Override\n    public void setFetchDirection(int direction) throws SQLException {}\n\n    @Override\n    public void setFetchSize(int rows) throws SQLException {}\n\n    @Override\n    public void setMaxFieldSize(int max) throws SQLException {}\n\n    @Override\n    public void setMaxRows(int max) throws SQLException {}\n\n    @Override\n    public void setPoolable(boolean poolable) throws SQLException {}\n\n    @Override\n    public void setParameter(String name, Object value) throws SQLException {}\n\n    @Override\n    public void setQueryTimeout(int seconds) throws SQLException {}\n\n    @Override\n    public void setAsyncQueryTimeout(int seconds) throws SQLException {}\n\n    @Override\n    public boolean isWrapperFor(Class<?> iface) throws SQLException {\n      logger.trace(\"isWrapperFor(Class<?> iface)\", false);\n\n      return iface.isInstance(this);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public <T> T unwrap(Class<T> iface) throws SQLException {\n      logger.trace(\"unwrap(Class<T> iface)\", false);\n\n      if (!iface.isInstance(this)) {\n        throw new SQLException(\n            this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n      }\n      return (T) this;\n    }\n\n    @Override\n    public void closeOnCompletion() throws SQLException {}\n\n    @Override\n    public boolean isCloseOnCompletion() throws SQLException {\n      throw new SQLException(NOOP_MESSAGE);\n    }\n\n    @Override\n    public void close() throws SQLException {}\n\n    @Override\n    public void close(boolean removeClosedStatementFromConnection) throws SQLException {}\n\n    @Override\n    public void cancel() throws SQLException {}\n\n    @Override\n    public void clearWarnings() throws SQLException {}\n\n    @Override\n    public void addBatch(String sql) throws SQLException {}\n\n    @Override\n    public void clearBatch() throws SQLException {}\n\n    @Override\n    public void removeClosedResultSet(ResultSet rs) {}\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/common/core/SFBinary.java",
    "content": "package net.snowflake.client.internal.common.core;\n\nimport java.util.Arrays;\nimport java.util.Objects;\nimport org.apache.commons.codec.binary.Base16;\nimport org.apache.commons.codec.binary.Base64;\n\n/**\n * Represents binary values.\n *\n * <p>Just a wrapper around a byte array.\n *\n * <p>Instances of this class are immutable.\n *\n * @author mkember, copied from net.snowflake.snowflake-common artifact\n */\npublic class SFBinary {\n  private static final Base16 INSTANCE = new Base16();\n  /** An empty SFBinary. */\n  public static final SFBinary EMPTY = new SFBinary(new byte[] {});\n\n  /**\n   * Special SFBinary that is greater than all others.\n   *\n   * <p>Specifically, MAXIMUM.compareTo(x) &gt; 0 for all x != MAXIMUM. This value has no byte array\n   * representation, so calling getBytes, toBase64, or concat on it is NOT allowed. It does,\n   * however, have a special representation \"Z\", so SFBinary.fromHex(\"Z\") returns MAXIMUM, and\n   * MAXIMUM.toHex() returns \"Z\".\n   */\n  public static final SFBinary MAXIMUM = new SFBinary(null);\n\n  private static final String MAXIMUM_HEX = \"Z\";\n\n  // Used for validating hex-encoded strings.\n  private static final byte[] HEX_TABLE;\n  private static final byte INVALID = 0;\n  private static final byte HEX_DIGIT = 1;\n  private static final byte WHITESPACE = 2;\n\n  static {\n    // Initialize HEX_TABLE with 0-9, a-f, A-F, and whitespace.\n    byte[] temp = new byte['f' + 1];\n    for (char c = '0'; c <= '9'; c++) {\n      temp[c] = HEX_DIGIT;\n    }\n    for (char c = 'A'; c <= 'F'; c++) {\n      temp[c] = HEX_DIGIT;\n    }\n    for (char c = 'a'; c <= 'f'; c++) {\n      temp[c] = HEX_DIGIT;\n    }\n    temp[' '] = WHITESPACE;\n    temp['\\n'] = WHITESPACE;\n    temp['\\r'] = WHITESPACE;\n    HEX_TABLE = temp;\n  }\n\n  private final byte[] bytes;\n\n  /**\n   * Constructs an SFBinary from a byte array.\n   *\n   * @param bytes an byte array\n   */\n  public SFBinary(byte[] bytes) {\n    this.bytes = bytes;\n  }\n\n  /**\n   * Returns true if it's safe to call SFBinary.fromHex(str).\n   *\n   * <p>This is meant for checking user input, so it allows spaces, newlines, and carriage returns.\n   * It returns false for the special value \"Z\".\n   *\n   * @param str a string\n   * @return true if a string is a hexadecimal value otherwise false\n   */\n  public static boolean validHex(String str) {\n    int count = 0;\n    for (int i = 0; i < str.length(); i++) {\n      char c = str.charAt(i);\n      int type = c < HEX_TABLE.length ? HEX_TABLE[c] : INVALID;\n      if (type == INVALID) {\n        return false;\n      }\n      if (type == HEX_DIGIT) {\n        count++;\n      }\n    }\n\n    return count % 2 == 0;\n  }\n\n  /**\n   * Creates an SFBinary by decoding a hex-encoded string (uppercase letters).\n   *\n   * <p>Handles the special value \"Z\" by returning MAXIMUM.\n   *\n   * @param str a string\n   * @return SFBinary\n   * @throws IllegalArgumentException if the string is not hex-encoded.\n   */\n  public static SFBinary fromHex(String str) {\n    if (str.equals(MAXIMUM_HEX)) {\n      return MAXIMUM;\n    }\n    if (!validHex(str)) {\n      throw new IllegalArgumentException(\"Invalid hex in '\" + str + \"'\");\n    }\n\n    return new SFBinary(INSTANCE.decode(str));\n  }\n\n  /**\n   * Creates an SFBinary by decoding a Base64-encoded string (RFC 4648).\n   *\n   * @param str a string.\n   * @return SFBinary\n   * @throws IllegalArgumentException if the string is not Base64-encoded.\n   */\n  public static SFBinary fromBase64(String str) {\n    return new SFBinary(INSTANCE.decode(str));\n  }\n\n  /**\n   * Returns the underlying byte array.\n   *\n   * @return a byte array\n   */\n  public byte[] getBytes() {\n    assert !this.equals(MAXIMUM);\n    return bytes;\n  }\n\n  /**\n   * Returns the length of the SFBinary in bytes.\n   *\n   * @return byte length\n   */\n  public int length() {\n    return bytes.length;\n  }\n\n  /**\n   * Encodes the binary value as a hex string (uppercase letters).\n   *\n   * <p>Handles MAXIMUM by returning the special value \"Z\".\n   *\n   * @return a hexdecimal string\n   */\n  public String toHex() {\n    if (this.equals(MAXIMUM)) {\n      return MAXIMUM_HEX;\n    }\n\n    return INSTANCE.encodeAsString(bytes);\n  }\n\n  /**\n   * Encodes the binary value as a Base64 string (RFC 4648).\n   *\n   * @return a base64 string\n   */\n  public String toBase64() {\n    assert !this.equals(MAXIMUM);\n    return Base64.encodeBase64String(bytes);\n  }\n\n  /**\n   * Returns a new SFBinary that is a substring of this SFBinary.\n   *\n   * <p>Same semantics as String.substring: 'start' is inclusive, 'end' is exclusive, and 'start'\n   * cannot be greater than 'end'.\n   *\n   * @param start the starting index of byte array\n   * @param end the ending index of byte array\n   * @return SFBinary\n   */\n  public SFBinary substring(int start, int end) {\n    if (start == end) {\n      return EMPTY;\n    }\n\n    return new SFBinary(Arrays.copyOfRange(bytes, start, end));\n  }\n\n  /**\n   * Concatenates two binary values.\n   *\n   * <p>Concatenates the bytes of this SFBinary with the bytes of the other SFBinary, returning a\n   * new SFBinary instance.\n   *\n   * @param other SFBinary to append\n   * @return concatenated SFBinary\n   */\n  public SFBinary concat(SFBinary other) {\n    assert !this.equals(MAXIMUM);\n    assert !Objects.equals(other, MAXIMUM);\n\n    byte[] result = Arrays.copyOf(bytes, bytes.length + other.bytes.length);\n    System.arraycopy(\n        other.bytes,\n        0, // source\n        result,\n        bytes.length, // destination\n        other.bytes.length); // length\n    return new SFBinary(result);\n  }\n\n  /**\n   * Compares SFBinary\n   *\n   * @param other the target SFBinary\n   * @return 1 if this SFBinary is larger, 0 if identical otherwise -1\n   */\n  public int compareTo(SFBinary other) {\n    if (this.equals(MAXIMUM) && Objects.equals(other, MAXIMUM)) {\n      // This logic is correct for most cases. For example, when choosing the\n      // larger of two EP maxes, and both are Z, it doesn't matter what this\n      // returns. It *would* be a problem if min and max were both Z and this\n      // returned 0 (implying the expression is constant), but that comparison\n      // will never happen, because XP never produces Z for the min.\n      return 0;\n    }\n    if (this.equals(MAXIMUM)) {\n      return 1;\n    } else if (Objects.equals(other, MAXIMUM)) {\n      return -1;\n    }\n\n    // Compare the byte arrays lexicographically.\n    for (int i = 0; i < bytes.length && i < other.bytes.length; i++) {\n      int a = bytes[i] & 0xFF;\n      int b = other.bytes[i] & 0xFF;\n      if (a > b) {\n        return 1;\n      } else if (a < b) {\n        return -1;\n      }\n    }\n\n    return bytes.length - other.bytes.length;\n  }\n\n  /** {@inheritDoc} */\n  @Override\n  public int hashCode() {\n    if (this.equals(MAXIMUM)) {\n      return 0;\n    }\n\n    return Arrays.hashCode(bytes);\n  }\n\n  /** {@inheritDoc} */\n  @Override\n  public boolean equals(Object other) {\n    if (this == MAXIMUM || other == MAXIMUM) {\n      return this == MAXIMUM && other == MAXIMUM;\n    }\n\n    return other instanceof SFBinary && Arrays.equals(bytes, ((SFBinary) other).bytes);\n  }\n\n  /** {@inheritDoc} */\n  @Override\n  public String toString() {\n    return \"SFBinary(hex=\" + toHex() + \")\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/common/core/SFBinaryFormat.java",
    "content": "package net.snowflake.client.internal.common.core;\n\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.charset.CharacterCodingException;\nimport java.nio.charset.CharsetDecoder;\nimport java.nio.charset.CodingErrorAction;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Format (encoding scheme) for binary values.\n *\n * <p>This corresponds to the BinaryFormat class in XP.\n *\n * <p>There are three formats: 1. HEX (hexadecimal encoding) 2. BASE64 (RFC 4648 Base64 encoding) 3.\n * UTF-8 or UTF8\n *\n * <p>Each kind does two things: 1. format - convert from SFBinary to String 2. parse - convert from\n * String to SFBinary\n *\n * <p>For HEX and BASE64, \"format\" means \"encode\", and \"parse\" (which can fail) means \"decode\". For\n * UTF-8, it is the reverse: \"format\" means to decode bytes as Unicode characters (which can fail),\n * and \"parse\" means to encode a string of as UTF-8 bytes (which always suceeds).\n *\n * @author mkember, copied from net.snowflake.snowflake-common artifact\n */\npublic enum SFBinaryFormat {\n  HEX {\n    @Override\n    public String format(SFBinary binary) {\n      return binary.toHex();\n    }\n\n    @Override\n    public SFBinary parse(String string) {\n      return SFBinary.fromHex(string);\n    }\n  },\n\n  BASE64 {\n    @Override\n    public String format(SFBinary binary) {\n      return binary.toBase64();\n    }\n\n    @Override\n    public SFBinary parse(String string) {\n      return SFBinary.fromBase64(string);\n    }\n  },\n\n  UTF8 {\n    @Override\n    public String format(SFBinary binary) {\n      final CharBuffer buf;\n      try {\n        buf = UTF8_DECODER.decode(ByteBuffer.wrap(binary.getBytes()));\n      } catch (CharacterCodingException ex) {\n        throw new IllegalArgumentException(\"Invalid UTF-8\");\n      }\n      return buf.toString();\n    }\n\n    @Override\n    public SFBinary parse(String string) {\n      return new SFBinary(string.getBytes(StandardCharsets.UTF_8));\n    }\n  };\n\n  /**\n   * Format a binary value as a string.\n   *\n   * @param binary SFBinary\n   * @return formatted binary value\n   * @throws IllegalArgumentException if the binary cannot be formatted.\n   */\n  public abstract String format(SFBinary binary);\n\n  /**\n   * Parse a binary value as a string.\n   *\n   * @param string a string\n   * @return SFBinary instance\n   * @throws IllegalArgumentException if the string cannot be parsed.\n   */\n  public abstract SFBinary parse(String string);\n\n  /**\n   * The default binary format.\n   *\n   * <p>NOTE: Keep this in sync with BinaryFormat::DEFAULT in XP.\n   */\n  public static final SFBinaryFormat DEFAULT = HEX;\n\n  /** Decoder object used for parsing and formatting UTF-8. */\n  private static final CharsetDecoder UTF8_DECODER =\n      StandardCharsets.UTF_8\n          .newDecoder()\n          .onMalformedInput(CodingErrorAction.REPORT)\n          .onUnmappableCharacter(CodingErrorAction.REPORT);\n\n  /**\n   * Looks up a binary format by case-insensitive name. HEX, BASE64 or UTF-8\n   *\n   * @param name binary format\n   * @return SFBinaryFormat instance\n   * @throws IllegalArgumentException if the name is invalid.\n   */\n  public static SFBinaryFormat getFormat(String name) {\n    try {\n      return lookup(name);\n    } catch (IllegalArgumentException ex) {\n      throw new IllegalArgumentException(\"Must be 'HEX', 'BASE64', or 'UTF-8'\");\n    }\n  }\n\n  /**\n   * Looks up a binary format suitable for text output.\n   *\n   * <p>Specifically, does not allow UTF-8 because not all binary values can be formatted with the\n   * UTF-8 format.\n   *\n   * @param name binary format\n   * @return SFBinaryFormat instance\n   * @throws IllegalArgumentException if the name is invalid.\n   */\n  public static SFBinaryFormat getSafeOutputFormat(String name) {\n    try {\n      SFBinaryFormat fmt = lookup(name);\n      if (fmt == UTF8) {\n        throw new IllegalArgumentException();\n      }\n      return fmt;\n    } catch (IllegalArgumentException ex) {\n      throw new IllegalArgumentException(\"Must be 'HEX' or 'BASE64'\");\n    }\n  }\n\n  /**\n   * Helper function to look up a binary format by case-insensitive name.\n   *\n   * @param name binary format name\n   * @return SFBinaryFormat instance\n   * @throws IllegalArgumentException if the name is invalid.\n   */\n  private static SFBinaryFormat lookup(String name) {\n    name = name.toUpperCase();\n    if (name.equals(\"UTF-8\")) {\n      return UTF8;\n    }\n    return SFBinaryFormat.valueOf(name);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/config/ConnectionParameters.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport java.util.Properties;\n\npublic class ConnectionParameters {\n  private final String url;\n  private final Properties params;\n\n  public ConnectionParameters(String uri, Properties params) {\n    this.url = uri;\n    this.params = params;\n  }\n\n  public String getUrl() {\n    return url;\n  }\n\n  public Properties getParams() {\n    return params;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/config/SFClientConfig.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport com.fasterxml.jackson.annotation.JsonAnySetter;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/** POJO class for Snowflake's client config. */\npublic class SFClientConfig {\n  // Used to keep the unknown properties when deserializing\n  @JsonIgnore @JsonAnySetter private Map<String, Object> unknownParams = new LinkedHashMap<>();\n\n  @JsonProperty(\"common\")\n  private CommonProps commonProps;\n\n  @JsonIgnore private String configFilePath;\n\n  public SFClientConfig() {}\n\n  public SFClientConfig(CommonProps commonProps) {\n    this.commonProps = commonProps;\n  }\n\n  public CommonProps getCommonProps() {\n    return commonProps;\n  }\n\n  public void setCommonProps(CommonProps commonProps) {\n    this.commonProps = commonProps;\n  }\n\n  public String getConfigFilePath() {\n    return configFilePath;\n  }\n\n  public void setConfigFilePath(String configFilePath) {\n    this.configFilePath = configFilePath;\n  }\n\n  Set<String> getUnknownParamKeys() {\n    Set<String> unknownParamKeys = new LinkedHashSet<>(unknownParams.keySet());\n\n    if (!commonProps.unknownParams.isEmpty()) {\n      unknownParamKeys.addAll(\n          commonProps.unknownParams.keySet().stream()\n              .map(s -> \"common:\" + s)\n              .collect(Collectors.toCollection(LinkedHashSet::new)));\n    }\n    return unknownParamKeys;\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    SFClientConfig that = (SFClientConfig) o;\n    return Objects.equals(commonProps, that.commonProps);\n  }\n\n  @Override\n  public int hashCode() {\n    return Objects.hash(commonProps);\n  }\n\n  public static class CommonProps {\n    // Used to keep the unknown properties when deserializing\n    @JsonIgnore @JsonAnySetter Map<String, Object> unknownParams = new LinkedHashMap<>();\n\n    @JsonProperty(\"log_level\")\n    private String logLevel;\n\n    @JsonProperty(\"log_path\")\n    private String logPath;\n\n    public CommonProps() {}\n\n    public void CommonProps(String logLevel, String logPath) {\n      this.logLevel = logLevel;\n      this.logPath = logPath;\n    }\n\n    public String getLogLevel() {\n      return logLevel;\n    }\n\n    public void setLogLevel(String logLevel) {\n      this.logLevel = logLevel;\n    }\n\n    public String getLogPath() {\n      return logPath;\n    }\n\n    public void setLogPath(String logPath) {\n      this.logPath = logPath;\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      CommonProps that = (CommonProps) o;\n      return Objects.equals(logLevel, that.logLevel) && Objects.equals(logPath, that.logPath);\n    }\n\n    @Override\n    public int hashCode() {\n      return Objects.hash(logLevel, logPath);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/config/SFClientConfigParser.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SFClientConfigParser {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFClientConfigParser.class);\n  public static final String SF_CLIENT_CONFIG_FILE_NAME = \"sf_client_config.json\";\n  public static final String SF_CLIENT_CONFIG_ENV_NAME = \"SF_CLIENT_CONFIG_FILE\";\n\n  /**\n   * Construct SFClientConfig from client config file passed by user. This method searches the\n   * config file in following order: 1. configFilePath param which is read from connection URL or\n   * connection property. 2. Environment variable: SF_CLIENT_CONFIG_FILE containing full path to\n   * sf_client_config file. 3. Searches for default config file name(sf_client_config.json under the\n   * driver directory from where the driver gets loaded. 4. Searches for default config file\n   * name(sf_client_config.json) under user home directory.\n   *\n   * @param configFilePath SF_CLIENT_CONFIG_FILE parameter read from connection URL or connection\n   *     properties\n   * @return SFClientConfig\n   * @throws IOException if exception encountered when reading config file.\n   */\n  public static SFClientConfig loadSFClientConfig(String configFilePath) throws IOException {\n    try {\n      return loadSFClientConfigInsecure(configFilePath);\n    } catch (SecurityException e) {\n      logger.debug(\"Cannot load config file due to security exception: {}\", e.getMessage());\n      return null;\n    }\n  }\n\n  private static SFClientConfig loadSFClientConfigInsecure(String configFilePath)\n      throws IOException {\n    if (configFilePath != null) {\n      logger.debug(\"Attempting to enable easy logging with file path {}\", configFilePath);\n    }\n    String derivedConfigFilePath = null;\n    if (configFilePath != null && !configFilePath.isEmpty()) {\n      // 1. Try to read the file at  configFilePath.\n      logger.debug(\"Using config file specified from connection string: {}\", configFilePath);\n      derivedConfigFilePath = configFilePath;\n    } else if (systemGetEnv(SF_CLIENT_CONFIG_ENV_NAME) != null) {\n      // 2. If SF_CLIENT_CONFIG_ENV_NAME is set, read from env.\n      String filePath = systemGetEnv(SF_CLIENT_CONFIG_ENV_NAME);\n      logger.debug(\"Using config file specified from environment variable: {}\", filePath);\n      derivedConfigFilePath = filePath;\n    } else {\n      // 3. Read SF_CLIENT_CONFIG_FILE_NAME from where jdbc jar is loaded.\n      String driverLocation =\n          Paths.get(getConfigFilePathFromJDBCJarLocation(), SF_CLIENT_CONFIG_FILE_NAME).toString();\n      if (Files.exists(Paths.get(driverLocation))) {\n        logger.debug(\"Using config file specified from driver directory: {}\", driverLocation);\n        derivedConfigFilePath = driverLocation;\n      } else {\n        // 4. Read SF_CLIENT_CONFIG_FILE_NAME if it is present in user home directory.\n        String homeDirectory = systemGetProperty(\"user.home\");\n        if (homeDirectory != null) {\n          String userHomeFilePath = Paths.get(homeDirectory, SF_CLIENT_CONFIG_FILE_NAME).toString();\n          if (Files.exists(Paths.get(userHomeFilePath))) {\n            logger.debug(\"Using config file specified from home directory: {}\", userHomeFilePath);\n            derivedConfigFilePath = userHomeFilePath;\n          }\n        }\n      }\n    }\n    if (derivedConfigFilePath != null) {\n      try {\n        File configFile = new File(derivedConfigFilePath);\n        ObjectMapper objectMapper = new ObjectMapper();\n        SFClientConfig clientConfig = objectMapper.readValue(configFile, SFClientConfig.class);\n        logger.debug(\n            \"Reading values logLevel {} and logPath {} from client configuration\",\n            clientConfig.getCommonProps().getLogLevel(),\n            clientConfig.getCommonProps().getLogPath());\n\n        Set<String> unknownParams = clientConfig.getUnknownParamKeys();\n        if (!unknownParams.isEmpty()) {\n          for (String unknownParam : unknownParams) {\n            logger.warn(\"Unknown field from config: {}\", unknownParam);\n          }\n        }\n        clientConfig.setConfigFilePath(derivedConfigFilePath);\n\n        return clientConfig;\n      } catch (IOException e) {\n        String customErrorMessage = \"Error while reading config file: \" + derivedConfigFilePath;\n        throw new IOException(customErrorMessage, e);\n      }\n    }\n    // return null if none of the above conditions are satisfied.\n    return null;\n  }\n\n  public static String getConfigFilePathFromJDBCJarLocation() {\n    try {\n      if (SnowflakeDriver.class.getProtectionDomain() != null\n          && SnowflakeDriver.class.getProtectionDomain().getCodeSource() != null\n          && SnowflakeDriver.class.getProtectionDomain().getCodeSource().getLocation() != null) {\n\n        String jarPath =\n            SnowflakeDriver.class.getProtectionDomain().getCodeSource().getLocation().getPath();\n\n        // remove /snowflake-jdbc-3.13.29.jar and anything that follows it from the path.\n        String updatedPath = new File(jarPath).getParentFile().getPath();\n\n        if (systemGetProperty(\"os.name\") != null\n            && systemGetProperty(\"os.name\").toLowerCase().startsWith(\"windows\")) {\n          updatedPath = convertToWindowsPath(updatedPath);\n        }\n        return updatedPath;\n      }\n\n      return \"\";\n    } catch (Exception ex) {\n      // return empty path and move to step 4 of loadSFClientConfig()\n      return \"\";\n    }\n  }\n\n  public static void checkConfigFilePermissions(String derivedConfigFilePath) throws IOException {\n    try {\n      if (Constants.getOS().isPosix()) {\n        // Check permissions of config file\n        if (checkGroupOthersWritePermissions(derivedConfigFilePath)) {\n          String error =\n              String.format(\n                  \"Error due to other users having permission to modify the config file: %s\",\n                  derivedConfigFilePath);\n          // TODO: SNOW-1503722 to change warning log to throw an error instead\n          logger.warn(error);\n        }\n      }\n    } catch (IOException e) {\n      throw e;\n    }\n  }\n\n  static Boolean checkGroupOthersWritePermissions(String configFilePath) throws IOException {\n    Set<PosixFilePermission> folderPermissions =\n        Files.getPosixFilePermissions(Paths.get(configFilePath));\n    return folderPermissions.contains(PosixFilePermission.GROUP_WRITE)\n        || folderPermissions.contains(PosixFilePermission.OTHERS_WRITE);\n  }\n\n  static String convertToWindowsPath(String filePath) {\n    // Find the Windows file path pattern: ex) C:\\ or D:\\\n    Pattern windowsFilePattern = Pattern.compile(\"[C-Z]:[\\\\\\\\/]\");\n    Matcher matcher = windowsFilePattern.matcher(filePath);\n    String prefix = \"\";\n\n    // Path translation for windows\n    if (filePath.startsWith(\"/\")) {\n      filePath = filePath.substring(1);\n    } else if (filePath.startsWith(\"file:\\\\\")) {\n      filePath = filePath.substring(6);\n    } else if (filePath.startsWith(\"nested:\\\\\")) {\n      filePath = filePath.substring(8);\n    } else if (filePath.startsWith(\"\\\\\")) {\n      filePath = filePath.substring(2);\n    } else if (matcher.find() && matcher.start() != 0) {\n      prefix = filePath.substring(0, matcher.start());\n      filePath = filePath.substring(matcher.start());\n    }\n    filePath = prefix + filePath.replace(\"/\", \"\\\\\");\n    return filePath;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/config/SFConnectionConfigParser.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.convertSystemGetEnvToBooleanValue;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isBlank;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.fasterxml.jackson.dataformat.toml.TomlMapper;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFileAttributeView;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.Set;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SFConnectionConfigParser {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFConnectionConfigParser.class);\n  private static final TomlMapper mapper = new TomlMapper();\n  public static final String SNOWFLAKE_HOME_KEY = \"SNOWFLAKE_HOME\";\n  public static final String SNOWFLAKE_DIR = \".snowflake\";\n  public static final String SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY =\n      \"SNOWFLAKE_DEFAULT_CONNECTION_NAME\";\n  public static final String DEFAULT = \"default\";\n  public static final String SNOWFLAKE_TOKEN_FILE_PATH = \"/snowflake/session/token\";\n  public static final String SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION =\n      \"SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION\";\n  public static final String SF_SKIP_WARNING_FOR_READ_PERMISSIONS_ON_CONFIG_FILE =\n      \"SF_SKIP_WARNING_FOR_READ_PERMISSIONS_ON_CONFIG_FILE\";\n\n  private static final List<PosixFilePermission> REQUIRED_PERMISSIONS =\n      Arrays.asList(PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_READ);\n\n  public static ConnectionParameters buildConnectionParameters(String connectionUrl)\n      throws SnowflakeSQLException {\n    Map<String, String> urlParameters = parseAutoConfigJdbcUrlParameters(connectionUrl);\n    String defaultConnectionName = urlParameters.get(\"connectionName\");\n    if (isBlank(defaultConnectionName)) {\n      defaultConnectionName =\n          Optional.ofNullable(systemGetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY)).orElse(DEFAULT);\n    }\n    logger.debug(\"Attempting to load the configuration {} from toml file.\", defaultConnectionName);\n    Map<String, String> fileConnectionConfiguration =\n        loadDefaultConnectionConfiguration(defaultConnectionName);\n\n    if (fileConnectionConfiguration != null && !fileConnectionConfiguration.isEmpty()) {\n      mergeUrlParametersIntoConfiguration(fileConnectionConfiguration, urlParameters);\n\n      Properties connectionProperties = new Properties();\n      connectionProperties.putAll(fileConnectionConfiguration);\n\n      String url = createUrl(fileConnectionConfiguration);\n      logger.debug(\"Url created using parameters from connection configuration file: {}\", url);\n\n      if (\"oauth\".equals(fileConnectionConfiguration.get(\"authenticator\"))\n          && fileConnectionConfiguration.get(\"token\") == null) {\n        Path path =\n            Paths.get(\n                Optional.ofNullable(fileConnectionConfiguration.get(\"token_file_path\"))\n                    .orElse(SNOWFLAKE_TOKEN_FILE_PATH));\n        logger.debug(\"Token used in connect is read from file: {}\", path);\n        try {\n          boolean shouldSkipTokenFilePermissionsVerification =\n              convertSystemGetEnvToBooleanValue(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION, false);\n          if (!shouldSkipTokenFilePermissionsVerification) {\n            verifyFilePermissionSecure(path);\n          } else {\n            logger.debug(\"Skip token file permissions verification\");\n          }\n          String token = new String(Files.readAllBytes(path), Charset.defaultCharset());\n          if (!token.isEmpty()) {\n            putPropertyIfNotNull(connectionProperties, \"token\", token.trim());\n          } else {\n            throw new SnowflakeSQLException(\n                \"Non-empty token must be set when the authenticator type is OAUTH\");\n          }\n        } catch (Exception ex) {\n          throw new SnowflakeSQLException(ex, \"There is a problem during reading token from file\");\n        }\n      }\n      return new ConnectionParameters(url, connectionProperties);\n    } else {\n      return null;\n    }\n  }\n\n  static String getConnectionNameFromUrl(String connectionUrl) {\n    Map<String, String> autoConfigJdbcUrlParameters =\n        parseAutoConfigJdbcUrlParameters(connectionUrl);\n    String connectionNameValue = autoConfigJdbcUrlParameters.get(\"connectionName\");\n    if (isBlank(connectionNameValue)) {\n      logger.debug(\"'connectionName' parameter is not configured\");\n      return \"\";\n    } else {\n      logger.debug(\"'connectionName' parameter is configured. The value is \" + connectionNameValue);\n      return connectionNameValue;\n    }\n  }\n\n  private static Map<String, String> parseAutoConfigJdbcUrlParameters(String connectionUrl) {\n    Map<String, String> paramMap = new HashMap<>();\n\n    int queryStart = connectionUrl.indexOf('?');\n    if (queryStart == -1) {\n      return paramMap;\n    }\n\n    String query = connectionUrl.substring(queryStart + 1);\n    String[] propertyPairs = query.split(\"&\");\n\n    for (String property : propertyPairs) {\n      String[] peopertyKeyVal = property.split(\"=\", 2);\n      if (peopertyKeyVal.length == 2) {\n        try {\n          String key = URLDecoder.decode(peopertyKeyVal[0], \"UTF-8\");\n          String value = URLDecoder.decode(peopertyKeyVal[1], \"UTF-8\");\n          paramMap.put(key, value);\n        } catch (UnsupportedEncodingException e) {\n          logger.warn(\"Failed to decode a parameter {}. Ignored.\", property);\n        }\n      }\n    }\n\n    return paramMap;\n  }\n\n  private static void mergeUrlParametersIntoConfiguration(\n      Map<String, String> fileConfig, Map<String, String> urlParameters) {\n    for (Map.Entry<String, String> entry : urlParameters.entrySet()) {\n      String key = entry.getKey();\n      if (\"connectionName\".equalsIgnoreCase(key)) {\n        continue;\n      }\n      String urlValue = entry.getValue();\n      String tomlValue = fileConfig.get(key);\n      if (tomlValue != null && !tomlValue.equalsIgnoreCase(urlValue)) {\n        logger.debug(\n            \"For config item '{}' the values from connections.toml and the connection string\"\n                + \" differ; the connection string value will be applied.\",\n            key);\n      }\n      fileConfig.put(key, urlValue);\n    }\n  }\n\n  private static Map<String, String> loadDefaultConnectionConfiguration(\n      String defaultConnectionName) throws SnowflakeSQLException {\n    String configDirectory = systemGetEnv(SNOWFLAKE_HOME_KEY);\n    if (configDirectory == null) {\n      String homeDir = systemGetProperty(\"user.home\");\n      if (homeDir == null) {\n        logger.debug(\"cannot determine user home directory\");\n        return new HashMap<>();\n      }\n      configDirectory = Paths.get(homeDir, SNOWFLAKE_DIR).toString();\n    }\n    Path configFilePath = Paths.get(configDirectory, \"connections.toml\");\n\n    if (Files.exists(configFilePath)) {\n      logger.debug(\n          \"Reading connection parameters from file {} using key: {}\",\n          configFilePath,\n          defaultConnectionName);\n      Map<String, Map> parametersMap = readParametersMap(configFilePath);\n      Map<String, String> defaultConnectionParametersMap = parametersMap.get(defaultConnectionName);\n      if (defaultConnectionParametersMap == null) {\n        logger.debug(\"The Connection {} not found in connections.toml.\", defaultConnectionName);\n        throw new SnowflakeSQLException(\n            \"The Connection \" + defaultConnectionName + \" not found in connections.toml file.\");\n      } else {\n        logger.debug(\"The Connection {} found in connections.toml.\", defaultConnectionName);\n      }\n      return defaultConnectionParametersMap;\n    } else {\n      logger.debug(\"Connection configuration file does not exist\");\n      return new HashMap<>();\n    }\n  }\n\n  private static Map<String, Map> readParametersMap(Path configFilePath)\n      throws SnowflakeSQLException {\n    try {\n      File file = new File(configFilePath.toUri());\n      boolean shouldSkipTokenFilePermissionsVerification =\n          convertSystemGetEnvToBooleanValue(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION, false);\n      if (!shouldSkipTokenFilePermissionsVerification) {\n        verifyFilePermissionSecure(configFilePath);\n      } else {\n        logger.debug(\n            \"Skip connection configuration file permissions verification for {}\", configFilePath);\n      }\n      return mapper.readValue(file, Map.class);\n    } catch (IOException ex) {\n      throw new SnowflakeSQLException(ex, \"Problem during reading a configuration file.\");\n    }\n  }\n\n  static void verifyFilePermissionSecure(Path configFilePath)\n      throws IOException, SnowflakeSQLException {\n    final String fileName = \"connections.toml\";\n    if (!isWindows()) {\n      if (configFilePath.getFileName().toString().equals(fileName)) {\n        boolean shouldSkipWarningForReadPermissions =\n            convertSystemGetEnvToBooleanValue(\n                SF_SKIP_WARNING_FOR_READ_PERMISSIONS_ON_CONFIG_FILE, false);\n        PosixFileAttributeView posixFileAttributeView =\n            Files.getFileAttributeView(configFilePath, PosixFileAttributeView.class);\n        Set<PosixFilePermission> permissions =\n            posixFileAttributeView.readAttributes().permissions();\n\n        if (!shouldSkipWarningForReadPermissions) {\n          boolean groupRead = permissions.contains(PosixFilePermission.GROUP_READ);\n          boolean othersRead = permissions.contains(PosixFilePermission.OTHERS_READ);\n          // Warning if readable by group/others (must be 600 or stricter)\n          if (groupRead || othersRead) {\n            logger.warn(\n                \"File %s is readable by group or others. Permissions should be 600 or stricter for maximum security.\",\n                configFilePath);\n          }\n        }\n\n        boolean groupWrite = permissions.contains(PosixFilePermission.GROUP_WRITE);\n        boolean othersWrite = permissions.contains(PosixFilePermission.OTHERS_WRITE);\n        // Error if writable by group/others (must be 644 or stricter)\n        if (groupWrite || othersWrite) {\n          logger.error(\n              \"File %s is writable by group or others. Permissions must be 644 or stricter.\",\n              configFilePath);\n          throw new SnowflakeSQLException(\n              String.format(\n                  \"File %s is writable by group or others. Permissions must be 644 or stricter.\",\n                  configFilePath));\n        }\n\n        // Error if executable by anyone\n        boolean ownerExec = permissions.contains(PosixFilePermission.OWNER_EXECUTE);\n        boolean groupExec = permissions.contains(PosixFilePermission.GROUP_EXECUTE);\n        boolean othersExec = permissions.contains(PosixFilePermission.OTHERS_EXECUTE);\n        // Executable permission is not allowed\n        if (ownerExec || groupExec || othersExec) {\n          logger.error(\n              \"File %s is executable. Executable permission is not allowed.\", configFilePath);\n          throw new SnowflakeSQLException(\n              String.format(\n                  \"File %s is executable. Executable permission is not allowed.\", configFilePath));\n        }\n      } else {\n        PosixFileAttributeView posixFileAttributeView =\n            Files.getFileAttributeView(configFilePath, PosixFileAttributeView.class);\n        if (!posixFileAttributeView.readAttributes().permissions().stream()\n            .allMatch(o -> REQUIRED_PERMISSIONS.contains(o))) {\n          logger.error(\n              \"Reading from file %s is not safe because file permissions are different than read/write for user\",\n              configFilePath);\n          throw new SnowflakeSQLException(\n              String.format(\n                  \"Reading from file %s is not safe because file permissions are different than read/write for user\",\n                  configFilePath));\n        }\n      }\n    }\n  }\n\n  private static String createUrl(Map<String, String> fileConnectionConfiguration)\n      throws SnowflakeSQLException {\n    Optional<String> maybeAccount = Optional.ofNullable(fileConnectionConfiguration.get(\"account\"));\n    Optional<String> maybeHost = Optional.ofNullable(fileConnectionConfiguration.get(\"host\"));\n    if (maybeAccount.isPresent()\n        && maybeHost.isPresent()\n        && !maybeHost.get().contains(maybeAccount.get())) {\n      logger.warn(\n          String.format(\n              \"Inconsistent host and account values in file configuration. ACCOUNT: {} , HOST: {}. The host value will be used.\",\n              maybeAccount.get(),\n              maybeHost.get()));\n    }\n    String host =\n        maybeHost.orElse(\n            maybeAccount\n                .map(acnt -> String.format(\"%s.snowflakecomputing.com\", acnt))\n                .orElse(null));\n    if (host == null || host.isEmpty()) {\n      logger.warn(\"Neither host nor account is specified in connection parameters\");\n      throw new SnowflakeSQLException(\n          \"Unable to connect because neither host nor account is specified in connection parameters\");\n    }\n    logger.debug(\"Host created using parameters from connection configuration file: {}\", host);\n    String port = fileConnectionConfiguration.get(\"port\");\n    String protocol = fileConnectionConfiguration.get(\"protocol\");\n    if (\"http\".equalsIgnoreCase(protocol)) {\n      return String.format(\n          \"jdbc:snowflake://http://%s:%s\", host, isNullOrEmpty(port) ? \"80\" : port);\n    }\n    return String.format(\"jdbc:snowflake://%s:%s\", host, isNullOrEmpty(port) ? \"443\" : port);\n  }\n\n  private static void putPropertyIfNotNull(Properties props, Object key, Object value) {\n    if (key != null && value != null) {\n      props.put(key, value);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/ArrowSqlInput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.mapSFExceptionToSQLException;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.core.structs.SQLDataCreationHelper;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.ThrowingBiFunction;\nimport org.apache.arrow.vector.util.JsonStringArrayList;\nimport org.apache.arrow.vector.util.JsonStringHashMap;\n\npublic class ArrowSqlInput extends BaseSqlInput {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ArrowSqlInput.class);\n\n  private final Map<String, Object> input;\n  private int currentIndex = 0;\n  private boolean wasNull = false;\n\n  public ArrowSqlInput(\n      Map<String, Object> input,\n      SFBaseSession session,\n      Converters converters,\n      List<FieldMetadata> fields) {\n    super(session, converters, fields);\n    this.input = input;\n  }\n\n  public Map<String, Object> getInput() {\n    return input;\n  }\n\n  @Override\n  public String readString() throws SQLException {\n    return withNextValue((this::convertString));\n  }\n\n  @Override\n  public boolean readBoolean() throws SQLException {\n    return withNextValue(this::convertBoolean);\n  }\n\n  @Override\n  public byte readByte() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) ->\n            mapSFExceptionToSQLException(() -> converters.getNumberConverter().getByte(value)));\n  }\n\n  @Override\n  public short readShort() throws SQLException {\n    return withNextValue(this::convertShort);\n  }\n\n  @Override\n  public int readInt() throws SQLException {\n    return withNextValue(this::convertInt);\n  }\n\n  @Override\n  public long readLong() throws SQLException {\n    return withNextValue(this::convertLong);\n  }\n\n  @Override\n  public float readFloat() throws SQLException {\n    return withNextValue(this::convertFloat);\n  }\n\n  @Override\n  public double readDouble() throws SQLException {\n    return withNextValue(this::convertDouble);\n  }\n\n  @Override\n  public BigDecimal readBigDecimal() throws SQLException {\n    return withNextValue(this::convertBigDecimal);\n  }\n\n  @Override\n  public byte[] readBytes() throws SQLException {\n    return withNextValue(this::convertBytes);\n  }\n\n  @Override\n  public Date readDate() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          return convertDate((int) value);\n        });\n  }\n\n  private Date convertDate(int value) throws SQLException {\n    return mapSFExceptionToSQLException(\n        () ->\n            converters.getStructuredTypeDateTimeConverter().getDate(value, TimeZone.getDefault()));\n  }\n\n  @Override\n  public Time readTime() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          return convertTime((long) value, fieldMetadata);\n        });\n  }\n\n  private Time convertTime(long value, FieldMetadata fieldMetadata) throws SQLException {\n    return mapSFExceptionToSQLException(\n        () -> {\n          int scale = fieldMetadata.getScale();\n          return converters.getStructuredTypeDateTimeConverter().getTime(value, scale);\n        });\n  }\n\n  @Override\n  public Timestamp readTimestamp(TimeZone tz) throws SQLException {\n    return withNextValue((value, fieldMetadata) -> convertTimestamp(tz, value, fieldMetadata));\n  }\n\n  private Timestamp convertTimestamp(TimeZone tz, Object value, FieldMetadata fieldMetadata)\n      throws SQLException {\n    if (value == null) {\n      return null;\n    }\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    int columnSubType = fieldMetadata.getType();\n    int scale = fieldMetadata.getScale();\n    return mapSFExceptionToSQLException(\n        () ->\n            converters\n                .getStructuredTypeDateTimeConverter()\n                .getTimestamp((Map<String, Object>) value, columnType, columnSubType, tz, scale));\n  }\n\n  @Override\n  public Object readObject() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (!(value instanceof JsonStringHashMap)) {\n            throw new SQLException(\n                \"Invalid value passed to 'readObject()', expected Map; got: \" + value.getClass());\n          }\n          return value;\n        });\n  }\n\n  @Override\n  public <T> T readObject(Class<T> type) throws SQLException {\n    return readObject(type, TimeZone.getDefault());\n  }\n\n  @Override\n  public <T> T readObject(Class<T> type, TimeZone tz) throws SQLException {\n    return withNextValue((value, fieldMetadata) -> convertObject(type, tz, value, fieldMetadata));\n  }\n\n  private <T> T convertObject(Class<T> type, TimeZone tz, Object value, FieldMetadata fieldMetadata)\n      throws SQLException {\n    if (value == null) {\n      return null;\n    } else if (SQLData.class.isAssignableFrom(type)) {\n      ArrowSqlInput sqlInput =\n          new ArrowSqlInput(\n              (Map<String, Object>) value, session, converters, fieldMetadata.getFields());\n      SQLData instance = (SQLData) SQLDataCreationHelper.create(type);\n      instance.readSQL(sqlInput, null);\n      return (T) instance;\n    } else if (Map.class.isAssignableFrom(type)) {\n      return (T) convertSqlInputToMap((SQLInput) value);\n    } else if (String.class.isAssignableFrom(type)) {\n      return (T) convertString(value, fieldMetadata);\n    } else if (Boolean.class.isAssignableFrom(type)) {\n      return (T) convertBoolean(value, fieldMetadata);\n    } else if (Byte.class.isAssignableFrom(type)) {\n      return (T) convertBytes(value, fieldMetadata);\n    } else if (Short.class.isAssignableFrom(type)) {\n      return (T) convertShort(value, fieldMetadata);\n    } else if (Integer.class.isAssignableFrom(type)) {\n      return (T) convertInt(value, fieldMetadata);\n    } else if (Long.class.isAssignableFrom(type)) {\n      return (T) convertLong(value, fieldMetadata);\n    } else if (Float.class.isAssignableFrom(type)) {\n      return (T) convertFloat(value, fieldMetadata);\n    } else if (Double.class.isAssignableFrom(type)) {\n      return (T) convertDouble(value, fieldMetadata);\n    } else if (Date.class.isAssignableFrom(type)) {\n      return (T) convertDate((int) value);\n    } else if (Time.class.isAssignableFrom(type)) {\n      return (T) convertTime((long) value, fieldMetadata);\n    } else if (Timestamp.class.isAssignableFrom(type)) {\n      return (T) convertTimestamp(tz, value, fieldMetadata);\n    } else if (BigDecimal.class.isAssignableFrom(type)) {\n      return (T) convertBigDecimal(value, fieldMetadata);\n    } else if (byte[].class.isAssignableFrom(type)) {\n      return (T) convertBytes(value, fieldMetadata);\n    } else {\n      logger.debug(\n          \"Unsupported type passed to readObject(int columnIndex,Class<T> type): \"\n              + type.getName());\n      throw new SQLException(\n          \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n              + type.getName());\n    }\n  }\n\n  @Override\n  public <T> List<T> readList(Class<T> type) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          List<T> result = new ArrayList();\n          JsonStringArrayList<Map> maps = (JsonStringArrayList) value;\n          for (Object ob : maps) {\n            result.add(\n                convertObject(type, TimeZone.getDefault(), ob, fieldMetadata.getFields().get(0)));\n          }\n          return result;\n        });\n  }\n\n  @Override\n  public <T> T[] readArray(Class<T> type) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          JsonStringArrayList<Map> internalValues = (JsonStringArrayList) value;\n          T[] array = (T[]) java.lang.reflect.Array.newInstance(type, internalValues.size());\n          int counter = 0;\n          for (Object ob : internalValues) {\n            array[counter++] =\n                convertObject(type, TimeZone.getDefault(), ob, fieldMetadata.getFields().get(0));\n          }\n          return array;\n        });\n  }\n\n  @Override\n  public <T> Map<String, T> readMap(Class<T> type) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          Map<String, T> result = new HashMap();\n          JsonStringArrayList<Map> maps = (JsonStringArrayList) value;\n          for (Map map : maps) {\n            result.put(\n                map.get(\"key\").toString(),\n                convertObject(\n                    type,\n                    TimeZone.getDefault(),\n                    map.get(\"value\"),\n                    fieldMetadata.getFields().get(1)));\n          }\n          return result;\n        });\n  }\n\n  @Override\n  public boolean wasNull() {\n    return wasNull;\n  }\n\n  @Override\n  Map<String, Object> convertSqlInputToMap(SQLInput sqlInput) {\n    return ((ArrowSqlInput) sqlInput).getInput();\n  }\n\n  private <T> T withNextValue(ThrowingBiFunction<Object, FieldMetadata, T, SQLException> action)\n      throws SQLException {\n    FieldMetadata field = fields.get(currentIndex++);\n    Object value = input.get(field.getName());\n    wasNull = value == null;\n    return action.apply(value, field);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/AssertUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport net.snowflake.client.api.exception.ErrorCode;\n\npublic class AssertUtil {\n  /**\n   * Assert the condition is true, otherwise throw an internal error exception with the given\n   * message.\n   *\n   * @param condition The variable to test the 'truthiness' of\n   * @param internalErrorMesg The error message to display if condition is false\n   * @throws SFException Will be thrown if condition is false\n   */\n  public static void assertTrue(boolean condition, String internalErrorMesg) throws SFException {\n    if (!condition) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, internalErrorMesg);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/AttributeEnhancingHttpRequestRetryHandler.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.IOException;\nimport org.apache.http.impl.client.DefaultHttpRequestRetryHandler;\nimport org.apache.http.protocol.HttpContext;\n\n/**\n * Extends {@link DefaultHttpRequestRetryHandler} to store the current execution count (attempt\n * number) in the {@link HttpContext}. This allows interceptors to identify retry attempts.\n *\n * <p>The execution count is stored using the key defined by {@link #EXECUTION_COUNT_ATTRIBUTE}.\n */\nclass AttributeEnhancingHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {\n  /**\n   * The key used to store the current execution count (attempt number) in the {@link HttpContext}.\n   * Interceptors can use this key to retrieve the count. The value stored will be an {@link\n   * Integer}.\n   */\n  static final String EXECUTION_COUNT_ATTRIBUTE = \"net.snowflake.client.core.execution-count\";\n\n  @Override\n  public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {\n    context.setAttribute(EXECUTION_COUNT_ATTRIBUTE, executionCount);\n    return super.retryRequest(exception, executionCount, context);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/BaseSqlInput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.mapSFExceptionToSQLException;\n\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.RowId;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLXML;\nimport java.sql.Timestamp;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\n\npublic abstract class BaseSqlInput implements SFSqlInput {\n\n  protected final SFBaseSession session;\n  protected final Converters converters;\n  protected final List<FieldMetadata> fields;\n\n  protected BaseSqlInput(SFBaseSession session, Converters converters, List<FieldMetadata> fields) {\n    this.session = session;\n    this.converters = converters;\n    this.fields = fields;\n  }\n\n  @Override\n  public Timestamp readTimestamp() throws SQLException {\n    return readTimestamp(null);\n  }\n\n  @Override\n  public Reader readCharacterStream() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readCharacterStream\");\n  }\n\n  @Override\n  public InputStream readAsciiStream() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readAsciiStream\");\n  }\n\n  @Override\n  public InputStream readBinaryStream() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readBinaryStream\");\n  }\n\n  @Override\n  public Ref readRef() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readRef\");\n  }\n\n  @Override\n  public Blob readBlob() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readBlob\");\n  }\n\n  @Override\n  public Clob readClob() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readClob\");\n  }\n\n  @Override\n  public Array readArray() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readArray\");\n  }\n\n  @Override\n  public boolean wasNull() throws SQLException {\n    return false; // nulls are not allowed in structure types\n  }\n\n  @Override\n  public URL readURL() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readCharacterStream\");\n  }\n\n  @Override\n  public NClob readNClob() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readNClob\");\n  }\n\n  @Override\n  public String readNString() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readNString\");\n  }\n\n  @Override\n  public SQLXML readSQLXML() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readSQLXML\");\n  }\n\n  @Override\n  public RowId readRowId() throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session, \"readRowId\");\n  }\n\n  abstract Map<String, Object> convertSqlInputToMap(SQLInput sqlInput);\n\n  protected String convertString(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    int columnSubType = fieldMetadata.getType();\n    int scale = fieldMetadata.getScale();\n    return mapSFExceptionToSQLException(\n        () -> converters.getStringConverter().getString(value, columnType, columnSubType, scale));\n  }\n\n  protected Boolean convertBoolean(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getBooleanConverter().getBoolean(value, columnType));\n  }\n\n  protected Short convertShort(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getNumberConverter().getShort(value, columnType));\n  }\n\n  protected Integer convertInt(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getNumberConverter().getInt(value, columnType));\n  }\n\n  protected Long convertLong(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getNumberConverter().getLong(value, columnType));\n  }\n\n  protected Float convertFloat(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getNumberConverter().getFloat(value, columnType));\n  }\n\n  protected Double convertDouble(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getNumberConverter().getDouble(value, columnType));\n  }\n\n  protected BigDecimal convertBigDecimal(Object value, FieldMetadata fieldMetadata)\n      throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    return mapSFExceptionToSQLException(\n        () -> converters.getNumberConverter().getBigDecimal(value, columnType));\n  }\n\n  protected byte[] convertBytes(Object value, FieldMetadata fieldMetadata) throws SQLException {\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    int columnSubType = fieldMetadata.getType();\n    int scale = fieldMetadata.getScale();\n    return mapSFExceptionToSQLException(\n        () -> converters.getBytesConverter().getBytes(value, columnType, columnSubType, scale));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/BasicEvent.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\n/** Base Event class for events that don't need to deviate from the default flush behavior. */\npublic class BasicEvent extends Event {\n  // Format strings for query state transitions\n  private static final String requestId = \"requestId: %s\";\n  private static final String numPings = \"numberPings: %d\";\n  private static final String jobId = \"jobId: %s\";\n  private static final String chunkIdx = \"chunkIndex: %d\";\n\n  private static final String EVENT_DUMP_PROP = \"snowflake.dump_events\";\n  private static final Boolean doDump = systemGetProperty(EVENT_DUMP_PROP) != null;\n\n  public enum QueryState {\n    QUERY_STARTED(1, \"Query Started\", \"{\" + requestId + \"}\"),\n    SENDING_QUERY(2, \"Sending Query\", \"{\" + requestId + \"}\"),\n    WAITING_FOR_RESULT(3, \"Waiting for Result\", \"{\" + requestId + \",\" + numPings + \"}\"),\n    PROCESSING_RESULT(4, \"Processing Result\", \"{\" + requestId + \"}\"),\n    CONSUMING_RESULT(5, \"Consuming Result\", \"{\" + jobId + \",\" + chunkIdx + \"}\"),\n    QUERY_ENDED(6, \"Query ended\", \"{\" + requestId + \"}\"),\n    GETTING_FILES(8, \"Getting Files\", \"{\" + requestId + \"}\"),\n    PUTTING_FILES(9, \"Putting Files\", \"{\" + requestId + \"}\"),\n    ;\n\n    QueryState(int id, String description, String argString) {\n      this.id = id;\n      this.description = description;\n      this.argString = argString;\n    }\n\n    public int getId() {\n      return id;\n    }\n\n    public String getDescription() {\n      return description;\n    }\n\n    public String getArgString() {\n      return argString;\n    }\n\n    private final int id;\n    private final String description;\n    private final String argString;\n  }\n\n  public BasicEvent(Event.EventType type, String message) {\n    super(type, message);\n  }\n\n  @Override\n  public void flush() {\n    if (doDump) {\n      // this.writeEventDumpLine(\"Event: \" + getType() + \"; Message: \" + getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/CachedCredentialType.java",
    "content": "package net.snowflake.client.internal.core;\n\nenum CachedCredentialType {\n  ID_TOKEN(\"ID_TOKEN\"),\n  MFA_TOKEN(\"MFATOKEN\"),\n  OAUTH_ACCESS_TOKEN(\"OAUTH_ACCESS_TOKEN\"),\n  OAUTH_REFRESH_TOKEN(\"OAUTH_REFRESH_TOKEN\"),\n  DPOP_BUNDLED_ACCESS_TOKEN(\n      \"DPOP_BUNDLED_ACCESS_TOKEN\"); // contains '.' separated, base64 encoded access token and DPoP\n  // public key\n\n  private final String value;\n\n  CachedCredentialType(String value) {\n    this.value = value;\n  }\n\n  String getValue() {\n    return value;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/CancellationReason.java",
    "content": "package net.snowflake.client.internal.core;\n\npublic enum CancellationReason {\n  UNKNOWN,\n  CLIENT_REQUESTED,\n  TIMEOUT\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/ChunkDownloader.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultChunk;\n\n/** Provide offline result chunk (which contains result data) to back to result set */\npublic interface ChunkDownloader {\n  /**\n   * Get next SnowflakeResultChunk that is ready to be consumed by the main thread. The caller will\n   * be blocked if the chunk is not ready to be consumed (a.k.a not loaded into memory yet)\n   *\n   * @return result chunk with data loaded\n   * @throws InterruptedException if downloading thread was interrupted\n   * @throws SnowflakeSQLException if downloader encountered an error\n   */\n  SnowflakeResultChunk getNextChunkToConsume() throws InterruptedException, SnowflakeSQLException;\n\n  /**\n   * Terminate the chunk downloader, release all resources allocated\n   *\n   * @return metrics measuring downloader performance\n   * @throws InterruptedException if error encountered\n   */\n  DownloaderMetrics terminate() throws InterruptedException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/ColumnTypeHelper.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.Types;\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\npublic class ColumnTypeHelper {\n  public static int getColumnType(int internalColumnType, SFBaseSession session) {\n    int externalColumnType = internalColumnType;\n\n    if (internalColumnType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ) {\n      externalColumnType = Types.TIMESTAMP;\n    } else if (internalColumnType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ) {\n      externalColumnType =\n          session == null\n              ? Types.TIMESTAMP_WITH_TIMEZONE\n              : session.getEnableReturnTimestampWithTimeZone()\n                  ? Types.TIMESTAMP_WITH_TIMEZONE\n                  : Types.TIMESTAMP;\n    }\n    return externalColumnType;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/Constants.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.util.Optional;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\n/*\n * Constants used in JDBC implementation\n */\npublic final class Constants {\n  // Session expired error code as returned from Snowflake\n  public static final int SESSION_EXPIRED_GS_CODE = 390112;\n\n  // Cloud storage credentials expired error code\n  public static final int CLOUD_STORAGE_CREDENTIALS_EXPIRED = 240001;\n\n  // Session gone error code as returned from Snowflake\n  public static final int SESSION_GONE = 390111;\n\n  // Error code for all invalid id token cases during login request\n  public static final int ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE = 390195;\n\n  public static final int OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE = 390318;\n\n  public static final int OAUTH_ACCESS_TOKEN_INVALID_GS_CODE = 390303;\n\n  // Error message for IOException when no space is left for GET\n  public static final String NO_SPACE_LEFT_ON_DEVICE_ERR = \"No space left on device\";\n\n  public enum OS {\n    WINDOWS,\n    LINUX,\n    MAC,\n    SOLARIS,\n    UNKNOWN;\n\n    /**\n     * Returns true if this OS supports POSIX file APIs (e.g., PosixFilePermissions). UNKNOWN\n     * returns false to fail safely when OS cannot be determined.\n     */\n    public boolean isPosix() {\n      return this == LINUX || this == MAC || this == SOLARIS;\n    }\n  }\n\n  public enum Architecture {\n    X86_64(\"x86_64\"),\n    AARCH64(\"aarch64\"),\n    PPC64(\"ppc64\"),\n    X86(\"x86\"),\n    UNKNOWN(\"unknown\");\n\n    private final String identifier;\n\n    Architecture(String identifier) {\n      this.identifier = identifier;\n    }\n\n    public String getIdentifier() {\n      return identifier;\n    }\n  }\n\n  private static OS os = null;\n  private static Architecture architecture = null;\n  private static Boolean isAix = null;\n\n  public static synchronized OS getOS() {\n    if (os == null) {\n      String operSys =\n          Optional.ofNullable(systemGetProperty(\"os.name\")).map(String::toLowerCase).orElse(\"\");\n      if (operSys.contains(\"win\")) {\n        os = OS.WINDOWS;\n      } else if (operSys.contains(\"nix\") || operSys.contains(\"nux\") || operSys.contains(\"aix\")) {\n        os = OS.LINUX;\n      } else if (operSys.contains(\"mac\")) {\n        os = OS.MAC;\n      } else if (operSys.contains(\"sunos\")) {\n        os = OS.SOLARIS;\n      } else {\n        os = OS.UNKNOWN;\n      }\n    }\n    return os;\n  }\n\n  public static synchronized Architecture getArchitecture() {\n    if (architecture == null) {\n      architecture = Architecture.UNKNOWN;\n      String osArch = systemGetProperty(\"os.arch\");\n      if (!SnowflakeUtil.isNullOrEmpty(osArch)) {\n        osArch = osArch.toLowerCase();\n        if (osArch.contains(\"amd64\") || osArch.contains(\"x86_64\")) {\n          architecture = Architecture.X86_64;\n        } else if (osArch.contains(\"aarch64\") || osArch.contains(\"arm64\")) {\n          architecture = Architecture.AARCH64;\n        } else if (osArch.contains(\"ppc64\")) {\n          architecture = Architecture.PPC64;\n        } else if (osArch.contains(\"x86\") || osArch.contains(\"i386\") || osArch.contains(\"i686\")) {\n          architecture = Architecture.X86;\n        }\n      }\n    }\n    return architecture;\n  }\n\n  public static boolean isAix() {\n    if (isAix == null) {\n      String osName = systemGetProperty(\"os.name\");\n      isAix = osName != null && osName.toLowerCase().contains(\"aix\");\n    }\n    return isAix;\n  }\n\n  public static void clearOSForTesting() {\n    os = null;\n    architecture = null;\n  }\n\n  public static final int MB = 1024 * 1024;\n  public static final long GB = 1024 * 1024 * 1024;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/CredentialManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class CredentialManager {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CredentialManager.class);\n\n  private SecureStorageManager secureStorageManager;\n\n  private CredentialManager() {\n    initSecureStorageManager();\n  }\n\n  private void initSecureStorageManager() {\n    try {\n      if (Constants.getOS() == Constants.OS.MAC) {\n        secureStorageManager = SecureStorageAppleManager.builder();\n      } else if (Constants.getOS() == Constants.OS.WINDOWS) {\n        secureStorageManager = SecureStorageWindowsManager.builder();\n      } else if (Constants.getOS() == Constants.OS.LINUX) {\n        secureStorageManager = SecureStorageLinuxManager.getInstance();\n      } else {\n        logger.error(\"Unsupported Operating System. Expected: OSX, Windows, Linux\", false);\n      }\n    } catch (NoClassDefFoundError error) {\n      logMissingJnaJarForSecureLocalStorage();\n    }\n  }\n\n  /** Helper function for tests to go back to normal settings. */\n  static void resetSecureStorageManager() {\n    logger.debug(\"Resetting the secure storage manager\");\n    getInstance().initSecureStorageManager();\n  }\n\n  /**\n   * Testing purpose. Inject a mock manager.\n   *\n   * @param manager SecureStorageManager\n   */\n  static void injectSecureStorageManager(SecureStorageManager manager) {\n    logger.debug(\"Injecting secure storage manager\");\n    getInstance().secureStorageManager = manager;\n  }\n\n  private static class CredentialManagerHolder {\n    private static final CredentialManager INSTANCE = new CredentialManager();\n  }\n\n  public static CredentialManager getInstance() {\n    return CredentialManagerHolder.INSTANCE;\n  }\n\n  /**\n   * Reuse the cached id token stored locally\n   *\n   * @param loginInput login input to attach id token\n   */\n  static void fillCachedIdToken(SFLoginInput loginInput) throws SFException {\n    logger.debug(\n        \"Looking for cached id token for user: {}, host: {}\",\n        loginInput.getUserName(),\n        loginInput.getHostFromServerUrl());\n    getInstance()\n        .fillCachedCredential(\n            loginInput,\n            loginInput.getHostFromServerUrl(),\n            loginInput.getUserName(),\n            CachedCredentialType.ID_TOKEN);\n  }\n\n  /**\n   * Reuse the cached mfa token stored locally\n   *\n   * @param loginInput login input to attach mfa token\n   */\n  static void fillCachedMfaToken(SFLoginInput loginInput) throws SFException {\n    logger.debug(\n        \"Looking for cached mfa token for user: {}, host: {}\",\n        loginInput.getUserName(),\n        loginInput.getHostFromServerUrl());\n    getInstance()\n        .fillCachedCredential(\n            loginInput,\n            loginInput.getHostFromServerUrl(),\n            loginInput.getUserName(),\n            CachedCredentialType.MFA_TOKEN);\n  }\n\n  /**\n   * Reuse the cached OAuth access token stored locally\n   *\n   * @param loginInput login input to attach access token\n   */\n  static void fillCachedOAuthAccessToken(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    logger.debug(\n        \"Looking for cached OAuth access token for user: {}, host: {}\",\n        loginInput.getUserName(),\n        host);\n    getInstance()\n        .fillCachedCredential(\n            loginInput, host, loginInput.getUserName(), CachedCredentialType.OAUTH_ACCESS_TOKEN);\n  }\n\n  /**\n   * Reuse the cached OAuth refresh token stored locally\n   *\n   * @param loginInput login input to attach refresh token\n   */\n  static void fillCachedOAuthRefreshToken(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    logger.debug(\n        \"Looking for cached OAuth refresh token for user: {}, host: {}\",\n        loginInput.getUserName(),\n        host);\n    getInstance()\n        .fillCachedCredential(\n            loginInput, host, loginInput.getUserName(), CachedCredentialType.OAUTH_REFRESH_TOKEN);\n  }\n\n  /**\n   * Reuse the cached OAuth access token & DPoP public key tied to it\n   *\n   * @param loginInput login input to attach refresh token\n   */\n  static void fillCachedDPoPBundledAccessToken(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    logger.debug(\n        \"Looking for cached DPoP public key for user: {}, host: {}\",\n        loginInput.getUserName(),\n        host);\n    getInstance()\n        .fillCachedCredential(\n            loginInput,\n            host,\n            loginInput.getUserName(),\n            CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN);\n  }\n\n  /** Reuse the cached token stored locally */\n  synchronized void fillCachedCredential(\n      SFLoginInput loginInput, String host, String username, CachedCredentialType credType)\n      throws SFException {\n    if (isNullOrEmpty(username)) {\n      logger.debug(\"Missing username; Cannot read from credential cache\");\n      return;\n    }\n    if (secureStorageManager == null) {\n      logMissingJnaJarForSecureLocalStorage();\n      return;\n    }\n\n    String base64EncodedCred, cred = null;\n    try {\n      base64EncodedCred = secureStorageManager.getCredential(host, username, credType.getValue());\n    } catch (NoClassDefFoundError error) {\n      logMissingJnaJarForSecureLocalStorage();\n      return;\n    }\n\n    if (base64EncodedCred == null) {\n      logger.debug(\"Retrieved {} is null\", credType);\n    }\n\n    logger.debug(\n        \"Setting {}{} token for user: {}, host: {}\",\n        base64EncodedCred == null ? \"null \" : \"\",\n        credType.getValue(),\n        username,\n        host);\n\n    if (base64EncodedCred != null && credType != CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN) {\n      try {\n        cred = new String(Base64.getDecoder().decode(base64EncodedCred));\n      } catch (Exception e) {\n        // handle legacy non-base64 encoded cache values (CredentialManager fails to decode)\n        deleteTemporaryCredential(host, username, credType);\n        return;\n      }\n    }\n    switch (credType) {\n      case ID_TOKEN:\n        loginInput.setIdToken(cred);\n        break;\n      case MFA_TOKEN:\n        loginInput.setMfaToken(cred);\n        break;\n      case OAUTH_ACCESS_TOKEN:\n        loginInput.setOauthAccessToken(cred);\n        break;\n      case OAUTH_REFRESH_TOKEN:\n        loginInput.setOauthRefreshToken(cred);\n        break;\n      case DPOP_BUNDLED_ACCESS_TOKEN:\n        updateInputWithTokenAndPublicKey(base64EncodedCred, loginInput);\n        break;\n      default:\n        throw new SFException(\n            ErrorCode.INTERNAL_ERROR, \"Unrecognized type {} for local cached credential\", credType);\n    }\n  }\n\n  private void updateInputWithTokenAndPublicKey(String cred, SFLoginInput loginInput)\n      throws SFException {\n    if (isNullOrEmpty(cred)) {\n      String[] values = cred.split(\"\\\\.\");\n      if (values.length != 2) {\n        throw new SFException(\n            ErrorCode.INTERNAL_ERROR, \"Invalid DPoP bundled access token credential format\");\n      }\n      Base64.Decoder decoder = Base64.getDecoder();\n      loginInput.setOauthAccessToken(new String(decoder.decode(values[0])));\n      loginInput.setDPoPPublicKey(new String(decoder.decode(values[1])));\n    }\n  }\n\n  static void writeIdToken(SFLoginInput loginInput, String idToken) throws SFException {\n    logger.debug(\n        \"Caching id token in a secure storage for user: {}, host: {}\",\n        loginInput.getUserName(),\n        loginInput.getHostFromServerUrl());\n    getInstance()\n        .writeTemporaryCredential(\n            loginInput.getHostFromServerUrl(),\n            loginInput.getUserName(),\n            idToken,\n            CachedCredentialType.ID_TOKEN);\n  }\n\n  static void writeMfaToken(SFLoginInput loginInput, String mfaToken) throws SFException {\n    logger.debug(\n        \"Caching mfa token in a secure storage for user: {}, host: {}\",\n        loginInput.getUserName(),\n        loginInput.getHostFromServerUrl());\n    getInstance()\n        .writeTemporaryCredential(\n            loginInput.getHostFromServerUrl(),\n            loginInput.getUserName(),\n            mfaToken,\n            CachedCredentialType.MFA_TOKEN);\n  }\n\n  /**\n   * Store OAuth Access Token\n   *\n   * @param loginInput loginInput to denote to the cache\n   */\n  static void writeOAuthAccessToken(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    logger.debug(\n        \"Caching OAuth access token in a secure storage for user: {}, host: {}\",\n        loginInput.getUserName(),\n        host);\n    getInstance()\n        .writeTemporaryCredential(\n            host,\n            loginInput.getUserName(),\n            loginInput.getOauthAccessToken(),\n            CachedCredentialType.OAUTH_ACCESS_TOKEN);\n  }\n\n  /**\n   * Store OAuth Refresh Token\n   *\n   * @param loginInput loginInput to denote to the cache\n   */\n  static void writeOAuthRefreshToken(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    logger.debug(\n        \"Caching OAuth refresh token in a secure storage for user: {}, host: {}\",\n        loginInput.getUserName(),\n        host);\n    getInstance()\n        .writeTemporaryCredential(\n            host,\n            loginInput.getUserName(),\n            loginInput.getOauthRefreshToken(),\n            CachedCredentialType.OAUTH_REFRESH_TOKEN);\n  }\n\n  /**\n   * Store OAuth DPoP Public Key With Token\n   *\n   * @param loginInput loginInput to denote to the cache\n   */\n  static void writeDPoPBundledAccessToken(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    logger.debug(\n        \"Caching DPoP public key in a secure storage for user: {}, host: {}\",\n        loginInput.getUserName(),\n        host);\n    Base64.Encoder encoder = Base64.getEncoder();\n    String tokenBase64 =\n        encoder.encodeToString(loginInput.getOauthAccessToken().getBytes(StandardCharsets.UTF_8));\n    String publicKeyBase64 =\n        encoder.encodeToString(loginInput.getDPoPPublicKey().getBytes(StandardCharsets.UTF_8));\n    getInstance()\n        .writeTemporaryCredential(\n            host,\n            loginInput.getUserName(),\n            tokenBase64 + \".\" + publicKeyBase64,\n            CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN);\n  }\n\n  /** Store the temporary credential */\n  synchronized void writeTemporaryCredential(\n      String host, String user, String cred, CachedCredentialType credType) {\n    if (isNullOrEmpty(user)) {\n      logger.debug(\"Missing username; Cannot write to credential cache\");\n      return;\n    }\n    if (isNullOrEmpty(cred)) {\n      logger.debug(\"No {} is given.\", credType);\n      return; // no credential\n    }\n\n    if (secureStorageManager == null) {\n      logMissingJnaJarForSecureLocalStorage();\n      return;\n    }\n\n    try {\n      if (credType == CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN) {\n        // DPOP_ACCESS_TOKEN is already preformatted and Base64 encoded\n        secureStorageManager.setCredential(host, user, credType.getValue(), cred);\n      } else {\n        String base64EncodedCred =\n            Base64.getEncoder().encodeToString(cred.getBytes(StandardCharsets.UTF_8));\n        secureStorageManager.setCredential(host, user, credType.getValue(), base64EncodedCred);\n      }\n    } catch (NoClassDefFoundError error) {\n      logMissingJnaJarForSecureLocalStorage();\n    }\n  }\n\n  /** Delete the id token cache */\n  static void deleteIdTokenCacheEntry(String host, String user) {\n    logger.debug(\n        \"Removing cached id token from a secure storage for user: {}, host: {}\", user, host);\n    getInstance().deleteTemporaryCredential(host, user, CachedCredentialType.ID_TOKEN);\n  }\n\n  /** Delete the mfa token cache */\n  static void deleteMfaTokenCacheEntry(String host, String user) {\n    logger.debug(\n        \"Removing cached mfa token from a secure storage for user: {}, host: {}\", user, host);\n    getInstance().deleteTemporaryCredential(host, user, CachedCredentialType.MFA_TOKEN);\n  }\n\n  /** Delete the Oauth access token cache */\n  static void deleteOAuthAccessTokenCacheEntry(String host, String user) {\n    logger.debug(\n        \"Removing cached oauth access token from a secure storage for user: {}, host: {}\",\n        user,\n        host);\n    getInstance().deleteTemporaryCredential(host, user, CachedCredentialType.OAUTH_ACCESS_TOKEN);\n  }\n\n  /** Delete the Oauth refresh token cache */\n  static void deleteOAuthRefreshTokenCacheEntry(String host, String user) {\n    logger.debug(\n        \"Removing cached OAuth refresh token from a secure storage for user: {}, host: {}\",\n        user,\n        host);\n    getInstance().deleteTemporaryCredential(host, user, CachedCredentialType.OAUTH_REFRESH_TOKEN);\n  }\n\n  /** Delete the DPoP bundled access token cache */\n  static void deleteDPoPBundledAccessTokenCacheEntry(String host, String user) {\n    logger.debug(\n        \"Removing cached DPoP public key from a secure storage for user: {}, host: {}\", user, host);\n    getInstance()\n        .deleteTemporaryCredential(host, user, CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN);\n  }\n\n  /** Delete the OAuth access token cache */\n  static void deleteOAuthAccessTokenCacheEntry(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    deleteOAuthAccessTokenCacheEntry(host, loginInput.getUserName());\n  }\n\n  /** Delete the OAuth refresh token cache */\n  static void deleteOAuthRefreshTokenCacheEntry(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    deleteOAuthRefreshTokenCacheEntry(host, loginInput.getUserName());\n  }\n\n  /** Delete the DPoP bundled access token cache */\n  static void deleteDPoPBundledAccessTokenCacheEntry(SFLoginInput loginInput) throws SFException {\n    String host = getHostForOAuthCacheKey(loginInput);\n    deleteDPoPBundledAccessTokenCacheEntry(host, loginInput.getUserName());\n  }\n\n  /**\n   * Method required for OAuth token caching, since actual token is not Snowflake account-specific,\n   * but rather IdP-specific\n   */\n  static String getHostForOAuthCacheKey(SFLoginInput loginInput) throws SFException {\n    String oauthTokenRequestUrl = loginInput.getOauthLoginInput().getTokenRequestUrl();\n    if (oauthTokenRequestUrl != null) {\n      URI parsedUrl = URI.create(oauthTokenRequestUrl);\n      return parsedUrl.getHost();\n    } else {\n      return loginInput.getHostFromServerUrl();\n    }\n  }\n\n  /**\n   * Delete the temporary credential\n   *\n   * @param host host name\n   * @param user user name\n   * @param credType type of the credential\n   */\n  synchronized void deleteTemporaryCredential(\n      String host, String user, CachedCredentialType credType) {\n    if (secureStorageManager == null) {\n      logMissingJnaJarForSecureLocalStorage();\n      return;\n    }\n    if (isNullOrEmpty(user)) {\n      logger.debug(\"Missing username; Cannot delete from credential cache\");\n      return;\n    }\n\n    try {\n      secureStorageManager.deleteCredential(host, user, credType.getValue());\n    } catch (NoClassDefFoundError error) {\n      logMissingJnaJarForSecureLocalStorage();\n    }\n  }\n\n  private static void logMissingJnaJarForSecureLocalStorage() {\n    logger.warn(\n        \"JNA jar files are needed for Secure Local Storage service. Please follow the Snowflake JDBC instruction for Secure Local Storage feature. Fall back to normal process.\",\n        false);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/DataConversionContext.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.TimeZone;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\n/**\n * This class contains formatter info about each data type and related flags etc. And it is scoped\n * to a single result set. a.k.a each result set object should have its own formatter info\n */\npublic interface DataConversionContext {\n  /**\n   * @return timestamp_ltz formatter\n   */\n  SnowflakeDateTimeFormat getTimestampLTZFormatter();\n\n  /**\n   * @return timestamp_ntz formatter\n   */\n  SnowflakeDateTimeFormat getTimestampNTZFormatter();\n\n  /**\n   * @return timestamp_ntz formatter\n   */\n  SnowflakeDateTimeFormat getTimestampTZFormatter();\n\n  /**\n   * @return date formatter\n   */\n  SnowflakeDateTimeFormat getDateFormatter();\n\n  /**\n   * @return time formatter\n   */\n  SnowflakeDateTimeFormat getTimeFormatter();\n\n  /**\n   * @return binary formatter\n   */\n  SFBinaryFormat getBinaryFormatter();\n\n  /**\n   * get scale from Snowflake metadata\n   *\n   * @param columnIndex column index\n   * @return scale value\n   */\n  int getScale(int columnIndex);\n\n  /**\n   * @return current session\n   */\n  SFBaseSession getSession();\n\n  /**\n   * @return session time zone\n   */\n  TimeZone getTimeZone();\n\n  /**\n   * @return whether to honor client time zone for timestamp_ntz\n   */\n  boolean getHonorClientTZForTimestampNTZ();\n\n  /**\n   * @return result version\n   */\n  long getResultVersion();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/DefaultFileCacheManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStreamWriter;\nimport java.io.Reader;\nimport java.io.Writer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.Date;\nimport java.util.function.Supplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass DefaultFileCacheManager implements FileCacheManager {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(DefaultFileCacheManager.class);\n\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n  private static final Charset DEFAULT_FILE_ENCODING = StandardCharsets.UTF_8;\n\n  private File cacheFile;\n  private File cacheLockFile;\n  private String baseCacheFileName;\n  private final boolean onlyOwnerPermissions;\n  private final long cacheFileLockExpirationInMilliseconds;\n\n  DefaultFileCacheManager(\n      File cacheFile,\n      File cacheLockFile,\n      String baseCacheFileName,\n      boolean onlyOwnerPermissions,\n      long cacheFileLockExpirationInMilliseconds) {\n    this.cacheFile = cacheFile;\n    this.cacheLockFile = cacheLockFile;\n    this.baseCacheFileName = baseCacheFileName;\n    this.onlyOwnerPermissions = onlyOwnerPermissions;\n    this.cacheFileLockExpirationInMilliseconds = cacheFileLockExpirationInMilliseconds;\n    logger.debug(\"Using cache file: {}\", cacheFile.getAbsolutePath());\n  }\n\n  @Override\n  public synchronized String getCacheFilePath() {\n    return cacheFile.getAbsolutePath();\n  }\n\n  @Override\n  public synchronized void overrideCacheFile(File newCacheFile) {\n    if (!FileUtil.exists(newCacheFile)) {\n      logger.debug(\"Cache file doesn't exist. File: {}\", newCacheFile);\n    }\n    if (onlyOwnerPermissions) {\n      FileUtil.handleWhenFilePermissionsWiderThanUserOnly(newCacheFile, \"Override cache file\");\n      FileUtil.handleWhenParentDirectoryPermissionsWiderThanUserOnly(\n          newCacheFile, \"Override cache file\");\n    } else {\n      FileUtil.logFileUsage(newCacheFile, \"Override cache file\", false);\n    }\n    this.cacheFile = newCacheFile;\n    this.baseCacheFileName = newCacheFile.getName();\n    this.cacheLockFile = new File(newCacheFile.getParentFile(), this.baseCacheFileName + \".lck\");\n  }\n\n  @Override\n  public synchronized <T> T withLock(Supplier<T> supplier) {\n    if (cacheLockFile.exists()) {\n      deleteCacheLockIfExpired();\n    }\n\n    if (!tryToLockCacheFile()) {\n      logger.debug(\"Failed to lock the file. Skipping cache operation\", false);\n      return null;\n    }\n    try {\n      return supplier.get();\n    } finally {\n      if (!unlockCacheFile()) {\n        logger.debug(\"Failed to unlock cache file\", false);\n      }\n    }\n  }\n\n  @Override\n  public synchronized JsonNode readCacheFile() {\n    try {\n      if (!cacheFile.exists()) {\n        logger.debug(\"Cache file doesn't exist. Ignoring read. File: {}\", cacheFile);\n        return null;\n      }\n\n      try (Reader reader =\n          new InputStreamReader(new FileInputStream(cacheFile), DEFAULT_FILE_ENCODING)) {\n\n        if (onlyOwnerPermissions) {\n          FileUtil.handleWhenFilePermissionsWiderThanUserOnly(cacheFile, \"Read cache\");\n          FileUtil.handleWhenParentDirectoryPermissionsWiderThanUserOnly(cacheFile, \"Read cache\");\n          FileUtil.throwWhenOwnerDifferentThanCurrentUser(cacheFile, \"Read cache\");\n        } else {\n          FileUtil.logFileUsage(cacheFile, \"Read cache\", false);\n        }\n        return OBJECT_MAPPER.readTree(reader);\n      }\n    } catch (IOException ex) {\n      logger.debug(\"Failed to read the cache file. No worry. File: {}, Err: {}\", cacheFile, ex);\n    }\n    return null;\n  }\n\n  @Override\n  public synchronized void writeCacheFile(JsonNode input) {\n    logger.debug(\"Writing cache file. File: {}\", cacheFile);\n    try {\n      if (input == null || !cacheFile.exists()) {\n        logger.debug(\n            \"Cache file doesn't exist or input is null. Ignoring write. File: {}\", cacheFile);\n        return;\n      }\n      try (Writer writer =\n          new OutputStreamWriter(new FileOutputStream(cacheFile), DEFAULT_FILE_ENCODING)) {\n        if (onlyOwnerPermissions) {\n          FileUtil.handleWhenFilePermissionsWiderThanUserOnly(cacheFile, \"Write to cache\");\n          FileUtil.handleWhenParentDirectoryPermissionsWiderThanUserOnly(\n              cacheFile, \"Write to cache\");\n        } else {\n          FileUtil.logFileUsage(cacheFile, \"Write to cache\", false);\n        }\n        writer.write(input.toString());\n      }\n    } catch (IOException ex) {\n      logger.debug(\"Failed to write the cache file. File: {}\", cacheFile);\n    }\n  }\n\n  @Override\n  public synchronized void deleteCacheFile() {\n    logger.debug(\"Deleting cache file. File: {}, lock file: {}\", cacheFile, cacheLockFile);\n    unlockCacheFile();\n    if (!cacheFile.delete()) {\n      logger.debug(\"Failed to delete the file: {}\", cacheFile);\n    }\n  }\n\n  private synchronized boolean tryToLockCacheFile() {\n    int cnt = 0;\n    boolean locked = false;\n    while (cnt < 5 && !(locked = lockCacheFile())) {\n      try {\n        Thread.sleep(10);\n      } catch (InterruptedException ex) {\n        // doesn't matter\n      }\n      ++cnt;\n    }\n    if (!locked) {\n      deleteCacheLockIfExpired();\n      if (!lockCacheFile()) {\n        logger.debug(\"Failed to lock the cache file.\", false);\n      }\n    }\n    return locked;\n  }\n\n  private synchronized void deleteCacheLockIfExpired() {\n    long currentTime = new Date().getTime();\n    long lockFileTs = fileCreationTime(cacheLockFile);\n    if (lockFileTs < 0) {\n      logger.debug(\"Failed to get the timestamp of lock directory\");\n    } else if (lockFileTs < currentTime - this.cacheFileLockExpirationInMilliseconds) {\n      try {\n        if (!cacheLockFile.delete()) {\n          logger.debug(\"Failed to delete the directory. Dir: {}\", cacheLockFile);\n        } else {\n          logger.debug(\"Deleted expired cache lock directory.\", false);\n        }\n      } catch (Exception e) {\n        logger.debug(\n            \"Failed to delete the directory. Dir: {}, Error: {}\", cacheLockFile, e.getMessage());\n      }\n    }\n  }\n\n  private static synchronized long fileCreationTime(File targetFile) {\n    if (!FileUtil.exists(targetFile)) {\n      logger.debug(\"File does not exist. File: {}\", targetFile);\n      return -1;\n    }\n    try {\n      Path cacheFileLockPath = Paths.get(targetFile.getAbsolutePath());\n      BasicFileAttributes attr = Files.readAttributes(cacheFileLockPath, BasicFileAttributes.class);\n      return attr.creationTime().toMillis();\n    } catch (IOException ex) {\n      logger.debug(\"Failed to get creation time. File/Dir: {}, Err: {}\", targetFile, ex);\n    }\n    return -1;\n  }\n\n  private synchronized boolean lockCacheFile() {\n    return cacheLockFile.mkdirs();\n  }\n\n  private synchronized boolean unlockCacheFile() {\n    return cacheLockFile.delete();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/DownloaderMetrics.java",
    "content": "package net.snowflake.client.internal.core;\n\n/** Metrics related to chunk downloader performance */\npublic class DownloaderMetrics {\n  /** time in millis that main thread is blocked and waits for chunk is ready */\n  private final long millisWaiting;\n\n  /** time in millis that background thread is downloading the data */\n  private final long millisDownloading;\n\n  /** time in millis that background thread is parsing data */\n  private final long millisParsing;\n\n  public DownloaderMetrics(long millisWaiting, long millisDownloading, long millisParsing) {\n    this.millisWaiting = millisWaiting;\n    this.millisDownloading = millisDownloading;\n    this.millisParsing = millisParsing;\n  }\n\n  long getMillisWaiting() {\n    return millisWaiting;\n  }\n\n  long getMillisDownloading() {\n    return millisDownloading;\n  }\n\n  long getMillisParsing() {\n    return millisParsing;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/Event.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.google.common.base.Preconditions;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.util.zip.GZIPOutputStream;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Abstract class to encapsulate a Client-side Event and any methods associated with it. */\npublic abstract class Event {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(Event.class);\n\n  private static final String EVENT_DUMP_FILE_NAME = \"sf_event_\";\n  private static final String EVENT_DUMP_FILE_EXT = \".dmp.gz\";\n\n  // need to check if directory exists, if not try to create it\n  // need to check file size, see if it exceeds maximum (need parameter for this)\n\n  public static enum EventType {\n    NETWORK_ERROR(1, \"NETWORK ERROR\", BasicEvent.class),\n    STATE_TRANSITION(2, \"STATE TRANSITION\", BasicEvent.class),\n    NONE(100, \"NONE\", BasicEvent.class);\n\n    public int getId() {\n      return id;\n    }\n\n    public String getDescription() {\n      return description;\n    }\n\n    public Class<? extends Event> getEventClass() {\n      return eventClass;\n    }\n\n    EventType(int id, String description, Class<? extends Event> eventClass) {\n      this.id = id;\n      this.description = description;\n      this.eventClass = eventClass;\n    }\n\n    private final int id;\n    private final String description;\n    private final Class<? extends Event> eventClass;\n  }\n\n  private EventType type;\n  private String message;\n\n  public Event(EventType type, String message) {\n    Preconditions.checkArgument(type.getEventClass() == this.getClass());\n\n    this.type = type;\n    this.message = message;\n  }\n\n  public EventType getType() {\n    return this.type;\n  }\n\n  public void setType(EventType type) {\n    this.type = type;\n  }\n\n  public String getMessage() {\n    return this.message;\n  }\n\n  public void setMessage(String message) {\n    this.message = message;\n  }\n\n  protected void writeEventDumpLine(String message) {\n    final String eventDumpPath =\n        EventUtil.getDumpPathPrefix()\n            + \"/\"\n            + EVENT_DUMP_FILE_NAME\n            + EventUtil.getDumpFileId()\n            + EVENT_DUMP_FILE_EXT;\n\n    // If the event dump file is too large, truncate\n    if (new File(eventDumpPath).length() < EventUtil.getmaxDumpFileSizeBytes()) {\n      try {\n        final OutputStream outStream =\n            new GZIPOutputStream(new FileOutputStream(eventDumpPath, true));\n        PrintWriter eventDumper = new PrintWriter(outStream, true);\n        eventDumper.println(message);\n        eventDumper.flush();\n        eventDumper.close();\n      } catch (IOException ex) {\n        logger.error(\n            \"Could not open Event dump file {}, exception:{}\", eventDumpPath, ex.getMessage());\n      }\n    } else {\n      logger.error(\n          \"Failed to dump Event because dump file is \"\n              + \"too large. Delete dump file or increase maximum dump file size.\",\n          false);\n    }\n  }\n\n  /*\n   * An event is \"flushed\" when it is leaving the system, hence the behavior\n   * defined by flush dictates what to do with an Event when the Handler is\n   * finished buffering it.\n   */\n  public abstract void flush();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/EventHandler.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.Date;\nimport java.util.TimeZone;\nimport java.util.TreeSet;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.logging.Formatter;\nimport java.util.logging.Handler;\nimport java.util.logging.LogRecord;\nimport java.util.zip.GZIPOutputStream;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class EventHandler extends Handler {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(EventHandler.class);\n\n  // Number of entries in the log buffer in memory\n  protected static final long LOG_BUFFER_SIZE = (1L << 14);\n\n  // Maximum amount of time a Snowflake dump file can exist before being\n  // delete upon the next attempt to dump (1 week)\n  protected static final long FILE_EXPN_TIME_MS = 7L * 24L * 3600L * 1000L;\n\n  // Dump file properties\n  protected static final String LOG_DUMP_FILE_NAME = \"sf_log_\";\n  protected static final String LOG_DUMP_FILE_EXT = \".dmp\";\n  protected static final String LOG_DUMP_COMP_EXT = \".gz\";\n\n  // Property to control time (in hours) an incident signature will be throttled for\n  protected static final String THROTTLE_DURATION_PROP = \"snowflake.throttle_duration\";\n\n  // Property to control number of times an incident signature can be seen\n  // before being throttled\n  protected static final String THROTTLE_LIMIT_PROP = \"snowflake.throttle_limit\";\n\n  // Property to disable dumps completely\n  protected static final String DISABLE_DUMPS_PROP = \"snowflake.disable_debug_dumps\";\n\n  // Property to set the number of allowable snowflake dump files\n  protected static final String MAX_NUM_DUMP_FILES_PROP = \"snowflake.max_dumpfiles\";\n\n  // Property to set the number of allowable snowflake dump files\n  protected static final String MAX_SIZE_DUMPS_MB_PROP = \"snowflake.max_dumpdir_size_mb\";\n\n  // Property to disable GZIP compression of log dump files\n  protected static final String DISABLE_DUMP_COMPR_PROP = \"snowflake.disable_log_compression\";\n\n  private static final int THROTTLE_DURATION_HRS =\n      systemGetProperty(THROTTLE_DURATION_PROP) == null\n          ? 1\n          : Integer.valueOf(systemGetProperty(THROTTLE_DURATION_PROP));\n\n  private static final int INCIDENT_THROTTLE_LIMIT_PER_HR =\n      systemGetProperty(THROTTLE_LIMIT_PROP) == null\n          ? 1\n          : Integer.valueOf(systemGetProperty(THROTTLE_LIMIT_PROP));\n\n  // Default values\n  private static final int DEFAULT_MAX_DUMP_FILES = 100;\n  private static final int DEFAULT_MAX_DUMPDIR_SIZE_MB = 128;\n\n  /** Runnable to handle periodic flushing of the event buffer */\n  private class QueueFlusher implements Runnable {\n    @Override\n    public void run() {\n      flushEventBuffer();\n    }\n  }\n\n  // Location to dump log entries to\n  private final String logDumpPathPrefix;\n\n  // Max size the event buffer can reach before being forcibly flushed\n  private final int maxEntries;\n\n  // Period of time (in ms) to wait before waking the QueueFlusher\n  private final int flushPeriodMs;\n\n  // Queue to buffer events while they are waiting to be flushed\n  private final ArrayList<Event> eventBuffer;\n\n  // Queue to buffer log messages\n  private final ArrayList<LogRecord> logBuffer;\n\n  // Executor to periodically flush the eventBuffer\n  private ScheduledExecutorService flusher;\n\n  public EventHandler(int maxEntries, int flushPeriodMs) {\n    this.maxEntries = maxEntries;\n    this.flushPeriodMs = flushPeriodMs;\n\n    eventBuffer = new ArrayList<>();\n    logBuffer = new ArrayList<>();\n\n    logDumpPathPrefix = EventUtil.getDumpPathPrefix();\n  }\n\n  /**\n   * Returns current size of the event buffer\n   *\n   * @return size of eventBuffer\n   */\n  public synchronized int getBufferSize() {\n    return eventBuffer.size();\n  }\n\n  /**\n   * Returns the current size of the log buffer\n   *\n   * @return size of log buffer\n   */\n  public synchronized long getLogBufferSize() {\n    return logBuffer.size();\n  }\n\n  /** Creates and runs a new QueueFlusher thread */\n  synchronized void startFlusher() {\n    // Create a new scheduled executor service with a thread factory that\n    // creates daemonized threads; this way if the user doesn't exit nicely\n    // the JVM Runtime won't hang\n    flusher =\n        Executors.newScheduledThreadPool(\n            1,\n            new ThreadFactory() {\n              @Override\n              public Thread newThread(Runnable r) {\n                Thread t = Executors.defaultThreadFactory().newThread(r);\n                t.setDaemon(true);\n                return t;\n              }\n            });\n\n    flusher.scheduleWithFixedDelay(new QueueFlusher(), 0, flushPeriodMs, TimeUnit.MILLISECONDS);\n  }\n\n  /** Stops the running QueueFlusher thread, if any */\n  synchronized void stopFlusher() {\n    if (flusher != null) {\n      flusher.shutdown();\n    }\n  }\n\n  /*\n   * Pushes an event onto the event buffer and flushes if specified or if\n   * the buffer has reached maximum capacity.\n   */\n  private synchronized void pushEvent(Event event, boolean flushBuffer) {\n    eventBuffer.add(event);\n\n    if (flushBuffer || eventBuffer.size() >= maxEntries) {\n      this.flushEventBuffer();\n    }\n  }\n\n  /**\n   * Triggers a new event of type @type with message @message and flushes the eventBuffer if full\n   *\n   * @param type event type\n   * @param message triggering message\n   */\n  void triggerBasicEvent(Event.EventType type, String message) {\n    triggerBasicEvent(type, message, false);\n  }\n\n  /**\n   * Triggers a new BaseEvent of type @type with message @message and flushes the eventBuffer if\n   * full or @flushBuffer is true\n   *\n   * @param type event type\n   * @param message trigger message\n   * @param flushBuffer true if push the event to flush buffer\n   */\n  void triggerBasicEvent(Event.EventType type, String message, boolean flushBuffer) {\n    Event triggeredEvent = new BasicEvent(type, message);\n\n    pushEvent(triggeredEvent, flushBuffer);\n  }\n\n  /**\n   * Triggers a state transition event to @newState with an identifier (eg, requestId, jobUUID, etc)\n   *\n   * @param newState new state\n   * @param identifier event id\n   */\n  void triggerStateTransition(BasicEvent.QueryState newState, String identifier) {\n    String msg =\n        \"{newState: \"\n            + newState.getDescription()\n            + \", \"\n            + \"info: \"\n            + identifier\n            + \", \"\n            + \"timestamp: \"\n            + getCurrentTimestamp()\n            + \"}\";\n\n    Event triggeredEvent = new BasicEvent(Event.EventType.STATE_TRANSITION, msg);\n\n    pushEvent(triggeredEvent, false);\n  }\n\n  static String getCurrentTimestamp() {\n    DateFormat fmt = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss'Z'\");\n    fmt.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n    return fmt.format(new Date());\n  }\n\n  /**\n   * Dumps the contents of the in-memory log buffer to disk and clears the buffer.\n   *\n   * @param identifier event id\n   */\n  public void dumpLogBuffer(String identifier) {\n    final ArrayList<LogRecord> logBufferCopy;\n    final PrintWriter logDumper;\n    final OutputStream outStream;\n    Formatter formatter = this.getFormatter();\n\n    // Check if compression of dump file is enabled\n    boolean disableCompression = systemGetProperty(DISABLE_DUMP_COMPR_PROP) != null;\n\n    // If no identifying factor (eg, an incident id) was provided, get one\n    if (identifier == null) {\n      identifier = EventUtil.getDumpFileId();\n    }\n\n    // Do some sanity checking to make sure we're not flooding the user's\n    // disk with dump files\n    cleanupSfDumps(true);\n\n    String logDumpPath =\n        logDumpPathPrefix + File.separator + LOG_DUMP_FILE_NAME + identifier + LOG_DUMP_FILE_EXT;\n\n    if (!disableCompression) {\n      logDumpPath += LOG_DUMP_COMP_EXT;\n    }\n\n    logger.debug(\"EventHandler dumping log buffer to {}\", logDumpPath);\n\n    // Copy logBuffer because this is potentially long running.\n    synchronized (this) {\n      logBufferCopy = new ArrayList<>(logBuffer);\n      logBuffer.clear();\n    }\n\n    File outputFile = new File(logDumpPath);\n\n    /*\n     * Because log files could potentially be very large, we should never open\n     * them in append mode. It's rare that this should happen anyways...\n     */\n    try {\n      // If the dump path doesn't already exist, create it.\n      if (outputFile.getParentFile() != null) {\n        outputFile.getParentFile().mkdirs();\n      }\n\n      outStream =\n          disableCompression\n              ? new FileOutputStream(logDumpPath, false)\n              : new GZIPOutputStream(new FileOutputStream(logDumpPath, false));\n\n      logDumper = new PrintWriter(outStream, true);\n    } catch (IOException exc) {\n      // Not much to do here, can't dump logs so exit out.\n      logger.debug(\"Log dump failed, exception: {}\", exc.getMessage());\n\n      return;\n    }\n\n    // Iterate over log entries, format them, then dump them.\n    for (LogRecord entry : logBufferCopy) {\n      logDumper.write(formatter != null ? formatter.format(entry) : entry.getMessage());\n    }\n\n    // Clean up\n    logDumper.flush();\n    logDumper.close();\n  }\n\n  /**\n   * Function to remove old Snowflake Dump files to make room for new ones.\n   *\n   * @param deleteOldest if true, always deletes the oldest file found if max number of dump files\n   *     has been reached\n   */\n  protected void cleanupSfDumps(boolean deleteOldest) {\n    // Check what the maximum number of dumpfiles and the max allowable\n    // aggregate dump file size is.\n    int maxDumpFiles =\n        systemGetProperty(MAX_NUM_DUMP_FILES_PROP) != null\n            ? Integer.valueOf(systemGetProperty(MAX_NUM_DUMP_FILES_PROP))\n            : DEFAULT_MAX_DUMP_FILES;\n\n    int maxDumpDirSizeMB =\n        systemGetProperty(MAX_SIZE_DUMPS_MB_PROP) != null\n            ? Integer.valueOf(systemGetProperty(MAX_SIZE_DUMPS_MB_PROP))\n            : DEFAULT_MAX_DUMPDIR_SIZE_MB;\n\n    File dumpDir = new File(logDumpPathPrefix);\n    long dirSizeBytes = 0;\n\n    if (dumpDir.listFiles() == null) {\n      return;\n    }\n\n    // Keep a sorted list of files by size as we go in case we need to\n    // delete some\n    TreeSet<File> fileList =\n        new TreeSet<>(\n            new Comparator<File>() {\n              @Override\n              public int compare(File a, File b) {\n                return a.length() < b.length() ? -1 : 1;\n              }\n            });\n\n    // Loop over files in this directory and get rid of old ones\n    // while accumulating the total size\n    for (File file : dumpDir.listFiles()) {\n      if ((!file.getName().startsWith(LOG_DUMP_FILE_NAME)\n              && !file.getName().startsWith(\"sf_incident_\"))\n          || (System.currentTimeMillis() - file.lastModified() > FILE_EXPN_TIME_MS\n              && file.delete())) {\n        continue;\n      }\n\n      dirSizeBytes += file.length();\n      fileList.add(file);\n    }\n\n    // If we're exceeding our max allotted disk usage, cut some stuff out;\n    // else if we need to make space for a new dump delete the oldest.\n    if (dirSizeBytes >= ((long) maxDumpDirSizeMB << 20)) {\n      // While we take up more than half the allotted disk usage, keep deleting.\n      for (File file : fileList) {\n        if (dirSizeBytes < ((long) maxDumpDirSizeMB << 19)) {\n          break;\n        }\n\n        long victimSize = file.length();\n        if (file.delete()) {\n          dirSizeBytes -= victimSize;\n        }\n      }\n    } else if (deleteOldest && fileList.size() >= maxDumpFiles) {\n      fileList.first().delete();\n    }\n  } // cleanupSfDumps(...)\n\n  /**\n   * Function to copy the event buffer, clear it, and iterate of the copy, calling each event's\n   * flush() method one by one.\n   *\n   * <p>NOTE: This function is subject to a race condition; while the buffer copy is being iterated\n   * over, the next round of buffer entries could be flushed creating a flush order that is not\n   * \"strictly consistent\". While this could hypothetically also cause the system to run out of\n   * memory due to an unbounded number of eventBuffer copies, that scenario is unlikely.\n   */\n  private void flushEventBuffer() {\n    ArrayList<Event> eventBufferCopy;\n\n    logger.debug(\"Flushing eventBuffer\", false);\n\n    // Copy event buffer because this may be long running\n    synchronized (this) {\n      eventBufferCopy = new ArrayList<>(eventBuffer);\n      eventBuffer.clear();\n    }\n\n    for (Event event : eventBufferCopy) {\n      event.flush();\n    }\n  }\n\n  /* Overridden methods for Handler interface */\n\n  /** Flushes all eventBuffer entries. */\n  @Override\n  public synchronized void flush() {\n    logger.debug(\"EventHandler flushing logger buffer\", false);\n\n    dumpLogBuffer(\"\");\n  }\n\n  /**\n   * Overridden Logger.Handler publish(...) method. Buffers unformatted log records in memory in a\n   * circular buffer-like fashion.\n   *\n   * @param record log record\n   */\n  @Override\n  public synchronized void publish(LogRecord record) {\n    if (!super.isLoggable(record)\n        || this.getLevel() != null && record.getLevel().intValue() < this.getLevel().intValue()) {\n      return;\n    }\n\n    synchronized (logBuffer) {\n      if (logBuffer.size() == LOG_BUFFER_SIZE) {\n        logBuffer.remove(0);\n      }\n\n      logBuffer.add(record);\n    }\n  }\n\n  @Override\n  public void close() {\n    this.flushEventBuffer();\n    this.stopFlusher();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/EventUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.File;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/** Utility class to encapsulate support information pertaining to the EventHandler and events. */\npublic class EventUtil {\n  public static final String DUMP_PATH_PROP = \"snowflake.dump_path\";\n  public static final String DUMP_SIZE_PROP = \"snowflake.max_dump_size\";\n  public static final String DUMP_SUBDIR = \"snowflake_dumps\";\n\n  private static final String DUMP_FILE_ID = UUIDUtils.getUUID().toString();\n  private static String DUMP_PATH_PREFIX =\n      systemGetProperty(DUMP_PATH_PROP) == null ? \"/tmp\" : systemGetProperty(DUMP_PATH_PROP);\n  private static final long MAX_DUMP_FILE_SIZE_BYTES =\n      systemGetProperty(DUMP_SIZE_PROP) == null\n          ? (10 << 20)\n          : Long.valueOf(systemGetProperty(DUMP_SIZE_PROP));\n\n  private static AtomicReference<EventHandler> eventHandler = new AtomicReference<>(null);\n\n  private static int MAX_ENTRIES = 1000;\n\n  private static int FLUSH_PERIOD_MS = 10000;\n\n  /**\n   * Junit is not recognizing the system properties for EventTest, so overriding the value here\n   *\n   * @param value string value\n   */\n  public static void setDumpPathPrefixForTesting(String value) {\n    DUMP_PATH_PREFIX = value;\n  }\n\n  /**\n   * Initializes the common eventHandler instance for all sessions/threads\n   *\n   * @param maxEntries - maximum number of buffered events before flush\n   * @param flushPeriodMs - period of time between asynchronous buffer flushes\n   */\n  public static synchronized void initEventHandlerInstance(int maxEntries, int flushPeriodMs) {\n    if (eventHandler.get() == null) {\n      eventHandler.set(new EventHandler(maxEntries, flushPeriodMs));\n    }\n    // eventHandler.startFlusher();\n  }\n\n  /**\n   * @return the shared EventHandler instance\n   */\n  public static EventHandler getEventHandlerInstance() {\n    if (eventHandler.get() == null) {\n      initEventHandlerInstance(MAX_ENTRIES, FLUSH_PERIOD_MS);\n    }\n\n    return eventHandler.get();\n  }\n\n  public static void triggerBasicEvent(Event.EventType type, String message, boolean flushBuffer) {\n    EventHandler eh = eventHandler.get();\n    if (eh != null) {\n      eh.triggerBasicEvent(type, message, flushBuffer);\n    }\n  }\n\n  public static void triggerStateTransition(BasicEvent.QueryState newState, String identifier) {\n    EventHandler eh = eventHandler.get();\n    if (eh != null) {\n      eh.triggerStateTransition(newState, identifier);\n    }\n  }\n\n  public static String getDumpPathPrefix() {\n    return DUMP_PATH_PREFIX + File.separator + DUMP_SUBDIR;\n  }\n\n  public static String getDumpFileId() {\n    return DUMP_FILE_ID;\n  }\n\n  public static long getmaxDumpFileSizeBytes() {\n    return MAX_DUMP_FILE_SIZE_BYTES;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/FieldSchemaCreator.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.SQLException;\nimport java.sql.Types;\nimport java.util.Optional;\nimport net.snowflake.client.internal.jdbc.BindingParameterMetadata;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumn;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class FieldSchemaCreator {\n  static final SFLogger logger = SFLoggerFactory.getLogger(FieldSchemaCreator.class);\n  public static final int MAX_TEXT_COLUMN_SIZE = 134217728;\n  public static final int MAX_BINARY_COLUMN_SIZE = 67108864;\n\n  public static BindingParameterMetadata buildSchemaForText(\n      String fieldName, Optional<SnowflakeColumn> maybeColumn) {\n    return BindingParameterMetadata.BindingParameterMetadataBuilder.bindingParameterMetadata()\n        .withType(maybeColumn.map(cl -> cl.type()).filter(str -> !str.isEmpty()).orElse(\"text\"))\n        .withLength(maybeColumn.map(cl -> cl.length()).orElse(MAX_TEXT_COLUMN_SIZE))\n        .withName(maybeColumn.map(cl -> cl.name()).filter(str -> !str.isEmpty()).orElse(fieldName))\n        .build();\n  }\n\n  public static BindingParameterMetadata buildSchemaForBytesType(\n      String fieldName, Optional<SnowflakeColumn> maybeColumn) {\n    return BindingParameterMetadata.BindingParameterMetadataBuilder.bindingParameterMetadata()\n        .withType(maybeColumn.map(cl -> cl.type()).filter(str -> !str.isEmpty()).orElse(\"binary\"))\n        .withName(maybeColumn.map(cl -> cl.name()).filter(str -> !str.isEmpty()).orElse(fieldName))\n        .withLength(maybeColumn.map(cl -> cl.precision()).orElse(MAX_TEXT_COLUMN_SIZE))\n        .withByteLength(maybeColumn.map(cl -> cl.byteLength()).orElse(MAX_BINARY_COLUMN_SIZE))\n        .build();\n  }\n\n  public static BindingParameterMetadata buildSchemaTypeAndNameOnly(\n      String fieldName, String type, Optional<SnowflakeColumn> maybeColumn) {\n    return BindingParameterMetadata.BindingParameterMetadataBuilder.bindingParameterMetadata()\n        .withType(maybeColumn.map(cl -> cl.type()).filter(str -> !str.isEmpty()).orElse(type))\n        .withName(maybeColumn.map(cl -> cl.name()).filter(str -> !str.isEmpty()).orElse(fieldName))\n        .build();\n  }\n\n  public static BindingParameterMetadata buildSchemaWithScaleAndPrecision(\n      String fieldName,\n      String type,\n      int scale,\n      int precision,\n      Optional<SnowflakeColumn> maybeColumn) {\n    return BindingParameterMetadata.BindingParameterMetadataBuilder.bindingParameterMetadata()\n        .withType(maybeColumn.map(cl -> cl.type()).filter(str -> !str.isEmpty()).orElse(type))\n        .withScale(maybeColumn.map(cl -> cl.scale()).filter(i -> i > 0).orElse(scale))\n        .withName(maybeColumn.map(cl -> cl.name()).filter(str -> !str.isEmpty()).orElse(fieldName))\n        .withPrecision(maybeColumn.map(cl -> cl.precision()).filter(i -> i > 0).orElse(precision))\n        .build();\n  }\n\n  public static BindingParameterMetadata buildBindingSchemaForType(int baseType)\n      throws SQLException {\n    return buildBindingSchemaForType(baseType, true);\n  }\n\n  public static BindingParameterMetadata buildBindingSchemaForType(int baseType, boolean addName)\n      throws SQLException {\n    String name = addName ? SnowflakeTypeUtil.javaTypeToSFType(baseType, null).name() : null;\n    switch (baseType) {\n      case Types.VARCHAR:\n      case Types.CHAR:\n        return FieldSchemaCreator.buildSchemaForText(name, Optional.empty());\n      case Types.FLOAT:\n      case Types.DOUBLE:\n      case Types.DECIMAL:\n        return FieldSchemaCreator.buildSchemaWithScaleAndPrecision(\n            name, \"real\", 9, 38, Optional.empty());\n      case Types.NUMERIC:\n      case Types.INTEGER:\n      case Types.SMALLINT:\n      case Types.TINYINT:\n      case Types.BIGINT:\n        return FieldSchemaCreator.buildSchemaWithScaleAndPrecision(\n            null, \"fixed\", 0, 38, Optional.empty());\n      case Types.BOOLEAN:\n        return FieldSchemaCreator.buildSchemaTypeAndNameOnly(name, \"boolean\", Optional.empty());\n      case Types.DATE:\n        return FieldSchemaCreator.buildSchemaTypeAndNameOnly(name, \"date\", Optional.empty());\n      case Types.TIMESTAMP:\n        return FieldSchemaCreator.buildSchemaWithScaleAndPrecision(\n            name, \"timestamp\", 9, 0, Optional.empty());\n      case Types.TIME:\n        return FieldSchemaCreator.buildSchemaWithScaleAndPrecision(\n            name, \"time\", 9, 0, Optional.empty());\n      default:\n        logger.error(\"Could not create schema for type : \" + baseType);\n        throw new SQLException(\"Could not create schema for type : \" + baseType);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/FileCacheManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.io.File;\nimport java.util.function.Supplier;\n\ninterface FileCacheManager {\n\n  String getCacheFilePath();\n\n  void overrideCacheFile(File newCacheFile);\n\n  <T> T withLock(Supplier<T> supplier);\n\n  JsonNode readCacheFile();\n\n  void writeCacheFile(JsonNode input);\n\n  void deleteCacheFile();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/FileCacheManagerBuilder.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass FileCacheManagerBuilder {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(FileCacheManagerBuilder.class);\n\n  private String cacheDirectorySystemProperty;\n  private String cacheDirectoryEnvironmentVariable;\n  private String baseCacheFileName;\n  private long cacheFileLockExpirationInMilliseconds;\n  private boolean onlyOwnerPermissions = true;\n\n  FileCacheManagerBuilder() {}\n\n  FileCacheManagerBuilder setCacheDirectorySystemProperty(String cacheDirectorySystemProperty) {\n    this.cacheDirectorySystemProperty = cacheDirectorySystemProperty;\n    return this;\n  }\n\n  FileCacheManagerBuilder setCacheDirectoryEnvironmentVariable(\n      String cacheDirectoryEnvironmentVariable) {\n    this.cacheDirectoryEnvironmentVariable = cacheDirectoryEnvironmentVariable;\n    return this;\n  }\n\n  FileCacheManagerBuilder setBaseCacheFileName(String baseCacheFileName) {\n    this.baseCacheFileName = baseCacheFileName;\n    return this;\n  }\n\n  FileCacheManagerBuilder setCacheFileLockExpirationInSeconds(\n      long cacheFileLockExpirationInSeconds) {\n    this.cacheFileLockExpirationInMilliseconds = cacheFileLockExpirationInSeconds * 1000;\n    return this;\n  }\n\n  FileCacheManagerBuilder setOnlyOwnerPermissions(boolean onlyOwnerPermissions) {\n    this.onlyOwnerPermissions = onlyOwnerPermissions;\n    return this;\n  }\n\n  FileCacheManager build() {\n    String cacheDirPath =\n        this.cacheDirectorySystemProperty != null\n            ? systemGetProperty(this.cacheDirectorySystemProperty)\n            : null;\n    if (cacheDirPath == null) {\n      try {\n        cacheDirPath =\n            this.cacheDirectoryEnvironmentVariable != null\n                ? systemGetEnv(this.cacheDirectoryEnvironmentVariable)\n                : null;\n      } catch (Exception ex) {\n        logger.debug(\n            \"Cannot get environment variable for cache directory, skip using cache\", false);\n        return new NoOpFileCacheManager();\n      }\n    }\n\n    File cacheDir;\n    if (cacheDirPath != null) {\n      cacheDir = new File(cacheDirPath);\n    } else {\n      cacheDir = FileCacheUtil.getDefaultCacheDir();\n    }\n    if (cacheDir == null) {\n      return new NoOpFileCacheManager();\n    }\n    if (!cacheDir.exists()) {\n      try {\n        if (!isWindows() && onlyOwnerPermissions) {\n          Files.createDirectories(\n              cacheDir.toPath(),\n              PosixFilePermissions.asFileAttribute(\n                  Stream.of(\n                          PosixFilePermission.OWNER_READ,\n                          PosixFilePermission.OWNER_WRITE,\n                          PosixFilePermission.OWNER_EXECUTE)\n                      .collect(Collectors.toSet())));\n        } else {\n          Files.createDirectories(cacheDir.toPath());\n        }\n      } catch (IOException e) {\n        logger.info(\n            \"Failed to create the cache directory: {}. Ignored. {}\",\n            e.getMessage(),\n            cacheDir.getAbsoluteFile());\n        return new NoOpFileCacheManager();\n      }\n    }\n    if (!cacheDir.exists()) {\n      logger.debug(\"Cannot create the cache directory {}. Giving up.\", cacheDir.getAbsolutePath());\n      return new NoOpFileCacheManager();\n    }\n    logger.debug(\"Verified Directory {}\", cacheDir.getAbsolutePath());\n\n    File cacheFileTmp = new File(cacheDir, this.baseCacheFileName).getAbsoluteFile();\n    try {\n      if (!cacheFileTmp.exists()) {\n        if (!isWindows() && onlyOwnerPermissions) {\n          Files.createFile(\n              cacheFileTmp.toPath(),\n              PosixFilePermissions.asFileAttribute(\n                  Stream.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)\n                      .collect(Collectors.toSet())));\n        } else {\n          Files.createFile(cacheFileTmp.toPath());\n        }\n        logger.debug(\"Successfully created a cache file {}\", cacheFileTmp);\n      } else {\n        logger.debug(\"Cache file already exists {}\", cacheFileTmp);\n      }\n      FileUtil.logFileUsage(cacheFileTmp, \"Cache file creation\", false);\n      File cacheFile = cacheFileTmp.getCanonicalFile();\n      File cacheLockFile = new File(cacheFile.getParentFile(), this.baseCacheFileName + \".lck\");\n      return new DefaultFileCacheManager(\n          cacheFile,\n          cacheLockFile,\n          this.baseCacheFileName,\n          this.onlyOwnerPermissions,\n          this.cacheFileLockExpirationInMilliseconds);\n    } catch (IOException | SecurityException ex) {\n      logger.info(\n          \"Failed to touch the cache file: {}. Ignored. {}\",\n          ex.getMessage(),\n          cacheFileTmp.getAbsoluteFile());\n    }\n    return new NoOpFileCacheManager();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/FileCacheUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.FileUtil.isWritable;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.File;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class FileCacheUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(FileCacheUtil.class);\n\n  public static File getDefaultCacheDir() {\n    if (Constants.getOS() == Constants.OS.LINUX) {\n      String xdgCacheHome = getDir(systemGetEnv(\"XDG_CACHE_HOME\"));\n      if (xdgCacheHome != null) {\n        return new File(xdgCacheHome, \"snowflake\");\n      }\n      logger.debug(\"XDG cache home directory is not set or not writable.\");\n    }\n\n    String homeDir = getDir(systemGetProperty(\"user.home\"));\n    if (homeDir == null) {\n      logger.debug(\"Home directory is not set or not writable, no cache dir is set.\");\n      return null;\n    }\n    if (Constants.getOS() == Constants.OS.WINDOWS) {\n      return new File(\n          new File(new File(new File(homeDir, \"AppData\"), \"Local\"), \"Snowflake\"), \"Caches\");\n    } else if (Constants.getOS() == Constants.OS.MAC) {\n      return new File(new File(new File(homeDir, \"Library\"), \"Caches\"), \"Snowflake\");\n    } else {\n      return new File(new File(homeDir, \".cache\"), \"snowflake\");\n    }\n  }\n\n  private static String getDir(String dir) {\n    if (dir != null && isWritable(dir)) {\n      return dir;\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/FileTypeDetector.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport org.apache.tika.Tika;\n\n/** Use Tika to detect the mime type of files */\npublic class FileTypeDetector extends java.nio.file.spi.FileTypeDetector {\n  private final Tika tika = new Tika();\n\n  @Override\n  public String probeContentType(Path path) throws IOException {\n    return tika.detect(path.toFile());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/FileUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileOwnerAttributeView;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class FileUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(FileUtil.class);\n\n  /**\n   * Placeholder value returned by the JVM for {@code user.name} when the current OS user has no\n   * matching entry in the system user database (e.g. a container started with {@code runAsUser} and\n   * no {@code /etc/passwd} entry). Treated as \"unknown user\" — file owner validation is skipped in\n   * this case.\n   */\n  static final String UNKNOWN_USER_NAME_MARKER = \"?\";\n\n  private static final Collection<PosixFilePermission> WRITE_BY_OTHERS =\n      Arrays.asList(PosixFilePermission.GROUP_WRITE, PosixFilePermission.OTHERS_WRITE);\n  private static final Collection<PosixFilePermission> READ_BY_OTHERS =\n      Arrays.asList(PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ);\n  private static final Collection<PosixFilePermission> EXECUTABLE =\n      Arrays.asList(\n          PosixFilePermission.OWNER_EXECUTE,\n          PosixFilePermission.GROUP_EXECUTE,\n          PosixFilePermission.OTHERS_EXECUTE);\n\n  public static void logFileUsage(Path filePath, String context, boolean logReadAccess) {\n    logWarnWhenAccessibleByOthers(filePath, context, logReadAccess);\n  }\n\n  public static void logFileUsage(File file, String context, boolean logReadAccess) {\n    logFileUsage(file.toPath(), context, logReadAccess);\n  }\n\n  public static void logFileUsage(String stringPath, String context, boolean logReadAccess) {\n    Path path = Paths.get(stringPath);\n    logFileUsage(path, context, logReadAccess);\n  }\n\n  public static boolean isWritable(String path) {\n    File file = new File(path);\n    if (!file.canWrite()) {\n      logger.debug(\"File/directory not writeable: {}\", path);\n      return false;\n    }\n    return true;\n  }\n\n  public static void handleWhenParentDirectoryPermissionsWiderThanUserOnly(\n      File file, String context) {\n    handleWhenDirectoryPermissionsWiderThanUserOnly(file.getParentFile(), context);\n  }\n\n  public static void handleWhenFilePermissionsWiderThanUserOnly(File file, String context) {\n    if (Files.isSymbolicLink(file.toPath())) {\n      throw new SecurityException(\"Symbolic link is not allowed for file cache: \" + file);\n    }\n    handleWhenPermissionsWiderThanUserOnly(file.toPath(), context, false);\n  }\n\n  public static void handleWhenDirectoryPermissionsWiderThanUserOnly(File file, String context) {\n    handleWhenPermissionsWiderThanUserOnly(file.toPath(), context, true);\n  }\n\n  public static void handleWhenPermissionsWiderThanUserOnly(\n      Path filePath, String context, boolean isDirectory) {\n    // we do not check the permissions for Windows\n    if (isWindows()) {\n      return;\n    }\n\n    try {\n      Collection<PosixFilePermission> filePermissions = Files.getPosixFilePermissions(filePath);\n      boolean isWritableByOthers = isPermPresent(filePermissions, WRITE_BY_OTHERS);\n      boolean isReadableByOthers = isPermPresent(filePermissions, READ_BY_OTHERS);\n      boolean isExecutable = isPermPresent(filePermissions, EXECUTABLE);\n\n      boolean permissionsTooOpen;\n      if (isDirectory) {\n        permissionsTooOpen = isWritableByOthers || isReadableByOthers;\n      } else {\n        permissionsTooOpen = isWritableByOthers || isReadableByOthers || isExecutable;\n      }\n      if (permissionsTooOpen) {\n        logger.debug(\n            \"{}File/directory {} access rights: {}\",\n            getContextStr(context),\n            filePath,\n            filePermissions);\n        String message =\n            String.format(\n                \"Access to file or directory %s is wider than allowed. Remove cache file/directory and re-run the driver.\",\n                filePath);\n        if (isDirectory) {\n          logger.warn(message);\n        } else {\n          throw new SecurityException(message);\n        }\n      } else {\n        if (!isDirectory && Files.isSymbolicLink(filePath)) {\n          throw new SecurityException(\"Symbolic link is not allowed for file cache: \" + filePath);\n        }\n      }\n    } catch (IOException e) {\n      String message =\n          String.format(\n              \"%s Unable to access the file/directory to check the permissions. Error: %s\",\n              filePath, e);\n      if (isDirectory) {\n        logger.warn(message);\n      } else {\n        throw new SecurityException(message);\n      }\n    }\n  }\n\n  private static void logWarnWhenAccessibleByOthers(\n      Path filePath, String context, boolean logReadAccess) {\n    // we do not check the permissions for Windows\n    if (isWindows()) {\n      return;\n    }\n\n    try {\n      Collection<PosixFilePermission> filePermissions = Files.getPosixFilePermissions(filePath);\n      logger.debug(\n          \"{}File {} access rights: {}\", getContextStr(context), filePath, filePermissions);\n\n      boolean isWritableByOthers = isPermPresent(filePermissions, WRITE_BY_OTHERS);\n      boolean isReadableByOthers = isPermPresent(filePermissions, READ_BY_OTHERS);\n      boolean isExecutable = isPermPresent(filePermissions, EXECUTABLE);\n\n      if (isWritableByOthers || (isReadableByOthers && logReadAccess) || isExecutable) {\n        logger.warn(\n            \"{}File {} is accessible by others to:{}{}\",\n            getContextStr(context),\n            filePath,\n            isReadableByOthers && logReadAccess ? \" read\" : \"\",\n            isWritableByOthers ? \" write\" : \"\",\n            isExecutable ? \" execute\" : \"\");\n      }\n    } catch (IOException e) {\n      logger.warn(\n          \"{}Unable to access the file to check the permissions: {}. Error: {}\",\n          getContextStr(context),\n          filePath,\n          e);\n    }\n  }\n\n  public static void throwWhenOwnerDifferentThanCurrentUser(File file, String context) {\n    // we do not check the permissions for Windows\n    if (isWindows()) {\n      return;\n    }\n\n    Path filePath = Paths.get(file.getPath());\n\n    try {\n      String fileOwnerName = getFileOwnerName(filePath);\n      String currentUser = systemGetProperty(\"user.name\");\n      if (currentUser == null || UNKNOWN_USER_NAME_MARKER.equals(currentUser)) {\n        logger.warn(\n            \"Cannot determine user (possibly due to security manager restrictions or missing passwd entry in container), skipping file owner validation\");\n        return;\n      }\n      if (!currentUser.equalsIgnoreCase(fileOwnerName)) {\n        logger.debug(\n            \"The file owner: {} is different than current user: {}\", fileOwnerName, currentUser);\n        throw new SecurityException(\"The file owner is different than current user\");\n      }\n    } catch (IOException e) {\n      logger.warn(\n          \"{}Unable to access the file to check the owner: {}. Error: {}\",\n          getContextStr(context),\n          filePath,\n          e);\n    }\n  }\n\n  private static boolean isPermPresent(\n      Collection<PosixFilePermission> filePerms, Collection<PosixFilePermission> permsToCheck)\n      throws IOException {\n    return filePerms.stream().anyMatch(permsToCheck::contains);\n  }\n\n  static String getFileOwnerName(Path filePath) throws IOException {\n    FileOwnerAttributeView ownerAttributeView =\n        Files.getFileAttributeView(filePath, FileOwnerAttributeView.class);\n    return ownerAttributeView.getOwner().getName();\n  }\n\n  private static String getContextStr(String context) {\n    return isNullOrEmpty(context) ? \"\" : context + \": \";\n  }\n\n  public static boolean exists(File file) {\n    if (file == null) {\n      return false;\n    }\n    return file.exists();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HeaderCustomizerHttpRequestInterceptor.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.Header;\nimport org.apache.http.HttpException;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.HttpRequestInterceptor;\nimport org.apache.http.protocol.HttpContext;\nimport software.amazon.awssdk.core.interceptor.Context;\nimport software.amazon.awssdk.core.interceptor.ExecutionAttributes;\nimport software.amazon.awssdk.core.interceptor.ExecutionInterceptor;\nimport software.amazon.awssdk.http.SdkHttpRequest;\n\n/**\n * Implements Apache HttpClient's {@link HttpRequestInterceptor} and {@link ExecutionInterceptor} to\n * provide a mechanism for adding custom HTTP headers to outgoing requests made by the Snowflake\n * JDBC driver.\n *\n * <p>This class iterates through a list of user-provided {@link HttpHeadersCustomizer}\n * implementations. For each customizer, it checks if it applies to the current request. If it does,\n * it retrieves new headers from the customizer and adds them to the request, ensuring that existing\n * driver-set headers are not overridden.\n *\n * <p>For Apache HttpClient, retry detection is handled by checking the {@link\n * AttributeEnhancingHttpRequestRetryHandler#EXECUTION_COUNT_ATTRIBUTE} attribute in the {@link\n * HttpContext} set by {@link AttributeEnhancingHttpRequestRetryHandler} to honor the {@code\n * invokeOnce()} contract of the customizer.\n *\n * @see HttpHeadersCustomizer\n */\npublic class HeaderCustomizerHttpRequestInterceptor\n    implements HttpRequestInterceptor, ExecutionInterceptor {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(HeaderCustomizerHttpRequestInterceptor.class);\n  private final List<HttpHeadersCustomizer> headersCustomizers;\n\n  public HeaderCustomizerHttpRequestInterceptor(List<HttpHeadersCustomizer> headersCustomizers) {\n    if (headersCustomizers != null) {\n      this.headersCustomizers = new ArrayList<>(headersCustomizers); // Defensive copy\n    } else {\n      this.headersCustomizers = new ArrayList<>();\n    }\n  }\n\n  /**\n   * Processes an Apache HttpClient {@link HttpRequest} before it is sent. It iterates through\n   * registered {@link HttpHeadersCustomizer}s, checks applicability, retrieves new headers,\n   * verifies against overriding driver headers, and adds them to the request. Handles the {@code\n   * invokeOnce()} flag based on the \"execution-count\" attribute in the {@link HttpContext}.\n   *\n   * @param httpRequest The HTTP request to process.\n   * @param httpContext The context for the HTTP request execution, used to retrieve retry count.\n   */\n  @Override\n  public void process(HttpRequest httpRequest, HttpContext httpContext)\n      throws HttpException, IOException {\n    if (this.headersCustomizers.isEmpty()) {\n      return;\n    }\n    String httpMethod = httpRequest.getRequestLine().getMethod();\n    String uri = httpRequest.getRequestLine().getUri();\n    Map<String, List<String>> currentHeaders = extractHeaders(httpRequest);\n    // convert header names to lower case for case in-sensitive lookup\n    Set<String> protectedHeaders =\n        currentHeaders.keySet().stream().map(String::toLowerCase).collect(Collectors.toSet());\n    Integer executionCount =\n        (Integer)\n            httpContext.getAttribute(\n                AttributeEnhancingHttpRequestRetryHandler.EXECUTION_COUNT_ATTRIBUTE);\n    boolean isRetry = (executionCount == null || executionCount > 0);\n\n    for (HttpHeadersCustomizer customizer : this.headersCustomizers) {\n      if (customizer.applies(httpMethod, uri, currentHeaders)) {\n        if (customizer.invokeOnce() && isRetry) {\n          logger.debug(\n              \"Customizer {} should only run on the first attempt and this is a {} retry. Skipping.\",\n              customizer.getClass().getCanonicalName(),\n              executionCount);\n          continue;\n        }\n        Map<String, List<String>> newHeaders = customizer.newHeaders();\n\n        logger.debug(\n            \"Customizer {} is adding headers {}\",\n            customizer.getClass().getCanonicalName(),\n            newHeaders.keySet());\n        for (Map.Entry<String, List<String>> entry : newHeaders.entrySet()) {\n          if (isTryingToOverrideDriverHeader(entry, protectedHeaders)) {\n            logger.debug(\n                \"Customizer {} attempted to override existing driver header {} which is not allowed. Skipping.\",\n                customizer.getClass().getCanonicalName(),\n                entry.getKey());\n          } else {\n            for (String value : entry.getValue()) {\n              httpRequest.addHeader(entry.getKey(), value);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  /**\n   * Modifies an AWS HTTP {@link SdkHttpRequest}. It iterates through registered {@link\n   * HttpHeadersCustomizer}s, checks applicability, retrieves new headers, verifies against\n   * overriding driver headers, and adds them to the request. Ignores the {@code invokeOnce()} flag.\n   *\n   * @param context The AWS request context to process.\n   * @param executionAttributes The AWS execution attributes.\n   */\n  @Override\n  public SdkHttpRequest modifyHttpRequest(\n      Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {\n    if (this.headersCustomizers.isEmpty()) {\n      return context.httpRequest();\n    }\n\n    SdkHttpRequest httpRequest = context.httpRequest();\n    SdkHttpRequest.Builder requestBuilder = context.httpRequest().toBuilder();\n\n    String httpMethod = httpRequest.method().name();\n    String uri = httpRequest.getUri().toString();\n    Map<String, List<String>> currentHeaders = httpRequest.headers();\n    Set<String> protectedHeaders =\n        currentHeaders.keySet().stream()\n            .map(String::toLowerCase)\n            .collect(Collectors.toSet()); // convert to lower case for case in-sensitive lookup\n\n    for (HttpHeadersCustomizer customizer : this.headersCustomizers) {\n      if (customizer.applies(httpMethod, uri, currentHeaders)) {\n        Map<String, List<String>> newHeaders = customizer.newHeaders();\n\n        logger.debug(\n            \"Customizer {} is adding headers {}\",\n            customizer.getClass().getCanonicalName(),\n            newHeaders.keySet());\n        for (Map.Entry<String, List<String>> entry : newHeaders.entrySet()) {\n          if (isTryingToOverrideDriverHeader(entry, protectedHeaders)) {\n            logger.debug(\n                \"Customizer {} attempted to override existing driver header {} which is not allowed. Skipping.\",\n                customizer.getClass().getCanonicalName(),\n                entry.getKey());\n          } else {\n            for (String value : entry.getValue()) {\n              requestBuilder.appendHeader(entry.getKey(), value);\n            }\n          }\n        }\n      }\n    }\n\n    return requestBuilder.build();\n  }\n\n  private static boolean isTryingToOverrideDriverHeader(\n      Map.Entry<String, List<String>> entry, Set<String> protectedHeaders) {\n    return protectedHeaders.contains(entry.getKey().toLowerCase());\n  }\n\n  private static Map<String, List<String>> extractHeaders(HttpRequest request) {\n    Map<String, List<String>> headerMap = new HashMap<>();\n    for (Header header : request.getAllHeaders()) {\n      headerMap.computeIfAbsent(header.getName(), k -> new ArrayList<>()).add(header.getValue());\n    }\n    return headerMap;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HeartbeatIntervalSelector.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.Set;\n\n/**\n * Selects the best existing heartbeat interval when a new interval cannot be created due to thread\n * limits.\n *\n * <p>The selector always prefers shorter (more frequent) intervals to prevent session token\n * expiration. It never selects an interval longer than requested unless no shorter alternatives\n * exist.\n */\nclass HeartbeatIntervalSelector {\n\n  /**\n   * Find the best existing interval to use when the requested interval cannot be created.\n   *\n   * <p>Selection strategy: 1. First choice: Closest interval that is <= requested (more frequent or\n   * equal) 2. Fallback: If no smaller intervals exist, use the SHORTEST available interval (most\n   * frequent), even if it is still longer than requested\n   *\n   * <p>This is a best-effort strategy to keep heartbeats as frequent as possible with existing\n   * intervals, but it cannot guarantee avoiding less frequent heartbeats or token expiration when\n   * only longer intervals are available.\n   *\n   * @param requestedInterval The interval that was requested but cannot be created\n   * @param existingIntervals The set of intervals that currently have threads\n   * @return The best existing interval to use\n   * @throws IllegalArgumentException if existingIntervals is null, empty, or requestedInterval <= 0\n   */\n  static long selectBestInterval(long requestedInterval, Set<Long> existingIntervals) {\n    if (requestedInterval <= 0) {\n      throw new IllegalArgumentException(\n          \"Requested interval must be positive: \" + requestedInterval);\n    }\n    if (existingIntervals == null || existingIntervals.isEmpty()) {\n      throw new IllegalArgumentException(\"Existing intervals cannot be null or empty\");\n    }\n\n    Long closestSmallerOrEqual = null;\n    Long shortestOverall = null;\n\n    for (Long existingInterval : existingIntervals) {\n      // Track the shortest interval overall (fallback)\n      if (shortestOverall == null || existingInterval < shortestOverall) {\n        shortestOverall = existingInterval;\n      }\n\n      // Find the closest interval that is <= requested (more frequent or equal)\n      if (existingInterval <= requestedInterval) {\n        if (closestSmallerOrEqual == null || existingInterval > closestSmallerOrEqual) {\n          closestSmallerOrEqual = existingInterval;\n        }\n      }\n    }\n\n    // Prefer smaller-or-equal interval; fallback to shortest if none exists\n    return closestSmallerOrEqual != null ? closestSmallerOrEqual : shortestOverall;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HeartbeatRegistry.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport java.time.Clock;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.WeakHashMap;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Registry that manages multiple HeartbeatThread instances.\n *\n * <p>Each unique heartbeat interval gets its own thread. This solves the problem where a session\n * with short interval could expire when a session with long interval is added.\n *\n * <p>Replaces the singleton HeartbeatBackground.\n *\n * <p>Thread-safe: Uses concurrent data structures and synchronization where needed.\n */\npublic class HeartbeatRegistry {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(HeartbeatRegistry.class);\n\n  /** Maximum number of different heartbeat intervals (prevents thread explosion) */\n  private static final int MAX_HEARTBEAT_THREADS = 10;\n\n  /** Singleton instance */\n  private static volatile HeartbeatRegistry instance;\n\n  /** Map: heartbeat interval (seconds) -> thread managing that interval */\n  private final ConcurrentHashMap<Long, HeartbeatThread> threads = new ConcurrentHashMap<>();\n\n  /**\n   * Map: session -> its required interval (for removal). Uses WeakHashMap for automatic cleanup if\n   * session is garbage collected.\n   */\n  private final Map<SFSession, Long> sessionIntervals =\n      Collections.synchronizedMap(new WeakHashMap<>());\n\n  private final ScheduledExecutorService executor;\n  private final Clock clock;\n  private volatile boolean isShutdown = false;\n\n  /** Private constructor for singleton. */\n  private HeartbeatRegistry() {\n    this(\n        Executors.newScheduledThreadPool(\n            MAX_HEARTBEAT_THREADS,\n            new ThreadFactoryBuilder().setNameFormat(\"heartbeat-pool-%d\").setDaemon(true).build()),\n        Clock.systemUTC());\n\n    // Register shutdown hook to clean up resources on JVM exit\n    try {\n      Runtime.getRuntime()\n          .addShutdownHook(\n              new Thread(\n                  () -> {\n                    logger.debug(\"JVM shutdown detected - cleaning up HeartbeatRegistry\");\n                    shutdown();\n                  },\n                  \"heartbeat-registry-shutdown\"));\n    } catch (SecurityException | IllegalStateException ex) {\n      logger.debug(\"Unable to register HeartbeatRegistry shutdown hook; continuing\", ex);\n    }\n  }\n\n  /**\n   * Package-private constructor for testing.\n   *\n   * @param executor Scheduler for heartbeat tasks\n   * @param clock Time source (injectable for testing)\n   */\n  HeartbeatRegistry(ScheduledExecutorService executor, Clock clock) {\n    if (executor == null) {\n      throw new IllegalArgumentException(\"Executor cannot be null\");\n    }\n    if (clock == null) {\n      throw new IllegalArgumentException(\"Clock cannot be null\");\n    }\n\n    this.executor = executor;\n    this.clock = clock;\n\n    logger.debug(\"HeartbeatRegistry initialized\");\n  }\n\n  /**\n   * Get singleton instance.\n   *\n   * @return The singleton HeartbeatRegistry instance\n   */\n  public static HeartbeatRegistry getInstance() {\n    if (instance == null) {\n      synchronized (HeartbeatRegistry.class) {\n        if (instance == null) {\n          instance = new HeartbeatRegistry();\n        }\n      }\n    }\n    return instance;\n  }\n\n  /**\n   * Add session to heartbeat management.\n   *\n   * <p>Calculates the required heartbeat interval and adds the session to the appropriate\n   * HeartbeatThread. If no thread exists for this interval, creates one. If a thread already\n   * exists, the session is simply added (no rescheduling).\n   *\n   * @param session The session to heartbeat\n   * @param masterTokenValidityInSecs Master token validity in seconds\n   * @param heartbeatFrequencyInSecs Desired heartbeat frequency in seconds\n   */\n  public void addSession(\n      SFSession session, long masterTokenValidityInSecs, int heartbeatFrequencyInSecs) {\n    if (session == null) {\n      throw new IllegalArgumentException(\"Session cannot be null\");\n    }\n    if (masterTokenValidityInSecs <= 0) {\n      throw new IllegalArgumentException(\n          \"Master token validity must be positive: \" + masterTokenValidityInSecs);\n    }\n    if (heartbeatFrequencyInSecs <= 0) {\n      throw new IllegalArgumentException(\n          \"Heartbeat frequency must be positive: \" + heartbeatFrequencyInSecs);\n    }\n    if (isShutdown) {\n      throw new IllegalStateException(\"Cannot add session to shutdown HeartbeatRegistry\");\n    }\n\n    // Calculate required interval: min of requested frequency and 1/4 of token validity\n    long requiredInterval = Math.min(heartbeatFrequencyInSecs, masterTokenValidityInSecs / 4);\n\n    // Enforce minimum interval of 1 second\n    requiredInterval = Math.max(requiredInterval, 1);\n\n    logger.debug(\n        \"Adding session {} with interval {}s (requested: {}s, validity: {}s)\",\n        session.getSessionId(),\n        requiredInterval,\n        heartbeatFrequencyInSecs,\n        masterTokenValidityInSecs);\n\n    // Synchronize thread creation to prevent race conditions with limit check\n    synchronized (this) {\n      if (isShutdown) {\n        throw new IllegalStateException(\"Cannot add session to shutdown HeartbeatRegistry\");\n      }\n      // Check thread count limit\n      if (threads.size() >= MAX_HEARTBEAT_THREADS && !threads.containsKey(requiredInterval)) {\n        logger.debug(\"Maximum threads reached - attempting to prune empty threads before fallback\");\n        pruneEmptyThreads();\n\n        // Check again after pruning\n        if (threads.size() >= MAX_HEARTBEAT_THREADS && !threads.containsKey(requiredInterval)) {\n          logger.warn(\n              \"Maximum heartbeat threads ({}) reached. Session {} will use closest existing interval.\",\n              MAX_HEARTBEAT_THREADS,\n              session.getSessionId());\n\n          // Send telemetry event\n          sendMaxThreadsExceededTelemetry(session, requiredInterval);\n\n          final int requestedInterval = (int) requiredInterval;\n\n          // Find best existing interval (prefers shorter intervals for safety)\n          requiredInterval =\n              HeartbeatIntervalSelector.selectBestInterval(requiredInterval, threads.keySet());\n\n          logger.debug(\n              \"Mapped requested heartbeat interval {}s to existing interval {}s as best-effort fallback after reaching maximum heartbeat threads.\",\n              requestedInterval,\n              requiredInterval);\n        }\n      }\n\n      // Get or create a live thread for this interval, retrying if a stale shutdown thread is\n      // found.\n      final int maxAddAttempts = 2;\n      boolean added = false;\n      for (int attempt = 0; attempt < maxAddAttempts; attempt++) {\n        HeartbeatThread thread =\n            threads.computeIfAbsent(\n                requiredInterval, interval -> new HeartbeatThread(interval, executor, clock));\n\n        if (thread.addSession(session)) {\n          sessionIntervals.put(session, requiredInterval);\n          logger.debug(\n              \"Session {} added to interval {}s. Active threads: {}\",\n              session.getSessionId(),\n              requiredInterval,\n              threads.size());\n          added = true;\n          break;\n        }\n\n        logger.debug(\n            \"Heartbeat thread for interval {}s was shutdown before session {} could be added. Retrying.\",\n            requiredInterval,\n            session.getSessionId());\n        threads.remove(requiredInterval, thread);\n      }\n\n      if (!added) {\n        throw new IllegalStateException(\n            \"Unable to add session \" + session.getSessionId() + \" to a live heartbeat thread\");\n      }\n    }\n  }\n\n  /**\n   * Send telemetry event when max heartbeat threads limit is exceeded.\n   *\n   * @param session The session that triggered the limit\n   * @param requestedInterval The interval that was requested but couldn't be created\n   */\n  private void sendMaxThreadsExceededTelemetry(SFSession session, long requestedInterval) {\n    try {\n      Telemetry telemetry = session.getTelemetryClient(internalCallMarker());\n      if (!(telemetry instanceof TelemetryClient)) {\n        logger.trace(\"Telemetry client not available, skipping max threads telemetry\");\n        return;\n      }\n      TelemetryClient telemetryClient = (TelemetryClient) telemetry;\n\n      ObjectNode telemetryData = ObjectMapperFactory.getObjectMapper().createObjectNode();\n      telemetryData.put(\n          TelemetryField.TYPE.toString(), TelemetryField.HEARTBEAT_MAX_THREADS_EXCEEDED.toString());\n      telemetryData.put(\"max_heartbeat_threads\", MAX_HEARTBEAT_THREADS);\n      telemetryData.put(\"active_threads\", threads.size());\n      telemetryData.put(\"requested_interval\", requestedInterval);\n      telemetryData.put(\"session_id\", session.getSessionId());\n\n      telemetryClient.addLogToBatch(telemetryData, System.currentTimeMillis());\n      logger.trace(\"Queued max threads exceeded telemetry for sending\");\n\n    } catch (Exception e) {\n      // Never fail the session due to telemetry\n      logger.trace(\"Failed to send max threads exceeded telemetry: {}\", e.getMessage());\n    }\n  }\n\n  /**\n   * Remove empty threads from the registry.\n   *\n   * <p>Sessions in HeartbeatThread are stored in a WeakHashMap. When sessions are garbage collected\n   * without explicit removal, the thread becomes empty but remains in the registry. This method\n   * cleans up such threads to free up thread slots.\n   *\n   * <p>Called when approaching thread limit before falling back to existing intervals. Uses atomic\n   * operations to prevent races with concurrent addSession calls.\n   */\n  private void pruneEmptyThreads() {\n    int beforeCount = threads.size();\n\n    // Use computeIfPresent to atomically check and remove empty threads\n    threads.forEach(\n        (interval, thread) -> {\n          threads.computeIfPresent(\n              interval,\n              (key, t) -> {\n                // Double-check it's the same thread and it's empty\n                if (t == thread && t.isEmpty()) {\n                  logger.debug(\n                      \"Pruning empty heartbeat thread for interval {}s (sessions were GC'd)\",\n                      interval);\n                  t.shutdown();\n                  return null; // Remove from map\n                }\n                return t; // Keep in map\n              });\n        });\n\n    int prunedCount = beforeCount - threads.size();\n    if (prunedCount > 0) {\n      logger.debug(\n          \"Pruned {} empty heartbeat threads. Active threads: {} -> {}\",\n          prunedCount,\n          beforeCount,\n          threads.size());\n    }\n  }\n\n  /**\n   * Remove session from heartbeat management.\n   *\n   * <p>Session's interval is looked up from internal tracking. If the HeartbeatThread becomes\n   * empty, it is shutdown and removed atomically to prevent races with concurrent addSession calls.\n   *\n   * @param session The session to remove\n   */\n  public void removeSession(SFSession session) {\n    if (session == null) {\n      logger.debug(\"Attempted to remove null session\");\n      return;\n    }\n    if (isShutdown) {\n      logger.debug(\"Registry is shutdown, ignoring removeSession for {}\", session.getSessionId());\n      return;\n    }\n\n    synchronized (this) {\n      Long interval = sessionIntervals.remove(session);\n\n      if (interval == null) {\n        logger.debug(\"Session {} not found in registry\", session.getSessionId());\n        return;\n      }\n\n      logger.debug(\"Removing session {} with interval {}s\", session.getSessionId(), interval);\n\n      // Remove session from thread and cleanup if empty - atomic operation\n      threads.computeIfPresent(\n          interval,\n          (key, thread) -> {\n            thread.removeSession(session);\n\n            // If thread is now empty, shut it down and return null to remove from map\n            if (thread.isEmpty()) {\n              logger.debug(\n                  \"Removed empty heartbeat thread for interval {}s. Remaining threads: {}\",\n                  interval,\n                  threads.size() - 1); // -1 because we're about to remove this one\n              thread.shutdown();\n              return null; // Remove from map\n            }\n            return thread; // Keep in map\n          });\n    }\n  }\n\n  // === Testing Support ===\n\n  /**\n   * Trigger heartbeat immediately for a specific interval (for testing).\n   *\n   * @param intervalSeconds The interval to trigger\n   */\n  @VisibleForTesting\n  public void triggerHeartbeatForInterval(long intervalSeconds) {\n    HeartbeatThread thread = threads.get(intervalSeconds);\n    if (thread != null) {\n      thread.triggerHeartbeatNow();\n    } else {\n      logger.debug(\"No heartbeat thread found for interval {}s\", intervalSeconds);\n    }\n  }\n\n  /**\n   * Get active thread count (for testing/monitoring).\n   *\n   * @return Number of active heartbeat threads\n   */\n  @VisibleForTesting\n  public int getActiveThreadCount() {\n    return threads.size();\n  }\n\n  /**\n   * Get session count for a specific interval (for testing).\n   *\n   * @param intervalSeconds The interval to query\n   * @return Number of sessions, or 0 if no thread exists\n   */\n  @VisibleForTesting\n  public int getSessionCountForInterval(long intervalSeconds) {\n    HeartbeatThread thread = threads.get(intervalSeconds);\n    return thread != null ? thread.getSessionCount() : 0;\n  }\n\n  /**\n   * Shutdown all threads and cleanup.\n   *\n   * <p>Should be called during application shutdown to cleanly release resources. Also used in test\n   * cleanup. Idempotent - safe to call multiple times.\n   */\n  public synchronized void shutdown() {\n    if (isShutdown) {\n      logger.debug(\"HeartbeatRegistry already shutdown\");\n      return;\n    }\n\n    logger.debug(\"Shutting down HeartbeatRegistry\");\n    isShutdown = true;\n\n    threads.values().forEach(HeartbeatThread::shutdown);\n    threads.clear();\n    sessionIntervals.clear();\n\n    executor.shutdown();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HeartbeatThread.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.time.Clock;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.WeakHashMap;\nimport java.util.concurrent.RejectedExecutionException;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Manages heartbeat for all sessions that share the same heartbeat interval.\n *\n * <p>Each unique interval gets its own HeartbeatThread instance. Uses WeakHashMap to automatically\n * clean up sessions that are garbage collected.\n *\n * <p>Thread-safe: Session management methods are synchronized. The scheduled {@code run()} method\n * coordinates with shared state using the class's existing concurrency controls.\n */\nclass HeartbeatThread implements Runnable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(HeartbeatThread.class);\n\n  private final long intervalSeconds;\n  private final ScheduledExecutorService executor;\n  private final Clock clock;\n\n  /**\n   * Sessions managed by this thread. Uses a Set backed by WeakHashMap to automatically remove\n   * sessions when garbage collected, preventing memory leaks if cleanup fails.\n   */\n  private final Set<SFSession> sessions = Collections.newSetFromMap(new WeakHashMap<>());\n\n  private volatile ScheduledFuture<?> scheduledTask;\n  private volatile long lastHeartbeatStartTimeInSecs;\n  private volatile boolean isShutdown = false;\n\n  /**\n   * Creates a new HeartbeatThread for a specific interval.\n   *\n   * @param intervalSeconds How often to heartbeat (in seconds)\n   * @param executor Scheduler for running heartbeat tasks\n   * @param clock Time source (injectable for testing)\n   */\n  HeartbeatThread(long intervalSeconds, ScheduledExecutorService executor, Clock clock) {\n    if (intervalSeconds <= 0) {\n      throw new IllegalArgumentException(\"Interval must be positive: \" + intervalSeconds);\n    }\n    if (executor == null) {\n      throw new IllegalArgumentException(\"Executor cannot be null\");\n    }\n    if (clock == null) {\n      throw new IllegalArgumentException(\"Clock cannot be null\");\n    }\n\n    this.intervalSeconds = intervalSeconds;\n    this.executor = executor;\n    this.clock = clock;\n    this.lastHeartbeatStartTimeInSecs = 0;\n\n    logger.debug(\"Created HeartbeatThread for interval {}s\", intervalSeconds);\n  }\n\n  /**\n   * Add session to this heartbeat thread.\n   *\n   * <p>If this is the first session, starts the heartbeat task. If thread is already running, the\n   * new session will be included in the next scheduled heartbeat.\n   *\n   * @param session The session to add\n   * @return true if session was added, false if thread is shutdown\n   * @throws IllegalArgumentException if session is null\n   */\n  synchronized boolean addSession(SFSession session) {\n    if (session == null) {\n      throw new IllegalArgumentException(\"Session cannot be null\");\n    }\n\n    if (isShutdown) {\n      logger.warn(\n          \"Attempted to add session {} to shutdown HeartbeatThread (interval={}s)\",\n          session.getSessionId(),\n          intervalSeconds);\n      return false;\n    }\n\n    boolean wasEmpty = sessions.isEmpty();\n    sessions.add(session);\n\n    logger.debug(\n        \"Added session {} to HeartbeatThread (interval={}s), total sessions: {}\",\n        session.getSessionId(),\n        intervalSeconds,\n        sessions.size());\n\n    // Start heartbeat if this is the first session\n    if (wasEmpty) {\n      start();\n    }\n\n    return true;\n  }\n\n  /**\n   * Remove session from this heartbeat thread.\n   *\n   * <p>WeakHashMap ensures sessions are cleaned up even if this method isn't called.\n   *\n   * @param session The session to remove\n   */\n  synchronized void removeSession(SFSession session) {\n    sessions.remove(session);\n\n    logger.debug(\n        \"Removed session {} from HeartbeatThread (interval={}s), remaining sessions: {}\",\n        session.getSessionId(),\n        intervalSeconds,\n        sessions.size());\n\n    // Don't auto-shutdown here - let the registry decide when to cleanup\n  }\n\n  /**\n   * Check if no sessions remain.\n   *\n   * @return true if this thread has no sessions\n   */\n  synchronized boolean isEmpty() {\n    return sessions.isEmpty();\n  }\n\n  /**\n   * Get the current number of sessions.\n   *\n   * @return Number of sessions managed by this thread\n   */\n  @VisibleForTesting\n  synchronized int getSessionCount() {\n    return sessions.size();\n  }\n\n  /**\n   * Get the heartbeat interval in seconds.\n   *\n   * @return Interval in seconds\n   */\n  long getIntervalSeconds() {\n    return intervalSeconds;\n  }\n\n  /** Start scheduled heartbeat task. Called automatically when first session is added. */\n  private synchronized void start() {\n    if (isShutdown) {\n      logger.warn(\"Cannot start shutdown HeartbeatThread (interval={}s)\", intervalSeconds);\n      return;\n    }\n\n    if (scheduledTask != null) {\n      logger.debug(\"HeartbeatThread (interval={}s) already started\", intervalSeconds);\n      return;\n    }\n\n    logger.debug(\"Starting HeartbeatThread (interval={}s)\", intervalSeconds);\n    scheduleHeartbeat();\n  }\n\n  /** Schedule the next heartbeat task. */\n  private void scheduleHeartbeat() {\n    // Calculate elapsed time since last heartbeat\n    long currentTimeInSecs = clock.millis() / 1000;\n    long elapsedSecsSinceLastHeartbeat = currentTimeInSecs - lastHeartbeatStartTimeInSecs;\n\n    /*\n     * The initial delay is 0 if enough time has elapsed,\n     * otherwise it's the remaining time until the next interval.\n     */\n    long initialDelay = Math.max(intervalSeconds - elapsedSecsSinceLastHeartbeat, 0);\n\n    logger.debug(\n        \"Scheduling heartbeat task (interval={}s) with initial delay of {}s\",\n        intervalSeconds,\n        initialDelay);\n\n    // Use schedule() for single execution, not scheduleAtFixedRate()\n    // We manually reschedule after each run to:\n    // 1. Stop scheduling when sessions list becomes empty\n    // 2. Handle exceptions without stopping future executions\n    try {\n      scheduledTask = executor.schedule(this, initialDelay, TimeUnit.SECONDS);\n    } catch (RejectedExecutionException e) {\n      logger.error(\n          \"Failed to schedule heartbeat task (interval={}s): executor rejected task. Marking thread as shutdown.\",\n          intervalSeconds,\n          e);\n      isShutdown = true;\n    }\n  }\n\n  /**\n   * Stop scheduled heartbeat task and prevent new sessions from being added.\n   *\n   * <p>Called when no sessions remain or during cleanup.\n   */\n  synchronized void shutdown() {\n    if (isShutdown) {\n      logger.debug(\n          \"HeartbeatThread (interval={}s) already shutdown; performing cleanup\", intervalSeconds);\n    } else {\n      logger.debug(\"Shutting down HeartbeatThread (interval={}s)\", intervalSeconds);\n    }\n    isShutdown = true;\n\n    if (scheduledTask != null) {\n      scheduledTask.cancel(false);\n      scheduledTask = null;\n    }\n\n    sessions.clear();\n  }\n\n  /**\n   * Run heartbeat for all sessions in this thread.\n   *\n   * <p>Called by scheduler at fixed interval. Synchronization is only around accessing the sessions\n   * map, not around the actual heartbeat calls, to minimize blocking.\n   */\n  @Override\n  public void run() {\n    if (isShutdown) {\n      logger.debug(\"Skipping heartbeat run for shutdown thread (interval={}s)\", intervalSeconds);\n      return;\n    }\n\n    // Record current time as heartbeat start time\n    lastHeartbeatStartTimeInSecs = clock.millis() / 1000;\n\n    // Get a copy of sessions to heartbeat (outside synchronized block)\n    Set<SFSession> sessionsToHeartbeat = new HashSet<>();\n    synchronized (this) {\n      sessionsToHeartbeat.addAll(sessions);\n    }\n\n    logger.debug(\n        \"Running heartbeat for {} sessions (interval={}s)\",\n        sessionsToHeartbeat.size(),\n        intervalSeconds);\n\n    // Heartbeat each session (outside synchronized block to avoid blocking)\n    for (SFSession session : sessionsToHeartbeat) {\n      try {\n        session.heartbeat();\n      } catch (Throwable ex) {\n        logger.error(\n            \"Heartbeat error for session {} - message={}\",\n            session.getSessionId(),\n            ex.getMessage(),\n            ex);\n      }\n    }\n\n    // Schedule next heartbeat if there are still sessions\n    synchronized (this) {\n      if (!isShutdown && !sessions.isEmpty()) {\n        logger.debug(\"Scheduling next heartbeat run (interval={}s)\", intervalSeconds);\n        scheduleHeartbeat();\n      } else {\n        logger.debug(\n            \"Not scheduling next heartbeat (shutdown={}, sessions={})\",\n            isShutdown,\n            sessions.size());\n        scheduledTask = null;\n      }\n    }\n  }\n\n  /**\n   * Trigger heartbeat immediately without waiting for scheduled time.\n   *\n   * <p>For testing purposes only.\n   */\n  @VisibleForTesting\n  synchronized void triggerHeartbeatNow() {\n    if (!isShutdown) {\n      run();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpClientSettingsKey.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.Serializable;\nimport java.util.Objects;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * This class defines all non-static parameters needed to create an HttpClient object. It is used as\n * the key for the static hashmap of reusable http clients.\n */\npublic class HttpClientSettingsKey implements Serializable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(HttpClientSettingsKey.class);\n  private static final int DEFAULT_OCSP_RESPONDER_CONNECTION_TIMEOUT = 10000;\n  private static final String SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT =\n      \"SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT\";\n\n  private OCSPMode ocspMode;\n  private CertRevocationCheckMode revocationCheckMode;\n  private boolean allowCertificatesWithoutCrlUrl;\n  private boolean useProxy;\n  private String proxyHost = \"\";\n  private int proxyPort = 0;\n  private String nonProxyHosts = \"\";\n  private String proxyUser = \"\";\n  private String proxyPassword = \"\";\n  private String proxyProtocol = \"http\";\n  // Adds a suffix to the user agent header in the http requests made by the jdbc driver.\n  // More details in SNOW-717606\n  private String userAgentSuffix = \"\";\n\n  private Boolean gzipDisabled = false;\n  private Integer ocspTimeout = null;\n\n  public HttpClientSettingsKey(\n      OCSPMode mode,\n      String host,\n      int port,\n      String nonProxyHosts,\n      String user,\n      String password,\n      String scheme,\n      String userAgentSuffix,\n      Boolean gzipDisabled) {\n    this.useProxy = true;\n    this.ocspMode = mode != null ? mode : OCSPMode.FAIL_OPEN;\n    this.ocspTimeout = getDefaultOcspTimeout();\n    this.proxyHost = !isNullOrEmpty(host) ? host.trim() : \"\";\n    this.proxyPort = port;\n    this.nonProxyHosts = !isNullOrEmpty(nonProxyHosts) ? nonProxyHosts.trim() : \"\";\n    this.proxyUser = !isNullOrEmpty(user) ? user.trim() : \"\";\n    this.proxyPassword = !isNullOrEmpty(password) ? password.trim() : \"\";\n    this.proxyProtocol = !isNullOrEmpty(scheme) ? scheme.trim() : \"http\";\n    this.gzipDisabled = gzipDisabled;\n    this.userAgentSuffix = !isNullOrEmpty(userAgentSuffix) ? userAgentSuffix.trim() : \"\";\n  }\n\n  public HttpClientSettingsKey(OCSPMode mode) {\n    this.useProxy = false;\n    this.ocspMode = mode != null ? mode : OCSPMode.FAIL_OPEN;\n    this.ocspTimeout = getDefaultOcspTimeout();\n  }\n\n  HttpClientSettingsKey(OCSPMode mode, String userAgentSuffix, Boolean gzipDisabled) {\n    this(mode);\n    this.userAgentSuffix = !isNullOrEmpty(userAgentSuffix) ? userAgentSuffix.trim() : \"\";\n    this.gzipDisabled = gzipDisabled;\n  }\n\n  @Override\n  public boolean equals(final Object obj) {\n    if (obj instanceof HttpClientSettingsKey) {\n      HttpClientSettingsKey comparisonKey = (HttpClientSettingsKey) obj;\n      if (comparisonKey.ocspMode.getValue() == this.ocspMode.getValue()) {\n        if (comparisonKey.revocationCheckMode == this.revocationCheckMode\n            && comparisonKey.allowCertificatesWithoutCrlUrl\n                == this.allowCertificatesWithoutCrlUrl) {\n          if (comparisonKey.gzipDisabled.equals(this.gzipDisabled)) {\n            if (comparisonKey.userAgentSuffix.equalsIgnoreCase(this.userAgentSuffix)) {\n              if (Objects.equals(comparisonKey.ocspTimeout, this.ocspTimeout)) {\n                if (!comparisonKey.useProxy && !this.useProxy) {\n                  return true;\n                } else if (comparisonKey.proxyHost.equalsIgnoreCase(this.proxyHost)\n                    && comparisonKey.proxyPort == this.proxyPort\n                    && comparisonKey.proxyUser.equalsIgnoreCase(this.proxyUser)\n                    && comparisonKey.proxyPassword.equalsIgnoreCase(this.proxyPassword)\n                    && comparisonKey.proxyProtocol.equalsIgnoreCase(this.proxyProtocol)) {\n                  // update nonProxyHost if changed\n                  if (!this.nonProxyHosts.equalsIgnoreCase(comparisonKey.nonProxyHosts)) {\n                    comparisonKey.nonProxyHosts = this.nonProxyHosts;\n                  }\n                  return true;\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n    return false;\n  }\n\n  @Override\n  public int hashCode() {\n    return this.ocspMode.getValue()\n        + (this.proxyHost\n                + this.proxyPort\n                + this.proxyUser\n                + this.proxyPassword\n                + this.proxyProtocol)\n            .hashCode()\n        + Objects.hash(\n            this.revocationCheckMode, this.allowCertificatesWithoutCrlUrl, this.ocspTimeout);\n  }\n\n  public OCSPMode getOcspMode() {\n    return this.ocspMode;\n  }\n\n  public boolean usesProxy() {\n    return this.useProxy;\n  }\n\n  public String getProxyHost() {\n    return this.proxyHost;\n  }\n\n  public int getProxyPort() {\n    return this.proxyPort;\n  }\n\n  public String getProxyUser() {\n    return this.proxyUser;\n  }\n\n  public String getUserAgentSuffix() {\n    return this.userAgentSuffix;\n  }\n\n  /**\n   * Be careful of using this! Should only be called when password is later masked.\n   *\n   * @return proxy password\n   */\n  public String getProxyPassword() {\n    return this.proxyPassword;\n  }\n\n  public String getNonProxyHosts() {\n    return this.nonProxyHosts;\n  }\n\n  public HttpProtocol getProxyHttpProtocol() {\n    return this.proxyProtocol.equalsIgnoreCase(\"https\") ? HttpProtocol.HTTPS : HttpProtocol.HTTP;\n  }\n\n  public Boolean getGzipDisabled() {\n    return gzipDisabled;\n  }\n\n  public CertRevocationCheckMode getRevocationCheckMode() {\n    return revocationCheckMode;\n  }\n\n  public boolean isAllowCertificatesWithoutCrlUrl() {\n    return allowCertificatesWithoutCrlUrl;\n  }\n\n  public void setRevocationCheckMode(CertRevocationCheckMode revocationCheckMode) {\n    this.revocationCheckMode = revocationCheckMode;\n  }\n\n  public void setAllowCertificatesWithoutCrlUrl(boolean allowCertificatesWithoutCrlUrl) {\n    this.allowCertificatesWithoutCrlUrl = allowCertificatesWithoutCrlUrl;\n  }\n\n  public int getOcspTimeout() {\n    return ocspTimeout;\n  }\n\n  private static int getDefaultOcspTimeout() {\n    int timeout = DEFAULT_OCSP_RESPONDER_CONNECTION_TIMEOUT;\n    String configuredTimeout = systemGetProperty(SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT);\n    if (!isNullOrEmpty(configuredTimeout)) {\n      try {\n        timeout = Integer.parseInt(configuredTimeout);\n      } catch (Exception ex) {\n        // ignore invalid override and keep default\n        logger.debug(\"Invalid override for OCSP timeout: {}\", configuredTimeout);\n      }\n    }\n    return timeout;\n  }\n\n  @Override\n  public String toString() {\n    return \"HttpClientSettingsKey[\"\n        + \"ocspMode=\"\n        + ocspMode\n        + \", revocationCheckMode=\"\n        + revocationCheckMode\n        + \", allowCertificatesWithoutCrlUrl=\"\n        + allowCertificatesWithoutCrlUrl\n        + \", useProxy=\"\n        + useProxy\n        + \", proxyHost='\"\n        + proxyHost\n        + '\\''\n        + \", proxyPort=\"\n        + proxyPort\n        + \", nonProxyHosts='\"\n        + nonProxyHosts\n        + '\\''\n        + \", proxyUser='\"\n        + proxyUser\n        + '\\''\n        + \", proxyPassword is \"\n        + (proxyPassword.isEmpty() ? \"not set\" : \"set\")\n        + \", proxyProtocol='\"\n        + proxyProtocol\n        + '\\''\n        + \", userAgentSuffix='\"\n        + userAgentSuffix\n        + '\\''\n        + \", gzipDisabled=\"\n        + gzipDisabled\n        + \", ocspTimeout=\"\n        + ocspTimeout\n        + ']';\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpExecutingContext.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.internal.util.DecorrelatedJitterBackoff;\n\npublic class HttpExecutingContext {\n\n  // min backoff in milli before we retry due to transient issues\n  private static final long minBackoffMillis = 1000;\n\n  // max backoff in milli before we retry due to transient issues\n  // we double the backoff after each retry till we reach the max backoff\n  private static final long maxBackoffMillis = 16000;\n\n  // retry at least once even if timeout limit has been reached\n  private static final int MIN_RETRY_COUNT = 1;\n\n  // retry at least once even if timeout limit has been reached\n  private static final int DEFAULT_RETRY_TIMEOUT = 300;\n\n  private final String requestId;\n  private final String requestInfoScrubbed;\n  private final long startTime;\n  // start time for each request,\n  // used for keeping track how much time we have spent\n  // due to network issues so that we can compare against the user\n  // specified network timeout to make sure we do not retry infinitely\n  // when there are transient network/GS issues\n  private long startTimePerRequest;\n  // Used to indicate that this is a login/auth request and will be using the new retry strategy.\n  private boolean isLoginRequest;\n  //  Tracks the total time spent handling transient network issues and retries during HTTP requests\n  private long elapsedMilliForTransientIssues;\n  private long retryTimeout;\n  private long authTimeout;\n  private DecorrelatedJitterBackoff backoff;\n  private long backoffInMillis;\n  private int origSocketTimeout;\n  private String breakRetryReason;\n  private String breakRetryEventName;\n  private String lastStatusCodeForRetry;\n  private int retryCount;\n  private int maxRetries;\n  private boolean noRetry;\n  private int injectSocketTimeout;\n  private boolean retryHTTP403;\n  private boolean shouldRetry;\n  private boolean skipRetriesBecauseOf200; // todo create skip retry reason enum\n  private boolean withoutCookies;\n  private boolean includeRetryParameters;\n  private boolean includeSnowflakeHeaders;\n  private boolean unpackResponse;\n  private AtomicBoolean canceling;\n  private SFBaseSession sfSession;\n\n  public HttpExecutingContext(String requestIdStr, String requestInfoScrubbed) {\n    this.requestId = requestIdStr;\n    this.requestInfoScrubbed = requestInfoScrubbed;\n    this.startTime = System.currentTimeMillis();\n    this.startTimePerRequest = startTime;\n    this.backoff = new DecorrelatedJitterBackoff(getMinBackoffInMillis(), getMaxBackoffInMilli());\n    this.backoffInMillis = minBackoffMillis;\n  }\n\n  public String getRequestId() {\n    return requestId;\n  }\n\n  public long getStartTime() {\n    return startTime;\n  }\n\n  public long getStartTimePerRequest() {\n    return startTimePerRequest;\n  }\n\n  public void setStartTimePerRequest(long startTimePerRequest) {\n    this.startTimePerRequest = startTimePerRequest;\n  }\n\n  public boolean isLoginRequest() {\n    return isLoginRequest;\n  }\n\n  public void setLoginRequest(boolean loginRequest) {\n    isLoginRequest = loginRequest;\n  }\n\n  public long getElapsedMilliForTransientIssues() {\n    return elapsedMilliForTransientIssues;\n  }\n\n  public long getRetryTimeoutInMilliseconds() {\n    return retryTimeout * 1000;\n  }\n\n  public long getRetryTimeout() {\n    return retryTimeout;\n  }\n\n  public void setRetryTimeout(long retryTimeout) {\n    this.retryTimeout = retryTimeout;\n  }\n\n  public long getMinBackoffInMillis() {\n    return minBackoffMillis;\n  }\n\n  public long getBackoffInMillis() {\n    return backoffInMillis;\n  }\n\n  public void setBackoffInMillis(long backoffInMillis) {\n    this.backoffInMillis = backoffInMillis;\n  }\n\n  public long getMaxBackoffInMilli() {\n    return maxBackoffMillis;\n  }\n\n  public long getAuthTimeout() {\n    return authTimeout;\n  }\n\n  public long getAuthTimeoutInMilliseconds() {\n    return authTimeout * 1000;\n  }\n\n  public void setAuthTimeout(long authTimeout) {\n    this.authTimeout = authTimeout;\n  }\n\n  public DecorrelatedJitterBackoff getBackoff() {\n    return backoff;\n  }\n\n  public void setBackoff(DecorrelatedJitterBackoff backoff) {\n    this.backoff = backoff;\n  }\n\n  public int getOrigSocketTimeout() {\n    return origSocketTimeout;\n  }\n\n  public void setOrigSocketTimeout(int origSocketTimeout) {\n    this.origSocketTimeout = origSocketTimeout;\n  }\n\n  public String getBreakRetryReason() {\n    return breakRetryReason;\n  }\n\n  public void setBreakRetryReason(String breakRetryReason) {\n    this.breakRetryReason = breakRetryReason;\n  }\n\n  public String getBreakRetryEventName() {\n    return breakRetryEventName;\n  }\n\n  public void setBreakRetryEventName(String breakRetryEventName) {\n    this.breakRetryEventName = breakRetryEventName;\n  }\n\n  public String getLastStatusCodeForRetry() {\n    return lastStatusCodeForRetry;\n  }\n\n  public void setLastStatusCodeForRetry(String lastStatusCodeForRetry) {\n    this.lastStatusCodeForRetry = lastStatusCodeForRetry;\n  }\n\n  public int getRetryCount() {\n    return retryCount;\n  }\n\n  public void setRetryCount(int retryCount) {\n    this.retryCount = retryCount;\n  }\n\n  public void resetRetryCount() {\n    this.retryCount = 0;\n  }\n\n  public void incrementRetryCount() {\n    this.retryCount++;\n  }\n\n  public int getMaxRetries() {\n    return maxRetries;\n  }\n\n  public void setMaxRetries(int maxRetries) {\n    this.maxRetries = maxRetries;\n  }\n\n  public String getRequestInfoScrubbed() {\n    return requestInfoScrubbed;\n  }\n\n  public boolean isNoRetry() {\n    return noRetry;\n  }\n\n  public void setNoRetry(boolean noRetry) {\n    this.noRetry = noRetry;\n  }\n\n  public boolean isRetryHTTP403() {\n    return retryHTTP403;\n  }\n\n  public void setRetryHTTP403(boolean retryHTTP403) {\n    this.retryHTTP403 = retryHTTP403;\n  }\n\n  public boolean isShouldRetry() {\n    return shouldRetry;\n  }\n\n  public void setShouldRetry(boolean shouldRetry) {\n    this.shouldRetry = shouldRetry;\n  }\n\n  public void increaseElapsedMilliForTransientIssues(long elapsedMilliForLastCall) {\n    this.elapsedMilliForTransientIssues += elapsedMilliForLastCall;\n  }\n\n  public boolean elapsedTimeExceeded() {\n    return elapsedMilliForTransientIssues > getRetryTimeoutInMilliseconds();\n  }\n\n  public boolean moreThanMinRetries() {\n    return retryCount >= MIN_RETRY_COUNT;\n  }\n\n  public boolean maxRetriesExceeded() {\n    return maxRetries > 0 && retryCount >= maxRetries;\n  }\n\n  public boolean socketOrConnectTimeoutReached() {\n    return authTimeout > 0\n        && elapsedMilliForTransientIssues > getAuthTimeoutInMilliseconds()\n        && (origSocketTimeout == 0 || elapsedMilliForTransientIssues < origSocketTimeout);\n  }\n\n  public AtomicBoolean getCanceling() {\n    return canceling;\n  }\n\n  public void setCanceling(AtomicBoolean canceling) {\n    this.canceling = canceling;\n  }\n\n  public boolean isIncludeSnowflakeHeaders() {\n    return includeSnowflakeHeaders;\n  }\n\n  public void setIncludeSnowflakeHeaders(boolean includeSnowflakeHeaders) {\n    this.includeSnowflakeHeaders = includeSnowflakeHeaders;\n  }\n\n  public boolean isWithoutCookies() {\n    return withoutCookies;\n  }\n\n  public void setWithoutCookies(boolean withoutCookies) {\n    this.withoutCookies = withoutCookies;\n  }\n\n  public int isInjectSocketTimeout() {\n    return injectSocketTimeout;\n  }\n\n  public void setInjectSocketTimeout(int injectSocketTimeout) {\n    this.injectSocketTimeout = injectSocketTimeout;\n  }\n\n  public int getInjectSocketTimeout() {\n    return injectSocketTimeout;\n  }\n\n  public boolean isIncludeRetryParameters() {\n    return includeRetryParameters;\n  }\n\n  public boolean isUnpackResponse() {\n    return unpackResponse;\n  }\n\n  public void setUnpackResponse(boolean unpackResponse) {\n    this.unpackResponse = unpackResponse;\n  }\n\n  public void setIncludeRetryParameters(boolean includeRetryParameters) {\n    this.includeRetryParameters = includeRetryParameters;\n  }\n\n  public boolean isSkipRetriesBecauseOf200() {\n    return skipRetriesBecauseOf200;\n  }\n\n  public void setSkipRetriesBecauseOf200(boolean skipRetriesBecauseOf200) {\n    this.skipRetriesBecauseOf200 = skipRetriesBecauseOf200;\n  }\n\n  public SFBaseSession getSfSession() {\n    return sfSession;\n  }\n\n  public void setSfSession(SFBaseSession sfSession) {\n    this.sfSession = sfSession;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpExecutingContextBuilder.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Builder class for {@link HttpExecutingContext}. Provides a fluent interface for constructing\n * HttpExecutingContext instances with many optional parameters.\n */\npublic class HttpExecutingContextBuilder {\n  private final String requestId;\n  private final String requestInfoScrubbed;\n  private long retryTimeout;\n  private long authTimeout;\n  private int origSocketTimeout;\n  private int maxRetries;\n  private int injectSocketTimeout;\n  private AtomicBoolean canceling;\n  private boolean withoutCookies;\n  private boolean includeRetryParameters;\n  private boolean includeSnowflakeHeaders;\n  private boolean retryHTTP403;\n  private boolean noRetry;\n  private boolean unpackResponse;\n  private boolean isLoginRequest;\n  private SFBaseSession sfSession;\n\n  /**\n   * Creates a new builder instance with required parameters.\n   *\n   * @param requestId Request ID for logging and tracking\n   * @param requestInfoScrubbed Scrubbed request info for logging\n   */\n  public HttpExecutingContextBuilder(String requestId, String requestInfoScrubbed) {\n    this.requestId = requestId;\n    this.requestInfoScrubbed = requestInfoScrubbed;\n  }\n\n  /**\n   * Copy constructor to create a new builder from an existing HttpExecutingContext.\n   *\n   * @param context The context to copy settings from\n   */\n  public HttpExecutingContextBuilder(HttpExecutingContext context) {\n    this.requestId = context.getRequestId();\n    this.requestInfoScrubbed = context.getRequestInfoScrubbed();\n    this.retryTimeout = context.getRetryTimeout();\n    this.authTimeout = context.getAuthTimeout();\n    this.origSocketTimeout = context.getOrigSocketTimeout();\n    this.maxRetries = context.getMaxRetries();\n    this.injectSocketTimeout = context.getInjectSocketTimeout();\n    this.canceling = context.getCanceling();\n    this.withoutCookies = context.isWithoutCookies();\n    this.includeRetryParameters = context.isIncludeRetryParameters();\n    this.includeSnowflakeHeaders = context.isIncludeSnowflakeHeaders();\n    this.retryHTTP403 = context.isRetryHTTP403();\n    this.noRetry = context.isNoRetry();\n    this.unpackResponse = context.isUnpackResponse();\n    this.isLoginRequest = context.isLoginRequest();\n  }\n\n  /**\n   * Creates a new builder for a login request with common defaults.\n   *\n   * @param requestId Request ID for logging and tracking\n   * @param requestInfoScrubbed Scrubbed request info for logging\n   * @return A new builder instance configured for login requests\n   */\n  public static HttpExecutingContextBuilder forLogin(String requestId, String requestInfoScrubbed) {\n    return new HttpExecutingContextBuilder(requestId, requestInfoScrubbed)\n        .loginRequest(true)\n        .includeSnowflakeHeaders(true)\n        .retryHTTP403(true);\n  }\n\n  /**\n   * Creates a new builder for a query request with common defaults.\n   *\n   * @param requestId Request ID for logging and tracking\n   * @param requestInfoScrubbed Scrubbed request info for logging\n   * @return A new builder instance configured for query requests\n   */\n  public static HttpExecutingContextBuilder forQuery(String requestId, String requestInfoScrubbed) {\n    return new HttpExecutingContextBuilder(requestId, requestInfoScrubbed)\n        .includeRetryParameters(true)\n        .includeSnowflakeHeaders(true)\n        .unpackResponse(true);\n  }\n\n  /**\n   * Creates a new builder for a simple HTTP request with minimal retry settings.\n   *\n   * @param requestId Request ID for logging and tracking\n   * @param requestInfoScrubbed Scrubbed request info for logging\n   * @return A new builder instance configured for simple requests\n   */\n  public static HttpExecutingContextBuilder forSimpleRequest(\n      String requestId, String requestInfoScrubbed) {\n    return new HttpExecutingContextBuilder(requestId, requestInfoScrubbed)\n        .noRetry(true)\n        .includeSnowflakeHeaders(true);\n  }\n\n  /**\n   * Creates a new builder with default settings for retryable requests.\n   *\n   * @param requestId Request ID for logging and tracking\n   * @param requestInfoScrubbed Scrubbed request info for logging\n   * @return A new builder instance with default retry settings\n   */\n  public static HttpExecutingContextBuilder withRequest(\n      String requestId, String requestInfoScrubbed) {\n    return new HttpExecutingContextBuilder(requestId, requestInfoScrubbed);\n  }\n\n  /**\n   * Sets the retry timeout in seconds.\n   *\n   * @param retryTimeout Retry timeout in seconds\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder retryTimeout(long retryTimeout) {\n    this.retryTimeout = retryTimeout;\n    return this;\n  }\n\n  /**\n   * Sets the authentication timeout in seconds.\n   *\n   * @param authTimeout Authentication timeout in seconds\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder authTimeout(long authTimeout) {\n    this.authTimeout = authTimeout;\n    return this;\n  }\n\n  /**\n   * Sets the original socket timeout in milliseconds.\n   *\n   * @param origSocketTimeout Socket timeout in milliseconds\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder origSocketTimeout(int origSocketTimeout) {\n    this.origSocketTimeout = origSocketTimeout;\n    return this;\n  }\n\n  /**\n   * Sets the maximum number of retries.\n   *\n   * @param maxRetries Maximum number of retries\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder maxRetries(int maxRetries) {\n    this.maxRetries = maxRetries;\n    return this;\n  }\n\n  /**\n   * Sets the injected socket timeout for testing.\n   *\n   * @param injectSocketTimeout Socket timeout to inject\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder injectSocketTimeout(int injectSocketTimeout) {\n    this.injectSocketTimeout = injectSocketTimeout;\n    return this;\n  }\n\n  /**\n   * Sets the canceling flag.\n   *\n   * @param canceling AtomicBoolean for cancellation\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder canceling(AtomicBoolean canceling) {\n    this.canceling = canceling;\n    return this;\n  }\n\n  /**\n   * Sets whether to disable cookies.\n   *\n   * @param withoutCookies true to disable cookies\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder withoutCookies(boolean withoutCookies) {\n    this.withoutCookies = withoutCookies;\n    return this;\n  }\n\n  /**\n   * Sets whether to include retry parameters in requests.\n   *\n   * @param includeRetryParameters true to include retry parameters\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder includeRetryParameters(boolean includeRetryParameters) {\n    this.includeRetryParameters = includeRetryParameters;\n    return this;\n  }\n\n  /**\n   * Sets whether to include request GUID.\n   *\n   * @param includeSnowflakeHeaders true to include request GUID and other Snowflake headers\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder includeSnowflakeHeaders(boolean includeSnowflakeHeaders) {\n    this.includeSnowflakeHeaders = includeSnowflakeHeaders;\n    return this;\n  }\n\n  /**\n   * Sets whether to retry on HTTP 403 errors.\n   *\n   * @param retryHTTP403 true to retry on HTTP 403\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder retryHTTP403(boolean retryHTTP403) {\n    this.retryHTTP403 = retryHTTP403;\n    return this;\n  }\n\n  /**\n   * Sets whether to disable retries.\n   *\n   * @param noRetry true to disable retries\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder noRetry(boolean noRetry) {\n    this.noRetry = noRetry;\n    return this;\n  }\n\n  /**\n   * Sets whether to unpack the response.\n   *\n   * @param unpackResponse true to unpack response\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder unpackResponse(boolean unpackResponse) {\n    this.unpackResponse = unpackResponse;\n    return this;\n  }\n\n  /**\n   * Sets whether this is a login request.\n   *\n   * @param isLoginRequest true if this is a login request\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder loginRequest(boolean isLoginRequest) {\n    this.isLoginRequest = isLoginRequest;\n    return this;\n  }\n\n  /**\n   * Sets the session associated with this context.\n   *\n   * @param sfSession SFBaseSession to associate with this context\n   * @return this builder instance\n   */\n  public HttpExecutingContextBuilder withSfSession(SFBaseSession sfSession) {\n    this.sfSession = sfSession;\n    return this;\n  }\n\n  /**\n   * Builds and returns a new HttpExecutingContext instance with the configured parameters.\n   *\n   * @return A new HttpExecutingContext instance\n   */\n  public HttpExecutingContext build() {\n    HttpExecutingContext context = new HttpExecutingContext(requestId, requestInfoScrubbed);\n    context.setRetryTimeout(retryTimeout);\n    context.setAuthTimeout(authTimeout);\n    context.setOrigSocketTimeout(origSocketTimeout);\n    context.setMaxRetries(maxRetries);\n    context.setInjectSocketTimeout(injectSocketTimeout);\n    context.setCanceling(canceling);\n    context.setWithoutCookies(withoutCookies);\n    context.setIncludeRetryParameters(includeRetryParameters);\n    context.setIncludeSnowflakeHeaders(includeSnowflakeHeaders);\n    context.setRetryHTTP403(retryHTTP403);\n    context.setNoRetry(noRetry);\n    context.setUnpackResponse(unpackResponse);\n    context.setLoginRequest(isLoginRequest);\n    context.setSfSession(sfSession);\n    return context;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpProtocol.java",
    "content": "package net.snowflake.client.internal.core;\n\npublic enum HttpProtocol {\n  HTTP(\"http\"),\n\n  HTTPS(\"https\");\n\n  private final String scheme;\n\n  HttpProtocol(String scheme) {\n    this.scheme = scheme;\n  }\n\n  public String getScheme() {\n    return scheme;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpResponseContextDto.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport org.apache.http.client.methods.CloseableHttpResponse;\n\npublic class HttpResponseContextDto {\n\n  private CloseableHttpResponse httpResponse;\n  private String unpackedCloseableHttpResponse;\n  private Exception savedEx;\n\n  // Constructors\n  public HttpResponseContextDto() {}\n\n  public HttpResponseContextDto(\n      CloseableHttpResponse httpResponse, String unpackedCloseableHttpResponse) {\n    this.httpResponse = httpResponse;\n    this.unpackedCloseableHttpResponse = unpackedCloseableHttpResponse;\n  }\n\n  public CloseableHttpResponse getHttpResponse() {\n    return httpResponse;\n  }\n\n  public void setHttpResponse(CloseableHttpResponse httpResponse) {\n    this.httpResponse = httpResponse;\n  }\n\n  public String getUnpackedCloseableHttpResponse() {\n    return unpackedCloseableHttpResponse;\n  }\n\n  public void setUnpackedCloseableHttpResponse(String unpackedCloseableHttpResponse) {\n    this.unpackedCloseableHttpResponse = unpackedCloseableHttpResponse;\n  }\n\n  public Exception getSavedEx() {\n    return savedEx;\n  }\n\n  public void setSavedEx(Exception savedEx) {\n    this.savedEx = savedEx;\n  }\n\n  @Override\n  public String toString() {\n    return \"CloseableHttpResponseContextDto{\"\n        + \"httpResponse=\"\n        + httpResponse\n        + \", unpackedCloseableHttpResponse=\"\n        + unpackedCloseableHttpResponse\n        + '}';\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpResponseWithHeaders.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n * Simple container for HTTP response data including both body and headers. This provides a clean\n * interface for methods that need access to response headers without exposing internal HTTP client\n * implementation details.\n */\npublic class HttpResponseWithHeaders {\n  private final String responseBody;\n  private final Map<String, String> headers;\n\n  public HttpResponseWithHeaders(String responseBody, Map<String, String> headers) {\n    this.responseBody = responseBody;\n    this.headers = headers != null ? Collections.unmodifiableMap(headers) : Collections.emptyMap();\n  }\n\n  /**\n   * Get the HTTP response body as a string.\n   *\n   * @return the response body\n   */\n  public String getResponseBody() {\n    return responseBody;\n  }\n\n  /**\n   * Get the HTTP response headers as an immutable map. If multiple headers exist with the same\n   * name, only the last value is included.\n   *\n   * @return immutable map of header name to header value\n   */\n  public Map<String, String> getHeaders() {\n    return headers;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/HttpUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.apache.http.client.config.CookieSpecs.DEFAULT;\nimport static org.apache.http.client.config.CookieSpecs.IGNORE_COOKIES;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.net.Proxy;\nimport java.net.Socket;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport javax.annotation.Nullable;\nimport javax.net.ssl.TrustManager;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport net.snowflake.client.internal.jdbc.RestRequest;\nimport net.snowflake.client.internal.jdbc.RetryContextManager;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.log.SFLoggerUtil;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport org.apache.http.Header;\nimport org.apache.http.HttpHost;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.auth.AuthScope;\nimport org.apache.http.auth.Credentials;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.client.CredentialsProvider;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.config.Registry;\nimport org.apache.http.config.RegistryBuilder;\nimport org.apache.http.conn.ClientConnectionManager;\nimport org.apache.http.conn.HttpClientConnectionManager;\nimport org.apache.http.conn.socket.ConnectionSocketFactory;\nimport org.apache.http.conn.socket.PlainConnectionSocketFactory;\nimport org.apache.http.impl.client.BasicCredentialsProvider;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.DefaultRedirectStrategy;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.impl.client.LaxRedirectStrategy;\nimport org.apache.http.impl.conn.PoolingHttpClientConnectionManager;\nimport org.apache.http.protocol.HttpContext;\nimport org.apache.http.ssl.SSLInitializationException;\n\n/** HttpUtil class */\npublic class HttpUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(HttpUtil.class);\n\n  static final int DEFAULT_MAX_CONNECTIONS = 300;\n  static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 300;\n  private static final int DEFAULT_HTTP_CLIENT_CONNECTION_TIMEOUT_IN_MS = 60000;\n  static final int DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT_IN_MS = 300000; // ms\n  static final int DEFAULT_TTL = 60; // secs\n  static final int DEFAULT_IDLE_CONNECTION_TIMEOUT = 30; // secs\n  static final int DEFAULT_DOWNLOADED_CONDITION_TIMEOUT = 3600; // secs\n\n  public static final String JDBC_TTL = \"net.snowflake.jdbc.ttl\";\n  public static final String JDBC_IDLE_CONNECTION_PROPERTY =\n      \"net.snowflake.jdbc.idle_connection_timeout\";\n  public static final String JDBC_MAX_CONNECTIONS_PROPERTY = \"net.snowflake.jdbc.max_connections\";\n  public static final String JDBC_MAX_CONNECTIONS_PER_ROUTE_PROPERTY =\n      \"net.snowflake.jdbc.max_connections_per_route\";\n  private static Duration connectionTimeout;\n  private static Duration socketTimeout;\n\n  /**\n   * The unique httpClient shared by all connections. This will benefit long-lived clients. Key =\n   * proxy host + proxy port + nonProxyHosts, Value = Map of [ClientSettings, HttpClient]\n   */\n  public static Map<HttpClientSettingsKey, CloseableHttpClient> httpClient =\n      new ConcurrentHashMap<>();\n\n  /**\n   * The unique httpClient map shared by all connections that don't want decompression. This will\n   * benefit long-lived clients. Key = proxy host + proxy port + nonProxyHosts, Value = Map\n   * [ClientSettings, HttpClient]\n   */\n  private static Map<HttpClientSettingsKey, CloseableHttpClient> httpClientWithoutDecompression =\n      new ConcurrentHashMap<>();\n\n  /** The map of snowflake route planners */\n  static Map<HttpClientSettingsKey, SnowflakeMutableProxyRoutePlanner> httpClientRoutePlanner =\n      new ConcurrentHashMap<>();\n\n  /** Handle on the static connection manager, to gather statistics mainly */\n  private static final Map<HttpClientSettingsKey, PoolingHttpClientConnectionManager>\n      connectionManagers = new ConcurrentHashMap<>();\n\n  /** default request configuration, to be copied on individual requests. */\n  private static RequestConfig defaultRequestConfig = null;\n\n  private static boolean socksProxyDisabled = false;\n\n  public static void reset() {\n    setConnectionTimeout(DEFAULT_HTTP_CLIENT_CONNECTION_TIMEOUT_IN_MS);\n    setSocketTimeout(DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT_IN_MS);\n    httpClient\n        .values()\n        .forEach(\n            client -> {\n              try {\n                client.close();\n              } catch (IOException e) {\n                logger.warn(\"Cannot close HTTP client\", e);\n              }\n            });\n    httpClient.clear();\n    httpClientWithoutDecompression\n        .values()\n        .forEach(\n            client -> {\n              try {\n                client.close();\n              } catch (IOException e) {\n                logger.warn(\"Cannot close HTTP client\", e);\n              }\n            });\n    httpClientWithoutDecompression.clear();\n    httpClientRoutePlanner.clear();\n    connectionManagers.values().forEach(PoolingHttpClientConnectionManager::close);\n    connectionManagers.clear();\n    defaultRequestConfig = null;\n  }\n\n  public static Duration getConnectionTimeout() {\n    return connectionTimeout != null\n        ? connectionTimeout\n        : Duration.ofMillis(DEFAULT_HTTP_CLIENT_CONNECTION_TIMEOUT_IN_MS);\n  }\n\n  public static Duration getSocketTimeout() {\n    return socketTimeout != null\n        ? socketTimeout\n        : Duration.ofMillis(DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT_IN_MS);\n  }\n\n  public static void setConnectionTimeout(int timeout) {\n    connectionTimeout = Duration.ofMillis(timeout);\n    initDefaultRequestConfig(connectionTimeout.toMillis(), getSocketTimeout().toMillis());\n  }\n\n  public static void setSocketTimeout(int timeout) {\n    socketTimeout = Duration.ofMillis(timeout);\n    initDefaultRequestConfig(getConnectionTimeout().toMillis(), socketTimeout.toMillis());\n  }\n\n  public static long getDownloadedConditionTimeoutInSeconds() {\n    return DEFAULT_DOWNLOADED_CONDITION_TIMEOUT;\n  }\n\n  /**\n   * Constructs a user-agent header with the following pattern: connector_name/connector_version\n   * (os-platform_info) language_implementation/language_version\n   *\n   * @param customSuffix custom suffix that would be appended to user agent to identify the jdbc\n   *     usage.\n   * @return string for user-agent header\n   */\n  @VisibleForTesting\n  static String buildUserAgent(String customSuffix) {\n    // Start with connector name\n    StringBuilder builder = new StringBuilder(\"JDBC/\");\n    // Append connector version and parenthesis start\n    builder.append(SnowflakeDriver.getImplementationVersion());\n    builder.append(\" (\");\n    // Generate OS platform and version from system properties\n    String osPlatform = (systemGetProperty(\"os.name\") != null) ? systemGetProperty(\"os.name\") : \"\";\n    String osVersion =\n        (systemGetProperty(\"os.version\") != null) ? systemGetProperty(\"os.version\") : \"\";\n    // Append OS platform and version separated by a space\n    builder.append(osPlatform);\n    builder.append(\" \");\n    builder.append(osVersion);\n    // Append language name\n    builder.append(\") JAVA/\");\n    // Generate string for language version from system properties and append it\n    String languageVersion =\n        (systemGetProperty(\"java.version\") != null) ? systemGetProperty(\"java.version\") : \"\";\n    builder.append(languageVersion);\n    if (!customSuffix.isEmpty()) {\n      builder.append(\" \" + customSuffix);\n    }\n    String userAgent = builder.toString();\n    return userAgent;\n  }\n\n  /**\n   * Build an Http client using our set of default.\n   *\n   * @param key Key to HttpClient hashmap containing OCSP mode and proxy information, could be null\n   * @param ocspCacheFile OCSP response cache file. If null, the default OCSP response file will be\n   *     used.\n   * @param downloadUnCompressed Whether the HTTP client should be built requesting no decompression\n   * @return HttpClient object\n   */\n  public static CloseableHttpClient buildHttpClient(\n      @Nullable HttpClientSettingsKey key, File ocspCacheFile, boolean downloadUnCompressed) {\n    return buildHttpClient(key, ocspCacheFile, downloadUnCompressed, null);\n  }\n\n  /**\n   * Build an Http client using our set of default.\n   *\n   * @param key Key to HttpClient hashmap containing OCSP mode and proxy information, could be null\n   * @param ocspCacheFile OCSP response cache file. If null, the default OCSP response file will be\n   *     used.\n   * @param downloadUnCompressed Whether the HTTP client should be built requesting no decompression\n   * @param httpHeadersCustomizers List of HTTP headers customizers\n   * @return HttpClient object\n   */\n  public static CloseableHttpClient buildHttpClient(\n      @Nullable HttpClientSettingsKey key,\n      File ocspCacheFile,\n      boolean downloadUnCompressed,\n      List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    int idleConnectionTimeout =\n        SystemUtil.convertSystemPropertyToIntValue(\n            JDBC_IDLE_CONNECTION_PROPERTY, DEFAULT_IDLE_CONNECTION_TIMEOUT);\n    logger.debug(\n        \"Building http client with client settings key: {}, ocsp cache file: {}, download uncompressed: {}\",\n        key != null ? key.toString() : null,\n        ocspCacheFile,\n        downloadUnCompressed);\n    // Build a connection manager with enough connections\n    HttpClientConnectionManager connectionManager =\n        connectionManagers.computeIfAbsent(\n            key, httpClientSettingsKey -> initHttpClientConnectionManager(key, ocspCacheFile));\n\n    logger.debug(\"Disabling cookie management for http client\");\n    String userAgentSuffix = key != null ? key.getUserAgentSuffix() : \"\";\n    HttpClientBuilder httpClientBuilder =\n        HttpClientBuilder.create()\n            .setConnectionManager(connectionManager)\n            // Support JVM proxy settings\n            .useSystemProperties()\n            .evictIdleConnections(idleConnectionTimeout, TimeUnit.SECONDS)\n            .evictExpiredConnections()\n            .setRedirectStrategy(\n                new LaxRedirectStrategy()) // handle /POST redirects and retry if failed\n            .setUserAgent(buildUserAgent(userAgentSuffix)) // needed for Okta\n            .disableCookieManagement() // SNOW-39748\n            .setDefaultRequestConfig(defaultRequestConfig);\n    if (key != null && key.usesProxy()) {\n      HttpHost proxy =\n          new HttpHost(\n              key.getProxyHost(), key.getProxyPort(), key.getProxyHttpProtocol().getScheme());\n      logger.debug(\n          \"Configuring proxy and route planner - host: {}, port: {}, scheme: {}, nonProxyHosts: {}\",\n          key.getProxyHost(),\n          key.getProxyPort(),\n          key.getProxyHttpProtocol().getScheme(),\n          key.getNonProxyHosts());\n      // use the custom proxy properties\n      SnowflakeMutableProxyRoutePlanner sdkProxyRoutePlanner =\n          httpClientRoutePlanner.computeIfAbsent(\n              key,\n              k ->\n                  new SnowflakeMutableProxyRoutePlanner(\n                      key.getProxyHost(),\n                      key.getProxyPort(),\n                      key.getProxyHttpProtocol(),\n                      key.getNonProxyHosts()));\n      httpClientBuilder.setProxy(proxy).setRoutePlanner(sdkProxyRoutePlanner);\n      if (!isNullOrEmpty(key.getProxyUser()) && !isNullOrEmpty(key.getProxyPassword())) {\n        Credentials credentials =\n            new UsernamePasswordCredentials(key.getProxyUser(), key.getProxyPassword());\n        AuthScope authScope = new AuthScope(key.getProxyHost(), key.getProxyPort());\n        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n        logger.debug(\n            \"Using user: {}, password is {} for proxy host: {}, port: {}\",\n            key.getProxyUser(),\n            SFLoggerUtil.isVariableProvided(key.getProxyPassword()),\n            key.getProxyHost(),\n            key.getProxyPort());\n        credentialsProvider.setCredentials(authScope, credentials);\n        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n      }\n    }\n    if (downloadUnCompressed) {\n      logger.debug(\"Disabling content compression for http client\");\n      httpClientBuilder.disableContentCompression();\n    }\n    if (httpHeadersCustomizers != null && !httpHeadersCustomizers.isEmpty()) {\n      logger.debug(\"Setting up http headers customizers\");\n      httpClientBuilder.setRetryHandler(new AttributeEnhancingHttpRequestRetryHandler());\n      httpClientBuilder.addInterceptorLast(\n          new HeaderCustomizerHttpRequestInterceptor(httpHeadersCustomizers));\n    }\n    return httpClientBuilder.build();\n  }\n\n  private static PoolingHttpClientConnectionManager initHttpClientConnectionManager(\n      HttpClientSettingsKey key, File ocspCacheFile) {\n    int timeToLiveSeconds = SystemUtil.convertSystemPropertyToIntValue(JDBC_TTL, DEFAULT_TTL);\n    long validateAfterInactivitySeconds =\n        SystemUtil.convertSystemPropertyToIntValue(\n            JDBC_IDLE_CONNECTION_PROPERTY, DEFAULT_IDLE_CONNECTION_TIMEOUT);\n    long connectTimeout = getConnectionTimeout().toMillis();\n    long socketTimeout = getSocketTimeout().toMillis();\n    logger.debug(\n        \"Connection pooling manager connect timeout: {} ms, socket timeout: {} ms, ttl: {} s, validate after inactivity: %s\",\n        connectTimeout, socketTimeout, timeToLiveSeconds, validateAfterInactivitySeconds);\n\n    // Create default request config without proxy since different connections could use different\n    // proxies in multi tenant environments\n    // Proxy is set later with route planner\n    if (defaultRequestConfig == null) {\n      initDefaultRequestConfig(connectTimeout, socketTimeout);\n    }\n\n    try {\n      TrustManager[] trustManagers = configureTrustManagerIfNeeded(key, ocspCacheFile);\n      logger.debug(\n          \"Registering https connection socket factory with socks proxy disabled: {} and http \"\n              + \"connection socket factory\",\n          socksProxyDisabled);\n\n      Registry<ConnectionSocketFactory> registry =\n          RegistryBuilder.<ConnectionSocketFactory>create()\n              .register(\n                  \"https\", new SFSSLConnectionSocketFactory(trustManagers, socksProxyDisabled))\n              .register(\"http\", new SFConnectionSocketFactory())\n              .build();\n      PoolingHttpClientConnectionManager connectionManager =\n          new PoolingHttpClientConnectionManager(\n              registry, null, null, null, timeToLiveSeconds, TimeUnit.SECONDS);\n      int maxConnections =\n          SystemUtil.convertSystemPropertyToIntValue(\n              JDBC_MAX_CONNECTIONS_PROPERTY, DEFAULT_MAX_CONNECTIONS);\n      int maxConnectionsPerRoute =\n          SystemUtil.convertSystemPropertyToIntValue(\n              JDBC_MAX_CONNECTIONS_PER_ROUTE_PROPERTY, DEFAULT_MAX_CONNECTIONS_PER_ROUTE);\n      logger.debug(\n          \"Max connections total in connection pooling manager: {}; max connections per route: {}\",\n          maxConnections,\n          maxConnectionsPerRoute);\n      connectionManager.setMaxTotal(maxConnections);\n      connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);\n      connectionManager.setValidateAfterInactivity((int) validateAfterInactivitySeconds);\n      return connectionManager;\n    } catch (NoSuchAlgorithmException | KeyManagementException ex) {\n      throw new SSLInitializationException(ex.getMessage(), ex);\n    }\n  }\n\n  static TrustManager[] configureTrustManagerIfNeeded(\n      HttpClientSettingsKey key, File ocspCacheFile) {\n    if (key != null && key.getOcspMode() != OCSPMode.DISABLE_OCSP_CHECKS) {\n      // A custom TrustManager is required only if disableOCSPChecks is disabled,\n      // which is by default in the production. disableOCSPChecks can be enabled\n      // 1) OCSP service is down for reasons, 2) PowerMock test that doesn't\n      // care OCSP checks.\n      // OCSP FailOpen is ON by default\n      try {\n        if (ocspCacheFile == null) {\n          logger.debug(\"Instantiating trust manager with default ocsp cache file\");\n        } else {\n          logger.debug(\"Instantiating trust manager with ocsp cache file: {}\", ocspCacheFile);\n        }\n        return new TrustManager[] {new SFTrustManager(key, ocspCacheFile)};\n      } catch (Exception | Error err) {\n        // dump error stack\n        StringWriter errors = new StringWriter();\n        err.printStackTrace(new PrintWriter(errors));\n        logger.error(errors.toString(), true);\n        throw new RuntimeException(err); // rethrow the exception\n      }\n    } else if (key != null\n        && key.getOcspMode() == OCSPMode.DISABLE_OCSP_CHECKS\n        && crlRevocationChecksDisabled(key)) {\n      logger.debug(\n          \"Omitting trust manager instantiation as OCSP mode is set to {}\", key.getOcspMode());\n    } else if (key != null && !crlRevocationChecksDisabled(key)) {\n      try {\n        logger.debug(\"Instantiating trust manager with CRL based revocation checks\");\n        return new TrustManager[] {SFCrlTrustManagerFactory.createCrlTrustManager(key)};\n      } catch (CertificateException e) {\n        throw new SSLInitializationException(e.getMessage(), e);\n      }\n    }\n    logger.debug(\"Omitting trust manager instantiation as configuration is not provided\");\n    return null;\n  }\n\n  private static boolean crlRevocationChecksDisabled(HttpClientSettingsKey key) {\n    return key.getRevocationCheckMode() == null\n        || key.getRevocationCheckMode() == CertRevocationCheckMode.DISABLED;\n  }\n\n  private static void initDefaultRequestConfig(long connectTimeout, long socketTimeout) {\n    RequestConfig.Builder builder =\n        RequestConfig.custom()\n            .setConnectTimeout((int) connectTimeout)\n            .setConnectionRequestTimeout((int) connectTimeout)\n            .setSocketTimeout((int) socketTimeout);\n    logger.debug(\n        \"Rebuilding request config. Connect timeout: {} ms, connection request timeout: {} ms, socket timeout: {} ms\",\n        connectTimeout,\n        connectTimeout,\n        socketTimeout);\n    defaultRequestConfig = builder.build();\n  }\n\n  public static void updateRoutePlanner(HttpClientSettingsKey key) {\n    if (httpClientRoutePlanner.containsKey(key)\n        && !httpClientRoutePlanner\n            .get(key)\n            .getNonProxyHosts()\n            .equalsIgnoreCase(key.getNonProxyHosts())) {\n      logger.debug(\n          \"Updating route planner non-proxy hosts for proxy: {}:{} to: {}\",\n          key.getProxyHost(),\n          key.getProxyPort(),\n          key.getNonProxyHosts());\n      httpClientRoutePlanner.get(key).setNonProxyHosts(key.getNonProxyHosts());\n    }\n  }\n\n  /**\n   * Gets HttpClient with insecureMode false\n   *\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @return HttpClient object shared across all connections\n   */\n  public static CloseableHttpClient getHttpClient(HttpClientSettingsKey ocspAndProxyKey) {\n    return initHttpClient(ocspAndProxyKey, null, null);\n  }\n\n  /**\n   * Gets HttpClient with insecureMode false\n   *\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param httpHeadersCustomizers List of HTTP headers customizers\n   * @return HttpClient object shared across all connections\n   */\n  public static CloseableHttpClient getHttpClient(\n      HttpClientSettingsKey ocspAndProxyKey, List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    return initHttpClient(ocspAndProxyKey, null, httpHeadersCustomizers);\n  }\n\n  /**\n   * Gets HttpClient with insecureMode false and disabling decompression\n   *\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param httpHeadersCustomizers List of HTTP headers customizers\n   * @return HttpClient object shared across all connections\n   */\n  public static CloseableHttpClient getHttpClientWithoutDecompression(\n      HttpClientSettingsKey ocspAndProxyKey, List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    return initHttpClientWithoutDecompression(ocspAndProxyKey, null, httpHeadersCustomizers);\n  }\n\n  /**\n   * Accessor for the HTTP client singleton.\n   *\n   * @param key contains information needed to build specific HttpClient\n   * @param ocspCacheFile OCSP response cache file name. if null, the default file will be used.\n   * @param httpHeadersCustomizers List of HTTP headers customizers\n   * @return HttpClient object shared across all connections\n   */\n  public static CloseableHttpClient initHttpClientWithoutDecompression(\n      HttpClientSettingsKey key,\n      File ocspCacheFile,\n      List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    updateRoutePlanner(key);\n    return httpClientWithoutDecompression.computeIfAbsent(\n        key, k -> buildHttpClient(key, ocspCacheFile, true, httpHeadersCustomizers));\n  }\n\n  /**\n   * Accessor for the HTTP client singleton.\n   *\n   * @param key contains information needed to build specific HttpClient\n   * @param ocspCacheFile OCSP response cache file name. if null, the default file will be used.\n   * @param httpHeadersCustomizers List of HTTP headers customizers\n   * @return HttpClient object shared across all connections\n   */\n  public static CloseableHttpClient initHttpClient(\n      HttpClientSettingsKey key,\n      File ocspCacheFile,\n      List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    updateRoutePlanner(key);\n    return httpClient.computeIfAbsent(\n        key,\n        k -> buildHttpClient(key, ocspCacheFile, key.getGzipDisabled(), httpHeadersCustomizers));\n  }\n\n  /**\n   * Return a request configuration inheriting from the default request configuration of the shared\n   * HttpClient with a different socket timeout.\n   *\n   * @param soTimeoutMs - custom socket timeout in milli-seconds\n   * @param withoutCookies - whether this request should ignore cookies or not\n   * @return RequestConfig object\n   */\n  public static RequestConfig getDefaultRequestConfigWithSocketTimeout(\n      int soTimeoutMs, boolean withoutCookies) {\n    final String cookieSpec = withoutCookies ? IGNORE_COOKIES : DEFAULT;\n    return RequestConfig.copy(defaultRequestConfig)\n        .setSocketTimeout(soTimeoutMs)\n        .setCookieSpec(cookieSpec)\n        .build();\n  }\n\n  /**\n   * Return a request configuration inheriting from the default request configuration of the shared\n   * HttpClient with a different socket and connect timeout.\n   *\n   * @param requestSocketAndConnectTimeout - custom socket and connect timeout in milli-seconds\n   * @param withoutCookies - whether this request should ignore cookies or not\n   * @return RequestConfig object\n   */\n  public static RequestConfig getDefaultRequestConfigWithSocketAndConnectTimeout(\n      int requestSocketAndConnectTimeout, boolean withoutCookies) {\n    final String cookieSpec = withoutCookies ? IGNORE_COOKIES : DEFAULT;\n    return RequestConfig.copy(defaultRequestConfig)\n        .setSocketTimeout(requestSocketAndConnectTimeout)\n        .setConnectTimeout(requestSocketAndConnectTimeout)\n        .setCookieSpec(cookieSpec)\n        .build();\n  }\n\n  /**\n   * Return a request configuration inheriting from the default request configuration of the shared\n   * HttpClient with the cookie spec set to ignore.\n   *\n   * @return RequestConfig object\n   */\n  public static RequestConfig getRequestConfigWithoutCookies() {\n    return RequestConfig.copy(defaultRequestConfig).setCookieSpec(IGNORE_COOKIES).build();\n  }\n\n  public static void setRequestConfig(RequestConfig requestConfig) {\n    logger.debug(\"Setting default request config to: {}\", requestConfig);\n    defaultRequestConfig = requestConfig;\n  }\n\n  /**\n   * Accessor for the HTTP client singleton.\n   *\n   * @return HTTP Client stats in string representation\n   */\n  private static String getHttpClientStats(ClientConnectionManager connectionManager) {\n    if (!(connectionManager instanceof PoolingHttpClientConnectionManager)) {\n      return \"\";\n    } else {\n      return ((PoolingHttpClientConnectionManager) connectionManager).getTotalStats().toString();\n    }\n  }\n\n  /**\n   * Enables/disables use of the SOCKS proxy when creating sockets\n   *\n   * @param socksProxyDisabled new value\n   */\n  public static void setSocksProxyDisabled(boolean socksProxyDisabled) {\n    logger.debug(\"Setting socks proxy disabled to {}\", socksProxyDisabled);\n    HttpUtil.socksProxyDisabled = socksProxyDisabled;\n  }\n\n  /**\n   * Returns whether the SOCKS proxy is disabled for this JVM\n   *\n   * @return whether the SOCKS proxy is disabled\n   */\n  public static boolean isSocksProxyDisabled() {\n    return HttpUtil.socksProxyDisabled;\n  }\n\n  /**\n   * Executes an HTTP request with the cookie spec set to IGNORE_COOKIES\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator specific timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param retryCount max retry count for the request - if it is set to 0, it will be ignored and\n   *     only retryTimeout will determine when to end the retries\n   * @param injectSocketTimeout injecting socket timeout\n   * @param canceling canceling?\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  @Deprecated\n  static String executeRequestWithoutCookies(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int retryCount,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      HttpClientSettingsKey ocspAndProxyKey)\n      throws SnowflakeSQLException, IOException {\n    return executeRequestWithoutCookies(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        retryCount,\n        injectSocketTimeout,\n        canceling,\n        ocspAndProxyKey,\n        null);\n  }\n\n  /**\n   * Executes an HTTP request with the cookie spec set to IGNORE_COOKIES\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator specific timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param retryCount max retry count for the request - if it is set to 0, it will be ignored and\n   *     only retryTimeout will determine when to end the retries\n   * @param injectSocketTimeout injecting socket timeout\n   * @param canceling canceling?\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param sfSession the session associated with the request\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  static String executeRequestWithoutCookies(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int retryCount,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      HttpClientSettingsKey ocspAndProxyKey,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    logger.debug(\"Executing request without cookies\");\n    return executeRequestInternal(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        retryCount,\n        injectSocketTimeout,\n        canceling,\n        true, // no cookie\n        false, // no retry parameter\n        true, // guid? (do we need this?)\n        false, // no retry on HTTP 403\n        getHttpClient(ocspAndProxyKey, null),\n        new ExecTimeTelemetryData(),\n        null,\n        sfSession,\n        ocspAndProxyKey,\n        null,\n        false);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator specific timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param retryCount max retry count for the request - if it is set to 0, it will be ignored and\n   *     only retryTimeout will determine when to end the retries\n   * @param ocspAndProxyAndGzipKey OCSP mode and proxy settings for httpclient\n   * @param sfSession the session associated with the request\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  public static String executeGeneralRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int retryCount,\n      HttpClientSettingsKey ocspAndProxyAndGzipKey,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    return executeGeneralRequest(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        retryCount,\n        ocspAndProxyAndGzipKey,\n        null,\n        sfSession);\n  }\n\n  public static String executeGeneralRequestOmitSnowflakeHeaders(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int retryCount,\n      HttpClientSettingsKey ocspAndProxyAndGzipKey,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    return executeRequestInternal(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        retryCount,\n        0,\n        null,\n        false,\n        false,\n        false,\n        false,\n        getHttpClient(ocspAndProxyAndGzipKey, null),\n        new ExecTimeTelemetryData(),\n        null,\n        sfSession,\n        ocspAndProxyAndGzipKey,\n        null,\n        false);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator specific timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param retryCount max retry count for the request - if it is set to 0, it will be ignored and\n   *     only retryTimeout will determine when to end the retries\n   * @param ocspAndProxyAndGzipKey OCSP mode and proxy settings for httpclient\n   * @param retryContextManager RetryContext used to customize retry handling functionality\n   * @param sfSession the session associated with the request\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  public static String executeGeneralRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int retryCount,\n      HttpClientSettingsKey ocspAndProxyAndGzipKey,\n      RetryContextManager retryContextManager,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    logger.debug(\"Executing general request\");\n    return executeRequest(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        retryCount,\n        0, // no inject socket timeout\n        null, // no canceling\n        false, // no retry parameter\n        false, // no retry on HTTP 403\n        ocspAndProxyAndGzipKey,\n        new ExecTimeTelemetryData(),\n        retryContextManager,\n        sfSession);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake and returns response with headers. This variant allows\n   * access to both the response body and HTTP headers as a simple map.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator specific timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetryCount max retry count for the request - if it is set to 0, it will be ignored\n   *     and only retryTimeout will determine when to end the retries\n   * @param retriedCount The number of retries attempted to execute the request.\n   * @param ocspAndProxyAndGzipKey OCSP mode and proxy settings for httpclient\n   * @param sfSession the session associated with the request\n   * @return HttpResponseWithHeaders containing response body and headers as a map\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  public static HttpResponseWithHeaders executeGeneralRequestWithContext(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetryCount,\n      int retriedCount,\n      HttpClientSettingsKey ocspAndProxyAndGzipKey,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    // Set up telemetry with OCSP status\n    boolean ocspEnabled =\n        !(ocspAndProxyAndGzipKey.getOcspMode().equals(OCSPMode.DISABLE_OCSP_CHECKS));\n    logger.debug(\"Executing general request with OCSP enabled: {}\", ocspEnabled);\n    ExecTimeTelemetryData execTimeData = new ExecTimeTelemetryData();\n    execTimeData.setOCSPStatus(ocspEnabled);\n\n    // Delegate to common internal method with appropriate defaults\n    HttpResponseContextDto responseContext =\n        executeRequestInternalWithContext(\n            httpRequest,\n            retryTimeout,\n            authTimeout,\n            socketTimeout,\n            maxRetryCount,\n            retriedCount,\n            0, // no inject socket timeout\n            null, // no canceling\n            false, // with cookie\n            false, // no retry parameters\n            true, // include request GUID\n            false, // no retry on HTTP 403\n            getHttpClient(ocspAndProxyAndGzipKey, null),\n            execTimeData,\n            null, // no retry context manager\n            sfSession,\n            ocspAndProxyAndGzipKey,\n            null, // no custom headers\n            false); // no decompression\n\n    // Convert to clean response object with headers as map\n    return convertToHttpResponseWithHeaders(responseContext);\n  }\n\n  private static HttpResponseWithHeaders convertToHttpResponseWithHeaders(\n      HttpResponseContextDto responseContext) {\n    String responseBody = responseContext.getUnpackedCloseableHttpResponse();\n    return new HttpResponseWithHeaders(\n        responseBody, extractHeadersAsMap(responseContext.getHttpResponse()));\n  }\n\n  public static Map<String, String> extractHeadersAsMap(HttpResponse httpResponse) {\n    Map<String, String> headersMap = new HashMap<>();\n    if (httpResponse != null) {\n      Header[] headers = httpResponse.getAllHeaders();\n      if (headers != null) {\n        for (Header header : headers) {\n          headersMap.put(header.getName(), header.getValue());\n        }\n      }\n    }\n    return headersMap;\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator specific timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param retryCount max retry count for the request - if it is set to 0, it will be ignored and\n   *     only retryTimeout will determine when to end the retries\n   * @param httpClient client object used to communicate with other machine\n   * @param sfSession the session associated with the request\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  public static String executeGeneralRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int retryCount,\n      CloseableHttpClient httpClient,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    logger.debug(\"Executing general request\");\n    return executeRequestInternal(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        retryCount,\n        0, // no inject socket timeout\n        null, // no canceling\n        false, // with cookie\n        false, // no retry parameter\n        true, // include request GUID\n        false, // no retry on HTTP 403\n        httpClient,\n        new ExecTimeTelemetryData(),\n        null,\n        sfSession,\n        null,\n        null,\n        false);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param injectSocketTimeout injecting socket timeout\n   * @param canceling canceling?\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param retryOnHTTP403 whether to retry on HTTP 403 or not\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param execTimeData query execution time telemetry data object\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  @Deprecated\n  public static String executeRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean includeRetryParameters,\n      boolean retryOnHTTP403,\n      HttpClientSettingsKey ocspAndProxyKey,\n      ExecTimeTelemetryData execTimeData)\n      throws SnowflakeSQLException, IOException {\n    return executeRequest(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        includeRetryParameters,\n        retryOnHTTP403,\n        ocspAndProxyKey,\n        execTimeData,\n        (SFBaseSession) null);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param injectSocketTimeout injecting socket timeout\n   * @param canceling canceling?\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param retryOnHTTP403 whether to retry on HTTP 403 or not\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param execTimeData query execution time telemetry data object\n   * @param sfSession the session associated with the request\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  public static String executeRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean includeRetryParameters,\n      boolean retryOnHTTP403,\n      HttpClientSettingsKey ocspAndProxyKey,\n      ExecTimeTelemetryData execTimeData,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    return executeRequest(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        includeRetryParameters,\n        retryOnHTTP403,\n        ocspAndProxyKey,\n        execTimeData,\n        null,\n        sfSession);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param injectSocketTimeout injecting socket timeout\n   * @param canceling canceling?\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param retryOnHTTP403 whether to retry on HTTP 403 or not\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param execTimeData query execution time telemetry data object\n   * @param retryContextManager RetryContext used to customize retry handling functionality\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  @Deprecated\n  public static String executeRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean includeRetryParameters,\n      boolean retryOnHTTP403,\n      HttpClientSettingsKey ocspAndProxyKey,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager)\n      throws SnowflakeSQLException, IOException {\n    return executeRequest(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        includeRetryParameters,\n        retryOnHTTP403,\n        ocspAndProxyKey,\n        execTimeData,\n        retryContextManager,\n        null);\n  }\n\n  /**\n   * Executes an HTTP request for Snowflake.\n   *\n   * @param httpRequest HttpRequestBase\n   * @param retryTimeout retry timeout\n   * @param authTimeout authenticator timeout\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param injectSocketTimeout injecting socket timeout\n   * @param canceling canceling?\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param retryOnHTTP403 whether to retry on HTTP 403 or not\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @param execTimeData query execution time telemetry data object\n   * @param retryContextManager RetryContext used to customize retry handling functionality\n   * @param sfSession the session associated with the request\n   * @return response\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  public static String executeRequest(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean includeRetryParameters,\n      boolean retryOnHTTP403,\n      HttpClientSettingsKey ocspAndProxyKey,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException, IOException {\n    boolean ocspEnabled = !(ocspAndProxyKey.getOcspMode().equals(OCSPMode.DISABLE_OCSP_CHECKS));\n    logger.debug(\"Executing request with OCSP enabled: {}\", ocspEnabled);\n    execTimeData.setOCSPStatus(ocspEnabled);\n    return executeRequestInternal(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        false, // with cookie (do we need cookie?)\n        includeRetryParameters,\n        true, // include request GUID\n        retryOnHTTP403,\n        getHttpClient(ocspAndProxyKey, null),\n        execTimeData,\n        retryContextManager,\n        sfSession,\n        ocspAndProxyKey,\n        null,\n        false);\n  }\n\n  /**\n   * Helper to execute a request with retry and check and throw exception if response is not\n   * success. This should be used only for small request has it execute the REST request and get\n   * back the result as a string.\n   *\n   * <p>Connection under the httpRequest is released.\n   *\n   * @param httpRequest request object contains all the information\n   * @param retryTimeout retry timeout (in seconds)\n   * @param authTimeout authenticator specific timeout (in seconds)\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param injectSocketTimeout simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether this request should ignore cookies\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryOnHTTP403 whether to retry on HTTP 403\n   * @param httpClient client object used to communicate with other machine\n   * @param retryContextManager RetryContext used to customize retry handling functionality\n   * @param sfSession the session associated with the request\n   * @param key HttpClientSettingsKey object\n   * @param httpHeaderCustomizer HttpHeadersCustomizer object for customization of HTTP headers for\n   *     requests sent by the Snowflake JDBC driver.\n   * @param isHttpClientWithoutDecompression flag for create client without Decompression\n   * @return response in String\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  private static String executeRequestInternal(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryOnHTTP403,\n      CloseableHttpClient httpClient,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager,\n      SFBaseSession sfSession,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException, IOException {\n    // Delegate to common method and extract string from response context\n    HttpResponseContextDto responseContext =\n        executeRequestInternalWithContext(\n            httpRequest,\n            retryTimeout,\n            authTimeout,\n            socketTimeout,\n            maxRetries,\n            injectSocketTimeout,\n            canceling,\n            withoutCookies,\n            includeRetryParameters,\n            includeSnowflakeHeaders,\n            retryOnHTTP403,\n            httpClient,\n            execTimeData,\n            retryContextManager,\n            sfSession,\n            key,\n            httpHeaderCustomizer,\n            isHttpClientWithoutDecompression);\n    return responseContext.getUnpackedCloseableHttpResponse();\n  }\n\n  /**\n   * Common internal method to execute HTTP requests and return full response context. This method\n   * contains the shared logic for building the request context and executing the request with\n   * retries.\n   *\n   * @param httpRequest request object contains all the information\n   * @param retryTimeout retry timeout (in seconds)\n   * @param authTimeout authenticator specific timeout (in seconds)\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param injectSocketTimeout simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether this request should ignore cookies\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryOnHTTP403 whether to retry on HTTP 403\n   * @param httpClient client object used to communicate with other machine\n   * @param execTimeData execution time telemetry data\n   * @param retryContextManager RetryContext used to customize retry handling functionality\n   * @param sfSession the session associated with the request\n   * @param key HttpClientSettingsKey object\n   * @param httpHeaderCustomizer HttpHeadersCustomizer object for customization of HTTP headers\n   * @param isHttpClientWithoutDecompression flag for create client without Decompression\n   * @return HttpResponseContextDto containing both response body and headers\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  private static HttpResponseContextDto executeRequestInternalWithContext(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryOnHTTP403,\n      CloseableHttpClient httpClient,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager,\n      SFBaseSession sfSession,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException, IOException {\n\n    return executeRequestInternalWithContext(\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        0,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryOnHTTP403,\n        httpClient,\n        execTimeData,\n        retryContextManager,\n        sfSession,\n        key,\n        httpHeaderCustomizer,\n        isHttpClientWithoutDecompression);\n  }\n\n  /**\n   * Common internal method to execute HTTP requests and return full response context. This method\n   * contains the shared logic for building the request context and executing the request with\n   * retries.\n   *\n   * @param httpRequest request object contains all the information\n   * @param retryTimeout retry timeout (in seconds)\n   * @param authTimeout authenticator specific timeout (in seconds)\n   * @param socketTimeout socket timeout (in ms)\n   * @param maxRetries retry count for the request\n   * @param retriedCount The number of retries attempted to execute the request.\n   * @param injectSocketTimeout simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether this request should ignore cookies\n   * @param includeRetryParameters whether to include retry parameters in retried requests\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryOnHTTP403 whether to retry on HTTP 403\n   * @param httpClient client object used to communicate with other machine\n   * @param execTimeData execution time telemetry data\n   * @param retryContextManager RetryContext used to customize retry handling functionality\n   * @param sfSession the session associated with the request\n   * @param key HttpClientSettingsKey object\n   * @param httpHeaderCustomizer HttpHeadersCustomizer object for customization of HTTP headers\n   * @param isHttpClientWithoutDecompression flag for create client without Decompression\n   * @return HttpResponseContextDto containing both response body and headers\n   * @throws SnowflakeSQLException if Snowflake error occurs\n   * @throws IOException raises if a general IO error occurs\n   */\n  private static HttpResponseContextDto executeRequestInternalWithContext(\n      HttpRequestBase httpRequest,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int retriedCount,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryOnHTTP403,\n      CloseableHttpClient httpClient,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager,\n      SFBaseSession sfSession,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException, IOException {\n    String requestInfoScrubbed = SecretDetector.maskSASToken(httpRequest.toString());\n    logger.debug(\n        \"Pool: {} Executing: {}\",\n        (ArgSupplier) () -> getHttpClientStats(httpClient.getConnectionManager()),\n        requestInfoScrubbed);\n\n    Stopwatch stopwatch = null;\n\n    String requestIdStr = URLUtil.getRequestIdLogStr(httpRequest.getURI());\n    HttpExecutingContext context =\n        HttpExecutingContextBuilder.forSimpleRequest(requestIdStr, requestInfoScrubbed)\n            .retryTimeout(retryTimeout)\n            .authTimeout(authTimeout)\n            .origSocketTimeout(socketTimeout)\n            .maxRetries(maxRetries)\n            .injectSocketTimeout(injectSocketTimeout)\n            .canceling(canceling)\n            .withoutCookies(withoutCookies)\n            .includeRetryParameters(includeRetryParameters)\n            .includeSnowflakeHeaders(includeSnowflakeHeaders)\n            .retryHTTP403(retryOnHTTP403)\n            .unpackResponse(true)\n            .noRetry(false)\n            .loginRequest(SessionUtil.isNewRetryStrategyRequest(httpRequest))\n            .withSfSession(sfSession)\n            .build();\n    context.setRetryCount(retriedCount);\n    HttpResponseContextDto responseContext =\n        RestRequest.executeWithRetries(\n            httpClient,\n            httpRequest,\n            context,\n            execTimeData,\n            retryContextManager,\n            key,\n            httpHeaderCustomizer,\n            isHttpClientWithoutDecompression);\n\n    logger.debug(\n        \"Pool: {} Request returned for: {} took {} ms\",\n        (ArgSupplier) () -> HttpUtil.getHttpClientStats(httpClient.getConnectionManager()),\n        requestInfoScrubbed,\n        stopwatch == null ? \"n/a\" : stopwatch.elapsedMillis());\n\n    return responseContext;\n  }\n\n  // This is a workaround for JDK-7036144.\n  //\n  // The GZIPInputStream prematurely closes its input if a) it finds\n  // a whole GZIP block and b) input.available() returns 0. In order\n  // to work around this issue, we inject a thin wrapper for the\n  // InputStream whose available() method always returns at least 1.\n  //\n  // Further details on this bug:\n  //   http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7036144\n  public static final class HttpInputStream extends InputStream {\n    private final InputStream httpIn;\n\n    public HttpInputStream(InputStream httpIn) {\n      this.httpIn = httpIn;\n    }\n\n    // This is the only modified function, all other\n    // methods are simple wrapper around the HTTP stream.\n    @Override\n    public final int available() throws IOException {\n      int available = httpIn.available();\n      return available == 0 ? 1 : available;\n    }\n\n    // ONLY WRAPPER METHODS FROM HERE ON.\n    @Override\n    public final int read() throws IOException {\n      return httpIn.read();\n    }\n\n    @Override\n    public final int read(byte b[]) throws IOException {\n      return httpIn.read(b);\n    }\n\n    @Override\n    public final int read(byte b[], int off, int len) throws IOException {\n      return httpIn.read(b, off, len);\n    }\n\n    @Override\n    public final long skip(long n) throws IOException {\n      return httpIn.skip(n);\n    }\n\n    @Override\n    public final void close() throws IOException {\n      httpIn.close();\n    }\n\n    @Override\n    public synchronized void mark(int readlimit) {\n      httpIn.mark(readlimit);\n    }\n\n    @Override\n    public synchronized void reset() throws IOException {\n      httpIn.reset();\n    }\n\n    @Override\n    public final boolean markSupported() {\n      return httpIn.markSupported();\n    }\n  }\n\n  static final class SFConnectionSocketFactory extends PlainConnectionSocketFactory {\n    @Override\n    public Socket createSocket(HttpContext ctx) throws IOException {\n      if (socksProxyDisabled) {\n        logger.trace(\"Creating socket with no proxy\");\n        return new Socket(Proxy.NO_PROXY);\n      }\n      logger.trace(\"Creating socket with proxy\");\n      return super.createSocket(ctx);\n    }\n  }\n\n  /**\n   * Helper function to attach additional headers to a request if present. This takes a (nullable)\n   * map of headers in <name,value> format and adds them to the incoming request using addHeader.\n   *\n   * <p>Snowsight uses this to attach headers with additional telemetry information, see\n   * https://snowflakecomputing.atlassian.net/wiki/spaces/EN/pages/2960557006/GS+Communication\n   *\n   * @param request The request to add headers to. Must not be null.\n   * @param additionalHeaders The headers to add. May be null.\n   */\n  static void applyAdditionalHeadersForSnowsight(\n      HttpRequestBase request, Map<String, String> additionalHeaders) {\n    if (additionalHeaders != null && !additionalHeaders.isEmpty()) {\n      additionalHeaders.forEach(request::addHeader);\n    }\n  }\n\n  public static CloseableHttpClient getHttpClientForCrl(HttpClientSettingsKey key) {\n    return getHttpClient((int) HttpUtil.getConnectionTimeout().toMillis(), key);\n  }\n\n  public static CloseableHttpClient getHttpClientForOcsp(HttpClientSettingsKey key) {\n    return getHttpClient(key.getOcspTimeout(), key);\n  }\n\n  private static CloseableHttpClient getHttpClient(int timeout, HttpClientSettingsKey key) {\n    int idleConnectionTimeout =\n        SystemUtil.convertSystemPropertyToIntValue(\n            JDBC_IDLE_CONNECTION_PROPERTY, DEFAULT_IDLE_CONNECTION_TIMEOUT);\n    RequestConfig config =\n        RequestConfig.custom()\n            .setConnectTimeout(timeout)\n            .setConnectionRequestTimeout(timeout)\n            .setSocketTimeout(timeout)\n            .build();\n\n    Registry<ConnectionSocketFactory> registry =\n        RegistryBuilder.<ConnectionSocketFactory>create()\n            .register(\"http\", new HttpUtil.SFConnectionSocketFactory())\n            .build();\n\n    // Build a connection manager with enough connections\n    PoolingHttpClientConnectionManager connectionManager =\n        new PoolingHttpClientConnectionManager(registry);\n    connectionManager.setMaxTotal(1);\n    connectionManager.setValidateAfterInactivity(idleConnectionTimeout);\n\n    HttpClientBuilder httpClientBuilder =\n        HttpClientBuilder.create()\n            .setDefaultRequestConfig(config)\n            .setConnectionManager(connectionManager)\n            .evictExpiredConnections()\n            .evictIdleConnections(idleConnectionTimeout, TimeUnit.SECONDS)\n            // Support JVM proxy settings\n            .useSystemProperties()\n            .setRedirectStrategy(new DefaultRedirectStrategy())\n            .disableCookieManagement();\n\n    if (key.usesProxy()) {\n      // use the custom proxy properties\n      HttpHost proxy = new HttpHost(key.getProxyHost(), key.getProxyPort());\n      SnowflakeMutableProxyRoutePlanner sdkProxyRoutePlanner =\n          new SnowflakeMutableProxyRoutePlanner(\n              key.getProxyHost(), key.getProxyPort(), HttpProtocol.HTTP, key.getNonProxyHosts());\n      httpClientBuilder.setProxy(proxy).setRoutePlanner(sdkProxyRoutePlanner);\n      if (!isNullOrEmpty(key.getProxyUser()) && !isNullOrEmpty(key.getProxyPassword())) {\n        Credentials credentials =\n            new UsernamePasswordCredentials(key.getProxyUser(), key.getProxyPassword());\n        AuthScope authScope = new AuthScope(key.getProxyHost(), key.getProxyPort());\n        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n        credentialsProvider.setCredentials(authScope, credentials);\n        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n      }\n    }\n    return httpClientBuilder.build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/JsonSqlInput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SFBaseResultSet.OBJECT_MAPPER;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.mapSFExceptionToSQLException;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.time.Instant;\nimport java.time.ZoneOffset;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.core.structs.SQLDataCreationHelper;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.ThrowingBiFunction;\nimport net.snowflake.common.core.SFTimestamp;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\npublic class JsonSqlInput extends BaseSqlInput {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(JsonSqlInput.class);\n  private final String text;\n  private final JsonNode input;\n  private final Iterator<JsonNode> elements;\n  private final TimeZone sessionTimeZone;\n  private int currentIndex = 0;\n  private boolean wasNull = false;\n\n  public JsonSqlInput(\n      String text,\n      JsonNode input,\n      SFBaseSession session,\n      Converters converters,\n      List<FieldMetadata> fields,\n      TimeZone sessionTimeZone) {\n    super(session, converters, fields);\n    this.text = text;\n    this.input = input;\n    this.elements = input.elements();\n    this.sessionTimeZone = sessionTimeZone;\n  }\n\n  public JsonNode getInput() {\n    return input;\n  }\n\n  public String getText() {\n    return text;\n  }\n\n  @Override\n  public String readString() throws SQLException {\n    return withNextValue((this::convertString));\n  }\n\n  @Override\n  public boolean readBoolean() throws SQLException {\n    return withNextValue(this::convertBoolean);\n  }\n\n  @Override\n  public byte readByte() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) ->\n            mapSFExceptionToSQLException(() -> converters.getNumberConverter().getByte(value)));\n  }\n\n  @Override\n  public short readShort() throws SQLException {\n    return withNextValue(this::convertShort);\n  }\n\n  @Override\n  public int readInt() throws SQLException {\n    return withNextValue(this::convertInt);\n  }\n\n  @Override\n  public long readLong() throws SQLException {\n    return withNextValue(this::convertLong);\n  }\n\n  @Override\n  public float readFloat() throws SQLException {\n    return withNextValue(this::convertFloat);\n  }\n\n  @Override\n  public double readDouble() throws SQLException {\n    return withNextValue(this::convertDouble);\n  }\n\n  @Override\n  public BigDecimal readBigDecimal() throws SQLException {\n    return withNextValue(this::convertBigDecimal);\n  }\n\n  @Override\n  public byte[] readBytes() throws SQLException {\n    return withNextValue(this::convertBytes);\n  }\n\n  @Override\n  public Date readDate() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          return convertDate((String) value);\n        });\n  }\n\n  private Date convertDate(String value) {\n    SnowflakeDateTimeFormat formatter = getFormat(session, \"DATE_OUTPUT_FORMAT\");\n    SFTimestamp timestamp = formatter.parse(value);\n    return Date.valueOf(\n        Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalDate());\n  }\n\n  @Override\n  public Time readTime() throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          return convertTime((String) value);\n        });\n  }\n\n  private Time convertTime(String value) {\n    SnowflakeDateTimeFormat formatter = getFormat(session, \"TIME_OUTPUT_FORMAT\");\n    SFTimestamp timestamp = formatter.parse(value);\n    return Time.valueOf(\n        Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalTime());\n  }\n\n  @Override\n  public Timestamp readTimestamp() throws SQLException {\n    return readTimestamp(null);\n  }\n\n  @Override\n  public Timestamp readTimestamp(TimeZone tz) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          return convertTimestamp(tz, value, fieldMetadata);\n        });\n  }\n\n  @Override\n  public <T> T readObject(Class<T> type, TimeZone tz) throws SQLException {\n    return withNextValue((value, fieldMetadata) -> convertObject(type, tz, value, fieldMetadata));\n  }\n\n  private <T> T convertObject(Class<T> type, TimeZone tz, Object value, FieldMetadata fieldMetadata)\n      throws SQLException {\n    if (value == null) {\n      return null;\n    } else if (SQLData.class.isAssignableFrom(type)) {\n      if (!JsonNode.class.isAssignableFrom(value.getClass())) {\n        logger.error(\"Object of class JsonNode is expected to convert to SqlData\");\n        return null;\n      }\n      JsonNode jsonNode = (JsonNode) value;\n      SQLInput sqlInput =\n          new JsonSqlInput(\n              null, jsonNode, session, converters, fieldMetadata.getFields(), sessionTimeZone);\n      SQLData instance = (SQLData) SQLDataCreationHelper.create(type);\n      instance.readSQL(sqlInput, null);\n      return (T) instance;\n    } else if (Map.class.isAssignableFrom(type)) {\n      if (value == null) {\n        return null;\n      } else {\n        return (T) convertSqlInputToMap((SQLInput) value);\n      }\n    } else if (String.class.isAssignableFrom(type)) {\n      return (T) convertString(value, fieldMetadata);\n    } else if (Boolean.class.isAssignableFrom(type)) {\n      return (T) convertBoolean(value, fieldMetadata);\n    } else if (Byte.class.isAssignableFrom(type)) {\n      return (T) convertString(value, fieldMetadata);\n    } else if (Short.class.isAssignableFrom(type)) {\n      return (T) convertShort(value, fieldMetadata);\n    } else if (Integer.class.isAssignableFrom(type)) {\n      return (T) convertInt(value, fieldMetadata);\n    } else if (Long.class.isAssignableFrom(type)) {\n      return (T) convertLong(value, fieldMetadata);\n    } else if (Float.class.isAssignableFrom(type)) {\n      return (T) convertFloat(value, fieldMetadata);\n    } else if (Double.class.isAssignableFrom(type)) {\n      return (T) convertFloat(value, fieldMetadata);\n    } else if (Date.class.isAssignableFrom(type)) {\n      return (T) convertDate((String) value);\n    } else if (Time.class.isAssignableFrom(type)) {\n      return (T) convertTime((String) value);\n    } else if (Timestamp.class.isAssignableFrom(type)) {\n      return (T) convertTimestamp(tz, value, fieldMetadata);\n    } else if (BigDecimal.class.isAssignableFrom(type)) {\n      return (T) convertBigDecimal(value, fieldMetadata);\n    } else if (byte[].class.isAssignableFrom(type)) {\n      return (T) convertBytes(value, fieldMetadata);\n    } else {\n      logger.debug(\n          \"Unsupported type passed to readObject(int columnIndex,Class<T> type): \"\n              + type.getName());\n      throw new SQLException(\n          \"Type passed to 'getObject(int columnIndex,Class<T> type)' is unsupported. Type: \"\n              + type.getName());\n    }\n  }\n\n  @Override\n  public <T> List<T> readList(Class<T> type) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          List<T> result = new ArrayList();\n          if (ArrayNode.class.isAssignableFrom(value.getClass())) {\n            for (JsonNode node : (ArrayNode) value) {\n              result.add(\n                  convertObject(\n                      type,\n                      TimeZone.getDefault(),\n                      getValue(node),\n                      fieldMetadata.getFields().get(0)));\n            }\n            return result;\n          } else {\n            logger.debug(\"Given object could not be converted to List of type: \" + type.getName());\n            throw new SQLException(\n                \"Given object could not be converted to List of type: \" + type.getName());\n          }\n        });\n  }\n\n  @Override\n  public <T> T[] readArray(Class<T> type) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          if (ArrayNode.class.isAssignableFrom(value.getClass())) {\n            ArrayNode valueNodes = (ArrayNode) value;\n            T[] array = (T[]) java.lang.reflect.Array.newInstance(type, valueNodes.size());\n            int counter = 0;\n            for (JsonNode node : valueNodes) {\n              array[counter++] =\n                  convertObject(\n                      type,\n                      TimeZone.getDefault(),\n                      getValue(node),\n                      fieldMetadata.getFields().get(0));\n            }\n            return array;\n          } else {\n            logger.debug(\"Given object could not be converted to Array of type: \" + type.getName());\n            throw new SQLException(\n                \"Given object could not be converted to List of type: \" + type.getName());\n          }\n        });\n  }\n\n  @Override\n  public <T> Map<String, T> readMap(Class<T> type) throws SQLException {\n    return withNextValue(\n        (value, fieldMetadata) -> {\n          if (value == null) {\n            return null;\n          }\n          if (ObjectNode.class.isAssignableFrom(value.getClass())) {\n            Map<String, T> result = new HashMap<>();\n            ObjectNode arrayNode = (ObjectNode) value;\n            for (Iterator<String> it = arrayNode.fieldNames(); it.hasNext(); ) {\n              String key = it.next();\n              result.put(\n                  key,\n                  convertObject(\n                      type, TimeZone.getDefault(), getValue(arrayNode.get(key)), fieldMetadata));\n            }\n            return result;\n          } else {\n            logger.debug(\n                \"Given object could not be converted to Map of String and type: \" + type.getName());\n            throw new SQLException(\n                \"Given object could not be converted to Map of String and type: \" + type.getName());\n          }\n        });\n  }\n\n  private Timestamp convertTimestamp(TimeZone tz, Object value, FieldMetadata fieldMetadata)\n      throws SQLException {\n    if (value == null) {\n      return null;\n    }\n    int columnType = ColumnTypeHelper.getColumnType(fieldMetadata.getType(), session);\n    int columnSubType = fieldMetadata.getType();\n    int scale = fieldMetadata.getScale();\n    Timestamp result =\n        SfTimestampUtil.getTimestampFromType(\n            columnSubType, (String) value, session, sessionTimeZone, tz);\n    if (result != null) {\n      return result;\n    }\n    return mapSFExceptionToSQLException(\n        () ->\n            converters\n                .getDateTimeConverter()\n                .getTimestamp(value, columnType, columnSubType, tz, scale));\n  }\n\n  @Override\n  public Object readObject() throws SQLException {\n    return withNextValue((value, fieldMetadata) -> value);\n  }\n\n  @Override\n  public <T> T readObject(Class<T> type) throws SQLException {\n    return readObject(type, sessionTimeZone);\n  }\n\n  public boolean wasNull() {\n    return wasNull;\n  }\n\n  @Override\n  Map<String, Object> convertSqlInputToMap(SQLInput sqlInput) {\n    return OBJECT_MAPPER.convertValue(\n        ((JsonSqlInput) sqlInput).getInput(), new TypeReference<Map<String, Object>>() {});\n  }\n\n  private <T> T withNextValue(ThrowingBiFunction<Object, FieldMetadata, T, SQLException> action)\n      throws SQLException {\n    JsonNode jsonNode = elements.next();\n    Object value = getValue(jsonNode);\n    wasNull = value == null;\n    return action.apply(value, fields.get(currentIndex++));\n  }\n\n  private Object getValue(JsonNode jsonNode) {\n    if (jsonNode.isTextual()) {\n      return jsonNode.textValue();\n    } else if (jsonNode.isBoolean()) {\n      return jsonNode.booleanValue();\n    } else if (jsonNode.isNumber()) {\n      return jsonNode.numberValue();\n    } else if (jsonNode.isObject() || jsonNode.isArray()) {\n      return jsonNode;\n    }\n    return null;\n  }\n\n  private static SnowflakeDateTimeFormat getFormat(SFBaseSession session, String format) {\n    return SnowflakeDateTimeFormat.fromSqlFormat(\n        (String) session.getCommonParameters().get(format));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/JsonSqlOutput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.FieldSchemaCreator.buildSchemaTypeAndNameOnly;\nimport static net.snowflake.client.internal.core.FieldSchemaCreator.buildSchemaWithScaleAndPrecision;\n\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.Date;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.RowId;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLOutput;\nimport java.sql.SQLXML;\nimport java.sql.Struct;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.TimeZone;\nimport java.util.stream.Collectors;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.jdbc.BindingParameterMetadata;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumn;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.ThrowingTriCallable;\nimport net.snowflake.common.core.SFTime;\nimport net.snowflake.common.core.SFTimestamp;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\npublic class JsonSqlOutput implements SQLOutput {\n  static final SFLogger logger = SFLoggerFactory.getLogger(JsonSqlOutput.class);\n  private JSONObject json;\n  private SQLData original;\n  private SFBaseSession session;\n  private Iterator<Field> fields;\n  private BindingParameterMetadata schema;\n  private TimeZone sessionTimezone;\n\n  public JsonSqlOutput(SQLData original, SFBaseSession sfBaseSession) {\n    this.original = original;\n    this.session = sfBaseSession;\n    this.sessionTimezone = getSessionTimezone(sfBaseSession);\n    fields = getClassFields(original).iterator();\n    schema = new BindingParameterMetadata(\"object\");\n    schema.setFields(new ArrayList<>());\n    json = new JSONObject();\n  }\n\n  private TimeZone getSessionTimezone(SFBaseSession sfBaseSession) {\n    String timeZoneName =\n        (String) ResultUtil.effectiveParamValue(sfBaseSession.getCommonParameters(), \"TIMEZONE\");\n    return TimeZone.getTimeZone(timeZoneName);\n  }\n\n  private static List<Field> getClassFields(SQLData original) {\n    return Arrays.stream(original.getClass().getDeclaredFields())\n        .filter(\n            field ->\n                !Modifier.isStatic(field.getModifiers())\n                    && !Modifier.isTransient(field.getModifiers()))\n        .collect(Collectors.toList());\n  }\n\n  @Override\n  public void writeString(String value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema.getFields().add(FieldSchemaCreator.buildSchemaForText(fieldName, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeBoolean(boolean value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema.getFields().add(buildSchemaTypeAndNameOnly(fieldName, \"boolean\", maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeByte(byte value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema\n              .getFields()\n              .add(buildSchemaWithScaleAndPrecision(fieldName, \"fixed\", 0, 38, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeShort(short value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema\n              .getFields()\n              .add(buildSchemaWithScaleAndPrecision(fieldName, \"fixed\", 0, 38, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeInt(int input) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, input);\n          schema\n              .getFields()\n              .add(buildSchemaWithScaleAndPrecision(fieldName, \"fixed\", 0, 38, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeLong(long value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema\n              .getFields()\n              .add(buildSchemaWithScaleAndPrecision(fieldName, \"fixed\", 0, 38, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeFloat(float value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema.getFields().add(buildSchemaTypeAndNameOnly(fieldName, \"real\", maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeDouble(double value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema.getFields().add(buildSchemaTypeAndNameOnly(fieldName, \"real\", maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeBigDecimal(BigDecimal value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, value);\n          schema\n              .getFields()\n              .add(\n                  buildSchemaWithScaleAndPrecision(\n                      fieldName, \"fixed\", value.scale(), 38, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeBytes(byte[] value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(fieldName, new SFBinary(value).toHex());\n          schema\n              .getFields()\n              .add(FieldSchemaCreator.buildSchemaForBytesType(fieldName, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeDate(Date value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          json.put(\n              fieldName,\n              ResultUtil.getDateAsString(value, getDateTimeFormat(\"DATE_OUTPUT_FORMAT\")));\n          schema.getFields().add(buildSchemaTypeAndNameOnly(fieldName, \"date\", maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeTime(Time x) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          long nanosSinceMidnight;\n          if (session.getTreatTimeAsWallClockTime()) {\n            nanosSinceMidnight = x.toLocalTime().toNanoOfDay();\n          } else {\n            nanosSinceMidnight = SfTimestampUtil.getTimeInNanoseconds(x);\n          }\n\n          String result =\n              ResultUtil.getSFTimeAsString(\n                  SFTime.fromNanoseconds(nanosSinceMidnight),\n                  9,\n                  getDateTimeFormat(\"TIME_OUTPUT_FORMAT\"));\n\n          json.put(fieldName, result);\n          schema\n              .getFields()\n              .add(buildSchemaWithScaleAndPrecision(fieldName, \"time\", 9, 0, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeTimestamp(Timestamp value) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          String timestampSessionType =\n              (String)\n                  ResultUtil.effectiveParamValue(\n                      session.getCommonParameters(), \"CLIENT_TIMESTAMP_TYPE_MAPPING\");\n          SnowflakeType snowflakeType =\n              SnowflakeTypeUtil.fromString(\n                  maybeColumn\n                      .map(cl -> cl.type())\n                      .filter(str -> !str.isEmpty())\n                      .orElse(timestampSessionType));\n          int columnType = snowflakeTypeToJavaType(snowflakeType);\n          TimeZone timeZone = timeZoneDependOnType(snowflakeType, session, null);\n          String timestampAsString =\n              SnowflakeUtil.mapSFExceptionToSQLException(\n                  () ->\n                      ResultUtil.getSFTimestampAsString(\n                          new SFTimestamp(value, timeZone),\n                          columnType,\n                          9,\n                          getDateTimeFormat(\"TIMESTAMP_NTZ_OUTPUT_FORMAT\"),\n                          getDateTimeFormat(\"TIMESTAMP_LTZ_OUTPUT_FORMAT\"),\n                          getDateTimeFormat(\"TIMESTAMP_TZ_OUTPUT_FORMAT\"),\n                          session));\n\n          json.put(fieldName, timestampAsString);\n          schema\n              .getFields()\n              .add(\n                  buildSchemaWithScaleAndPrecision(\n                      fieldName, snowflakeType.name(), 9, 0, maybeColumn));\n        }));\n  }\n\n  @Override\n  public void writeCharacterStream(Reader x) throws SQLException {\n    logger.debug(\" Unsupported method writeCharacterStream(Reader x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeAsciiStream(InputStream x) throws SQLException {\n    logger.debug(\"Unsupported method writeAsciiStream(InputStream x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeBinaryStream(InputStream x) throws SQLException {\n    logger.debug(\"Unsupported method writeBinaryStream(InputStream x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeObject(SQLData sqlData) throws SQLException {\n    withNextValue(\n        ((json, fieldName, maybeColumn) -> {\n          JsonSqlOutput jsonSqlOutput = new JsonSqlOutput(sqlData, session);\n          sqlData.writeSQL(jsonSqlOutput);\n          json.put(fieldName, jsonSqlOutput.getJsonObject());\n          BindingParameterMetadata structSchema = jsonSqlOutput.getSchema();\n          structSchema.setName(fieldName);\n          schema.getFields().add(structSchema);\n        }));\n  }\n\n  @Override\n  public void writeRef(Ref x) throws SQLException {\n    logger.debug(\"Unsupported method writeRef(Ref x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeBlob(Blob x) throws SQLException {\n    logger.debug(\"Unsupported method writeBlob(Blob x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeClob(Clob x) throws SQLException {\n    logger.debug(\"Unsupported method writeClob(Clob x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeStruct(Struct x) throws SQLException {\n    logger.debug(\"Unsupported method writeStruct(Struct x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeArray(Array x) throws SQLException {\n    logger.debug(\"Unsupported method writeArray(Array x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeURL(URL x) throws SQLException {\n    logger.debug(\"Unsupported method writeURL(URL x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeNString(String x) throws SQLException {\n    logger.debug(\"Unsupported method writeNString(String x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeNClob(NClob x) throws SQLException {\n    logger.debug(\"Unsupported method writeNClob(NClob x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeRowId(RowId x) throws SQLException {\n    logger.debug(\"Unsupported method writeRowId(RowId x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public void writeSQLXML(SQLXML x) throws SQLException {\n    logger.debug(\"Unsupported method  writeSQLXML(SQLXML x)\", false);\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  public String getJsonString() {\n    return json.toJSONString();\n  }\n\n  public JSONObject getJsonObject() {\n    return json;\n  }\n\n  private void withNextValue(\n      ThrowingTriCallable<JSONObject, String, Optional<SnowflakeColumn>, SQLException> action)\n      throws SQLException {\n    Field field = fields.next();\n    String fieldName = field.getName();\n    Optional<SnowflakeColumn> maybeColumn =\n        Optional.ofNullable(field.getAnnotation(SnowflakeColumn.class));\n    action.apply(json, fieldName, maybeColumn);\n  }\n\n  private SnowflakeDateTimeFormat getDateTimeFormat(String format) {\n    String rawFormat = (String) session.getCommonParameters().get(format);\n    if (rawFormat == null || rawFormat.isEmpty()) {\n      rawFormat = (String) session.getCommonParameters().get(\"TIMESTAMP_OUTPUT_FORMAT\");\n    }\n    SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat);\n    return formatter;\n  }\n\n  public BindingParameterMetadata getSchema() {\n    return schema;\n  }\n\n  private TimeZone timeZoneDependOnType(\n      SnowflakeType snowflakeType, SFBaseSession session, TimeZone tz) {\n    if (snowflakeType == SnowflakeType.TIMESTAMP_NTZ) {\n      return null;\n    } else if (snowflakeType == SnowflakeType.TIMESTAMP_LTZ) {\n      return getSessionTimezone(session);\n    } else if (snowflakeType == SnowflakeType.TIMESTAMP_TZ) {\n      return Optional.ofNullable(tz).orElse(sessionTimezone);\n    }\n    return TimeZone.getDefault();\n  }\n\n  private int snowflakeTypeToJavaType(SnowflakeType snowflakeType) {\n    if (snowflakeType == SnowflakeType.TIMESTAMP_NTZ) {\n      return SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ;\n    } else if (snowflakeType == SnowflakeType.TIMESTAMP_LTZ) {\n      return SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ;\n    }\n    return SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/MetaDataOfBinds.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.Serializable;\n\n/**\n * Class that creates constructor used for storing information about a binding parameter's metadata.\n * Each instantiation of a MetaDataOfBinds object corresponds to one binding parameter; an arraylist\n * of MetaDataOfBinds corresponds to a list of binding parameters in a prepared statement.\n */\npublic class MetaDataOfBinds implements Serializable {\n  private static final long serialVersionUID = 1L;\n\n  private int precision;\n\n  private boolean nullable;\n\n  private int scale;\n\n  private int byteLength;\n\n  private int length;\n\n  private String name;\n\n  private String type;\n\n  public MetaDataOfBinds(int prec, boolean n, int sc, int bL, int len, String name, String type) {\n    this.precision = prec;\n    this.nullable = n;\n    this.scale = sc;\n    this.byteLength = bL;\n    this.length = len;\n    this.name = name;\n    this.type = type;\n  }\n\n  public int getPrecision() {\n    return this.precision;\n  }\n\n  public boolean isNullable() {\n    return this.nullable;\n  }\n\n  public int getScale() {\n    return this.scale;\n  }\n\n  public int getByteLength() {\n    return this.byteLength;\n  }\n\n  public int getLength() {\n    return this.length;\n  }\n\n  public String getName() {\n    return this.name;\n  }\n\n  public String getTypeName() {\n    return this.type;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/NoOpFileCacheManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.io.File;\nimport java.util.function.Supplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass NoOpFileCacheManager implements FileCacheManager {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(NoOpFileCacheManager.class);\n\n  NoOpFileCacheManager() {\n    logger.warn(\n        \"Cache is not available. Caching will be disabled. \"\n            + \"Set the HOME, SF_TEMPORARY_CREDENTIAL_CACHE_DIR, \"\n            + \"or net.snowflake.jdbc.temporaryCredentialCacheDir to enable caching.\");\n  }\n\n  @Override\n  public String getCacheFilePath() {\n    return null;\n  }\n\n  @Override\n  public void overrideCacheFile(File newCacheFile) {\n    logger.debug(\"Cache is not enabled; ignoring override\", false);\n  }\n\n  @Override\n  public <T> T withLock(Supplier<T> supplier) {\n    return null;\n  }\n\n  @Override\n  public JsonNode readCacheFile() {\n    return null;\n  }\n\n  @Override\n  public void writeCacheFile(JsonNode input) {}\n\n  @Override\n  public void deleteCacheFile() {}\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/OCSPMode.java",
    "content": "package net.snowflake.client.internal.core;\n\n/** OCSP mode */\npublic enum OCSPMode {\n  /**\n   * Fail closed, aka. hard failure mode. The connection is blocked if the revocation status is\n   * revoked or it cannot identify the status.\n   */\n  FAIL_CLOSED(0),\n\n  /**\n   * Fail open, aka. soft failure mode. The connection is blocked only if the revocation status is\n   * revoked otherwise opened for any reason including the case where the revocation status cannot\n   * be retrieved.\n   */\n  FAIL_OPEN(1),\n\n  /**\n   * @deprecated Use {@link #DISABLE_OCSP_CHECKS} for clarity. This configuration option is used to\n   *     disable OCSP verification. Insure mode. No OCSP check is made.\n   */\n  @Deprecated\n  INSECURE(2),\n\n  /** Disable OCSP checks. It's used to disable OCSP verification. */\n  DISABLE_OCSP_CHECKS(3);\n\n  private final int value;\n\n  OCSPMode(int value) {\n    this.value = value;\n  }\n\n  public int getValue() {\n    return this.value;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/OCSPTelemetryData.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.security.cert.CertificateException;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\n\npublic class OCSPTelemetryData {\n  private String certId;\n  private String sfcPeerHost;\n  private String ocspUrl;\n  private String ocspReq;\n  private Boolean cacheEnabled;\n  private Boolean cacheHit;\n  private OCSPMode ocspMode;\n\n  public OCSPTelemetryData() {\n    this.ocspMode = OCSPMode.FAIL_OPEN;\n    this.cacheEnabled = true;\n  }\n\n  public void setCertId(String certId) {\n    this.certId = certId;\n  }\n\n  public void setSfcPeerHost(String sfcPeerHost) {\n    this.sfcPeerHost = sfcPeerHost;\n  }\n\n  public void setOcspUrl(String ocspUrl) {\n    this.ocspUrl = ocspUrl;\n  }\n\n  public void setOcspReq(String ocspReq) {\n    this.ocspReq = ocspReq;\n  }\n\n  public void setCacheEnabled(Boolean cacheEnabled) {\n    this.cacheEnabled = cacheEnabled;\n    if (!cacheEnabled) {\n      this.cacheHit = false;\n    }\n  }\n\n  public void setCacheHit(Boolean cacheHit) {\n    if (!this.cacheEnabled) {\n      this.cacheHit = false;\n    } else {\n      this.cacheHit = cacheHit;\n    }\n  }\n\n  public void setOCSPMode(OCSPMode ocspMode) {\n    this.ocspMode = ocspMode;\n  }\n\n  public String generateTelemetry(String eventType, CertificateException ex) {\n    JSONObject value = new JSONObject();\n    String valueStr;\n    value.put(\"eventType\", eventType);\n    value.put(\"sfcPeerHost\", this.sfcPeerHost);\n    value.put(\"certId\", this.certId);\n    value.put(\"ocspResponderURL\", this.ocspUrl);\n    value.put(\"ocspReqBase64\", this.ocspReq);\n    value.put(\"ocspMode\", this.ocspMode.name());\n    value.put(\"cacheEnabled\", this.cacheEnabled);\n    value.put(\"cacheHit\", this.cacheHit);\n    valueStr = value.toString(); // Avoid adding exception stacktrace to user logs.\n    TelemetryService.getInstance().logOCSPExceptionTelemetryEvent(eventType, value, ex);\n    return valueStr;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/ObjectMapperFactory.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.core.StreamReadConstraints;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.MapperFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.text.SimpleDateFormat;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\n/**\n * Factor method used to create ObjectMapper instance. All object mapper in JDBC should be created\n * by this method.\n */\npublic class ObjectMapperFactory {\n  private static final SFLogger log = SFLoggerFactory.getLogger(ObjectMapperFactory.class);\n\n  // Snowflake allows up to 128M (after updating Max LOB size) string size and returns base64\n  // encoded value that makes it up to 180M\n  public static final int DEFAULT_MAX_JSON_STRING_LEN = 180_000_000;\n\n  public static final String MAX_JSON_STRING_LENGTH_JVM =\n      \"net.snowflake.jdbc.objectMapper.maxJsonStringLength\";\n\n  public static ObjectMapper getObjectMapper() {\n    ObjectMapper mapper = new ObjectMapper();\n    mapper.configure(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS, false);\n    mapper.configure(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS, false);\n    mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);\n\n    // override the maxStringLength value in ObjectMapper\n    int maxJsonStringLength =\n        SystemUtil.convertSystemPropertyToIntValue(\n            MAX_JSON_STRING_LENGTH_JVM, DEFAULT_MAX_JSON_STRING_LEN);\n    mapper\n        .getFactory()\n        .setStreamReadConstraints(\n            StreamReadConstraints.builder().maxStringLength(maxJsonStringLength).build());\n    return mapper;\n  }\n\n  public static ObjectMapper getObjectMapperForSession(SFBaseSession session) {\n    ObjectMapper mapper = getObjectMapper();\n    if (session != null && session.getCommonParameters() != null) {\n      // Set the mapper to use the session's object mapper settings\n      Object dateOutputFormat = session.getCommonParameters().get(\"DATE_OUTPUT_FORMAT\");\n      if (dateOutputFormat != null) {\n        String dateFormat =\n            SnowflakeDateTimeFormat.fromSqlFormat(String.valueOf(dateOutputFormat))\n                .toSimpleDateTimePattern();\n        mapper.setDateFormat(new SimpleDateFormat(dateFormat));\n      } else {\n        log.debug(\"DATE_OUTPUT_FORMAT is not set in session parameters.\");\n      }\n    } else {\n      log.debug(\"Initialized object mapper without session or parameter settings.\");\n    }\n    return mapper;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/OpaqueContextDTO.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n/**\n * This context is opaque to the JDBC driver. It is generated by the Cloud service and pushed back\n * on each request invocation.\n */\npublic class OpaqueContextDTO {\n  @JsonInclude(JsonInclude.Include.NON_NULL)\n  private String base64Data;\n\n  @JsonCreator\n  public OpaqueContextDTO(@JsonProperty(\"base64Data\") String base64Data) {\n    this.base64Data = base64Data;\n  }\n\n  public String getBase64Data() {\n    return base64Data;\n  }\n\n  public void setBase64Data(String base64Data) {\n    this.base64Data = base64Data;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/ParameterBindingDTO.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport net.snowflake.client.internal.jdbc.BindingParameterMetadata;\n\n/** This class represents a binding object passed to server side. */\npublic class ParameterBindingDTO {\n  /** Type of binding */\n  private String type;\n\n  private String fmt;\n  private BindingParameterMetadata schema;\n\n  /** Value is a String object if it's a single bind, otherwise is an array of String */\n  private Object value;\n\n  public ParameterBindingDTO(\n      String fmt, String type, Object value, BindingParameterMetadata schema) {\n    this.fmt = fmt;\n    this.type = type;\n    this.value = value;\n    this.schema = schema;\n  }\n\n  public ParameterBindingDTO(String fmt, String type, Object value) {\n    this(fmt, type, value, null);\n  }\n\n  public ParameterBindingDTO(String type, Object value) {\n    this(null, type, value, null);\n  }\n\n  public Object getValue() {\n    return value;\n  }\n\n  public String getType() {\n    return type;\n  }\n\n  public void setType(String type) {\n    this.type = type;\n  }\n\n  public void setValue(Object value) {\n    this.value = value;\n  }\n\n  public String getFmt() {\n    return fmt;\n  }\n\n  public void setFmt(String fmt) {\n    this.fmt = fmt;\n  }\n\n  public BindingParameterMetadata getSchema() {\n    return schema;\n  }\n\n  public void setSchema(BindingParameterMetadata schema) {\n    this.schema = schema;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/PrivateLinkDetector.java",
    "content": "package net.snowflake.client.internal.core;\n\npublic class PrivateLinkDetector {\n  /**\n   * We can only tell if private link is enabled for certain hosts when the hostname contains the\n   * word 'privatelink' but we don't have a good way of telling if a private link connection is\n   * expected for internal stages for example.\n   *\n   * @param host host\n   * @return true if host is considered as privatelink environment\n   */\n  public static boolean isPrivateLink(String host) {\n    return host.toLowerCase().contains(\".privatelink.snowflakecomputing.\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/QueryContextCache.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TreeSet;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Most Recently Used and Priority based cache. A separate cache for each connection in the driver.\n */\npublic class QueryContextCache {\n  private final int capacity; // Capacity of the cache\n  private final HashMap<Long, QueryContextElement> idMap; // Map for id and QCC\n\n  private final TreeSet<QueryContextElement> treeSet; // Order data as per priority\n\n  private final HashMap<Long, QueryContextElement> priorityMap; // Map for priority and QCC\n\n  private final HashMap<Long, QueryContextElement>\n      newPriorityMap; // Intermediate map for priority and QCC for current round of merging\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(QueryContextCache.class);\n\n  private static ObjectMapper jsonObjectMapper;\n\n  static {\n    jsonObjectMapper = new ObjectMapper();\n  }\n\n  /**\n   * Constructor.\n   *\n   * @param capacity Maximum capacity of the cache.\n   */\n  public QueryContextCache(int capacity) {\n    this.capacity = capacity;\n    idMap = new HashMap<>();\n    priorityMap = new HashMap<>();\n    newPriorityMap = new HashMap<>();\n    treeSet =\n        new TreeSet<>(\n            Comparator.comparingLong(QueryContextElement::getPriority)\n                .thenComparingLong(QueryContextElement::getId)\n                .thenComparingLong(QueryContextElement::getReadTimestamp));\n  }\n\n  /**\n   * Merge a new element comes from the server with the existing cache. Merge is based on read time\n   * stamp for the same id and based on priority for two different ids.\n   *\n   * @param id Database id.\n   * @param readTimestamp Last time read metadata from FDB.\n   * @param priority 0 to N number, where 0 is the highest priority. Eviction policy is based on\n   *     priority.\n   * @param context Opaque query context.\n   */\n  void merge(long id, long readTimestamp, long priority, String context) {\n    if (idMap.containsKey(id)) {\n      // ID found in the cache\n      QueryContextElement qce = idMap.get(id);\n      if (readTimestamp > qce.readTimestamp) {\n        if (qce.priority == priority) {\n          // Same priority, overwrite new data at same place\n          qce.readTimestamp = readTimestamp;\n          qce.context = context;\n        } else {\n          // Change in priority\n          QueryContextElement newQCE =\n              new QueryContextElement(id, readTimestamp, priority, context);\n\n          replaceQCE(qce, newQCE);\n        } // new priority\n      } // new data is recent\n      else if (readTimestamp == qce.readTimestamp && qce.priority != priority) {\n        // Same read timestamp but change in priority\n        QueryContextElement newQCE = new QueryContextElement(id, readTimestamp, priority, context);\n        replaceQCE(qce, newQCE);\n      }\n    } // id found\n    else {\n      // new id\n      if (priorityMap.containsKey(priority)) {\n\n        // Same priority with different id\n        QueryContextElement qce = priorityMap.get(priority);\n        // Replace with new data\n        QueryContextElement newQCE = new QueryContextElement(id, readTimestamp, priority, context);\n        replaceQCE(qce, newQCE);\n      } else {\n        // new priority\n        // Add new element in the cache\n        QueryContextElement newQCE = new QueryContextElement(id, readTimestamp, priority, context);\n        addQCE(newQCE);\n      }\n    }\n  }\n\n  /** Sync the newPriorityMap with the priorityMap at the end of current round of merge */\n  void syncPriorityMap() {\n    logger.debug(\n        \"syncPriorityMap called priorityMap size: {}, newPrioirtyMap size: {}\",\n        priorityMap.size(),\n        newPriorityMap.size());\n    for (Map.Entry<Long, QueryContextElement> entry : newPriorityMap.entrySet()) {\n      priorityMap.put(entry.getKey(), entry.getValue());\n    }\n    // clear the newPriorityMap for next round of QCC merge(a round consists of multiple entries)\n    newPriorityMap.clear();\n  }\n\n  /**\n   * After the merge, loop through priority list and make sure cache is at most capacity. Remove all\n   * other elements from the list based on priority.\n   */\n  void checkCacheCapacity() {\n    logger.debug(\n        \"checkCacheCapacity() called. treeSet size: {} cache capacity: {}\",\n        treeSet.size(),\n        capacity);\n    if (treeSet.size() > capacity) {\n      // remove elements based on priority\n      while (treeSet.size() > capacity) {\n        QueryContextElement qce = treeSet.last();\n        removeQCE(qce);\n      }\n    }\n\n    logger.debug(\n        \"checkCacheCapacity() returns. treeSet size: {} cache capacity: {}\",\n        treeSet.size(),\n        capacity);\n  }\n\n  /** Clear the cache. */\n  public void clearCache() {\n    logger.trace(\"clearCache() called\");\n    idMap.clear();\n    priorityMap.clear();\n    treeSet.clear();\n    logger.trace(\"clearCache() returns. Number of entries in cache now: {}\", treeSet.size());\n  }\n\n  /**\n   * @param data: the QueryContext Object serialized as a JSON format string\n   */\n  public void deserializeQueryContextJson(String data) {\n\n    synchronized (this) {\n      // Log existing cache entries\n      logCacheEntries();\n\n      if (data == null || data.length() == 0) {\n        // Clear the cache\n        clearCache();\n        return;\n      }\n\n      try {\n        JsonNode rootNode = jsonObjectMapper.readTree(data);\n\n        // Deserialize the entries. The first entry with priority is the main entry. On JDBC side,\n        // we save all entries into one list to simplify the logic. An example JSON is:\n        // {\n        //   \"entries\": [\n        //    {\n        //     \"id\": 0,\n        //     \"read_timestamp\": 123456789,\n        //     \"priority\": 0,\n        //     \"context\": \"base64 encoded context\"\n        //    },\n        //     {\n        //       \"id\": 1,\n        //       \"read_timestamp\": 123456789,\n        //       \"priority\": 1,\n        //       \"context\": \"base64 encoded context\"\n        //     },\n        //     {\n        //       \"id\": 2,\n        //       \"read_timestamp\": 123456789,\n        //       \"priority\": 2,\n        //       \"context\": \"base64 encoded context\"\n        //     }\n        //   ]\n\n        JsonNode entriesNode = rootNode.path(\"entries\");\n        if (entriesNode != null && entriesNode.isArray()) {\n          for (JsonNode entryNode : entriesNode) {\n            QueryContextElement entry = deserializeQueryContextElement(entryNode);\n            if (entry != null) {\n              merge(entry.id, entry.readTimestamp, entry.priority, entry.context);\n            } else {\n              logger.warn(\n                  \"deserializeQueryContextJson: deserializeQueryContextElement meets mismatch field type. Clear the QueryContextCache.\");\n              clearCache();\n              return;\n            }\n          }\n          // after merging all entries, sync the internal priority map to priority map. Because of\n          // priority swicth from GS side,\n          // there could be priority key conflict if we directly operating on the priorityMap during\n          // a round of merge.\n          syncPriorityMap();\n        }\n      } catch (Exception e) {\n        logger.debug(\"deserializeQueryContextJson: Exception: {}\", e.getMessage());\n        // Not rethrowing. clear the cache as incomplete merge can lead to unexpected behavior.\n        clearCache();\n      }\n\n      // After merging all entries, truncate to capacity\n      checkCacheCapacity();\n\n      // Log existing cache entries\n      logCacheEntries();\n    } // Synchronized\n  }\n\n  private static QueryContextElement deserializeQueryContextElement(JsonNode node)\n      throws IOException {\n    QueryContextElement entry = new QueryContextElement();\n    JsonNode idNode = node.path(\"id\");\n    if (idNode.isNumber()) {\n      entry.setId(idNode.asLong());\n    } else {\n      logger.warn(\"deserializeQueryContextElement: `id` field is not Number type\");\n      return null;\n    }\n\n    JsonNode timestampNode = node.path(\"timestamp\");\n    if (timestampNode.isNumber()) {\n      entry.setReadTimestamp(timestampNode.asLong());\n    } else {\n      logger.warn(\"deserializeQueryContextElement: `timestamp` field is not Long type\");\n      return null;\n    }\n\n    JsonNode priorityNode = node.path(\"priority\");\n    if (priorityNode.isNumber()) {\n      entry.setPriority(priorityNode.asLong());\n    } else {\n      logger.warn(\"deserializeQueryContextElement: `priority` field is not Long type\");\n      return null;\n    }\n\n    JsonNode contextNode = node.path(\"context\");\n    if (contextNode.isTextual()) {\n      String contextBytes = contextNode.asText();\n      entry.setContext(contextBytes);\n    } else if (contextNode.isEmpty()) {\n      // Currenly the OpaqueContext field is empty in the JSON received from GS. In the future, it\n      // will\n      // be filled with OpaqueContext object in base64 format.\n      logger.debug(\"deserializeQueryContextElement `context` field is empty\");\n    } else {\n      logger.warn(\"deserializeQueryContextElement: `context` field is not String type\");\n      return null;\n    }\n\n    return entry;\n  }\n\n  /**\n   * Deserialize the QueryContext cache from a QueryContextDTO object. This function currently is\n   * only used in QueryContextCacheTest.java where we check that after serialization and\n   * deserialization, the cache is the same as before.\n   *\n   * @param queryContextDTO QueryContextDTO to deserialize.\n   */\n  public void deserializeQueryContextDTO(QueryContextDTO queryContextDTO) {\n    synchronized (this) {\n      // Log existing cache entries\n      logCacheEntries();\n\n      if (queryContextDTO == null) {\n        // Clear the cache\n        clearCache();\n\n        // Log existing cache entries\n        logCacheEntries();\n\n        return;\n      }\n\n      try {\n\n        List<QueryContextEntryDTO> entries = queryContextDTO.getEntries();\n        if (entries != null) {\n          for (QueryContextEntryDTO entryDTO : entries) {\n            // The main entry priority will always be 0, we simply save a list of\n            // QueryContextEntryDTO in QueryContextDTO\n            QueryContextElement entry = deserializeQueryContextElementDTO(entryDTO);\n            merge(entry.id, entry.readTimestamp, entry.priority, entry.context);\n            logCacheEntries();\n          }\n        }\n        // after merging all entries, sync the internal priority map to priority map. Because of\n        // priority swicth from GS side,\n        // there could be priority key conflict if we directly operating on the priorityMap during a\n        // round of merge.\n        syncPriorityMap();\n      } catch (Exception e) {\n        logger.debug(\"deserializeQueryContextDTO: Exception: {}\", e.getMessage());\n        // Not rethrowing. clear the cache as incomplete merge can lead to unexpected behavior.\n        clearCache();\n      }\n\n      // After merging all entries, truncate to capacity\n      checkCacheCapacity();\n\n      // Log existing cache entries\n      logCacheEntries();\n    } // Synchronized\n  }\n\n  private static QueryContextElement deserializeQueryContextElementDTO(\n      QueryContextEntryDTO entryDTO) throws IOException {\n    QueryContextElement entry =\n        new QueryContextElement(\n            entryDTO.getId(),\n            entryDTO.getTimestamp(),\n            entryDTO.getPriority(),\n            entryDTO.getContext().getBase64Data());\n    return entry;\n  }\n\n  /**\n   * Serialize the QueryContext cache to a QueryContextDTO object, which can be serialized to JSON\n   * automatically later.\n   *\n   * @return {@link QueryContextDTO}\n   */\n  public QueryContextDTO serializeQueryContextDTO() {\n    synchronized (this) {\n      // Log existing cache entries\n      logCacheEntries();\n\n      TreeSet<QueryContextElement> elements = getElements();\n      if (elements.size() == 0) {\n        return null;\n      }\n\n      try {\n        QueryContextDTO queryContextDTO = new QueryContextDTO();\n        List<QueryContextEntryDTO> entries = new ArrayList<QueryContextEntryDTO>();\n        // the first element is the main entry with priority 0. We use a list of\n        // QueryContextEntryDTO to store all entries in QueryContextDTO\n        // to simplify the JDBC side QueryContextCache design.\n        for (final QueryContextElement elem : elements) {\n          QueryContextEntryDTO queryContextElementDTO = serializeQueryContextEntryDTO(elem);\n          entries.add(queryContextElementDTO);\n        }\n        queryContextDTO.setEntries(entries);\n\n        return queryContextDTO;\n\n      } catch (Exception e) {\n        logger.debug(\"serializeQueryContextDTO(): Exception: {}\", e.getMessage());\n        return null;\n      }\n    }\n  }\n\n  private QueryContextEntryDTO serializeQueryContextEntryDTO(QueryContextElement entry)\n      throws IOException {\n    // OpaqueContextDTO contains a base64 encoded byte array. On JDBC side, we do not decode and\n    // encode it\n    QueryContextEntryDTO entryDTO =\n        new QueryContextEntryDTO(\n            entry.getId(),\n            entry.getReadTimestamp(),\n            entry.getPriority(),\n            new OpaqueContextDTO(entry.getContext()));\n    return entryDTO;\n  }\n\n  /**\n   * @param id the id of the element\n   * @param timestamp the last update timestamp\n   * @param priority the priority of the element\n   * @param opaqueContext the binary data of the opaque context\n   * @return a query context element\n   */\n  private static QueryContextElement createElement(\n      long id, long timestamp, long priority, String opaqueContext) {\n    return new QueryContextElement(id, timestamp, priority, opaqueContext);\n  }\n\n  /**\n   * Add an element in the cache.\n   *\n   * @param qce element to add\n   */\n  private void addQCE(QueryContextElement qce) {\n    idMap.put(qce.id, qce);\n    priorityMap.put(qce.priority, qce);\n    treeSet.add(qce);\n  }\n\n  /**\n   * Remove an element from the cache.\n   *\n   * @param qce element to remove.\n   */\n  private void removeQCE(QueryContextElement qce) {\n    treeSet.remove(qce);\n    priorityMap.remove(qce.priority);\n    idMap.remove(qce.id);\n  }\n\n  /**\n   * Replace the cache element with a new response element. Remove old element exist in the cache\n   * and add a new element received.\n   *\n   * @param oldQCE an element exist in the cache\n   * @param newQCE a new element just received.\n   */\n  private void replaceQCE(QueryContextElement oldQCE, QueryContextElement newQCE) {\n    // Remove old element from the cache\n    removeQCE(oldQCE);\n    // Add new element in the cache\n    addQCE(newQCE);\n  }\n\n  /**\n   * Get all elements in the cache in the order of the priority.\n   *\n   * @return TreeSet containing cache elements\n   */\n  private TreeSet<QueryContextElement> getElements() {\n    return treeSet;\n  }\n\n  int getSize() {\n    return treeSet.size();\n  }\n\n  void getElements(long[] ids, long[] readTimestamps, long[] priorities, String[] contexts) {\n    TreeSet<QueryContextElement> elems = getElements();\n    int i = 0;\n\n    for (QueryContextElement elem : elems) {\n      ids[i] = elem.id;\n      readTimestamps[i] = elem.readTimestamp;\n      priorities[i] = elem.priority;\n      contexts[i] = elem.context;\n      i++;\n    }\n  }\n\n  /** Debugging purpose, log the all entries in the cache. */\n  void logCacheEntries() {\n    if (logger.isDebugEnabled()) {\n      TreeSet<QueryContextElement> elements = getElements();\n      for (final QueryContextElement elem : elements) {\n        logger.debug(\n            \" Cache Entry: id: {} readTimestamp: {} priority: {}\",\n            elem.id,\n            elem.readTimestamp,\n            elem.priority);\n      }\n    }\n  }\n\n  /** Query context information. */\n  private static class QueryContextElement implements Comparable<QueryContextElement> {\n    long id; // database id as key. (bigint)\n    long readTimestamp; // When the query context read (bigint). Compare for same id.\n    long priority; // Priority of the query context (bigint). Compare for different ids.\n    String context; // Opaque information (varbinary).\n\n    public QueryContextElement() {\n      // Default constructor\n    }\n\n    /**\n     * Constructor.\n     *\n     * @param id database id\n     * @param readTimestamp Server time when this entry read\n     * @param priority Priority of this entry w.r.t other ids\n     * @param context Opaque query context, used by query processor in the server.\n     */\n    public QueryContextElement(long id, long readTimestamp, long priority, String context) {\n      this.id = id;\n      this.readTimestamp = readTimestamp;\n      this.priority = priority;\n      this.context = context;\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n      if (obj == this) {\n        return true;\n      }\n\n      if (!(obj instanceof QueryContextElement)) {\n        return super.equals(obj);\n      }\n\n      QueryContextElement other = (QueryContextElement) obj;\n      return (id == other.id\n          && readTimestamp == other.readTimestamp\n          && priority == other.priority\n          && context.equals(other.context));\n    }\n\n    @Override\n    public int hashCode() {\n      int hash = 31;\n\n      hash = hash * 31 + (int) id;\n      hash += (hash * 31) + (int) readTimestamp;\n      hash += (hash * 31) + (int) priority;\n      hash += (hash * 31) + context.hashCode();\n\n      return hash;\n    }\n\n    /**\n     * Keep elements in ascending order of the priority. This method called by TreeSet.\n     *\n     * @param obj the object to be compared.\n     * @return 0 if equals, -1 if this element is less than new element, otherwise 1.\n     */\n    public int compareTo(QueryContextElement obj) {\n      return (priority == obj.priority) ? 0 : (((priority - obj.priority) < 0) ? -1 : 1);\n    }\n\n    public void setId(long id) {\n      this.id = id;\n    }\n\n    public void setPriority(long priority) {\n      this.priority = priority;\n    }\n\n    public void setContext(String context) {\n      this.context = context;\n    }\n\n    public void setReadTimestamp(long readTimestamp) {\n      this.readTimestamp = readTimestamp;\n    }\n\n    public long getId() {\n      return id;\n    }\n\n    public long getReadTimestamp() {\n      return readTimestamp;\n    }\n\n    public long getPriority() {\n      return priority;\n    }\n\n    public String getContext() {\n      return context;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/QueryContextDTO.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport java.util.List;\n\n// The POJO object used by both JDBC and the Cloud service to exchange opaque informations.\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class QueryContextDTO {\n\n  // QueryContextDTO is a list of QueryContextEntryDTO. The first entry is the main entry with\n  // priority 0.\n  private List<QueryContextEntryDTO> entries;\n\n  public QueryContextDTO() {\n    entries = null;\n  }\n\n  public QueryContextDTO(List<QueryContextEntryDTO> entries) {\n    this.entries = entries;\n  }\n\n  public List<QueryContextEntryDTO> getEntries() {\n    return entries;\n  }\n\n  public void setEntries(List<QueryContextEntryDTO> entries) {\n    this.entries = entries;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/QueryContextEntryDTO.java",
    "content": "package net.snowflake.client.internal.core;\n\n/**\n * An entry in the set of query context exchanged with Cloud Services. This includes a domain\n * identifier(id), a timestamp that is monodically increasing, a priority for eviction and the\n * opaque information sent from the Cloud service.\n */\npublic class QueryContextEntryDTO {\n  private long id;\n  private long timestamp;\n  private long priority;\n  private OpaqueContextDTO context;\n\n  public QueryContextEntryDTO() {\n    // empty constructor\n  }\n\n  public QueryContextEntryDTO(long id, long timestamp, long priority, OpaqueContextDTO context) {\n    this.id = id;\n    this.timestamp = timestamp;\n    this.priority = priority;\n    this.context = context;\n  }\n\n  public long getId() {\n    return id;\n  }\n\n  public void setId(long id) {\n    this.id = id;\n  }\n\n  public long getTimestamp() {\n    return timestamp;\n  }\n\n  public void setTimestamp(long timestamp) {\n    this.timestamp = timestamp;\n  }\n\n  public long getPriority() {\n    return priority;\n  }\n\n  public void setPriority(long priority) {\n    this.priority = priority;\n  }\n\n  public OpaqueContextDTO getContext() {\n    return context;\n  }\n\n  public void setContext(OpaqueContextDTO context) {\n    this.context = context;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/QueryExecDTO.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.Map;\n\n/** Body of a query request */\npublic class QueryExecDTO {\n  private String sqlText;\n\n  @Deprecated private Integer sequenceId;\n\n  private Map<String, ParameterBindingDTO> bindings;\n\n  private String bindStage;\n\n  private boolean describeOnly;\n\n  private Map<String, Object> parameters;\n\n  // Optional query context sent to the JDBC driver from the Cloud Service.\n  private QueryContextDTO queryContextDTO;\n\n  private String describedJobId;\n\n  private long querySubmissionTime;\n\n  private boolean isInternal;\n\n  // Boolean value that, if true, indicates query should be asynchronous\n  private boolean asyncExec;\n\n  public QueryExecDTO(\n      String sqlText,\n      boolean describeOnly,\n      Integer sequenceId,\n      Map<String, ParameterBindingDTO> bindings,\n      String bindStage,\n      Map<String, Object> parameters,\n      QueryContextDTO queryContext,\n      long querySubmissionTime,\n      boolean internal,\n      boolean asyncExec) {\n    this.sqlText = sqlText;\n    this.describeOnly = describeOnly;\n    this.sequenceId = sequenceId;\n    this.bindings = bindings;\n    this.bindStage = bindStage;\n    this.parameters = parameters;\n    this.queryContextDTO = queryContext;\n    this.querySubmissionTime = querySubmissionTime;\n    this.isInternal = internal;\n    this.asyncExec = asyncExec; // indicates whether query should be asynchronous\n  }\n\n  public String getSqlText() {\n    return sqlText;\n  }\n\n  public void setSqlText(String sqlText) {\n    this.sqlText = sqlText;\n  }\n\n  @Deprecated\n  public Integer getSequenceId() {\n    return sequenceId;\n  }\n\n  @Deprecated\n  public void setSequenceId(Integer sequenceId) {\n    this.sequenceId = sequenceId;\n  }\n\n  public Map<String, ParameterBindingDTO> getBindings() {\n    return bindings;\n  }\n\n  public void setBindings(Map<String, ParameterBindingDTO> bindings) {\n    this.bindings = bindings;\n  }\n\n  public String getBindStage() {\n    return bindStage;\n  }\n\n  public void setBindStage(String bindStage) {\n    this.bindStage = bindStage;\n  }\n\n  public boolean isDescribeOnly() {\n    return describeOnly;\n  }\n\n  public void setDescribeOnly(boolean describeOnly) {\n    this.describeOnly = describeOnly;\n  }\n\n  public Map<String, Object> getParameters() {\n    return parameters;\n  }\n\n  public void setParameters(Map<String, Object> parameters) {\n    this.parameters = parameters;\n  }\n\n  public QueryContextDTO getqueryContextDTO() {\n    return queryContextDTO;\n  }\n\n  public void queryContextDTO(QueryContextDTO queryContext) {\n    this.queryContextDTO = queryContext;\n  }\n\n  public String getDescribedJobId() {\n    return describedJobId;\n  }\n\n  public void setDescribedJobId(String describedJobId) {\n    this.describedJobId = describedJobId;\n  }\n\n  public long getQuerySubmissionTime() {\n    return querySubmissionTime;\n  }\n\n  public void setQuerySubmissionTime(long querySubmissionTime) {\n    this.querySubmissionTime = querySubmissionTime;\n  }\n\n  public void setIsInternal(boolean isInternal) {\n    this.isInternal = isInternal;\n  }\n\n  public boolean getIsInternal() {\n    return this.isInternal;\n  }\n\n  public void setAsyncExec(boolean asyncExec) {\n    this.asyncExec = asyncExec;\n  }\n\n  public boolean getAsyncExec() {\n    return this.asyncExec;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/QueryResultFormat.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.Optional;\n\npublic enum QueryResultFormat {\n  JSON(\"json\"),\n  ARROW(\"arrow\");\n\n  private String name;\n\n  QueryResultFormat(String name) {\n    this.name = name;\n  }\n\n  public static Optional<QueryResultFormat> lookupByName(String n) {\n    for (QueryResultFormat format : QueryResultFormat.values()) {\n      if (format.name.equalsIgnoreCase(n)) {\n        return Optional.of(format);\n      }\n    }\n\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/ResultUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.SQLException;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SFTime;\nimport net.snowflake.common.core.SFTimestamp;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\nimport net.snowflake.common.util.TimeUtil;\n\npublic class ResultUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ResultUtil.class);\n\n  public static final int MILLIS_IN_ONE_DAY = 86400000;\n  public static final int DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS =\n      3; // default scale for SFTime fraction seconds\n\n  // Map of default parameter values, used by effectiveParamValue().\n  private static final Map<String, Object> defaultParameters;\n\n  static {\n    Map<String, Object> map = new HashMap<>();\n\n    // IMPORTANT: This must be consistent with CommonParameterEnum\n    map.put(\"TIMEZONE\", \"America/Los_Angeles\");\n    map.put(\"TIMESTAMP_OUTPUT_FORMAT\", \"DY, DD MON YYYY HH24:MI:SS TZHTZM\");\n    map.put(\"TIMESTAMP_NTZ_OUTPUT_FORMAT\", \"\");\n    map.put(\"TIMESTAMP_LTZ_OUTPUT_FORMAT\", \"\");\n    map.put(\"TIMESTAMP_TZ_OUTPUT_FORMAT\", \"\");\n    map.put(\"DATE_OUTPUT_FORMAT\", \"YYYY-MM-DD\");\n    map.put(\"TIME_OUTPUT_FORMAT\", \"HH24:MI:SS\");\n    map.put(\"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\", Boolean.TRUE);\n    map.put(\"CLIENT_DISABLE_INCIDENTS\", Boolean.TRUE);\n    map.put(\"BINARY_OUTPUT_FORMAT\", \"HEX\");\n    defaultParameters = map;\n  }\n\n  /**\n   * Returns the effective parameter value, using the value explicitly provided in parameters, or\n   * the default if absent\n   *\n   * @param parameters keyed in parameter name and valued in parameter value\n   * @param paramName Parameter to return the value of\n   * @return Effective value\n   */\n  public static Object effectiveParamValue(Map<String, Object> parameters, String paramName) {\n    String upper = paramName.toUpperCase();\n    Object value = parameters.get(upper);\n\n    if (value != null) {\n      return value;\n    }\n\n    value = defaultParameters.get(upper);\n    if (value != null) {\n      return value;\n    }\n\n    logger.debug(\"Unknown Common Parameter: {}\", paramName);\n    return null;\n  }\n\n  /**\n   * Helper function building a formatter for a specialized timestamp type. Note that it will be\n   * based on either the 'param' value if set, or the default format provided.\n   *\n   * @param parameters keyed in parameter name and valued in parameter value\n   * @param id id\n   * @param param timestamp output format param\n   * @param defaultFormat default format\n   * @return {@link SnowflakeDateTimeFormat}\n   */\n  public static SnowflakeDateTimeFormat specializedFormatter(\n      Map<String, Object> parameters, String id, String param, String defaultFormat) {\n    String sqlFormat =\n        SnowflakeDateTimeFormat.effectiveSpecializedTimestampFormat(\n            (String) effectiveParamValue(parameters, param), defaultFormat);\n    SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(sqlFormat);\n    logger.debug(\n        \"sql {} format: {}, java {} format: {}\",\n        id,\n        sqlFormat,\n        id,\n        (ArgSupplier) formatter::toSimpleDateTimePattern);\n    return formatter;\n  }\n\n  /**\n   * Adjust timestamp for dates before 1582-10-05\n   *\n   * @param timestamp needs to be adjusted\n   * @return adjusted timestamp\n   */\n  public static Timestamp adjustTimestamp(Timestamp timestamp) {\n    long milliToAdjust = ResultUtil.msDiffJulianToGregorian(timestamp);\n\n    if (milliToAdjust != 0) {\n      logger.debug(\n          \"adjust timestamp by {} days\", (ArgSupplier) () -> milliToAdjust / MILLIS_IN_ONE_DAY);\n\n      Timestamp newTimestamp = new Timestamp(timestamp.getTime() + milliToAdjust);\n\n      newTimestamp.setNanos(timestamp.getNanos());\n\n      return newTimestamp;\n    } else {\n      return timestamp;\n    }\n  }\n\n  /**\n   * For dates before 1582-10-05, calculate the number of millis to adjust.\n   *\n   * @param date date before 1582-10-05\n   * @return millis needs to be adjusted\n   */\n  public static long msDiffJulianToGregorian(java.util.Date date) {\n    // if date is before 1582-10-05, apply the difference\n    // by (H-(H/4)-2) where H is the hundreds digit of the year according to:\n    // http://en.wikipedia.org/wiki/Gregorian_calendar\n    if (date.getTime() < -12220156800000L) {\n      // get the year of the date\n      Calendar cal = Calendar.getInstance();\n      cal.setTime(date);\n      int year = cal.get(Calendar.YEAR);\n      int month = cal.get(Calendar.MONTH);\n      int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);\n\n      // for dates on or before 02/28, use the previous year otherwise use\n      // current year.\n      // TODO: we need to revisit this since there is a potential issue using\n      // the year/month/day from the calendar since that may not be the same\n      // year/month/day as the original date (which is the problem we are\n      // trying to solve here).\n\n      if (month == 0 || (month == 1 && dayOfMonth <= 28)) {\n        year = year - 1;\n      }\n\n      int hundreds = year / 100;\n      int differenceInDays = hundreds - (hundreds / 4) - 2;\n\n      return differenceInDays * MILLIS_IN_ONE_DAY;\n    } else {\n      return 0;\n    }\n  }\n\n  /**\n   * Convert a timestamp internal value (scaled number of seconds + fractional seconds) into a\n   * SFTimestamp.\n   *\n   * @param timestampStr timestamp object\n   * @param scale timestamp scale\n   * @param internalColumnType snowflake timestamp type\n   * @param resultVersion For new result version, timestamp with timezone is formatted as the\n   *     seconds since epoch with fractional part in the decimal followed by time zone index. E.g.:\n   *     \"123.456 1440\". Here 123.456 is the * number of seconds since epoch and 1440 is the\n   *     timezone index.\n   * @param sessionTZ session timezone\n   * @param session session object\n   * @return converted snowflake timestamp object\n   * @throws SFException if timestampStr is an invalid timestamp\n   */\n  public static SFTimestamp getSFTimestamp(\n      String timestampStr,\n      int scale,\n      int internalColumnType,\n      long resultVersion,\n      TimeZone sessionTZ,\n      SFBaseSession session)\n      throws SFException {\n    logger.trace(\"Timestamp getTimestamp(int columnIndex)\", false);\n\n    try {\n      TimeUtil.TimestampType tsType = null;\n\n      switch (internalColumnType) {\n        case Types.TIMESTAMP:\n          tsType = TimeUtil.TimestampType.TIMESTAMP_NTZ;\n          break;\n        case SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ:\n          tsType = TimeUtil.TimestampType.TIMESTAMP_TZ;\n          logger.trace(\n              \"Handle timestamp with timezone {} encoding: {}\",\n              (resultVersion > 0 ? \"new\" : \"old\"),\n              timestampStr);\n          break;\n        case SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ:\n          tsType = TimeUtil.TimestampType.TIMESTAMP_LTZ;\n          break;\n      }\n\n      // Construct a timestamp\n      return TimeUtil.getSFTimestamp(timestampStr, scale, tsType, resultVersion, sessionTZ);\n    } catch (IllegalArgumentException ex) {\n      throw new SFException(ErrorCode.IO_ERROR, \"Invalid timestamp value: \" + timestampStr);\n    }\n  }\n\n  /**\n   * Convert a time internal value (scaled number of seconds + fractional seconds) into an SFTime.\n   *\n   * <p>Example: getSFTime(\"123.456\", 5) returns an SFTime for 00:02:03.45600.\n   *\n   * @param obj time object\n   * @param scale time scale\n   * @param session session object\n   * @return snowflake time object\n   * @throws SFException if time is invalid\n   */\n  public static SFTime getSFTime(String obj, int scale, SFBaseSession session) throws SFException {\n    try {\n      return TimeUtil.getSFTime(obj, scale);\n    } catch (IllegalArgumentException ex) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"Invalid time value: \" + obj);\n    }\n  }\n\n  /**\n   * Convert a time value into a string\n   *\n   * @param sft snowflake time object\n   * @param scale time scale\n   * @param timeFormatter time formatter\n   * @return time in string\n   */\n  public static String getSFTimeAsString(\n      SFTime sft, int scale, SnowflakeDateTimeFormat timeFormatter) {\n    return timeFormatter.format(sft, scale);\n  }\n\n  /**\n   * Convert a boolean to a string\n   *\n   * @param bool boolean\n   * @return boolean in string\n   */\n  public static String getBooleanAsString(boolean bool) {\n    return bool ? \"TRUE\" : \"FALSE\";\n  }\n\n  /**\n   * Convert a SFTimestamp to a string value.\n   *\n   * @param sfTS snowflake timestamp object\n   * @param columnType internal snowflake t\n   * @param scale timestamp scale\n   * @param timestampNTZFormatter snowflake timestamp ntz format\n   * @param timestampLTZFormatter snowflake timestamp ltz format\n   * @param timestampTZFormatter snowflake timestamp tz format\n   * @param session session object\n   * @return timestamp in string in desired format\n   * @throws SFException timestamp format is missing\n   */\n  public static String getSFTimestampAsString(\n      SFTimestamp sfTS,\n      int columnType,\n      int scale,\n      SnowflakeDateTimeFormat timestampNTZFormatter,\n      SnowflakeDateTimeFormat timestampLTZFormatter,\n      SnowflakeDateTimeFormat timestampTZFormatter,\n      SFBaseSession session)\n      throws SFException {\n    // Derive the timestamp formatter to use\n    SnowflakeDateTimeFormat formatter;\n    if (columnType == Types.TIMESTAMP || columnType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ) {\n      formatter = timestampNTZFormatter;\n    } else if (columnType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ) {\n      formatter = timestampLTZFormatter;\n    } else // TZ\n    {\n      formatter = timestampTZFormatter;\n    }\n\n    if (formatter == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp formatter\");\n    }\n\n    try {\n      Timestamp adjustedTimestamp = ResultUtil.adjustTimestamp(sfTS.getTimestamp());\n\n      return formatter.format(adjustedTimestamp, sfTS.getTimeZone(), scale);\n    } catch (SFTimestamp.TimestampOperationNotAvailableException e) {\n      // this timestamp doesn't fit into a Java timestamp, and therefore we\n      // can't format it (for now). Just print it out as seconds since epoch.\n\n      BigDecimal nanosSinceEpoch = sfTS.getNanosSinceEpoch();\n\n      BigDecimal secondsSinceEpoch = nanosSinceEpoch.scaleByPowerOfTen(-9);\n\n      return secondsSinceEpoch.setScale(scale).toPlainString();\n    }\n  }\n\n  /**\n   * Convert a date value into a string\n   *\n   * @param date date will be converted\n   * @param dateFormatter date format\n   * @return date in string\n   */\n  public static String getDateAsString(Date date, SnowflakeDateTimeFormat dateFormatter) {\n    return dateFormatter.format(date, TimeZone.getDefault());\n  }\n\n  /**\n   * Adjust date for before 1582-10-05\n   *\n   * @param date date before 1582-10-05\n   * @return adjusted date\n   */\n  public static Date adjustDate(Date date) {\n    long milliToAdjust = ResultUtil.msDiffJulianToGregorian(date);\n\n    if (milliToAdjust != 0) {\n      // add the difference to the new date\n      return new Date(date.getTime() + milliToAdjust);\n    } else {\n      return date;\n    }\n  }\n\n  /**\n   * Convert a date internal object to a Date object in specified timezone.\n   *\n   * @param str snowflake date object\n   * @param tz timezone we want convert to\n   * @param session snowflake session object\n   * @return java date object\n   * @throws SFException if date is invalid\n   */\n  @Deprecated\n  public static Date getDate(String str, TimeZone tz, SFBaseSession session) throws SFException {\n    try {\n      long milliSecsSinceEpoch = Long.valueOf(str) * MILLIS_IN_ONE_DAY;\n\n      SFTimestamp tsInUTC =\n          SFTimestamp.fromDate(new Date(milliSecsSinceEpoch), 0, TimeZone.getTimeZone(\"UTC\"));\n\n      SFTimestamp tsInClientTZ = tsInUTC.moveToTimeZone(tz);\n\n      logger.debug(\n          \"getDate: tz offset={}\",\n          (ArgSupplier) () -> tsInClientTZ.getTimeZone().getOffset(tsInClientTZ.getTime()));\n\n      // return the date adjusted to the JVM default time zone\n      Date preDate = new Date(tsInClientTZ.getTime());\n\n      // if date is on or before 1582-10-04, apply the difference\n      // by (H-H/4-2) where H is the hundreds digit of the year according to:\n      // http://en.wikipedia.org/wiki/Gregorian_calendar\n      Date newDate = adjustDate(preDate);\n      logger.debug(\n          \"Adjust date from {} to {}\",\n          (ArgSupplier) preDate::toString,\n          (ArgSupplier) newDate::toString);\n      return newDate;\n    } catch (NumberFormatException ex) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"Invalid date value: \" + str);\n    }\n  }\n\n  /**\n   * Convert snowflake bool to java boolean\n   *\n   * @param str boolean type in string representation\n   * @return true if the value indicates true otherwise false\n   */\n  public static boolean getBoolean(String str) {\n    return str.equalsIgnoreCase(Boolean.TRUE.toString()) || str.equals(\"1\");\n  }\n\n  /**\n   * Calculate number of rows updated given a result set Interpret result format based on result\n   * set's statement type\n   *\n   * @param resultSet result set to extract update count from\n   * @return the number of rows updated\n   * @throws SFException if failed to calculate update count\n   * @throws SQLException if failed to calculate update count\n   */\n  public static long calculateUpdateCount(SFBaseResultSet resultSet)\n      throws SFException, SQLException {\n    long updateCount = 0;\n    SFStatementType statementType = resultSet.getStatementType();\n    if (statementType.isDML()) {\n      while (resultSet.next()) {\n        if (statementType == SFStatementType.COPY) {\n          SFResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n          int columnIndex = resultSetMetaData.getColumnIndex(\"rows_loaded\");\n          updateCount += columnIndex == -1 ? 0 : resultSet.getLong(columnIndex + 1);\n        } else if (statementType == SFStatementType.INSERT\n            || statementType == SFStatementType.UPDATE\n            || statementType == SFStatementType.DELETE\n            || statementType == SFStatementType.MERGE\n            || statementType == SFStatementType.MULTI_INSERT) {\n          int columnCount = resultSet.getMetaData().getColumnCount();\n          for (int i = 0; i < columnCount; i++) {\n            updateCount += resultSet.getLong(i + 1); // add up number of rows updated\n          }\n        } else {\n          updateCount = 0;\n        }\n      }\n    } else {\n      updateCount = statementType.isGenerateResultSet() ? -1 : 0;\n    }\n\n    return updateCount;\n  }\n\n  /**\n   * Given a list of String, do a case insensitive search for target string Used by\n   * resultsetMetadata to search for target column name\n   *\n   * @param source source string list\n   * @param target target string to match\n   * @return index in the source string list that matches the target string index starts from zero\n   */\n  public static int listSearchCaseInsensitive(List<String> source, String target) {\n    for (int i = 0; i < source.size(); i++) {\n      if (target.equalsIgnoreCase(source.get(i))) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  /**\n   * Return the list of result IDs provided in a result, if available; otherwise return an empty\n   * list.\n   *\n   * @param result result json\n   * @return list of result IDs which can be used for result scans\n   */\n  private static List<String> getResultIds(JsonNode result) {\n    JsonNode resultIds = result.path(\"data\").path(\"resultIds\");\n    if (resultIds.isNull() || resultIds.isMissingNode() || resultIds.asText().isEmpty()) {\n      return Collections.emptyList();\n    }\n    return new ArrayList<>(Arrays.asList(resultIds.asText().split(\",\")));\n  }\n\n  /**\n   * Return the list of result types provided in a result, if available; otherwise return an empty\n   * list.\n   *\n   * @param result result json\n   * @return list of result IDs which can be used for result scans\n   */\n  private static List<SFStatementType> getResultTypes(JsonNode result) {\n    JsonNode resultTypes = result.path(\"data\").path(\"resultTypes\");\n    if (resultTypes.isNull() || resultTypes.isMissingNode() || resultTypes.asText().isEmpty()) {\n      return Collections.emptyList();\n    }\n\n    String[] typeStrs = resultTypes.asText().split(\",\");\n\n    List<SFStatementType> res = new ArrayList<>();\n    for (String typeStr : typeStrs) {\n      long typeId = Long.valueOf(typeStr);\n      res.add(SFStatementType.lookUpTypeById(typeId));\n    }\n    return res;\n  }\n\n  /**\n   * Return the list of child results provided in a result, if available; otherwise return an empty\n   * list\n   *\n   * @param session the current session\n   * @param requestId the current request id\n   * @param result result json\n   * @return list of child results\n   * @throws SFException if the number of child IDs does not match child statement types\n   */\n  public static List<SFChildResult> getChildResults(\n      SFBaseSession session, String requestId, JsonNode result) throws SFException {\n    List<String> ids = getResultIds(result);\n    List<SFStatementType> types = getResultTypes(result);\n\n    if (ids.size() != types.size()) {\n      throw new SFException(\n          ErrorCode.CHILD_RESULT_IDS_AND_TYPES_DIFFERENT_SIZES, ids.size(), types.size());\n    }\n\n    List<SFChildResult> res = new ArrayList<>();\n    for (int i = 0; i < ids.size(); i++) {\n      res.add(new SFChildResult(ids.get(i), types.get(i)));\n    }\n\n    return res;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFArrowResultSet.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.StmtUtil.eventHandler;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.sql.Array;\nimport java.sql.Date;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport java.util.stream.Stream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.arrow.ArrayConverter;\nimport net.snowflake.client.internal.core.arrow.ArrowVectorConverter;\nimport net.snowflake.client.internal.core.arrow.MapConverter;\nimport net.snowflake.client.internal.core.arrow.StructConverter;\nimport net.snowflake.client.internal.core.arrow.StructObjectWrapper;\nimport net.snowflake.client.internal.core.arrow.VarCharConverter;\nimport net.snowflake.client.internal.core.arrow.VectorTypeConverter;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.ArrowResultChunk;\nimport net.snowflake.client.internal.jdbc.ArrowResultChunk.ArrowChunkIterator;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.Converter;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.util.JsonStringHashMap;\n\n/** Arrow result set implementation */\npublic class SFArrowResultSet extends SFBaseResultSet implements DataConversionContext {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFArrowResultSet.class);\n\n  /** iterator over current arrow result chunk */\n  private ArrowChunkIterator currentChunkIterator;\n\n  /** current query id */\n  private String queryId;\n\n  /** type of statement generate this result set */\n  private SFStatementType statementType;\n\n  private boolean totalRowCountTruncated;\n\n  /** true if sort first chunk */\n  private boolean sortResult;\n\n  /** statement generate current result set */\n  protected SFBaseStatement statement;\n\n  /** is array bind supported */\n  private final boolean arrayBindSupported;\n\n  /** index of next chunk to consume */\n  private long nextChunkIndex = 0;\n\n  /** total chunk count, not include first chunk */\n  private final long chunkCount;\n\n  /** chunk downloader */\n  private ChunkDownloader chunkDownloader;\n\n  /** time when first chunk arrived */\n  private final long firstChunkTime;\n\n  /** telemetry client to push stats to server */\n  private final Telemetry telemetryClient;\n\n  /**\n   * memory allocator for Arrow. Each SFArrowResultSet contains one rootAllocator. This\n   * rootAllocator will be cleared and closed when the resultSet is closed\n   */\n  private RootAllocator rootAllocator;\n\n  /**\n   * If customer wants Timestamp_NTZ values to be stored in UTC time instead of a local/session\n   * timezone, set to true\n   */\n  private boolean treatNTZAsUTC;\n\n  /** Set to true if want to use wallclock time */\n  private boolean useSessionTimezone;\n\n  /**\n   * If customer wants getDate(int col, Calendar cal) function to format date with Calendar\n   * timezone, set to true\n   */\n  private boolean formatDateWithTimezone;\n\n  private Converters converters;\n\n  private final ObjectMapper objectMapper;\n\n  /**\n   * Constructor takes a result from the API response that we get from executing a SQL statement.\n   *\n   * <p>The constructor will initialize the ResultSetMetaData.\n   *\n   * @param resultSetSerializable result data after parsing json\n   * @param session SFBaseSession object\n   * @param statement statement object\n   * @param sortResult true if sort results otherwise false\n   * @throws SQLException exception raised from general SQL layers\n   */\n  public SFArrowResultSet(\n      SnowflakeResultSetSerializableV1 resultSetSerializable,\n      SFBaseSession session,\n      SFBaseStatement statement,\n      boolean sortResult)\n      throws SQLException {\n    this(resultSetSerializable, session.getTelemetryClient(internalCallMarker()), sortResult);\n    this.converters =\n        new Converters(\n            resultSetSerializable.getTimeZone(),\n            session,\n            resultSetSerializable.getResultVersion(),\n            resultSetSerializable.isHonorClientTZForTimestampNTZ(),\n            resultSetSerializable.getTreatNTZAsUTC(),\n            resultSetSerializable.getUseSessionTimezone(),\n            resultSetSerializable.getFormatDateWithTimeZone(),\n            resultSetSerializable.getBinaryFormatter(),\n            resultSetSerializable.getDateFormatter(),\n            resultSetSerializable.getTimeFormatter(),\n            resultSetSerializable.getTimestampNTZFormatter(),\n            resultSetSerializable.getTimestampLTZFormatter(),\n            resultSetSerializable.getTimestampTZFormatter());\n\n    // update the session db/schema/wh/role etc\n    this.statement = statement;\n    session.setDatabase(resultSetSerializable.getFinalDatabaseName());\n    session.setSchema(resultSetSerializable.getFinalSchemaName());\n    session.setRole(resultSetSerializable.getFinalRoleName());\n    session.setWarehouse(resultSetSerializable.getFinalWarehouseName());\n    treatNTZAsUTC = resultSetSerializable.getTreatNTZAsUTC();\n    formatDateWithTimezone = resultSetSerializable.getFormatDateWithTimeZone();\n    useSessionTimezone = resultSetSerializable.getUseSessionTimezone();\n\n    // update the driver/session with common parameters from GS\n    SessionUtil.updateSfDriverParamValues(\n        this.parameters, statement.getSFBaseSession(internalCallMarker()));\n\n    // if server gives a send time, log time it took to arrive\n    if (resultSetSerializable.getSendResultTime() != 0) {\n      long timeConsumeFirstResult = this.firstChunkTime - resultSetSerializable.getSendResultTime();\n      logMetric(TelemetryField.TIME_CONSUME_FIRST_RESULT, timeConsumeFirstResult);\n    }\n\n    eventHandler.triggerStateTransition(\n        BasicEvent.QueryState.CONSUMING_RESULT,\n        String.format(BasicEvent.QueryState.CONSUMING_RESULT.getArgString(), queryId, 0));\n  }\n\n  /**\n   * This is a minimum initialization for SFArrowResult. Mainly used for testing purpose. However,\n   * real prod constructor will call this constructor as well\n   *\n   * @param resultSetSerializable data returned in query response\n   * @param telemetryClient telemetryClient\n   * @param sortResult set if results should be sorted\n   * @throws SQLException if exception encountered\n   */\n  public SFArrowResultSet(\n      SnowflakeResultSetSerializableV1 resultSetSerializable,\n      Telemetry telemetryClient,\n      boolean sortResult)\n      throws SQLException {\n    this.resultSetSerializable = resultSetSerializable;\n    this.rootAllocator = resultSetSerializable.getRootAllocator();\n    this.sortResult = sortResult;\n    this.queryId = resultSetSerializable.getQueryId();\n    this.statementType = resultSetSerializable.getStatementType();\n    this.totalRowCountTruncated = resultSetSerializable.isTotalRowCountTruncated();\n    this.parameters = resultSetSerializable.getParameters();\n    this.chunkCount = resultSetSerializable.getChunkFileCount();\n    this.chunkDownloader = resultSetSerializable.getChunkDownloader();\n    this.honorClientTZForTimestampNTZ = resultSetSerializable.isHonorClientTZForTimestampNTZ();\n    this.resultVersion = resultSetSerializable.getResultVersion();\n    this.numberOfBinds = resultSetSerializable.getNumberOfBinds();\n    this.arrayBindSupported = resultSetSerializable.isArrayBindSupported();\n    this.metaDataOfBinds = resultSetSerializable.getMetaDataOfBinds();\n    this.telemetryClient = telemetryClient;\n    this.firstChunkTime = System.currentTimeMillis();\n    this.timestampNTZFormatter = resultSetSerializable.getTimestampNTZFormatter();\n    this.timestampLTZFormatter = resultSetSerializable.getTimestampLTZFormatter();\n    this.timestampTZFormatter = resultSetSerializable.getTimestampTZFormatter();\n    this.dateFormatter = resultSetSerializable.getDateFormatter();\n    this.timeFormatter = resultSetSerializable.getTimeFormatter();\n    this.sessionTimeZone = resultSetSerializable.getTimeZone();\n    this.binaryFormatter = resultSetSerializable.getBinaryFormatter();\n    this.resultSetMetaData = resultSetSerializable.getSFResultSetMetaData(internalCallMarker());\n    this.treatNTZAsUTC = resultSetSerializable.getTreatNTZAsUTC();\n    this.formatDateWithTimezone = resultSetSerializable.getFormatDateWithTimeZone();\n    this.useSessionTimezone = resultSetSerializable.getUseSessionTimezone();\n    objectMapper = ObjectMapperFactory.getObjectMapperForSession(session);\n\n    // sort result set if needed\n    String rowsetBase64 = resultSetSerializable.getFirstChunkStringData();\n    if (rowsetBase64 == null || rowsetBase64.isEmpty()) {\n      this.currentChunkIterator = ArrowResultChunk.getEmptyChunkIterator();\n    } else {\n      if (sortResult) {\n        // we don't support sort result when there are offline chunks\n        if (resultSetSerializable.getChunkFileCount() > 0) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              ErrorCode.CLIENT_SIDE_SORTING_NOT_SUPPORTED.getMessageCode(),\n              SqlState.FEATURE_NOT_SUPPORTED);\n        }\n\n        this.currentChunkIterator =\n            getSortedFirstResultChunk(resultSetSerializable.getFirstChunkByteData())\n                .getIterator(this);\n      } else {\n        this.currentChunkIterator =\n            buildFirstChunk(resultSetSerializable.getFirstChunkByteData()).getIterator(this);\n      }\n    }\n  }\n\n  private boolean fetchNextRow() throws SnowflakeSQLException {\n    if (sortResult) {\n      return fetchNextRowSorted();\n    } else {\n      return fetchNextRowUnsorted();\n    }\n  }\n\n  /**\n   * Goto next row. If end of current chunk, update currentChunkIterator to the beginning of next\n   * chunk, if any chunk not being consumed yet.\n   *\n   * @return true if still have rows otherwise false\n   */\n  private boolean fetchNextRowUnsorted() throws SnowflakeSQLException {\n    boolean hasNext = currentChunkIterator.next();\n\n    if (hasNext) {\n      return true;\n    } else {\n      if (nextChunkIndex < chunkCount) {\n        try {\n          eventHandler.triggerStateTransition(\n              BasicEvent.QueryState.CONSUMING_RESULT,\n              String.format(\n                  BasicEvent.QueryState.CONSUMING_RESULT.getArgString(), queryId, nextChunkIndex));\n\n          ArrowResultChunk nextChunk = (ArrowResultChunk) chunkDownloader.getNextChunkToConsume();\n\n          if (nextChunk == null) {\n            throw new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"Expect chunk but got null for chunk index \" + nextChunkIndex);\n          }\n\n          currentChunkIterator.getChunk().freeData();\n          currentChunkIterator = nextChunk.getIterator(this);\n          if (currentChunkIterator.next()) {\n\n            logger.debug(\n                \"Moving to chunk index: {}, row count: {}\",\n                nextChunkIndex,\n                nextChunk.getRowCount());\n\n            nextChunkIndex++;\n            return true;\n          } else {\n            return false;\n          }\n        } catch (InterruptedException ex) {\n          throw new SnowflakeSQLLoggedException(\n              queryId, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n        }\n      } else {\n        // always free current chunk\n        try {\n          currentChunkIterator.getChunk().freeData();\n          if (chunkCount > 0) {\n            logger.debug(\"End of chunks\", false);\n            DownloaderMetrics metrics = chunkDownloader.terminate();\n            logChunkDownloaderMetrics(metrics);\n          }\n        } catch (InterruptedException e) {\n          throw new SnowflakeSQLLoggedException(\n              queryId, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n        }\n      }\n\n      return false;\n    }\n  }\n\n  /**\n   * Decode rowset returned in query response the load data into arrow vectors\n   *\n   * @param firstChunk first chunk of rowset in arrow format\n   * @return result chunk with arrow data already being loaded\n   */\n  private ArrowResultChunk buildFirstChunk(byte[] firstChunk) throws SQLException {\n    ByteArrayInputStream inputStream = new ByteArrayInputStream(firstChunk);\n\n    // create a result chunk\n    ArrowResultChunk resultChunk = new ArrowResultChunk(\"\", 0, 0, 0, rootAllocator, session);\n\n    try {\n      resultChunk.readArrowStream(inputStream);\n    } catch (IOException e) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          ErrorCode.INTERNAL_ERROR,\n          \"Failed to \" + \"load data in first chunk into arrow vector ex: \" + e.getMessage());\n    }\n\n    return resultChunk;\n  }\n\n  /**\n   * Decode rowset returned in query response the load data into arrow vectors and sort data\n   *\n   * @param firstChunk first chunk of rowset in arrow format\n   * @return result chunk with arrow data already being loaded\n   */\n  private ArrowResultChunk getSortedFirstResultChunk(byte[] firstChunk) throws SQLException {\n    ArrowResultChunk resultChunk = buildFirstChunk(firstChunk);\n\n    // enable sorted chunk, the sorting happens when the result chunk is ready to consume\n    resultChunk.enableSortFirstResultChunk();\n    return resultChunk;\n  }\n\n  /**\n   * Fetch next row of first chunked in sorted order. If the result set huge, then rest of the\n   * chunks are ignored.\n   */\n  private boolean fetchNextRowSorted() throws SnowflakeSQLException {\n    boolean hasNext = currentChunkIterator.next();\n    if (hasNext) {\n      return true;\n    } else {\n      currentChunkIterator.getChunk().freeData();\n\n      // no more chunks as sorted is only supported\n      // for one chunk\n      return false;\n    }\n  }\n\n  @Override\n  public Converters getConverters() {\n    return converters;\n  }\n\n  @Override\n  public SQLInput createSqlInputForColumn(\n      Object input,\n      Class<?> parentObjectClass,\n      int columnIndex,\n      SFBaseSession session,\n      List<FieldMetadata> fields) {\n    if (parentObjectClass.equals(JsonSqlInput.class)) {\n      return createJsonSqlInputForColumn(input, session, fields);\n    } else {\n      return new ArrowSqlInput((Map<String, Object>) input, session, converters, fields);\n    }\n  }\n\n  @Override\n  public Date convertToDate(Object object, TimeZone tz) throws SFException {\n    if (object instanceof String) {\n      return convertStringToDate((String) object, tz);\n    }\n    return converters.getStructuredTypeDateTimeConverter().getDate((int) object, tz);\n  }\n\n  @Override\n  public Time convertToTime(Object object, int scale) throws SFException {\n    if (object instanceof String) {\n      return convertStringToTime((String) object, scale);\n    }\n    return converters.getStructuredTypeDateTimeConverter().getTime((long) object, scale);\n  }\n\n  @Override\n  public Timestamp convertToTimestamp(\n      Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException {\n    if (object instanceof String) {\n      return convertStringToTimestamp((String) object, columnType, columnSubType, tz, scale);\n    }\n    return converters\n        .getStructuredTypeDateTimeConverter()\n        .getTimestamp(\n            (JsonStringHashMap<String, Object>) object, columnType, columnSubType, tz, scale);\n  }\n\n  /**\n   * Advance to next row\n   *\n   * @return true if next row exists, false otherwise\n   */\n  @Override\n  public boolean next() throws SFException, SnowflakeSQLException {\n    if (isClosed()) {\n      return false;\n    }\n\n    // otherwise try to fetch again\n    if (fetchNextRow()) {\n      row++;\n      if (isLast()) {\n        long timeConsumeLastResult = System.currentTimeMillis() - this.firstChunkTime;\n        logMetric(TelemetryField.TIME_CONSUME_LAST_RESULT, timeConsumeLastResult);\n      }\n      return true;\n    } else {\n      logger.debug(\"End of result\", false);\n\n      /*\n       * Here we check if the result has been truncated and throw exception if\n       * so.\n       */\n      if (totalRowCountTruncated\n          || Boolean.TRUE\n              .toString()\n              .equalsIgnoreCase(systemGetProperty(\"snowflake.enable_incident_test2\"))) {\n        throw new SFException(queryId, ErrorCode.MAX_RESULT_LIMIT_EXCEEDED);\n      }\n\n      // mark end of result\n      return false;\n    }\n  }\n\n  @Override\n  public byte getByte(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toByte(index);\n  }\n\n  @Override\n  public String getString(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toString(index);\n  }\n\n  @Override\n  public boolean getBoolean(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toBoolean(index);\n  }\n\n  @Override\n  public Period getPeriod(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toPeriod(index);\n  }\n\n  @Override\n  public Duration getDuration(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toDuration(index);\n  }\n\n  @Override\n  public short getShort(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toShort(index);\n  }\n\n  @Override\n  public int getInt(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toInt(index);\n  }\n\n  @Override\n  public long getLong(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toLong(index);\n  }\n\n  @Override\n  public float getFloat(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toFloat(index);\n  }\n\n  @Override\n  public double getDouble(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toDouble(index);\n  }\n\n  @Override\n  public byte[] getBytes(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    return converter.toBytes(index);\n  }\n\n  @Override\n  public Date getDate(int columnIndex, TimeZone tz) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    converter.setSessionTimeZone(sessionTimeZone);\n    converter.setUseSessionTimezone(useSessionTimezone);\n    return converter.toDate(index, tz, resultSetSerializable.getFormatDateWithTimeZone());\n  }\n\n  @Override\n  public Time getTime(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    converter.setSessionTimeZone(sessionTimeZone);\n    converter.setUseSessionTimezone(useSessionTimezone);\n    return converter.toTime(index);\n  }\n\n  @Override\n  public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    converter.setSessionTimeZone(sessionTimeZone);\n    converter.setUseSessionTimezone(useSessionTimezone);\n    wasNull = converter.isNull(index);\n    return converter.toTimestamp(index, tz);\n  }\n\n  @Override\n  public Object getObject(int columnIndex) throws SFException {\n    return getObjectRepresentation(columnIndex, true);\n  }\n\n  @Override\n  public Object getObjectWithoutString(int columnIndex) throws SFException {\n    return getObjectRepresentation(columnIndex, false);\n  }\n\n  private StructObjectWrapper getObjectRepresentation(int columnIndex, boolean withString)\n      throws SFException {\n    int type = resultSetMetaData.getColumnType(columnIndex);\n    if (type == SnowflakeType.EXTRA_TYPES_VECTOR) {\n      return new StructObjectWrapper(getString(columnIndex), null);\n    }\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    converter.setTreatNTZAsUTC(treatNTZAsUTC);\n    converter.setUseSessionTimezone(useSessionTimezone);\n    converter.setSessionTimeZone(sessionTimeZone);\n    Object obj = converter.toObject(index);\n    if (obj == null) {\n      return null;\n    }\n    boolean isStructuredType = resultSetMetaData.isStructuredTypeColumn(columnIndex);\n    if (isStructuredType) {\n      if (converter instanceof VarCharConverter) {\n        if (type == Types.STRUCT) {\n          JsonSqlInput jsonSqlInput = createJsonSqlInput(columnIndex, obj);\n          return new StructObjectWrapper(jsonSqlInput.getText(), jsonSqlInput);\n        } else if (type == Types.ARRAY) {\n          SfSqlArray sfArray = getJsonArray((String) obj, columnIndex, objectMapper);\n          return new StructObjectWrapper(sfArray.getText(), sfArray);\n        } else {\n          throw new SFException(queryId, ErrorCode.INVALID_STRUCT_DATA);\n        }\n      } else if (converter instanceof StructConverter) {\n        String jsonString = withString ? converter.toString(index) : null;\n        return new StructObjectWrapper(\n            jsonString, createArrowSqlInput(columnIndex, (Map<String, Object>) obj));\n      } else if (converter instanceof MapConverter) {\n        String jsonString = withString ? converter.toString(index) : null;\n        return new StructObjectWrapper(jsonString, obj);\n      } else if (converter instanceof ArrayConverter || converter instanceof VectorTypeConverter) {\n        String jsonString = converter.toString(index);\n        return new StructObjectWrapper(jsonString, obj);\n      } else {\n        throw new SFException(queryId, ErrorCode.INVALID_STRUCT_DATA);\n      }\n    } else {\n      return new StructObjectWrapper(null, obj);\n    }\n  }\n\n  private SQLInput createArrowSqlInput(int columnIndex, Map<String, Object> input)\n      throws SFException {\n    if (input == null) {\n      return null;\n    }\n    return new ArrowSqlInput(\n        input, session, converters, resultSetMetaData.getColumnFields(columnIndex));\n  }\n\n  private boolean isVarcharConvertedStruct(int type, ArrowVectorConverter converter) {\n    return (type == Types.STRUCT || type == Types.ARRAY) && converter instanceof VarCharConverter;\n  }\n\n  private JsonSqlInput createJsonSqlInput(int columnIndex, Object obj) throws SFException {\n    try {\n      if (obj == null) {\n        return null;\n      }\n      String text = (String) obj;\n      JsonNode jsonNode = objectMapper.readTree(text);\n      return new JsonSqlInput(\n          text,\n          jsonNode,\n          session,\n          converters,\n          resultSetMetaData.getColumnFields(columnIndex),\n          sessionTimeZone);\n    } catch (JsonProcessingException e) {\n      throw new SFException(queryId, e, ErrorCode.INVALID_STRUCT_DATA);\n    }\n  }\n\n  @Override\n  public Array getArray(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    Object obj = converter.toObject(index);\n    if (obj == null) {\n      return null;\n    }\n    if (converter instanceof VarCharConverter) {\n      return getJsonArray((String) obj, columnIndex, objectMapper);\n    } else if (converter instanceof ArrayConverter || converter instanceof VectorTypeConverter) {\n      String jsonString = converter.toString(index);\n      return getArrowArray(jsonString, (List<Object>) obj, columnIndex);\n    } else {\n      throw new SFException(queryId, ErrorCode.INVALID_STRUCT_DATA);\n    }\n  }\n\n  private SfSqlArray getArrowArray(String text, List<Object> elements, int columnIndex)\n      throws SFException {\n    try {\n      List<FieldMetadata> fieldMetadataList = resultSetMetaData.getColumnFields(columnIndex);\n      if (fieldMetadataList.size() != 1) {\n        throw new SFException(\n            queryId,\n            ErrorCode.INVALID_STRUCT_DATA,\n            \"Wrong size of fields for array type \" + fieldMetadataList.size());\n      }\n      FieldMetadata fieldMetadata = fieldMetadataList.get(0);\n      int columnSubType = fieldMetadata.getType();\n      int columnType = ColumnTypeHelper.getColumnType(columnSubType, session);\n      int scale = fieldMetadata.getScale();\n\n      switch (columnType) {\n        case Types.INTEGER:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.integerConverter(columnType))\n                  .toArray(Integer[]::new));\n        case Types.SMALLINT:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.smallIntConverter(columnType))\n                  .toArray(Short[]::new));\n        case Types.TINYINT:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.tinyIntConverter(columnType))\n                  .toArray(Byte[]::new));\n        case Types.BIGINT:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.bigIntConverter(columnType)).toArray(Long[]::new));\n        case Types.DECIMAL:\n        case Types.NUMERIC:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.bigDecimalConverter(columnType))\n                  .toArray(BigDecimal[]::new));\n        case Types.CHAR:\n        case Types.VARCHAR:\n        case Types.LONGNVARCHAR:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.varcharConverter(columnType, columnSubType, scale))\n                  .toArray(String[]::new));\n        case Types.BINARY:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.bytesConverter(columnType, scale))\n                  .toArray(Byte[][]::new));\n        case Types.FLOAT:\n        case Types.REAL:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.floatConverter(columnType)).toArray(Float[]::new));\n        case Types.DOUBLE:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.doubleConverter(columnType))\n                  .toArray(Double[]::new));\n        case Types.DATE:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.dateFromIntConverter(sessionTimeZone))\n                  .toArray(Date[]::new));\n        case Types.TIME:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.timeFromIntConverter(scale)).toArray(Time[]::new));\n        case Types.TIMESTAMP:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(\n                      elements,\n                      converters.timestampFromStructConverter(\n                          columnType, columnSubType, sessionTimeZone, scale))\n                  .toArray(Timestamp[]::new));\n        case Types.BOOLEAN:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, converters.booleanConverter(columnType))\n                  .toArray(Boolean[]::new));\n        case Types.STRUCT:\n          return getSfSqlArray(\n              text, columnSubType, mapAndConvert(elements, e -> e).toArray(Map[]::new));\n        case Types.ARRAY:\n          return getSfSqlArray(\n              text,\n              columnSubType,\n              mapAndConvert(elements, e -> ((List) e).stream().toArray(Map[]::new))\n                  .toArray(Map[][]::new));\n        default:\n          throw new SFException(\n              queryId,\n              ErrorCode.FEATURE_UNSUPPORTED,\n              \"Can't construct array for data type: \" + columnSubType);\n      }\n    } catch (RuntimeException e) {\n      throw new SFException(queryId, e, ErrorCode.INVALID_STRUCT_DATA);\n    }\n  }\n\n  private SfSqlArray getSfSqlArray(String text, int columnSubType, Object[] array) {\n    return new SfSqlArray(text, columnSubType, array, session, objectMapper);\n  }\n\n  private <T> Stream<T> mapAndConvert(List<Object> elements, Converter<T> converter) {\n    return elements.stream()\n        .map(\n            obj -> {\n              try {\n                return converter.convert(obj);\n              } catch (SFException e) {\n                throw new RuntimeException(e);\n              }\n            });\n  }\n\n  @Override\n  public BigDecimal getBigDecimal(int columnIndex) throws SFException {\n    ArrowVectorConverter converter = currentChunkIterator.getCurrentConverter(columnIndex - 1);\n    int index = currentChunkIterator.getCurrentRowInRecordBatch();\n    wasNull = converter.isNull(index);\n    converter.setSessionTimeZone(sessionTimeZone);\n    converter.setUseSessionTimezone(useSessionTimezone);\n    return converter.toBigDecimal(index);\n  }\n\n  @Override\n  public BigDecimal getBigDecimal(int columnIndex, int scale) throws SFException {\n    BigDecimal bigDec = getBigDecimal(columnIndex);\n    return bigDec == null ? null : bigDec.setScale(scale, RoundingMode.HALF_UP);\n  }\n\n  @Override\n  public boolean isLast() {\n    return nextChunkIndex == chunkCount && currentChunkIterator.isLast();\n  }\n\n  @Override\n  public boolean isAfterLast() {\n    return nextChunkIndex == chunkCount && currentChunkIterator.isAfterLast();\n  }\n\n  @Override\n  public void close() throws SnowflakeSQLException {\n    super.close();\n\n    // always make sure to free this current chunk\n    currentChunkIterator.getChunk().freeData();\n\n    try {\n      if (chunkDownloader != null) {\n        DownloaderMetrics metrics = chunkDownloader.terminate();\n        logChunkDownloaderMetrics(metrics);\n      } else {\n        // always close root allocator\n        closeRootAllocator(rootAllocator);\n      }\n    } catch (InterruptedException ex) {\n      throw new SnowflakeSQLLoggedException(\n          queryId, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n    }\n  }\n\n  public static void closeRootAllocator(RootAllocator rootAllocator) {\n    long rest = rootAllocator.getAllocatedMemory();\n    int count = 3;\n    try {\n      while (rest > 0 && count-- > 0) {\n        // this case should only happen when the resultSet is closed before consuming all chunks\n        // otherwise, the memory usage for each chunk will be cleared right after it has been fully\n        // consumed\n\n        // The reason is that it is possible that one downloading thread is pending to close when\n        // the main thread\n        // reaches here. A retry is to wait for the downloading thread to finish closing incoming\n        // streams and arrow\n        // resources.\n\n        Thread.sleep(10);\n        rest = rootAllocator.getAllocatedMemory();\n      }\n      if (rest == 0) {\n        rootAllocator.close();\n      }\n    } catch (InterruptedException ie) {\n      logger.debug(\"Interrupted during closing root allocator\", false);\n    } catch (Exception e) {\n      logger.debug(\"Exception happened when closing rootAllocator: \", e.getLocalizedMessage());\n    }\n  }\n\n  @Override\n  public SFStatementType getStatementType() {\n    return statementType;\n  }\n\n  @Override\n  public void setStatementType(SFStatementType statementType) {\n    this.statementType = statementType;\n  }\n\n  @Override\n  public boolean isArrayBindSupported() {\n    return this.arrayBindSupported;\n  }\n\n  @Override\n  public String getQueryId() {\n    return queryId;\n  }\n\n  private void logMetric(TelemetryField field, long value) {\n    TelemetryData data = TelemetryUtil.buildJobData(this.queryId, field, value);\n    this.telemetryClient.addLogToBatch(data);\n  }\n\n  private void logChunkDownloaderMetrics(DownloaderMetrics metrics) {\n    if (metrics != null) {\n      logMetric(TelemetryField.TIME_WAITING_FOR_CHUNKS, metrics.getMillisWaiting());\n      logMetric(TelemetryField.TIME_DOWNLOADING_CHUNKS, metrics.getMillisDownloading());\n      logMetric(TelemetryField.TIME_PARSING_CHUNKS, metrics.getMillisParsing());\n    }\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimestampLTZFormatter() {\n    return timestampLTZFormatter;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimestampNTZFormatter() {\n    return timestampNTZFormatter;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimestampTZFormatter() {\n    return timestampTZFormatter;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getDateFormatter() {\n    return dateFormatter;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimeFormatter() {\n    return timeFormatter;\n  }\n\n  @Override\n  public SFBinaryFormat getBinaryFormatter() {\n    return binaryFormatter;\n  }\n\n  @Override\n  public int getScale(int columnIndex) {\n    return resultSetMetaData.getScale(columnIndex);\n  }\n\n  @Override\n  public TimeZone getTimeZone() {\n    return sessionTimeZone;\n  }\n\n  @Override\n  public boolean getHonorClientTZForTimestampNTZ() {\n    return honorClientTZForTimestampNTZ;\n  }\n\n  @Override\n  public long getResultVersion() {\n    return resultVersion;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFBaseResultSet.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.getJsonNodeStringValue;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport java.math.BigDecimal;\nimport java.sql.Array;\nimport java.sql.Date;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Spliterator;\nimport java.util.Spliterators;\nimport java.util.TimeZone;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.Converter;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\n/** Base class for query result set and metadata result set */\npublic abstract class SFBaseResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFBaseResultSet.class);\n  protected static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n\n  boolean wasNull = false;\n\n  protected SFResultSetMetaData resultSetMetaData = null;\n\n  protected int row = 0;\n\n  protected Map<String, Object> parameters = new HashMap<>();\n\n  // Formatters for different datatypes\n  // TODO move all formatter to DataConversionContext.java\n  SnowflakeDateTimeFormat timestampNTZFormatter;\n  SnowflakeDateTimeFormat timestampLTZFormatter;\n  SnowflakeDateTimeFormat timestampTZFormatter;\n  SnowflakeDateTimeFormat dateFormatter;\n  SnowflakeDateTimeFormat timeFormatter;\n  boolean honorClientTZForTimestampNTZ = true;\n\n  SFBinaryFormat binaryFormatter;\n\n  protected long resultVersion = 0;\n\n  protected int numberOfBinds = 0;\n\n  protected List<MetaDataOfBinds> metaDataOfBinds = new ArrayList<>();\n\n  // For creating incidents\n  protected SFBaseSession session;\n\n  // indicate whether the result set has been closed or not.\n  protected boolean isClosed;\n\n  // The serializable object which can serialize the metadata for this\n  // result set\n  protected SnowflakeResultSetSerializableV1 resultSetSerializable;\n\n  protected TimeZone sessionTimeZone;\n\n  public abstract boolean isLast();\n\n  public abstract boolean isAfterLast();\n\n  public abstract String getString(int columnIndex) throws SFException;\n\n  public abstract boolean getBoolean(int columnIndex) throws SFException;\n\n  public abstract byte getByte(int columnIndex) throws SFException;\n\n  public abstract Period getPeriod(int columnIndex) throws SFException;\n\n  public abstract Duration getDuration(int columnIndex) throws SFException;\n\n  public abstract short getShort(int columnIndex) throws SFException;\n\n  public abstract int getInt(int columnIndex) throws SFException;\n\n  public abstract long getLong(int columnIndex) throws SFException;\n\n  public abstract float getFloat(int columnIndex) throws SFException;\n\n  public abstract double getDouble(int columnIndex) throws SFException;\n\n  public abstract byte[] getBytes(int columnIndex) throws SFException;\n\n  public abstract Time getTime(int columnIndex) throws SFException;\n\n  public abstract Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SFException;\n\n  public abstract Date getDate(int columnIndex, TimeZone tz) throws SFException;\n\n  public abstract Object getObject(int columnIndex) throws SFException;\n\n  public abstract Object getObjectWithoutString(int columnIndex) throws SFException;\n\n  public Array getArray(int columnIndex) throws SFException {\n    throw new UnsupportedOperationException();\n  }\n\n  public abstract BigDecimal getBigDecimal(int columnIndex) throws SFException;\n\n  public abstract BigDecimal getBigDecimal(int columnIndex, int scale) throws SFException;\n\n  public abstract SFStatementType getStatementType();\n\n  // this is useful to override the initial statement type if it is incorrect\n  // (e.g. result scan yields a query type, but the results are from a DML statement)\n  public abstract void setStatementType(SFStatementType statementType) throws SQLException;\n\n  public abstract String getQueryId();\n\n  public void setSession(SFBaseSession session) {\n    this.session = session;\n  }\n\n  public SFBaseSession getSession() {\n    return this.session;\n  }\n\n  // default implementation\n  public boolean next() throws SFException, SnowflakeSQLException {\n    logger.trace(\"boolean next()\", false);\n    return false;\n  }\n\n  public void close() throws SnowflakeSQLException {\n    logger.trace(\"void close()\", false);\n\n    // no exception even if already closed.\n    resultSetMetaData = null;\n    isClosed = true;\n  }\n\n  public boolean wasNull() {\n    logger.trace(\"boolean wasNull() returning {}\", wasNull);\n\n    return wasNull;\n  }\n\n  public SFResultSetMetaData getMetaData() {\n    return resultSetMetaData;\n  }\n\n  public TimeZone getSessionTimezone() {\n    return sessionTimeZone;\n  }\n\n  public int getRow() throws SQLException {\n    return row;\n  }\n\n  public boolean absolute(int row) throws SFException {\n    throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, \"seek to a specific row\");\n  }\n\n  public boolean relative(int rows) throws SFException {\n    throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, \"seek to a row relative to current row\");\n  }\n\n  public boolean previous() throws SFException {\n    throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, \"seek to a previous row\");\n  }\n\n  public int getNumberOfBinds() {\n    return numberOfBinds;\n  }\n\n  public List<MetaDataOfBinds> getMetaDataOfBinds() {\n    return metaDataOfBinds;\n  }\n\n  public boolean isFirst() {\n    return row == 1;\n  }\n\n  public boolean isBeforeFirst() {\n    return row == 0;\n  }\n\n  public boolean isClosed() {\n    return isClosed;\n  }\n\n  public boolean isArrayBindSupported() {\n    return false;\n  }\n\n  /**\n   * Split this whole SnowflakeResultSetSerializable into small pieces based on the user specified\n   * data size.\n   *\n   * @param maxSizeInBytes The expected max data size wrapped in the ResultSetSerializables object.\n   *     NOTE: this parameter is intended to make the data size in each serializable object to be\n   *     less than it. But if user specifies a small value which may be smaller than the data size\n   *     of one result chunk. So the definition can't be guaranteed completely. For this special\n   *     case, one serializable object is used to wrap the data chunk.\n   * @return a list of SnowflakeResultSetSerializable\n   * @throws SQLException if fails to split objects.\n   */\n  public List<SnowflakeResultSetSerializable> getResultSetSerializables(long maxSizeInBytes)\n      throws SQLException {\n    return this.resultSetSerializable.splitBySize(maxSizeInBytes);\n  }\n\n  public Converters getConverters() {\n    logger.debug(\"Json converters weren't created\");\n    return null;\n  }\n\n  public TimeZone getSessionTimeZone() {\n    return resultSetSerializable.getTimeZone();\n  }\n\n  public SQLInput createSqlInputForColumn(\n      Object input,\n      Class<?> parentObjectClass,\n      int columnIndex,\n      SFBaseSession session,\n      List<FieldMetadata> fields) {\n    throw new UnsupportedOperationException();\n  }\n\n  public Date convertToDate(Object object, TimeZone tz) throws SFException {\n    throw new UnsupportedOperationException();\n  }\n\n  public Time convertToTime(Object object, int scale) throws SFException {\n    throw new UnsupportedOperationException();\n  }\n\n  public Timestamp convertToTimestamp(\n      Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException {\n    throw new UnsupportedOperationException();\n  }\n\n  protected SQLInput createJsonSqlInputForColumn(\n      Object input, SFBaseSession session, List<FieldMetadata> fields) {\n    JsonNode inputNode;\n    if (input instanceof JsonNode) {\n      inputNode = (JsonNode) input;\n    } else {\n      inputNode = OBJECT_MAPPER.convertValue(input, JsonNode.class);\n    }\n    return new JsonSqlInput(\n        input.toString(), inputNode, session, getConverters(), fields, sessionTimeZone);\n  }\n\n  protected SfSqlArray getJsonArray(String arrayString, int columnIndex, ObjectMapper objectMapper)\n      throws SFException {\n    try {\n      List<FieldMetadata> fieldMetadataList = resultSetMetaData.getColumnFields(columnIndex);\n      if (fieldMetadataList.size() != 1) {\n        throw new SFException(\n            ErrorCode.FEATURE_UNSUPPORTED,\n            \"Wrong size of fields for array type \" + fieldMetadataList.size());\n      }\n\n      FieldMetadata fieldMetadata = fieldMetadataList.get(0);\n\n      int columnSubType = fieldMetadata.getType();\n      int columnType = ColumnTypeHelper.getColumnType(columnSubType, session);\n      int scale = fieldMetadata.getScale();\n\n      ArrayNode arrayNode = (ArrayNode) OBJECT_MAPPER.readTree(arrayString);\n      Iterator<JsonNode> nodeElements = arrayNode.elements();\n\n      switch (columnType) {\n        case Types.INTEGER:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().integerConverter(columnType))\n                  .toArray(Integer[]::new),\n              session,\n              objectMapper);\n        case Types.SMALLINT:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().smallIntConverter(columnType))\n                  .toArray(Short[]::new),\n              session,\n              objectMapper);\n        case Types.TINYINT:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().tinyIntConverter(columnType))\n                  .toArray(Byte[]::new),\n              session,\n              objectMapper);\n        case Types.BIGINT:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().bigIntConverter(columnType))\n                  .toArray(Long[]::new),\n              session,\n              objectMapper);\n        case Types.DECIMAL:\n        case Types.NUMERIC:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              convertToFixedArray(\n                  getStream(nodeElements, getConverters().bigDecimalConverter(columnType))),\n              session,\n              objectMapper);\n        case Types.CHAR:\n        case Types.VARCHAR:\n        case Types.LONGNVARCHAR:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(\n                      nodeElements,\n                      getConverters().varcharConverter(columnType, columnSubType, scale))\n                  .toArray(String[]::new),\n              session,\n              objectMapper);\n        case Types.BINARY:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().bytesConverter(columnType, scale))\n                  .toArray(Byte[][]::new),\n              session,\n              objectMapper);\n        case Types.FLOAT:\n        case Types.REAL:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().floatConverter(columnType))\n                  .toArray(Float[]::new),\n              session,\n              objectMapper);\n        case Types.DOUBLE:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().doubleConverter(columnType))\n                  .toArray(Double[]::new),\n              session,\n              objectMapper);\n        case Types.DATE:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().dateStringConverter(session))\n                  .toArray(Date[]::new),\n              session,\n              objectMapper);\n        case Types.TIME:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().timeFromStringConverter(session))\n                  .toArray(Time[]::new),\n              session,\n              objectMapper);\n        case Types.TIMESTAMP:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(\n                      nodeElements,\n                      getConverters()\n                          .timestampFromStringConverter(\n                              columnSubType, columnType, scale, session, null, sessionTimeZone))\n                  .toArray(Timestamp[]::new),\n              session,\n              objectMapper);\n        case Types.BOOLEAN:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().booleanConverter(columnType))\n                  .toArray(Boolean[]::new),\n              session,\n              objectMapper);\n        case Types.STRUCT:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().structConverter(OBJECT_MAPPER))\n                  .toArray(Map[]::new),\n              session,\n              objectMapper);\n        case Types.ARRAY:\n          return new SfSqlArray(\n              arrayString,\n              columnSubType,\n              getStream(nodeElements, getConverters().arrayConverter(OBJECT_MAPPER))\n                  .toArray(Map[][]::new),\n              session,\n              objectMapper);\n        default:\n          throw new SFException(\n              ErrorCode.FEATURE_UNSUPPORTED,\n              \"Can't construct array for data type: \" + columnSubType);\n      }\n    } catch (JsonProcessingException e) {\n      throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA);\n    }\n  }\n\n  protected Date convertStringToDate(String object, TimeZone tz) throws SFException {\n    return (Date) getConverters().dateStringConverter(session).convert(object);\n  }\n\n  protected Time convertStringToTime(String object, int scale) throws SFException {\n    return (Time) getConverters().timeFromStringConverter(session).convert(object);\n  }\n\n  protected Timestamp convertStringToTimestamp(\n      String object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException {\n    return (Timestamp)\n        getConverters()\n            .timestampFromStringConverter(columnSubType, columnType, scale, session, null, tz)\n            .convert(object);\n  }\n\n  private Stream getStream(Iterator nodeElements, Converter converter) {\n    return StreamSupport.stream(\n            Spliterators.spliteratorUnknownSize(nodeElements, Spliterator.ORDERED), false)\n        .map(\n            elem -> {\n              try {\n                return convert(converter, (JsonNode) elem);\n              } catch (SFException e) {\n                throw new RuntimeException(e);\n              }\n            });\n  }\n\n  private static Object convert(Converter converter, JsonNode node) throws SFException {\n    String nodeValue = getJsonNodeStringValue(node);\n    return converter.convert(nodeValue);\n  }\n\n  private Object[] convertToFixedArray(Stream inputStream) {\n    AtomicInteger bigDecimalCount = new AtomicInteger();\n    Object[] elements =\n        inputStream\n            .peek(\n                elem -> {\n                  if (elem instanceof BigDecimal) {\n                    bigDecimalCount.incrementAndGet();\n                  }\n                })\n            .toArray(\n                size -> {\n                  boolean shouldReturnAsBigDecimal = bigDecimalCount.get() > 0;\n                  Class<?> returnedClass = shouldReturnAsBigDecimal ? BigDecimal.class : Long.class;\n                  return java.lang.reflect.Array.newInstance(returnedClass, size);\n                });\n    return elements;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFBaseSession.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.sql.DriverPropertyInfo;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport net.snowflake.client.internal.jdbc.SFConnectionHandler;\nimport net.snowflake.client.internal.jdbc.SnowflakeConnectString;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Snowflake session implementation base. The methods and fields contained within this class are\n * setters and getters for shared session properties, i.e., those that may be set via connection\n * time, in properties, as well as those that may be parsed from response headers from Snowflake\n * (i.e., session parameters).\n *\n * <p>New connection properties and session parameters can be added here, as SFBaseSession contains\n * the logic for storing, setting, and getting these session properties.\n *\n * <p>For logic that is specific to a particular type of Session, four methods need to be\n * implemented:\n *\n * <p>open(), for establishing a connection. close(), for closing a connection. isSafeToClose(), for\n * checking whether the connection can be closed. checkProperties(), invoked at connection time to\n * verify if the requisite properties are set; and if not, returns a list of missing properties\n * raiseError(), which handles exceptions that may be raised in the session isTelemetryEnabled(),\n * which signals whether to enable client telemetry\n */\npublic abstract class SFBaseSession {\n  private static final Set<String> STICKY_HEADERS_NAMES =\n      new HashSet<>(Collections.singletonList(\"x-snowflake-session\"));\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFBaseSession.class);\n  private final Properties clientInfo = new Properties();\n  private final AtomicBoolean autoCommit = new AtomicBoolean(true);\n  // Injected delay for the purpose of connection timeout testing\n  // Any statement execution will sleep for the specified number of milliseconds\n  private final AtomicInteger _injectedDelay = new AtomicInteger(0);\n  // Connection properties map\n  private final Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n  // Custom key-value map for other options values *not* defined in SFSessionProperties,\n  // i.e., session-implementation specific\n  private final Map<String, Object> customSessionProperties = new HashMap<>(1);\n  private SFConnectionHandler sfConnectionHandler;\n  protected List<SFException> sqlWarnings = new ArrayList<>();\n  // Unique Session ID\n  private String sessionId;\n  // Database versions\n  private String databaseVersion = null;\n  private int databaseMajorVersion = 0;\n  private int databaseMinorVersion = 0;\n  // Used for parsing results\n  private SnowflakeType timestampMappedType = SnowflakeType.TIMESTAMP_LTZ;\n  private boolean isResultColumnCaseInsensitive;\n  private boolean isJdbcTreatDecimalAsInt = true;\n  private boolean treatNTZAsUTC;\n  private boolean preparedStatementLogging = false;\n  // Inject failure for testing\n  private String injectFileUploadFailure;\n  private boolean enableHeartbeat;\n  protected int heartbeatFrequency = 3600;\n  private boolean formatDateWithTimezone;\n  private boolean enableCombineDescribe;\n  private boolean clientTelemetryEnabled = false;\n  private boolean useSessionTimezone;\n  private boolean defaultFormatDateWithTimezone = true;\n  private boolean getDateUseNullTimezone = true;\n  // The server can read array binds from a stage instead of query payload.\n  // When there as many bind values as this threshold, we should upload them to a stage.\n  private int arrayBindStageThreshold = 0;\n  private boolean storeTemporaryCredential;\n  private String serviceName;\n  private boolean sfSQLMode;\n  // whether to enable conservative memory usage mode\n  private boolean enableConservativeMemoryUsage;\n  // the step in MB to adjust memory usage\n  private int conservativeMemoryAdjustStep = 64;\n  private int clientMemoryLimit;\n  private int clientResultChunkSize;\n  private int clientPrefetchThreads;\n  // validate the default parameters by GS?\n  private boolean validateDefaultParameters;\n  // Current session context w/ re to database, schema, role, warehouse\n  private String database;\n  private String schema;\n  private String role;\n  private String warehouse;\n  // For Metadata request(i.e. DatabaseMetadata.getTables or\n  // DatabaseMetadata.getSchemas,), whether to use connection ctx to\n  // improve the request time\n  private boolean metadataRequestUseConnectionCtx = false;\n  // For Metadata request(i.e. DatabaseMetadata.getTables or\n  // DatabaseMetadata.getSchemas), whether to search using multiple schemas with\n  // session database\n  private boolean metadataRequestUseSessionDatabase = false;\n  // Forces to use regional s3 end point API to generate regional url for aws endpoints\n  private boolean useRegionalS3EndpointsForPresignedURL = false;\n  // Stores other parameters sent by server\n  private final Map<String, Object> otherParameters = new HashMap<>();\n  private HttpClientSettingsKey ocspAndProxyAndGzipKey = null;\n  // Default value for memory limit in SFBaseSession\n  public static long MEMORY_LIMIT_UNSET = -1;\n  // Memory limit for SnowflakeChunkDownloader. This gets set from SFBaseSession for testing\n  // purposes only.\n  private long memoryLimitForTesting = MEMORY_LIMIT_UNSET;\n  // name of temporary stage to upload array binds to; null if none has been created yet\n  private String arrayBindStage = null;\n\n  // Maximum size of the query context cache for current session\n  private int queryContextCacheSize = 5;\n\n  // Whether enable returning timestamp with timezone as data type\n  private boolean enableReturnTimestampWithTimeZone = true;\n\n  // Server side value\n  private boolean jdbcEnablePutGet = true;\n\n  // Connection string setting\n  private boolean enablePutGet = true;\n  private boolean enableCopyResultSet = false;\n\n  // Enables the use of pattern searches for certain DatabaseMetaData methods\n  // which do not by definition allow the use of patterns, but\n  // we need to allow for it to maintain backwards compatibility.\n  private boolean enablePatternSearch = true;\n\n  // Enables the use of exact schema searches for certain DatabaseMetaData methods\n  // that should use schema from context (CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true)\n  // value is false for backwards compatibility.\n  private boolean enableExactSchemaSearch = false;\n\n  // This is true by default, but can be set to false to disable pattern matching in cases when\n  // wildcards are used as a part of identifiers eg. \"my_table\" or \"my_schema\"\n  private boolean enableWildcardsInShowMetadataCommands = true;\n\n  /** Disable lookup for default credentials by GCS library */\n  private boolean disableGcsDefaultCredentials = true;\n\n  private Map<String, Object> commonParameters;\n\n  // Headers that once they are returned from Snowflake, will then be added to each subsequent HTTP\n  // request\n  // e.g. x-snowflake-session header\n  private final Map<String, String> stickyHttpHeaders = new HashMap<>();\n\n  private boolean isJdbcArrowTreatDecimalAsInt = true;\n\n  private boolean implicitServerSideQueryTimeout = false;\n\n  private boolean clearBatchOnlyAfterSuccessfulExecution = false;\n\n  /** Treat java.sql.Time as wall clock time without converting it to UTC */\n  private boolean treatTimeAsWallClockTime = false;\n\n  private boolean ownerOnlyStageFilePermissionsEnabled = false;\n\n  private boolean allowCertificatesWithoutCrlUrl = false;\n\n  protected SFBaseSession(SFConnectionHandler sfConnectionHandler) {\n    this.sfConnectionHandler = sfConnectionHandler;\n  }\n\n  public void setMemoryLimitForTesting(long memLimit) {\n    this.memoryLimitForTesting = memLimit;\n  }\n\n  public long getMemoryLimitForTesting() {\n    return this.memoryLimitForTesting;\n  }\n\n  /**\n   * Part of the JDBC API, where client applications may fetch a Map of Properties to set various\n   * attributes. This is not used internally by any driver component, but should be maintained by\n   * the Session object.\n   *\n   * @return client info as Properties\n   */\n  public Properties getClientInfo() {\n    // defensive copy to avoid client from changing the properties\n    // directly w/o going through the API\n    Properties copy = new Properties();\n    copy.putAll(this.clientInfo);\n    return copy;\n  }\n\n  /**\n   * Set common parameters\n   *\n   * @param parameters the parameters to set\n   */\n  public void setCommonParameters(Map<String, Object> parameters) {\n    this.commonParameters = parameters;\n  }\n\n  /**\n   * Get common parameters\n   *\n   * @return Map of common parameters\n   */\n  public Map<String, Object> getCommonParameters() {\n    return this.commonParameters;\n  }\n\n  /**\n   * Gets the Property associated with the key 'name' in the ClientInfo map.\n   *\n   * @param name The key from which to fetch the Property.\n   * @return The ClientInfo entry property.\n   */\n  public String getClientInfo(String name) {\n    return this.clientInfo.getProperty(name);\n  }\n\n  /**\n   * Returns a unique id for this session.\n   *\n   * @return unique id for session\n   */\n  public String getSessionId() {\n    return sessionId;\n  }\n\n  /**\n   * Sets the session-id attribute in this session.\n   *\n   * @param sessionId The session id as a string.\n   */\n  public void setSessionId(String sessionId) {\n    this.sessionId = sessionId;\n  }\n\n  /**\n   * @return true if session is in SQLMode\n   */\n  public boolean isSfSQLMode() {\n    return sfSQLMode;\n  }\n\n  /**\n   * Set sfSQLMode\n   *\n   * @param sfSQLMode boolean\n   */\n  public void setSfSQLMode(boolean sfSQLMode) {\n    this.sfSQLMode = sfSQLMode;\n  }\n\n  /**\n   * Get the database version\n   *\n   * @return database version\n   */\n  public String getDatabaseVersion() {\n    return databaseVersion;\n  }\n\n  /**\n   * Set database version\n   *\n   * @param databaseVersion the version to set\n   */\n  public void setDatabaseVersion(String databaseVersion) {\n    this.databaseVersion = databaseVersion;\n  }\n\n  /**\n   * Get databse major version\n   *\n   * @return the database major version\n   */\n  public int getDatabaseMajorVersion() {\n    return databaseMajorVersion;\n  }\n\n  /**\n   * Set database major version\n   *\n   * @param databaseMajorVersion the database major version\n   */\n  public void setDatabaseMajorVersion(int databaseMajorVersion) {\n    this.databaseMajorVersion = databaseMajorVersion;\n  }\n\n  /**\n   * Get the database minor version\n   *\n   * @return database minor version\n   */\n  public int getDatabaseMinorVersion() {\n    return databaseMinorVersion;\n  }\n\n  /**\n   * Set the database minor version\n   *\n   * @param databaseMinorVersion the minor version\n   */\n  public void setDatabaseMinorVersion(int databaseMinorVersion) {\n    this.databaseMinorVersion = databaseMinorVersion;\n  }\n\n  /**\n   * Gets the value of CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS if one has been set. False by\n   * default.\n   *\n   * @see <a\n   *     href=\"https://docs.snowflake.com/en/sql-reference/parameters#client-enable-log-info-statement-parameters\">CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS</a>\n   * @return true if enabled\n   */\n  public boolean getPreparedStatementLogging() {\n    return this.preparedStatementLogging;\n  }\n\n  /**\n   * Set prepared statement logging\n   *\n   * @see SFBaseSession#getPreparedStatementLogging()\n   * @param value boolean\n   */\n  public void setPreparedStatementLogging(boolean value) {\n    this.preparedStatementLogging = value;\n  }\n\n  /**\n   * Get inject file upload failure. Note: Should only be used in internal tests!\n   *\n   * @return file to fail\n   */\n  public String getInjectFileUploadFailure() {\n    return this.injectFileUploadFailure;\n  }\n\n  /**\n   * Set inject file upload failure Note: Should only be used in internal tests!\n   *\n   * @param fileToFail the file to fail\n   */\n  public void setInjectFileUploadFailure(String fileToFail) {\n    this.injectFileUploadFailure = fileToFail;\n  }\n\n  /**\n   * Get timestamp mapped type\n   *\n   * @see <a\n   *     href=\"https://docs.snowflake.com/en/sql-reference/parameters#client-timestamp-type-mapping\">CLIENT_TIMESTAMP_TYPE_MAPPING</a>\n   * @return {@link SnowflakeType}\n   */\n  public SnowflakeType getTimestampMappedType() {\n    return timestampMappedType;\n  }\n\n  /**\n   * Set the timestamp mapped type\n   *\n   * @see SFBaseSession#getTimestampMappedType()\n   * @param timestampMappedType SnowflakeType\n   */\n  public void setTimestampMappedType(SnowflakeType timestampMappedType) {\n    this.timestampMappedType = timestampMappedType;\n  }\n\n  /**\n   * Get if result column is case-insensitive\n   *\n   * @see SFBaseSession#setResultColumnCaseInsensitive(boolean)\n   * @return true if result column is case-insensitive\n   */\n  public boolean isResultColumnCaseInsensitive() {\n    return isResultColumnCaseInsensitive;\n  }\n\n  /**\n   * Set if result column is case-insensitive\n   *\n   * @see <a\n   *     href=\"https://docs.snowflake.com/en/sql-reference/parameters#client-result-column-case-insensitive\">CLIENT_RESULT_COLUMN_CASE_INSENSITIVE</a>\n   * @param resultColumnCaseInsensitive boolean\n   */\n  public void setResultColumnCaseInsensitive(boolean resultColumnCaseInsensitive) {\n    isResultColumnCaseInsensitive = resultColumnCaseInsensitive;\n  }\n\n  /**\n   * Check if we want to treat decimal as int JDBC types\n   *\n   * @see <a\n   *     href=\"https://docs.snowflake.com/en/sql-reference/parameters#jdbc-treat-decimal-as-int\">JDBC_TREAT_DECIMAL_AS_INT</a>\n   * @return true if decimal is treated as int\n   */\n  public boolean isJdbcTreatDecimalAsInt() {\n    return isJdbcTreatDecimalAsInt;\n  }\n\n  /**\n   * Set if decimal should be treated as int type\n   *\n   * @see SFBaseSession#isJdbcTreatDecimalAsInt()\n   * @param jdbcTreatDecimalAsInt boolean\n   */\n  public void setJdbcTreatDecimalAsInt(boolean jdbcTreatDecimalAsInt) {\n    isJdbcTreatDecimalAsInt = jdbcTreatDecimalAsInt;\n  }\n\n  /**\n   * @return true if decimal should be treated as int for arrow types\n   */\n  public boolean isJdbcArrowTreatDecimalAsInt() {\n    return isJdbcArrowTreatDecimalAsInt;\n  }\n\n  /**\n   * Set if decimal should be treated as int for arrow types\n   *\n   * @param jdbcArrowTreatDecimalAsInt boolean\n   */\n  public void setJdbcArrowTreatDecimalAsInt(boolean jdbcArrowTreatDecimalAsInt) {\n    isJdbcArrowTreatDecimalAsInt = jdbcArrowTreatDecimalAsInt;\n  }\n\n  /**\n   * Get the server url\n   *\n   * @return the server url or null if it is not set\n   */\n  public String getServerUrl() {\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.SERVER_URL)) {\n      return (String) connectionPropertiesMap.get(SFSessionProperty.SERVER_URL);\n    }\n    return null;\n  }\n\n  /**\n   * Get whether columns strings are quoted.\n   *\n   * @return value of 'stringsQuotedForColumnDef' connection property or false if not set.\n   */\n  public boolean isStringQuoted() {\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.STRINGS_QUOTED)) {\n      return (Boolean) connectionPropertiesMap.get(SFSessionProperty.STRINGS_QUOTED);\n    }\n    return false;\n  }\n\n  /**\n   * Wrapper function for the other addProperty(String, Object) method that takes an\n   * SFSessionProperty instead of a String key.\n   *\n   * @param sfSessionProperty The property for which to set the value.\n   * @param propertyValue The value to set for the property.\n   * @throws SFException If the value already exists for the given key (should only be set once), or\n   *     if the value is invalid.\n   */\n  public void addProperty(SFSessionProperty sfSessionProperty, Object propertyValue)\n      throws SFException {\n    addProperty(sfSessionProperty.getPropertyKey(), propertyValue);\n  }\n\n  /**\n   * Adds a connection property to the connection-properties map. Connection properties are those\n   * that are defined in SFSessionProperty. They are set typically through instantiation of the\n   * Session.\n   *\n   * @param propertyName The name of the property, as a string. Recognized ones are defined in the\n   *     SFSessionProperty enum.\n   * @param propertyValue The value to set for this key.\n   * @throws SFException If the value already exists for the given key (should only be set once), or\n   *     if the value is invalid.\n   */\n  public void addProperty(String propertyName, Object propertyValue) throws SFException {\n    SFSessionProperty connectionProperty = SFSessionProperty.lookupByKey(propertyName);\n    // check if the value type is as expected\n    propertyValue = SFSessionProperty.checkPropertyValue(connectionProperty, propertyValue);\n\n    if (connectionPropertiesMap.containsKey(connectionProperty)) {\n      throw new SFException(ErrorCode.DUPLICATE_CONNECTION_PROPERTY_SPECIFIED, propertyName);\n    } else if (propertyValue != null && connectionProperty == SFSessionProperty.AUTHENTICATOR) {\n      String[] authenticatorWithParams = propertyValue.toString().split(\";\");\n      if (authenticatorWithParams.length == 1) {\n        connectionPropertiesMap.put(connectionProperty, propertyValue);\n      } else {\n        String[] oktaUserKeyPair = authenticatorWithParams[1].split(\"=\");\n        if (oktaUserKeyPair.length == 2) {\n          connectionPropertiesMap.put(connectionProperty, authenticatorWithParams[0]);\n          connectionPropertiesMap.put(SFSessionProperty.OKTA_USERNAME, oktaUserKeyPair[1]);\n        } else {\n          throw new SFException(ErrorCode.INVALID_OKTA_USERNAME, propertyName);\n        }\n      }\n    } else {\n      connectionPropertiesMap.put(connectionProperty, propertyValue);\n    }\n  }\n\n  /**\n   * Get the connection properties map\n   *\n   * @return the connection properties map\n   */\n  public Map<SFSessionProperty, Object> getConnectionPropertiesMap() {\n    return connectionPropertiesMap;\n  }\n\n  /**\n   * Get the http client key\n   *\n   * @return HttpClientSettingsKey\n   * @throws SnowflakeSQLException if exception encountered\n   */\n  public HttpClientSettingsKey getHttpClientKey() throws SnowflakeSQLException {\n    // if key is already created, return it without making a new one\n    if (ocspAndProxyAndGzipKey != null) {\n      return ocspAndProxyAndGzipKey;\n    }\n\n    OCSPMode ocspMode = getOCSPMode();\n    CertRevocationCheckMode certRevocationCheckMode = getCertRevocationCheckMode();\n\n    if (certRevocationCheckMode != CertRevocationCheckMode.DISABLED\n        && ocspMode != OCSPMode.DISABLE_OCSP_CHECKS) {\n      throw new SnowflakeSQLException(ErrorCode.BOTH_OCSP_AND_CERT_REVOCATION_CHECK);\n    }\n\n    Boolean gzipDisabled = false;\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.GZIP_DISABLED)) {\n      gzipDisabled = (Boolean) connectionPropertiesMap.get(SFSessionProperty.GZIP_DISABLED);\n    }\n\n    // if not, create a new key\n    boolean useProxy = false;\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.USE_PROXY)) {\n      useProxy = (boolean) connectionPropertiesMap.get(SFSessionProperty.USE_PROXY);\n    }\n    // Check for any user agent suffix\n    String userAgentSuffix = \"\";\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.USER_AGENT_SUFFIX)) {\n      userAgentSuffix = (String) connectionPropertiesMap.get(SFSessionProperty.USER_AGENT_SUFFIX);\n    }\n\n    if (useProxy) {\n      int proxyPort;\n      try {\n        proxyPort =\n            Integer.parseInt(connectionPropertiesMap.get(SFSessionProperty.PROXY_PORT).toString());\n      } catch (NumberFormatException | NullPointerException e) {\n        throw new SnowflakeSQLException(\n            ErrorCode.INVALID_PROXY_PROPERTIES, \"Could not parse port number\");\n      }\n      String proxyHost = (String) connectionPropertiesMap.get(SFSessionProperty.PROXY_HOST);\n      String proxyUser = (String) connectionPropertiesMap.get(SFSessionProperty.PROXY_USER);\n      String proxyPassword = (String) connectionPropertiesMap.get(SFSessionProperty.PROXY_PASSWORD);\n      String nonProxyHosts =\n          (String) connectionPropertiesMap.get(SFSessionProperty.NON_PROXY_HOSTS);\n      String proxyProtocol = (String) connectionPropertiesMap.get(SFSessionProperty.PROXY_PROTOCOL);\n      ocspAndProxyAndGzipKey =\n          new HttpClientSettingsKey(\n              ocspMode,\n              proxyHost,\n              proxyPort,\n              nonProxyHosts,\n              proxyUser,\n              proxyPassword,\n              proxyProtocol,\n              userAgentSuffix,\n              gzipDisabled);\n\n      logHttpClientInitInfo(ocspAndProxyAndGzipKey);\n    }\n    // If JVM proxy parameters are specified, proxies need to go through the JDBC driver's\n    // HttpClientSettingsKey logic in order to work properly.\n    else {\n      boolean httpUseProxy = Boolean.parseBoolean(systemGetProperty(\"http.useProxy\"));\n      String httpProxyHost = systemGetProperty(\"http.proxyHost\");\n      String httpProxyPort = systemGetProperty(\"http.proxyPort\");\n      String httpProxyUser = systemGetProperty(\"http.proxyUser\");\n      String httpProxyPassword = systemGetProperty(\"http.proxyPassword\");\n      String httpsProxyHost = systemGetProperty(\"https.proxyHost\");\n      String httpsProxyPort = systemGetProperty(\"https.proxyPort\");\n      String httpsProxyUser = systemGetProperty(\"https.proxyUser\");\n      String httpsProxyPassword = systemGetProperty(\"https.proxyPassword\");\n      String httpProxyProtocol = systemGetProperty(\"http.proxyProtocol\");\n      String noProxy = systemGetEnv(\"NO_PROXY\");\n      String nonProxyHosts = systemGetProperty(\"http.nonProxyHosts\");\n      // log the JVM parameters that are being used\n      if (httpUseProxy) {\n        logger.debug(\n            \"Using JVM parameters for proxy setup: http.useProxy={}, http.proxyHost={}, http.proxyPort={}, http.proxyUser={}, \"\n                + \"http.proxyPassword is {}, https.proxyHost={}, https.proxyPort={}, https.proxyUser={}, \"\n                + \"https.proxyPassword is {}, http.nonProxyHosts={}, NO_PROXY={}, http.proxyProtocol={}\",\n            httpUseProxy,\n            httpProxyHost,\n            httpProxyPort,\n            httpProxyUser,\n            httpProxyPassword == null || httpProxyPassword.isEmpty() ? \"not set\" : \"set\",\n            httpsProxyHost,\n            httpsProxyPort,\n            httpsProxyUser,\n            httpsProxyPassword == null || httpsProxyPassword.isEmpty() ? \"not set\" : \"set\",\n            nonProxyHosts,\n            noProxy,\n            httpProxyProtocol,\n            userAgentSuffix,\n            gzipDisabled);\n        // There are 2 possible parameters for non proxy hosts that can be combined into 1\n        String combinedNonProxyHosts = isNullOrEmpty(nonProxyHosts) ? \"\" : nonProxyHosts;\n        if (!isNullOrEmpty(noProxy)) {\n          combinedNonProxyHosts += combinedNonProxyHosts.length() == 0 ? \"\" : \"|\";\n          combinedNonProxyHosts += noProxy;\n        }\n\n        // It is possible that a user can have both http and https proxies specified in the JVM\n        // parameters. The default protocol is http.\n        String proxyProtocol = \"http\";\n        if (!isNullOrEmpty(httpProxyProtocol)) {\n          proxyProtocol = httpProxyProtocol;\n        } else if (!isNullOrEmpty(httpsProxyHost)\n            && !isNullOrEmpty(httpsProxyPort)\n            && isNullOrEmpty(httpProxyHost)\n            && isNullOrEmpty(httpProxyPort)) {\n          proxyProtocol = \"https\";\n        }\n\n        if (proxyProtocol.equals(\"https\")\n            && !isNullOrEmpty(httpsProxyHost)\n            && !isNullOrEmpty(httpsProxyPort)) {\n          logger.debug(\"Using https proxy configuration from JVM parameters\");\n          int proxyPort;\n          try {\n            proxyPort = Integer.parseInt(httpsProxyPort);\n          } catch (NumberFormatException | NullPointerException e) {\n            throw new SnowflakeSQLException(\n                ErrorCode.INVALID_PROXY_PROPERTIES, \"Could not parse port number\");\n          }\n          ocspAndProxyAndGzipKey =\n              new HttpClientSettingsKey(\n                  ocspMode,\n                  httpsProxyHost,\n                  proxyPort,\n                  combinedNonProxyHosts,\n                  httpsProxyUser,\n                  httpsProxyPassword,\n                  \"https\",\n                  userAgentSuffix,\n                  gzipDisabled);\n          logHttpClientInitInfo(ocspAndProxyAndGzipKey);\n        } else if (proxyProtocol.equals(\"http\")\n            && !isNullOrEmpty(httpProxyHost)\n            && !isNullOrEmpty(httpProxyPort)) {\n          logger.debug(\"Using http proxy configuration from JVM parameters\");\n          int proxyPort;\n          try {\n            proxyPort = Integer.parseInt(httpProxyPort);\n          } catch (NumberFormatException | NullPointerException e) {\n            throw new SnowflakeSQLException(\n                ErrorCode.INVALID_PROXY_PROPERTIES, \"Could not parse port number\");\n          }\n          ocspAndProxyAndGzipKey =\n              new HttpClientSettingsKey(\n                  ocspMode,\n                  httpProxyHost,\n                  proxyPort,\n                  combinedNonProxyHosts,\n                  httpProxyUser,\n                  httpProxyPassword,\n                  \"http\",\n                  userAgentSuffix,\n                  gzipDisabled);\n          logHttpClientInitInfo(ocspAndProxyAndGzipKey);\n        } else {\n          // Not enough parameters set to use the proxy.\n          logger.warn(\n              \"Failed parsing the proxy settings from JVM parameters as http.useProxy={},\"\n                  + \" but valid host and port were not provided.\",\n              httpUseProxy);\n          ocspAndProxyAndGzipKey =\n              new HttpClientSettingsKey(ocspMode, userAgentSuffix, gzipDisabled);\n          logHttpClientInitInfo(ocspAndProxyAndGzipKey);\n        }\n      } else {\n        // If no proxy is used or JVM http proxy is used, no need for setting parameters\n        logger.debug(\"http.useProxy={}. JVM proxy not used.\", httpUseProxy);\n        unsetInvalidProxyHostAndPort();\n        ocspAndProxyAndGzipKey = new HttpClientSettingsKey(ocspMode, userAgentSuffix, gzipDisabled);\n        logHttpClientInitInfo(ocspAndProxyAndGzipKey);\n      }\n    }\n    ocspAndProxyAndGzipKey.setRevocationCheckMode(certRevocationCheckMode);\n    ocspAndProxyAndGzipKey.setAllowCertificatesWithoutCrlUrl(allowCertificatesWithoutCrlUrl);\n    return ocspAndProxyAndGzipKey;\n  }\n\n  private void logHttpClientInitInfo(HttpClientSettingsKey key) {\n    if (key.usesProxy()) {\n      logger.info(\n          \"Driver OCSP mode: {}, gzip disabled: {}, proxy protocol: {},\"\n              + \" proxy host: {}, proxy port: {}, non proxy hosts: {}, proxy user: {}, proxy password is {}\",\n          key.getOcspMode(),\n          key.getGzipDisabled(),\n          key.getProxyHttpProtocol(),\n          key.getProxyHost(),\n          key.getProxyPort(),\n          key.getNonProxyHosts(),\n          key.getProxyUser(),\n          key.getProxyPassword().isEmpty() ? \"not set\" : \"set\");\n    } else {\n      logger.debug(\n          \"Driver OCSP mode: {}, gzip disabled: {} and no proxy\",\n          key.getOcspMode(),\n          key.getGzipDisabled());\n    }\n  }\n\n  /** Unset invalid proxy host and port values. */\n  public void unsetInvalidProxyHostAndPort() {\n    // If proxyHost and proxyPort are used without http or https unset them, so they are not used\n    // later by the ProxySelector.\n    if (!isNullOrEmpty(systemGetProperty(\"proxyHost\"))) {\n      System.clearProperty(\"proxyHost\");\n    }\n    if (!isNullOrEmpty(systemGetProperty(\"proxyPort\"))) {\n      System.clearProperty(\"proxyPort\");\n    }\n  }\n\n  /**\n   * Get OCSP mode\n   *\n   * @return {@link OCSPMode}\n   * @throws SnowflakeSQLException\n   */\n  public OCSPMode getOCSPMode() throws SnowflakeSQLException {\n    OCSPMode ret;\n\n    Boolean disableOCSPChecks =\n        (Boolean) connectionPropertiesMap.get(SFSessionProperty.DISABLE_OCSP_CHECKS);\n    Boolean insecureMode = (Boolean) connectionPropertiesMap.get(SFSessionProperty.INSECURE_MODE);\n    if (insecureMode != null && insecureMode) {\n      logger.warn(\n          \"The 'insecureMode' connection property is deprecated. Please use 'disableOCSPChecks' instead.\");\n    }\n\n    if ((disableOCSPChecks != null && insecureMode != null)\n        && (disableOCSPChecks != insecureMode)) {\n      logger.error(\n          \"The values for 'disableOCSPChecks' and 'insecureMode' must be identical. \"\n              + \"Please unset insecureMode.\");\n      throw new SnowflakeSQLException(\n          ErrorCode.DISABLEOCSP_INSECUREMODE_VALUE_MISMATCH,\n          \"The values for 'disableOCSPChecks' and 'insecureMode' \" + \"must be identical.\");\n    }\n    if ((disableOCSPChecks != null && disableOCSPChecks)\n        || (insecureMode != null && insecureMode)) {\n      // skip OCSP checks\n      ret = OCSPMode.DISABLE_OCSP_CHECKS;\n    } else if (!connectionPropertiesMap.containsKey(SFSessionProperty.OCSP_FAIL_OPEN)\n        || (boolean) connectionPropertiesMap.get(SFSessionProperty.OCSP_FAIL_OPEN)) {\n      // fail open (by default, not set)\n      ret = OCSPMode.FAIL_OPEN;\n    } else {\n      // explicitly set ocspFailOpen=false\n      ret = OCSPMode.FAIL_CLOSED;\n    }\n    return ret;\n  }\n\n  public CertRevocationCheckMode getCertRevocationCheckMode() throws SnowflakeSQLException {\n    String certRevocationCheckModeStr =\n        (String)\n            connectionPropertiesMap.getOrDefault(\n                SFSessionProperty.CERT_REVOCATION_CHECK_MODE,\n                CertRevocationCheckMode.DISABLED.name());\n    try {\n      return CertRevocationCheckMode.valueOf(certRevocationCheckModeStr);\n    } catch (IllegalArgumentException e) {\n      throw new SnowflakeSQLException(\n          ErrorCode.UNKNOWN_CERT_REVOCATION_CHECK_MODE,\n          \"The value passed for \"\n              + SFSessionProperty.CERT_REVOCATION_CHECK_MODE\n              + \" is invalid. Possible values are \"\n              + Arrays.toString(CertRevocationCheckMode.values()));\n    }\n  }\n\n  /**\n   * Get the query timeout\n   *\n   * @return the query timeout value\n   */\n  public Integer getQueryTimeout() {\n    return (Integer) this.connectionPropertiesMap.get(SFSessionProperty.QUERY_TIMEOUT);\n  }\n\n  /**\n   * Get the user name\n   *\n   * @return user name\n   */\n  public String getUser() {\n    return (String) this.connectionPropertiesMap.get(SFSessionProperty.USER);\n  }\n\n  /**\n   * Get the server URL\n   *\n   * @return the server URL\n   */\n  public String getUrl() {\n    return (String) this.connectionPropertiesMap.get(SFSessionProperty.SERVER_URL);\n  }\n\n  /**\n   * Get inject wait input\n   *\n   * @return the value of 'inject_wait_in_put' or 0 if not set\n   */\n  public int getInjectWaitInPut() {\n    Object retVal = this.connectionPropertiesMap.get(SFSessionProperty.INJECT_WAIT_IN_PUT);\n    if (retVal != null) {\n      try {\n        return (int) retVal;\n      } catch (Exception e) {\n        return 0;\n      }\n    }\n    return 0;\n  }\n\n  /**\n   * Get whether the metadata request should use the session database.\n   *\n   * @return true if it should use the session database\n   */\n  public boolean getMetadataRequestUseSessionDatabase() {\n    return metadataRequestUseSessionDatabase;\n  }\n\n  /**\n   * Set to true if the metadata request should use the session database.\n   *\n   * @param enabled boolean\n   */\n  public void setMetadataRequestUseSessionDatabase(boolean enabled) {\n    this.metadataRequestUseSessionDatabase = enabled;\n  }\n\n  /**\n   * Get if metadata request should use the connection ctx\n   *\n   * @return true if it should use the connection ctx\n   */\n  public boolean getMetadataRequestUseConnectionCtx() {\n    return this.metadataRequestUseConnectionCtx;\n  }\n\n  /**\n   * Set to true if metadata request should use connection ctx\n   *\n   * @param enabled boolean\n   */\n  public void setMetadataRequestUseConnectionCtx(boolean enabled) {\n    this.metadataRequestUseConnectionCtx = enabled;\n  }\n\n  /**\n   * Get injected delay\n   *\n   * @return {@link AtomicInteger}\n   */\n  AtomicInteger getInjectedDelay() {\n    return _injectedDelay;\n  }\n\n  /**\n   * Set the injected delay\n   *\n   * @param injectedDelay injectedDelay value\n   */\n  public void setInjectedDelay(int injectedDelay) {\n    this._injectedDelay.set(injectedDelay);\n  }\n\n  /**\n   * Get if NTZ should be treated as UTC\n   *\n   * @return true if NTZ should be treated as UTC\n   */\n  public boolean getTreatNTZAsUTC() {\n    return treatNTZAsUTC;\n  }\n\n  /**\n   * Set whether NTZ should be treated as UTC\n   *\n   * @param treatNTZAsUTC boolean\n   */\n  public void setTreatNTZAsUTC(boolean treatNTZAsUTC) {\n    this.treatNTZAsUTC = treatNTZAsUTC;\n  }\n\n  /**\n   * Get if heartbeat is enabled\n   *\n   * @return true if enabled\n   */\n  public boolean getEnableHeartbeat() {\n    return enableHeartbeat;\n  }\n\n  /**\n   * Set if heartbeat is enabled\n   *\n   * @param enableHeartbeat boolean\n   */\n  public void setEnableHeartbeat(boolean enableHeartbeat) {\n    this.enableHeartbeat = enableHeartbeat;\n  }\n\n  /**\n   * Set the heartbeat frequency in seconds. This is the frequency with which the session token is\n   * refreshed.\n   *\n   * @param frequency heartbeat frequency in seconds\n   */\n  public void setHeartbeatFrequency(int frequency) {\n    if (frequency < 900) {\n      this.heartbeatFrequency = 900;\n    } else if (frequency > 3600) {\n      this.heartbeatFrequency = 3600;\n    } else {\n      this.heartbeatFrequency = frequency;\n    }\n  }\n\n  /**\n   * Retrieve session heartbeat frequency in seconds\n   *\n   * @return the heartbeat frequency in seconds\n   */\n  public int getHeartbeatFrequency() {\n    return this.heartbeatFrequency;\n  }\n\n  /**\n   * autoCommit field specifies whether autocommit is enabled for the session. Autocommit determines\n   * whether a DML statement, when executed without an active transaction, is automatically\n   * committed after the statement successfully completes. default: true\n   *\n   * @see <a\n   *     href=\"https://docs.snowflake.com/en/sql-reference/transactions#label-txn-autocommit\">Transactions/Autocommit</a>\n   * @return a boolean value of autocommit field\n   */\n  public boolean getAutoCommit() {\n    return autoCommit.get();\n  }\n\n  /**\n   * Sets value of autoCommit field\n   *\n   * @see SFBaseSession#getAutoCommit()\n   * @param autoCommit boolean\n   */\n  public void setAutoCommit(boolean autoCommit) {\n    this.autoCommit.set(autoCommit);\n  }\n\n  /**\n   * Get if date should be formatted with timezone\n   *\n   * @return true if date should be formatted with timezone\n   */\n  public boolean getFormatDateWithTimezone() {\n    return formatDateWithTimezone;\n  }\n\n  /**\n   * Set if date should be formatted with timezone\n   *\n   * @param formatDateWithTimezone boolean\n   */\n  public void setFormatDateWithTimezone(boolean formatDateWithTimezone) {\n    this.formatDateWithTimezone = formatDateWithTimezone;\n  }\n\n  /**\n   * Get if session timezone should be used.\n   *\n   * @return true if using session timezone\n   */\n  public boolean getUseSessionTimezone() {\n    return useSessionTimezone;\n  }\n\n  /**\n   * Get if using default date format with timezone.\n   *\n   * @return true if using default date format with timezone.\n   */\n  public boolean getDefaultFormatDateWithTimezone() {\n    return defaultFormatDateWithTimezone;\n  }\n\n  /**\n   * Set if session timezone should be used.\n   *\n   * @param useSessionTimezone boolean\n   */\n  public void setUseSessionTimezone(boolean useSessionTimezone) {\n    this.useSessionTimezone = useSessionTimezone;\n  }\n\n  /**\n   * Set if default date format with timezone should be used\n   *\n   * @param defaultFormatDateWithTimezone boolean\n   */\n  public void setDefaultFormatDateWithTimezone(boolean defaultFormatDateWithTimezone) {\n    this.defaultFormatDateWithTimezone = defaultFormatDateWithTimezone;\n  }\n\n  public boolean getGetDateUseNullTimezone() {\n    return getDateUseNullTimezone;\n  }\n\n  public void setGetDateUseNullTimezone(boolean getDateUseNullTimezone) {\n    this.getDateUseNullTimezone = getDateUseNullTimezone;\n  }\n\n  public boolean getEnableCombineDescribe() {\n    return enableCombineDescribe;\n  }\n\n  public void setEnableCombineDescribe(boolean enableCombineDescribe) {\n    this.enableCombineDescribe = enableCombineDescribe;\n  }\n\n  public boolean isClientTelemetryEnabled() {\n    return clientTelemetryEnabled;\n  }\n\n  public void setClientTelemetryEnabled(boolean clientTelemetryEnabled) {\n    this.clientTelemetryEnabled = clientTelemetryEnabled;\n  }\n\n  public int getArrayBindStageThreshold() {\n    return arrayBindStageThreshold;\n  }\n\n  public void setArrayBindStageThreshold(int arrayBindStageThreshold) {\n    this.arrayBindStageThreshold = arrayBindStageThreshold;\n  }\n\n  public boolean getStoreTemporaryCredential() {\n    return storeTemporaryCredential;\n  }\n\n  public void setStoreTemporaryCredential(boolean storeTemporaryCredential) {\n    this.storeTemporaryCredential = storeTemporaryCredential;\n  }\n\n  public String getServiceName() {\n    return serviceName;\n  }\n\n  public void setServiceName(String serviceName) {\n    this.serviceName = serviceName;\n  }\n\n  public void setEnableConservativeMemoryUsage(boolean enableConservativeMemoryUsage) {\n    this.enableConservativeMemoryUsage = enableConservativeMemoryUsage;\n  }\n\n  public boolean isConservativeMemoryUsageEnabled() {\n    return enableConservativeMemoryUsage;\n  }\n\n  public int getConservativeMemoryAdjustStep() {\n    return conservativeMemoryAdjustStep;\n  }\n\n  public void setConservativeMemoryAdjustStep(int conservativeMemoryAdjustStep) {\n    this.conservativeMemoryAdjustStep = conservativeMemoryAdjustStep;\n  }\n\n  public int getClientMemoryLimit() {\n    return clientMemoryLimit;\n  }\n\n  public void setClientMemoryLimit(int clientMemoryLimit) {\n    this.clientMemoryLimit = clientMemoryLimit;\n  }\n\n  public int getQueryContextCacheSize() {\n    return queryContextCacheSize;\n  }\n\n  public void setQueryContextCacheSize(int queryContextCacheSize) {\n    this.queryContextCacheSize = queryContextCacheSize;\n  }\n\n  public boolean getJdbcEnablePutGet() {\n    return jdbcEnablePutGet;\n  }\n\n  public void setJdbcEnablePutGet(boolean jdbcEnablePutGet) {\n    this.jdbcEnablePutGet = jdbcEnablePutGet;\n  }\n\n  public boolean getEnablePutGet() {\n    return enablePutGet;\n  }\n\n  public boolean setEnablePutGet(boolean enablePutGet) {\n    return this.enablePutGet = enablePutGet;\n  }\n\n  public boolean isEnableCopyResultSet() {\n    return enableCopyResultSet;\n  }\n\n  public boolean setEnableCopyResultSet(boolean enableCopyResultSet) {\n    return this.enableCopyResultSet = enableCopyResultSet;\n  }\n\n  public boolean getEnablePatternSearch() {\n    return enablePatternSearch;\n  }\n\n  public void setEnablePatternSearch(boolean enablePatternSearch) {\n    this.enablePatternSearch = enablePatternSearch;\n  }\n\n  public boolean getEnableExactSchemaSearch() {\n    return enableExactSchemaSearch;\n  }\n\n  void setEnableExactSchemaSearch(boolean enableExactSchemaSearch) {\n    this.enableExactSchemaSearch = enableExactSchemaSearch;\n  }\n\n  public boolean getEnableWildcardsInShowMetadataCommands() {\n    return enableWildcardsInShowMetadataCommands;\n  }\n\n  void setEnableWildcardsInShowMetadataCommands(boolean enableWildcardsInShowMetadataCommands) {\n    this.enableWildcardsInShowMetadataCommands = enableWildcardsInShowMetadataCommands;\n  }\n\n  public boolean getDisableGcsDefaultCredentials() {\n    return disableGcsDefaultCredentials;\n  }\n\n  public void setDisableGcsDefaultCredentials(boolean disableGcsDefaultCredentials) {\n    this.disableGcsDefaultCredentials = disableGcsDefaultCredentials;\n  }\n\n  public int getClientResultChunkSize() {\n    return clientResultChunkSize;\n  }\n\n  public void setClientResultChunkSize(int clientResultChunkSize) {\n    this.clientResultChunkSize = clientResultChunkSize;\n  }\n\n  public Object getOtherParameter(String key) {\n    return this.otherParameters.get(key);\n  }\n\n  public void setOtherParameter(String key, Object value) {\n    this.otherParameters.put(key, value);\n  }\n\n  public int getClientPrefetchThreads() {\n    return clientPrefetchThreads;\n  }\n\n  public void setClientPrefetchThreads(int clientPrefetchThreads) {\n    this.clientPrefetchThreads = clientPrefetchThreads;\n  }\n\n  public boolean getValidateDefaultParameters() {\n    return validateDefaultParameters;\n  }\n\n  public void setValidateDefaultParameters(boolean validateDefaultParameters) {\n    this.validateDefaultParameters = validateDefaultParameters;\n  }\n\n  public String getDatabase() {\n    return database;\n  }\n\n  public void setDatabase(String database) {\n    if (!isNullOrEmpty(database)) {\n      this.database = database;\n    }\n  }\n\n  public String getSchema() {\n    return schema;\n  }\n\n  public void setSchema(String schema) {\n    if (!isNullOrEmpty(schema)) {\n      this.schema = schema;\n    }\n  }\n\n  public String getRole() {\n    return role;\n  }\n\n  public void setRole(String role) {\n    this.role = role;\n  }\n\n  public String getWarehouse() {\n    return warehouse;\n  }\n\n  public void setWarehouse(String warehouse) {\n    if (!isNullOrEmpty(warehouse)) {\n      this.warehouse = warehouse;\n    }\n  }\n\n  public void setUseRegionalS3EndpointsForPresignedURL(boolean regionalS3Endpoint) {\n    this.useRegionalS3EndpointsForPresignedURL = regionalS3Endpoint;\n  }\n\n  public boolean getUseRegionalS3EndpointsForPresignedURL() {\n    return useRegionalS3EndpointsForPresignedURL;\n  }\n\n  public String getArrayBindStage() {\n    return arrayBindStage;\n  }\n\n  public void setArrayBindStage(String arrayBindStage) {\n    this.arrayBindStage = String.format(\"%s.%s.%s\", getDatabase(), getSchema(), arrayBindStage);\n  }\n\n  /**\n   * Enables setting a value in the custom-properties map. This is used for properties that are\n   * implementation specific to the session, and not shared by the different implementations.\n   *\n   * @param propertyName A string key for the property to set.\n   * @param propertyValue The property value.\n   */\n  public void setSessionPropertyByKey(String propertyName, Object propertyValue) {\n    this.customSessionProperties.put(propertyName, propertyValue);\n  }\n\n  /**\n   * Fetch the value for a custom session property.\n   *\n   * @param propertyName The key of the session property to fetch.\n   * @return session property value\n   */\n  public Object getSessionPropertyByKey(String propertyName) {\n    return this.customSessionProperties.get(propertyName);\n  }\n\n  /**\n   * Function that checks if the active session can be closed when the connection is closed. Called\n   * by SnowflakeConnectionImpl.\n   *\n   * @return true if the active session is safe to close.\n   */\n  public abstract boolean isSafeToClose();\n\n  /**\n   * @param queryID query ID of the query whose status is being investigated\n   * @return QueryStatus indicating the query's status\n   * @throws SQLException if error encountered\n   */\n  public abstract QueryStatus getQueryStatus(String queryID) throws SQLException;\n\n  /**\n   * Validates the connection properties used by this session, and returns a list of missing\n   * properties.\n   *\n   * @return List of DriverPropertyInfo\n   */\n  public abstract List<DriverPropertyInfo> checkProperties();\n\n  /**\n   * Close the connection\n   *\n   * @throws SnowflakeSQLException if failed to close the connection\n   * @throws SFException if failed to close the connection\n   */\n  public void close() throws SFException, SnowflakeSQLException {\n    close(null);\n  }\n\n  /** Marker-aware overload for internal call paths. */\n  public abstract void close(InternalCallMarker internalCallMarker)\n      throws SFException, SnowflakeSQLException;\n\n  /**\n   * @return Returns the telemetry client, if supported, by this session. If not, should return a\n   *     NoOpTelemetryClient.\n   */\n  public Telemetry getTelemetryClient() {\n    return getTelemetryClient(null);\n  }\n\n  /**\n   * Marker-aware overload for internal call paths. Implementations may override to bypass\n   * external-usage telemetry tracking.\n   */\n  public abstract Telemetry getTelemetryClient(InternalCallMarker internalCallMarker);\n\n  /**\n   * Makes a heartbeat call to check for session validity.\n   *\n   * @param timeout timeout value\n   * @throws Exception if exception occurs\n   * @throws SFException if exception occurs\n   */\n  public abstract void callHeartBeat(int timeout) throws Exception, SFException;\n\n  /**\n   * JDBC API. Returns a list of warnings generated since starting this session, or the last time it\n   * was cleared.\n   *\n   * @return List of SFException's\n   */\n  public List<SFException> getSqlWarnings() {\n    return sqlWarnings;\n  }\n\n  /**\n   * JDBC API. Clears the list of warnings generated since the start of the session, or the last\n   * time it was cleared.\n   */\n  public void clearSqlWarnings() {\n    sqlWarnings.clear();\n  }\n\n  /**\n   * Get the SFConnectionHandler\n   *\n   * @return {@link SFConnectionHandler}\n   */\n  public SFConnectionHandler getSfConnectionHandler() {\n    return sfConnectionHandler;\n  }\n\n  /**\n   * Get network timeout in milliseconds\n   *\n   * @return network timeout in milliseconds\n   */\n  public abstract int getNetworkTimeoutInMilli();\n\n  /**\n   * @return auth timeout in seconds\n   */\n  @Deprecated\n  public abstract int getAuthTimeout();\n\n  /**\n   * @return max http retries\n   */\n  public abstract int getMaxHttpRetries();\n\n  /**\n   * @return {@link SnowflakeConnectString}\n   */\n  public abstract SnowflakeConnectString getSnowflakeConnectionString();\n\n  /**\n   * @return true if this is an async session\n   */\n  public abstract boolean isAsyncSession();\n\n  /**\n   * @return QueryContextDTO containing opaque information shared with the cloud service.\n   */\n  public abstract QueryContextDTO getQueryContextDTO();\n\n  /**\n   * Set query context\n   *\n   * @param queryContext the query context string\n   */\n  public abstract void setQueryContext(String queryContext);\n\n  /**\n   * @return If true, JDBC will enable returning TIMESTAMP_WITH_TIMEZONE as column type, otherwise\n   *     it will not. This function will always return true for JDBC client, so that the client JDBC\n   *     will not have any behavior change. Stored proc JDBC will override this function to return\n   *     the value of SP_JDBC_ENABLE_TIMESTAMP_WITH_TIMEZONE from server for backward compatibility.\n   */\n  public boolean getEnableReturnTimestampWithTimeZone() {\n    return enableReturnTimestampWithTimeZone;\n  }\n\n  boolean getImplicitServerSideQueryTimeout() {\n    return implicitServerSideQueryTimeout;\n  }\n\n  void setImplicitServerSideQueryTimeout(boolean value) {\n    this.implicitServerSideQueryTimeout = value;\n  }\n\n  void setClearBatchOnlyAfterSuccessfulExecution(boolean value) {\n    this.clearBatchOnlyAfterSuccessfulExecution = value;\n  }\n\n  public boolean getClearBatchOnlyAfterSuccessfulExecution() {\n    return this.clearBatchOnlyAfterSuccessfulExecution;\n  }\n\n  public boolean getTreatTimeAsWallClockTime() {\n    return treatTimeAsWallClockTime;\n  }\n\n  public void setTreatTimeAsWallClockTime(boolean treatTimeAsWallClockTime) {\n    this.treatTimeAsWallClockTime = treatTimeAsWallClockTime;\n  }\n\n  /**\n   * Get if owner-only stage file permissions feature is enabled.\n   *\n   * @return true if owner-only stage file permissions feature is enabled\n   */\n  public boolean isOwnerOnlyStageFilePermissionsEnabled() {\n    return ownerOnlyStageFilePermissionsEnabled;\n  }\n\n  public void setOwnerOnlyStageFilePermissionsEnabled(boolean booleanValue) {\n    this.ownerOnlyStageFilePermissionsEnabled = booleanValue;\n  }\n\n  public boolean isAllowCertificatesWithoutCrlUrl() {\n    return allowCertificatesWithoutCrlUrl;\n  }\n\n  public void setAllowCertificatesWithoutCrlUrl(boolean allowCertificatesWithoutCrlUrl) {\n    this.allowCertificatesWithoutCrlUrl = allowCertificatesWithoutCrlUrl;\n  }\n\n  public Map<String, String> getStickyHttpHeaders() {\n    return stickyHttpHeaders;\n  }\n\n  public void extractAndUpdateStickyHttpHeaders(Map<String, String> allHeaders) {\n    this.stickyHttpHeaders.putAll(filterStickyHeaders(allHeaders));\n  }\n\n  private static Map<String, String> filterStickyHeaders(Map<String, String> headers) {\n    return headers.entrySet().stream()\n        .filter(entry -> STICKY_HEADERS_NAMES.contains(entry.getKey().toLowerCase()))\n        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFBaseStatement.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport java.sql.SQLException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Base abstract class for an SFStatement implementation. Statements are used in executing queries,\n * both in standard and prepared forms. They are accessed by users via the public API class,\n * SnowflakeStatementV(x).\n */\npublic abstract class SFBaseStatement {\n  // maximum number of parameters for the statement; if this threshold is exceeded,\n  // we throw an exception\n  protected static final int MAX_STATEMENT_PARAMETERS = 1000;\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFBaseStatement.class);\n  // statement level parameters; just a string-key, object-value map.\n  protected final Map<String, Object> statementParametersMap = new HashMap<>();\n  // timeout in seconds for queries\n  protected int queryTimeout = 0;\n\n  /**\n   * Add a statement parameter\n   *\n   * <p>Make sure a property is not added more than once and the number of properties does not\n   * exceed limit.\n   *\n   * @param propertyName property name\n   * @param propertyValue property value\n   * @throws SFException if too many parameters for a statement\n   */\n  public void addProperty(String propertyName, Object propertyValue) throws SFException {\n    statementParametersMap.put(propertyName, propertyValue);\n\n    if (\"query_timeout\".equalsIgnoreCase(propertyName)) {\n      queryTimeout = (Integer) propertyValue;\n    }\n\n    // check if the number of session properties exceed limit\n    if (statementParametersMap.size() > MAX_STATEMENT_PARAMETERS) {\n      throw new SFException(ErrorCode.TOO_MANY_STATEMENT_PARAMETERS, MAX_STATEMENT_PARAMETERS);\n    }\n  }\n\n  public Map<String, Object> getStatementParameters() {\n    return statementParametersMap;\n  }\n\n  /**\n   * Describe a statement. This is invoked when prepareStatement() occurs. SFStatementMetadata\n   * should be returned by this action, which contains metadata such as the schema of the result.\n   *\n   * @param sql The SQL string of the query/statement.\n   * @return metadata of statement including resultset metadata and binding information\n   * @throws SQLException if connection is already closed\n   * @throws SFException if result set is null\n   */\n  public abstract SFPreparedStatementMetaData describe(String sql) throws SFException, SQLException;\n\n  /**\n   * Executes the given SQL string.\n   *\n   * @param sql The SQL string to execute, synchronously.\n   * @param parametersBinding parameters to bind\n   * @param caller the JDBC interface method that called this method, if any\n   * @param execTimeData OOB telemetry object to record timings\n   * @return whether there is result set or not\n   * @throws SQLException if failed to execute sql\n   * @throws SFException exception raised from Snowflake components\n   * @throws SQLException if SQL error occurs\n   */\n  public abstract SFBaseResultSet execute(\n      String sql,\n      Map<String, ParameterBindingDTO> parametersBinding,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException;\n\n  /**\n   * Execute sql asynchronously. Note that at a minimum, this does not have to be supported; if\n   * executeAsyncQuery() is called from SnowflakeStatement and the SFConnectionHandler's\n   * supportsAsyncQuery() returns false, an exception is thrown. If this is un-implemented, then\n   * supportsAsyncQuery() should return false.\n   *\n   * @param sql sql statement.\n   * @param parametersBinding parameters to bind\n   * @param caller the JDBC interface method that called this method, if any\n   * @param execTimeData ExecTimeTelemetryData\n   * @return whether there is result set or not\n   * @throws SQLException if failed to execute sql\n   * @throws SFException exception raised from Snowflake components\n   * @throws SQLException if SQL error occurs\n   */\n  public abstract SFBaseResultSet asyncExecute(\n      String sql,\n      Map<String, ParameterBindingDTO> parametersBinding,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException;\n\n  /**\n   * Closes the statement. Open result sets are closed, connections are terminated, state is\n   * cleared, etc.\n   */\n  public abstract void close();\n\n  /**\n   * Aborts the statement.\n   *\n   * @throws SFException if the statement is already closed.\n   * @throws SQLException if there are server-side errors from trying to abort.\n   * @deprecated use {@link #cancel(CancellationReason)} instead\n   */\n  @Deprecated\n  public abstract void cancel() throws SFException, SQLException;\n\n  /**\n   * Aborts the statement.\n   *\n   * @param cancellationReason reason for the cancellation\n   * @throws SFException if the statement is already closed.\n   * @throws SQLException if there are server-side errors from trying to abort.\n   */\n  public void cancel(CancellationReason cancellationReason) throws SFException, SQLException {\n    cancel(); // default cancel is called to keep interface backward compatibility\n  }\n\n  /**\n   * Sets a property within session properties, i.e., if the sql is using set-sf-property\n   *\n   * @param sql the set property sql\n   */\n  public void executeSetProperty(final String sql) {\n    logger.trace(\"Setting property\", false);\n\n    // tokenize the sql\n    String[] tokens = sql.split(\"\\\\s+\");\n\n    if (tokens.length < 2) {\n      return;\n    }\n\n    if (\"sort\".equalsIgnoreCase(tokens[1])) {\n      if (tokens.length >= 3 && \"on\".equalsIgnoreCase(tokens[2])) {\n        logger.debug(\"Setting sort on\", false);\n\n        this.getSFBaseSession(internalCallMarker()).setSessionPropertyByKey(\"sort\", true);\n      } else {\n        logger.debug(\"Setting sort off\", false);\n        this.getSFBaseSession(internalCallMarker()).setSessionPropertyByKey(\"sort\", false);\n      }\n    }\n  }\n\n  /**\n   * A method to check if a sql is file upload statement with consideration for potential comments\n   * in front of put keyword.\n   *\n   * @param sql sql statement\n   * @return true if the command is upload statement\n   */\n  public static boolean isFileTransfer(String sql) {\n    SFStatementType statementType = StmtUtil.checkStageManageCommand(sql);\n    return statementType == SFStatementType.PUT || statementType == SFStatementType.GET;\n  }\n\n  /**\n   * If this is a multi-statement, i.e., has child results.\n   *\n   * @return true if has child results\n   */\n  public abstract boolean hasChildren();\n\n  /**\n   * Get the SFBaseSession associated with this SFBaseStatement.\n   *\n   * @return The SFBaseSession associated with this SFBaseStatement.\n   */\n  public abstract SFBaseSession getSFBaseSession();\n\n  /**\n   * Marker-aware overload for internal call paths. Implementations may override to bypass\n   * external-usage telemetry tracking.\n   */\n  public abstract SFBaseSession getSFBaseSession(InternalCallMarker internalCallMarker);\n\n  /**\n   * Retrieves the current result as a ResultSet, if any. This is invoked by SnowflakeStatement and\n   * should return an SFBaseResultSet, which is then wrapped in a SnowflakeResultSet.\n   *\n   * @return {@link SFBaseResultSet}\n   */\n  public abstract SFBaseResultSet getResultSet();\n\n  /**\n   * Sets the result set to the next one, if available.\n   *\n   * @param current What to do with the current result. One of Statement.CLOSE_CURRENT_RESULT,\n   *     Statement.CLOSE_ALL_RESULTS, or Statement.KEEP_CURRENT_RESULT\n   * @return true if there is a next result and it's a result set false if there are no more\n   *     results, or there is a next result and it's an update count\n   * @throws SQLException if something fails while getting the next result\n   */\n  public abstract boolean getMoreResults(int current) throws SQLException;\n\n  /** The type of query that is being executed. Used internally by SnowflakeStatementV(x). */\n  public enum CallingMethod {\n    EXECUTE,\n    EXECUTE_UPDATE,\n    EXECUTE_QUERY\n  }\n\n  public abstract long getConservativeMemoryLimit();\n\n  public abstract int getConservativePrefetchThreads();\n\n  /**\n   * @param queryID the queryID\n   * @return the child query IDs for the multiple statements query.\n   * @throws SQLException if an error occurs while getting child query ID's\n   */\n  public abstract String[] getChildQueryIds(String queryID) throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFBasicCrlTrustManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.core.crl.CrlRevocationManager;\n\npublic class SFBasicCrlTrustManager implements X509TrustManager {\n  private final X509TrustManager trustManager;\n  private final CrlRevocationManager revocationManager;\n\n  public SFBasicCrlTrustManager(\n      CrlRevocationManager revocationManager, X509TrustManager trustManager) {\n    this.revocationManager = revocationManager;\n    this.trustManager = trustManager;\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    trustManager.checkClientTrusted(chain, authType);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    trustManager.checkServerTrusted(chain, authType);\n    revocationManager.validateRevocationStatus(chain, authType);\n  }\n\n  @Override\n  public X509Certificate[] getAcceptedIssuers() {\n    return trustManager.getAcceptedIssuers();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFChildResult.java",
    "content": "package net.snowflake.client.internal.core;\n\n/** Data class to wrap information about child job results */\npublic class SFChildResult {\n  // query id of child query, to look up child result\n  private final String id;\n  // statement type of child query, to properly interpret result\n  private final SFStatementType type;\n\n  public SFChildResult(String id, SFStatementType type) {\n    this.id = id;\n    this.type = type;\n  }\n\n  // For Snowflake internal use\n  public String getId() {\n    return id;\n  }\n\n  // For Snowflake internal use\n  public SFStatementType getType() {\n    return type;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFCrlTrustManagerFactory.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.core.crl.CrlRevocationManager;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.ssl.SSLInitializationException;\n\nclass SFCrlTrustManagerFactory {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFCrlTrustManagerFactory.class);\n\n  static X509TrustManager createCrlTrustManager(HttpClientSettingsKey key)\n      throws CertificateException {\n    X509TrustManager systemTrustManager = getSystemTrustManager();\n    CrlRevocationManager revocationManager = new CrlRevocationManager(key, systemTrustManager);\n\n    if (systemTrustManager instanceof X509ExtendedTrustManager) {\n      logger.debug(\"JVM provides X509ExtendedTrustManager - creating extended CRL trust manager\");\n      return new SFExtendedCrlTrustManager(\n          revocationManager, (X509ExtendedTrustManager) systemTrustManager);\n    } else {\n      logger.debug(\"JVM provides basic X509TrustManager - creating basic CRL trust manager\");\n      return new SFBasicCrlTrustManager(revocationManager, systemTrustManager);\n    }\n  }\n\n  private static X509TrustManager getSystemTrustManager() {\n    try {\n      TrustManagerFactory factory =\n          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n      factory.init((KeyStore) null);\n\n      for (TrustManager tm : factory.getTrustManagers()) {\n        if (tm instanceof X509TrustManager) {\n          return (X509TrustManager) tm;\n        }\n      }\n\n      throw new SSLInitializationException(\n          \"No X509TrustManager found in default trust managers\", null);\n    } catch (NoSuchAlgorithmException | KeyStoreException ex) {\n      throw new SSLInitializationException(\"Failed to initialize default trust manager\", ex);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFException.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.ResourceBundleManager;\n\npublic class SFException extends Throwable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFException.class);\n\n  private static final long serialVersionUID = 1L;\n\n  static final ResourceBundleManager errorResourceBundleManager =\n      ResourceBundleManager.getSingleton(ErrorCode.errorMessageResource);\n\n  private Throwable cause;\n  private String queryId;\n  private String sqlState;\n  private int vendorCode;\n  private Object[] params;\n\n  /**\n   * Use {@link SFException#SFException(String, Throwable, ErrorCode, Object...)}\n   *\n   * @param errorCode the error code\n   * @param params additional params\n   */\n  @Deprecated\n  public SFException(ErrorCode errorCode, Object... params) {\n    this(null, null, errorCode, params);\n  }\n\n  /**\n   * use {@link SFException#SFException(String, Throwable, ErrorCode, Object...)}\n   *\n   * @param queryID the query id\n   * @param errorCode the error code\n   * @param params additional params\n   */\n  @Deprecated\n  public SFException(String queryID, ErrorCode errorCode, Object... params) {\n    this(queryID, null, errorCode, params);\n  }\n\n  /**\n   * use {@link SFException#SFException(String, Throwable, ErrorCode, Object...)}\n   *\n   * @param cause throwable\n   * @param errorCode error code\n   * @param params additional params\n   */\n  @Deprecated\n  public SFException(Throwable cause, ErrorCode errorCode, Object... params) {\n    this(null, cause, errorCode, params);\n  }\n\n  /**\n   * @param queryId query ID\n   * @param cause throwable\n   * @param errorCode error code\n   * @param params additional params\n   */\n  public SFException(String queryId, Throwable cause, ErrorCode errorCode, Object... params) {\n    super(\n        errorResourceBundleManager.getLocalizedMessage(\n            String.valueOf(errorCode.getMessageCode()), params),\n        cause);\n\n    this.cause = null;\n    this.queryId = queryId;\n    this.sqlState = errorCode.getSqlState();\n    this.vendorCode = errorCode.getMessageCode();\n    this.params = params;\n  }\n\n  /**\n   * Get the error cause\n   *\n   * @return Throwable\n   */\n  public Throwable getCause() {\n    return cause;\n  }\n\n  /**\n   * Get the query ID\n   *\n   * @return query ID string\n   */\n  public String getQueryId() {\n    return queryId;\n  }\n\n  /**\n   * Get the SQL state\n   *\n   * @return SQL state string\n   */\n  public String getSqlState() {\n    return sqlState;\n  }\n\n  /**\n   * Get the vendor code\n   *\n   * @return vendor code\n   */\n  public int getVendorCode() {\n    return vendorCode;\n  }\n\n  /**\n   * Get additional parameters\n   *\n   * @return parameter array\n   */\n  public Object[] getParams() {\n    return params;\n  }\n\n  public static String oneLiner(String prefix, Throwable thrown) {\n    StackTraceElement[] stack = thrown.getStackTrace();\n    String topOfStack = null;\n    if (stack.length > 0) {\n      topOfStack = \" at \" + stack[0];\n    }\n    return prefix + \" \" + thrown + topOfStack;\n  }\n\n  @Override\n  public String toString() {\n    return super.toString()\n        + (getQueryId() != null ? \", query id = \" + getQueryId() : \"\")\n        + (getSqlState() != null ? \", sql state = \" + getSqlState() : \"\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFExtendedCrlTrustManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.net.Socket;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport net.snowflake.client.internal.core.crl.CrlRevocationManager;\n\npublic class SFExtendedCrlTrustManager extends X509ExtendedTrustManager {\n  private final X509ExtendedTrustManager exTrustManager;\n  private final CrlRevocationManager revocationManager;\n\n  public SFExtendedCrlTrustManager(\n      CrlRevocationManager revocationManager, X509ExtendedTrustManager trustManager) {\n    this.revocationManager = revocationManager;\n    this.exTrustManager = trustManager;\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    exTrustManager.checkClientTrusted(chain, authType);\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)\n      throws CertificateException {\n    exTrustManager.checkClientTrusted(chain, authType, socket);\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)\n      throws CertificateException {\n    exTrustManager.checkClientTrusted(chain, authType, engine);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    exTrustManager.checkServerTrusted(chain, authType);\n    revocationManager.validateRevocationStatus(chain, authType);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)\n      throws CertificateException {\n    exTrustManager.checkServerTrusted(chain, authType, engine);\n    revocationManager.validateRevocationStatus(chain, authType);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)\n      throws CertificateException {\n    exTrustManager.checkServerTrusted(chain, authType, socket);\n    revocationManager.validateRevocationStatus(chain, authType);\n  }\n\n  @Override\n  public X509Certificate[] getAcceptedIssuers() {\n    return exTrustManager.getAcceptedIssuers();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFFixedViewResultSet.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.SQLException;\nimport java.util.List;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SFBaseFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SFBaseFileTransferAgent.CommandType;\nimport net.snowflake.client.internal.jdbc.SnowflakeFixedView;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\n/**\n * Fixed view result set. This class iterates through any fixed view implementation and return the\n * objects as rows\n */\n// Works only for strings, numbers, etc, does not work for timestamps, dates, times etc.\npublic class SFFixedViewResultSet extends SFJsonResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFFixedViewResultSet.class);\n\n  private SnowflakeFixedView fixedView;\n  private Object[] nextRow = null;\n  private final CommandType commandType;\n  private final String queryID;\n\n  public SFFixedViewResultSet(SnowflakeFixedView fixedView, CommandType commandType, String queryID)\n      throws SnowflakeSQLException {\n    super(\n        null,\n        new Converters(\n            null,\n            new SFSession(),\n            0,\n            false,\n            false,\n            false,\n            false,\n            SFBinaryFormat.BASE64,\n            null,\n            null,\n            null,\n            null,\n            null));\n    this.fixedView = fixedView;\n    this.commandType = commandType;\n    this.queryID = queryID;\n\n    try {\n      resultSetMetaData =\n          new SFResultSetMetaData(\n              fixedView.describeColumns(session),\n              session,\n              timestampNTZFormatter,\n              timestampLTZFormatter,\n              timestampTZFormatter,\n              dateFormatter,\n              timeFormatter);\n\n    } catch (Exception ex) {\n      throw new SnowflakeSQLLoggedException(\n          queryID,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          \"Failed to describe fixed view: \" + fixedView.getClass().getName());\n    }\n  }\n\n  /**\n   * Advance to next row\n   *\n   * @return true if next row exists, false otherwise\n   * @throws net.snowflake.client.internal.core.SFException if failed to get next row\n   */\n  @Override\n  public boolean next() throws SFException {\n    logger.trace(\"next called\", false);\n\n    List<Object> nextRowList;\n    try {\n      // call the fixed view next row method\n      nextRowList = fixedView.getNextRow();\n    } catch (Exception ex) {\n      throw new SFException(\n          queryID,\n          ErrorCode.INTERNAL_ERROR,\n          SFException.oneLiner(\"Error getting next row from \" + \"fixed view:\", ex));\n    }\n\n    row++;\n\n    if (nextRowList == null) {\n      logger.debug(\"End of result\", false);\n      return false;\n    }\n\n    if (nextRow == null) {\n      nextRow = new Object[nextRowList.size()];\n    }\n    nextRow = nextRowList.toArray(nextRow);\n\n    return true;\n  }\n\n  @Override\n  protected Object getObjectInternal(int columnIndex) throws SFException {\n    logger.trace(\"Object getObjectInternal(int columnIndex)\", false);\n\n    if (nextRow == null) {\n      throw new SFException(queryID, ErrorCode.ROW_DOES_NOT_EXIST);\n    }\n\n    if (columnIndex <= 0 || columnIndex > nextRow.length) {\n      throw new SFException(queryID, ErrorCode.COLUMN_DOES_NOT_EXIST, columnIndex);\n    }\n\n    wasNull = nextRow[columnIndex - 1] == null;\n\n    return nextRow[columnIndex - 1];\n  }\n\n  @Override\n  public void close() throws SnowflakeSQLException {\n    super.close();\n    // free the object so that they can be Garbage collected\n    nextRow = null;\n    fixedView = null;\n  }\n\n  @Override\n  public SFStatementType getStatementType() {\n    if (this.commandType == SFBaseFileTransferAgent.CommandType.DOWNLOAD) {\n      return SFStatementType.GET;\n    } else {\n      return SFStatementType.PUT;\n    }\n  }\n\n  @Override\n  public void setStatementType(SFStatementType statementType) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public boolean isLast() {\n    return row == fixedView.getTotalRows();\n  }\n\n  @Override\n  public boolean isAfterLast() {\n    return row > fixedView.getTotalRows();\n  }\n\n  @Override\n  public String getQueryId() {\n    return queryID;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFJsonResultSet.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.math.BigDecimal;\nimport java.sql.Array;\nimport java.sql.Date;\nimport java.sql.SQLInput;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.List;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.arrow.StructObjectWrapper;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Abstract class used to represent snowflake result set in json format */\npublic abstract class SFJsonResultSet extends SFBaseResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFJsonResultSet.class);\n\n  protected final Converters converters;\n  protected final ObjectMapper objectMapper;\n\n  protected SFJsonResultSet(TimeZone sessionTimeZone, Converters converters) {\n    this.sessionTimeZone = sessionTimeZone;\n    this.converters = converters;\n    this.objectMapper = ObjectMapperFactory.getObjectMapperForSession(session);\n  }\n\n  /**\n   * Given a column index, get current row's value as an object\n   *\n   * @param columnIndex index of columns\n   * @return an object\n   * @throws SFException raises if any error occurs\n   */\n  protected abstract Object getObjectInternal(int columnIndex) throws SFException;\n\n  public Object getObject(int columnIndex) throws SFException {\n\n    int type = resultSetMetaData.getColumnType(columnIndex);\n\n    Object obj = getObjectInternal(columnIndex);\n    if (obj == null) {\n      return null;\n    }\n\n    switch (type) {\n      case Types.VARCHAR:\n      case Types.CHAR:\n      case SnowflakeType.EXTRA_TYPES_VECTOR:\n        return getString(columnIndex);\n\n      case Types.BINARY:\n        return getBytes(columnIndex);\n\n      case Types.INTEGER:\n        return getInt(columnIndex);\n\n      case Types.DECIMAL:\n        return getBigDecimal(columnIndex);\n\n      case Types.BIGINT:\n        return getBigInt(columnIndex, obj);\n\n      case Types.DOUBLE:\n        return getDouble(columnIndex);\n\n      case Types.TIMESTAMP:\n      case Types.TIMESTAMP_WITH_TIMEZONE:\n        return getTimestamp(columnIndex);\n\n      case Types.DATE:\n        return getDate(columnIndex);\n\n      case Types.TIME:\n        return getTime(columnIndex);\n\n      case Types.BOOLEAN:\n        return getBoolean(columnIndex);\n\n      case Types.STRUCT:\n        if (resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n          return new StructObjectWrapper((String) obj, getSqlInput((String) obj, columnIndex));\n        } else {\n          throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, \"data type: \" + type);\n        }\n      case Types.ARRAY:\n        if (resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n          return new StructObjectWrapper((String) obj, getArray(columnIndex));\n        } else {\n          throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, \"data type: \" + type);\n        }\n      default:\n        throw new SFException(ErrorCode.FEATURE_UNSUPPORTED, \"data type: \" + type);\n    }\n  }\n\n  @Override\n  public Object getObjectWithoutString(int columnIndex) throws SFException {\n    return getObject(columnIndex);\n  }\n  /**\n   * Sometimes large BIGINTS overflow the java Long type. In these cases, return a BigDecimal type\n   * instead.\n   *\n   * @param columnIndex the column index\n   * @return an object of type long or BigDecimal depending on number size\n   * @throws SFException if an error occurs\n   */\n  private Object getBigInt(int columnIndex, Object obj) throws SFException {\n    return converters.getNumberConverter().getBigInt(obj, columnIndex);\n  }\n\n  @Override\n  public Array getArray(int columnIndex) throws SFException {\n    Object obj = getObjectInternal(columnIndex);\n    if (obj == null) {\n      return null;\n    }\n    return getJsonArray((String) obj, columnIndex, objectMapper);\n  }\n\n  @Override\n  public String getString(int columnIndex) throws SFException {\n    logger.trace(\"String getString(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getInternalColumnType(columnIndex);\n    int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex);\n    int scale = resultSetMetaData.getScale(columnIndex);\n    return converters.getStringConverter().getString(obj, columnType, columnSubType, scale);\n  }\n\n  @Override\n  public boolean getBoolean(int columnIndex) throws SFException {\n    logger.trace(\"boolean getBoolean(int columnIndex)\", false);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getBooleanConverter().getBoolean(getObjectInternal(columnIndex), columnType);\n  }\n\n  @Override\n  public byte getByte(int columnIndex) throws SFException {\n    logger.trace(\"short getByte(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    return converters.getNumberConverter().getByte(obj);\n  }\n\n  @Override\n  public Period getPeriod(int columnIndex) throws SFException {\n    logger.trace(\"Period getPeriod(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getPeriod(obj, columnType);\n  }\n\n  @Override\n  public Duration getDuration(int columnIndex) throws SFException {\n    logger.trace(\"Duration getDuration(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getDuration(obj, columnType);\n  }\n\n  @Override\n  public short getShort(int columnIndex) throws SFException {\n    logger.trace(\"short getShort(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getShort(obj, columnType);\n  }\n\n  @Override\n  public int getInt(int columnIndex) throws SFException {\n    logger.trace(\"int getInt(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getInt(obj, columnType);\n  }\n\n  @Override\n  public long getLong(int columnIndex) throws SFException {\n    logger.trace(\"long getLong(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getLong(obj, columnType);\n  }\n\n  @Override\n  public BigDecimal getBigDecimal(int columnIndex) throws SFException {\n    logger.trace(\"BigDecimal getBigDecimal(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getBigDecimal(obj, columnType);\n  }\n\n  @Override\n  public BigDecimal getBigDecimal(int columnIndex, int scale) throws SFException {\n    logger.trace(\"BigDecimal getBigDecimal(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getBigDecimal(obj, columnType, scale);\n  }\n\n  @Override\n  public Time getTime(int columnIndex) throws SFException {\n    logger.trace(\"Time getTime(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex);\n    int scale = resultSetMetaData.getScale(columnIndex);\n    return converters\n        .getDateTimeConverter()\n        .getTime(obj, columnType, columnSubType, TimeZone.getDefault(), scale);\n  }\n\n  @Override\n  public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SFException {\n    logger.trace(\"Timestamp getTimestamp(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex);\n    int scale = resultSetMetaData.getScale(columnIndex);\n    return converters\n        .getDateTimeConverter()\n        .getTimestamp(obj, columnType, columnSubType, tz, scale);\n  }\n\n  @Override\n  public float getFloat(int columnIndex) throws SFException {\n    logger.trace(\"float getFloat(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getFloat(obj, columnType);\n  }\n\n  @Override\n  public double getDouble(int columnIndex) throws SFException {\n    logger.trace(\"double getDouble(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    return converters.getNumberConverter().getDouble(obj, columnType);\n  }\n\n  @Override\n  public byte[] getBytes(int columnIndex) throws SFException {\n    logger.trace(\"byte[] getBytes(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex);\n    int scale = resultSetMetaData.getScale(columnIndex);\n    return converters.getBytesConverter().getBytes(obj, columnType, columnSubType, scale);\n  }\n\n  public Date getDate(int columnIndex) throws SFException {\n    return getDate(columnIndex, TimeZone.getDefault());\n  }\n\n  @Override\n  public Date getDate(int columnIndex, TimeZone tz) throws SFException {\n    logger.trace(\"Date getDate(int columnIndex)\", false);\n    Object obj = getObjectInternal(columnIndex);\n    int columnType = resultSetMetaData.getColumnType(columnIndex);\n    int columnSubType = resultSetMetaData.getInternalColumnType(columnIndex);\n    int scale = resultSetMetaData.getScale(columnIndex);\n    return converters.getDateTimeConverter().getDate(obj, columnType, columnSubType, tz, scale);\n  }\n\n  @Override\n  public SQLInput createSqlInputForColumn(\n      Object input,\n      Class<?> parentObjectClass,\n      int columnIndex,\n      SFBaseSession session,\n      List<FieldMetadata> fields) {\n    return createJsonSqlInputForColumn(input, session, fields);\n  }\n\n  @Override\n  public Date convertToDate(Object object, TimeZone tz) throws SFException {\n    return convertStringToDate((String) object, tz);\n  }\n\n  @Override\n  public Time convertToTime(Object object, int scale) throws SFException {\n    return convertStringToTime((String) object, scale);\n  }\n\n  @Override\n  public Timestamp convertToTimestamp(\n      Object object, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException {\n    return convertStringToTimestamp((String) object, columnType, columnSubType, tz, scale);\n  }\n\n  private Timestamp getTimestamp(int columnIndex) throws SFException {\n    return getTimestamp(columnIndex, TimeZone.getDefault());\n  }\n\n  @Override\n  public Converters getConverters() {\n    return converters;\n  }\n\n  private Object getSqlInput(String input, int columnIndex) throws SFException {\n    try {\n      JsonNode jsonNode = OBJECT_MAPPER.readTree(input);\n      return new JsonSqlInput(\n          input,\n          jsonNode,\n          session,\n          converters,\n          resultSetMetaData.getColumnFields(columnIndex),\n          sessionTimeZone);\n    } catch (JsonProcessingException e) {\n      throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFLoginInput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.security.PrivateKey;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.auth.wif.WorkloadIdentityAttestation;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.http.client.methods.HttpRequestBase;\n\n/** A class for holding all information required for login */\npublic class SFLoginInput {\n  private String serverUrl;\n  private String databaseName;\n  private String schemaName;\n  private String warehouse;\n  private String role;\n  private boolean validateDefaultParameters;\n  private String originalAuthenticator;\n  private String authenticator;\n  private String oktaUserName;\n  private String accountName;\n  private int loginTimeout = -1; // default is invalid\n  private int retryTimeout = 300;\n  private int authTimeout = 0;\n  private String userName;\n  private String password;\n  private boolean passcodeInPassword;\n  private String passcode;\n  private String token;\n  private Duration connectionTimeout = HttpUtil.getConnectionTimeout();\n  private Duration socketTimeout = HttpUtil.getSocketTimeout();\n  private String appId;\n  private String appVersion;\n  private String sessionToken;\n  private String masterToken;\n  private Map<String, Object> sessionParameters;\n  private PrivateKey privateKey;\n  private String application;\n  private String idToken;\n  private String mfaToken;\n  private String oauthAccessToken;\n  private String oauthRefreshToken;\n  private String dpopPublicKey;\n  private boolean dpopEnabled = false;\n  private String serviceName;\n  private OCSPMode ocspMode;\n  private HttpClientSettingsKey httpClientKey;\n  private String privateKeyFile;\n  private String privateKeyBase64;\n  private String privateKeyPwd;\n  private String inFlightCtx; // Opaque string sent for Snowsight account activation\n  private int platformDetectionTimeoutMs = 200; // Default 200ms timeout for platform detection\n  private boolean disablePlatformDetection = false; // Default false - platform detection enabled\n  private int maxRetryCount;\n\n  private SFOauthLoginInput oauthLoginInput;\n\n  private boolean disableConsoleLogin = true;\n  private boolean disableSamlURLCheck = false;\n  private boolean enableClientStoreTemporaryCredential;\n  private boolean enableClientRequestMfaToken;\n\n  // Workload Identity Federation\n  private String workloadIdentityProvider;\n  private WorkloadIdentityAttestation workloadIdentityAttestation;\n  private String workloadIdentityEntraResource;\n  private List<String> workloadIdentityImpersonationPath = Collections.emptyList();\n  private String workloadIdentityAwsExternalId;\n\n  // OAuth\n  private int redirectUriPort = -1;\n  private String clientId;\n  private String clientSecret;\n  private SessionUtilExternalBrowser.AuthExternalBrowserHandlers browserHandler =\n      new SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers();\n\n  private Duration browserResponseTimeout;\n\n  // Additional headers to add for Snowsight.\n  Map<String, String> additionalHttpHeadersForSnowsight;\n\n  public SFLoginInput() {}\n\n  public Duration getBrowserResponseTimeout() {\n    return browserResponseTimeout;\n  }\n\n  public SFLoginInput setBrowserResponseTimeout(Duration browserResponseTimeout) {\n    this.browserResponseTimeout = browserResponseTimeout;\n    return this;\n  }\n\n  public String getServerUrl() {\n    return serverUrl;\n  }\n\n  public SFLoginInput setServerUrl(String serverUrl) {\n    this.serverUrl = serverUrl;\n    return this;\n  }\n\n  public boolean getDisableConsoleLogin() {\n    return disableConsoleLogin;\n  }\n\n  SFLoginInput setDisableConsoleLogin(boolean disableConsoleLogin) {\n    this.disableConsoleLogin = disableConsoleLogin;\n    return this;\n  }\n\n  String getDatabaseName() {\n    return databaseName;\n  }\n\n  SFLoginInput setDatabaseName(String databaseName) {\n    this.databaseName = databaseName;\n    return this;\n  }\n\n  public String getSchemaName() {\n    return schemaName;\n  }\n\n  public SFLoginInput setSchemaName(String schemaName) {\n    this.schemaName = schemaName;\n    return this;\n  }\n\n  public String getWarehouse() {\n    return warehouse;\n  }\n\n  public SFLoginInput setWarehouse(String warehouse) {\n    this.warehouse = warehouse;\n    return this;\n  }\n\n  public String getRole() {\n    return role;\n  }\n\n  public SFLoginInput setRole(String role) {\n    this.role = role;\n    return this;\n  }\n\n  public boolean isValidateDefaultParameters() {\n    return validateDefaultParameters;\n  }\n\n  public SFLoginInput setValidateDefaultParameters(Object v) {\n    validateDefaultParameters = getBooleanValue(v);\n    return this;\n  }\n\n  public String getAuthenticator() {\n    return authenticator;\n  }\n\n  public SFLoginInput setAuthenticator(String authenticator) {\n    this.authenticator = authenticator;\n    return this;\n  }\n\n  public String getOKTAUserName() {\n    return oktaUserName;\n  }\n\n  public SFLoginInput setOKTAUserName(String oktaUserName) {\n    this.oktaUserName = oktaUserName;\n    return this;\n  }\n\n  public String getAccountName() {\n    return accountName;\n  }\n\n  public SFLoginInput setAccountName(String accountName) {\n    this.accountName = accountName;\n    return this;\n  }\n\n  public int getLoginTimeout() {\n    return loginTimeout;\n  }\n\n  // We want to choose the smaller of the two values between retryTimeout and loginTimeout for the\n  // new retry strategy.\n  SFLoginInput setLoginTimeout(int loginTimeout) {\n    if (loginTimeout > retryTimeout && retryTimeout != 0) {\n      this.loginTimeout = retryTimeout;\n    } else {\n      this.loginTimeout = loginTimeout;\n    }\n    return this;\n  }\n\n  int getRetryTimeout() {\n    return retryTimeout;\n  }\n\n  SFLoginInput setRetryTimeout(int retryTimeout) {\n    this.retryTimeout = retryTimeout;\n    return this;\n  }\n\n  public int getAuthTimeout() {\n    return authTimeout;\n  }\n\n  SFLoginInput setAuthTimeout(int authTimeout) {\n    this.authTimeout = authTimeout;\n    return this;\n  }\n\n  public String getUserName() {\n    return userName;\n  }\n\n  SFLoginInput setUserName(String userName) {\n    this.userName = userName;\n    return this;\n  }\n\n  public String getPassword() {\n    return password;\n  }\n\n  public SFLoginInput setPassword(String password) {\n    this.password = password;\n    return this;\n  }\n\n  String getPasscode() {\n    return passcode;\n  }\n\n  SFLoginInput setPasscode(String passcode) {\n    this.passcode = passcode;\n    return this;\n  }\n\n  public String getToken() {\n    return token;\n  }\n\n  public SFLoginInput setToken(String token) {\n    this.token = token;\n    return this;\n  }\n\n  int getConnectionTimeoutInMillis() {\n    return (int) connectionTimeout.toMillis();\n  }\n\n  SFLoginInput setConnectionTimeout(Duration connectionTimeout) {\n    this.connectionTimeout = connectionTimeout;\n    return this;\n  }\n\n  public int getSocketTimeoutInMillis() {\n    return (int) socketTimeout.toMillis();\n  }\n\n  public SFLoginInput setSocketTimeout(Duration socketTimeout) {\n    this.socketTimeout = socketTimeout;\n    return this;\n  }\n\n  boolean isPasscodeInPassword() {\n    return passcodeInPassword;\n  }\n\n  SFLoginInput setPasscodeInPassword(boolean passcodeInPassword) {\n    this.passcodeInPassword = passcodeInPassword;\n    return this;\n  }\n\n  String getAppId() {\n    return appId;\n  }\n\n  SFLoginInput setAppId(String appId) {\n    this.appId = appId;\n    return this;\n  }\n\n  String getAppVersion() {\n    return appVersion;\n  }\n\n  SFLoginInput setAppVersion(String appVersion) {\n    this.appVersion = appVersion;\n    return this;\n  }\n\n  public String getSessionToken() {\n    return sessionToken;\n  }\n\n  public SFLoginInput setSessionToken(String sessionToken) {\n    this.sessionToken = sessionToken;\n    return this;\n  }\n\n  String getMasterToken() {\n    return masterToken;\n  }\n\n  SFLoginInput setMasterToken(String masterToken) {\n    this.masterToken = masterToken;\n    return this;\n  }\n\n  String getIdToken() {\n    return idToken;\n  }\n\n  SFLoginInput setIdToken(String idToken) {\n    this.idToken = idToken;\n    return this;\n  }\n\n  String getMfaToken() {\n    return mfaToken;\n  }\n\n  SFLoginInput setMfaToken(String mfaToken) {\n    this.mfaToken = mfaToken;\n    return this;\n  }\n\n  String getOauthAccessToken() {\n    return oauthAccessToken;\n  }\n\n  SFLoginInput setOauthAccessToken(String oauthAccessToken) {\n    this.oauthAccessToken = oauthAccessToken;\n    return this;\n  }\n\n  public String getOauthRefreshToken() {\n    return oauthRefreshToken;\n  }\n\n  SFLoginInput setOauthRefreshToken(String oauthRefreshToken) {\n    this.oauthRefreshToken = oauthRefreshToken;\n    return this;\n  }\n\n  String getWorkloadIdentityProvider() {\n    return workloadIdentityProvider;\n  }\n\n  SFLoginInput setWorkloadIdentityProvider(String workloadIdentityProvider) {\n    this.workloadIdentityProvider = workloadIdentityProvider;\n    return this;\n  }\n\n  public String getDPoPPublicKey() {\n    return dpopPublicKey;\n  }\n\n  SFLoginInput setDPoPPublicKey(String dpopPublicKey) {\n    this.dpopPublicKey = dpopPublicKey;\n    return this;\n  }\n\n  public boolean isDPoPEnabled() {\n    return dpopEnabled;\n  }\n\n  // Currently only used for testing purpose\n  public void setDPoPEnabled(boolean dpopEnabled) {\n    this.dpopEnabled = dpopEnabled;\n  }\n\n  Map<String, Object> getSessionParameters() {\n    return sessionParameters;\n  }\n\n  SFLoginInput setSessionParameters(Map<String, Object> sessionParameters) {\n    this.sessionParameters = sessionParameters;\n    return this;\n  }\n\n  PrivateKey getPrivateKey() {\n    return privateKey;\n  }\n\n  SFLoginInput setPrivateKey(PrivateKey privateKey) {\n    this.privateKey = privateKey;\n    return this;\n  }\n\n  String getPrivateKeyBase64() {\n    return privateKeyBase64;\n  }\n\n  SFLoginInput setPrivateKeyBase64(String privateKeyBase64) {\n    this.privateKeyBase64 = privateKeyBase64;\n    return this;\n  }\n\n  SFLoginInput setPrivateKeyFile(String privateKeyFile) {\n    this.privateKeyFile = privateKeyFile;\n    return this;\n  }\n\n  SFLoginInput setPrivateKeyPwd(String privateKeyPwd) {\n    this.privateKeyPwd = privateKeyPwd;\n    return this;\n  }\n\n  String getPrivateKeyFile() {\n    return privateKeyFile;\n  }\n\n  String getPrivateKeyPwd() {\n    return privateKeyPwd;\n  }\n\n  boolean isPrivateKeyProvided() {\n    return (getPrivateKey() != null\n        || getPrivateKeyFile() != null\n        || getPrivateKeyBase64() != null);\n  }\n\n  public String getApplication() {\n    return application;\n  }\n\n  public SFLoginInput setApplication(String application) {\n    this.application = application;\n    return this;\n  }\n\n  String getServiceName() {\n    return serviceName;\n  }\n\n  SFLoginInput setServiceName(String serviceName) {\n    this.serviceName = serviceName;\n    return this;\n  }\n\n  OCSPMode getOCSPMode() {\n    return ocspMode;\n  }\n\n  SFLoginInput setOCSPMode(OCSPMode ocspMode) {\n    this.ocspMode = ocspMode;\n    return this;\n  }\n\n  public HttpClientSettingsKey getHttpClientSettingsKey() {\n    return httpClientKey;\n  }\n\n  public SFLoginInput setHttpClientSettingsKey(HttpClientSettingsKey key) {\n    this.httpClientKey = key;\n    return this;\n  }\n\n  // Opaque string sent for Snowsight account activation\n  String getInFlightCtx() {\n    return inFlightCtx;\n  }\n\n  // Opaque string sent for Snowsight account activation\n  SFLoginInput setInFlightCtx(String inFlightCtx) {\n    this.inFlightCtx = inFlightCtx;\n    return this;\n  }\n\n  boolean getDisableSamlURLCheck() {\n    return disableSamlURLCheck;\n  }\n\n  SFLoginInput setDisableSamlURLCheck(boolean disableSamlURLCheck) {\n    this.disableSamlURLCheck = disableSamlURLCheck;\n    return this;\n  }\n\n  public int getRedirectUriPort() {\n    return redirectUriPort;\n  }\n\n  public SFLoginInput setRedirectUriPort(int redirectUriPort) {\n    this.redirectUriPort = redirectUriPort;\n    return this;\n  }\n\n  public String getClientId() {\n    return clientId;\n  }\n\n  public SFLoginInput setClientId(String clientId) {\n    this.clientId = clientId;\n    return this;\n  }\n\n  public String getClientSecret() {\n    return clientSecret;\n  }\n\n  public SFLoginInput setClientSecret(String clientSecret) {\n    this.clientSecret = clientSecret;\n    return this;\n  }\n\n  Map<String, String> getAdditionalHttpHeadersForSnowsight() {\n    return additionalHttpHeadersForSnowsight;\n  }\n  /**\n   * Set additional http headers to apply to the outgoing request. The additional headers cannot be\n   * used to replace or overwrite a header in use by the driver. These will be applied to the\n   * outgoing request. Primarily used by Snowsight, as described in {@link\n   * HttpUtil#applyAdditionalHeadersForSnowsight(HttpRequestBase, Map)}\n   *\n   * @param additionalHttpHeaders The new headers to add\n   * @return The input object, for chaining\n   * @see HttpUtil#applyAdditionalHeadersForSnowsight(HttpRequestBase, Map)\n   */\n  public SFLoginInput setAdditionalHttpHeadersForSnowsight(\n      Map<String, String> additionalHttpHeaders) {\n    this.additionalHttpHeadersForSnowsight = additionalHttpHeaders;\n    return this;\n  }\n\n  static boolean getBooleanValue(Object v) {\n    if (v instanceof Boolean) {\n      return (Boolean) v;\n    } else if (v instanceof String) {\n      return !Boolean.FALSE.toString().equalsIgnoreCase((String) v)\n          && !\"off\".equalsIgnoreCase((String) v)\n          && (Boolean.TRUE.toString().equalsIgnoreCase((String) v)\n              || \"on\".equalsIgnoreCase((String) v));\n    }\n    return false;\n  }\n\n  String getHostFromServerUrl() throws SFException {\n    URL url;\n    try {\n      if (!serverUrl.startsWith(\"http\")) {\n        url = new URL(\"https://\" + serverUrl);\n      } else {\n        url = new URL(serverUrl);\n      }\n    } catch (MalformedURLException e) {\n      throw new SFException(\n          e, ErrorCode.INTERNAL_ERROR, \"Invalid serverUrl for retrieving host name\");\n    }\n    return url.getHost();\n  }\n\n  boolean isEnableClientStoreTemporaryCredential() {\n    return enableClientStoreTemporaryCredential;\n  }\n\n  SFLoginInput setEnableClientStoreTemporaryCredential(\n      boolean enableClientStoreTemporaryCredential) {\n    this.enableClientStoreTemporaryCredential = enableClientStoreTemporaryCredential;\n    return this;\n  }\n\n  boolean isEnableClientRequestMfaToken() {\n    return enableClientRequestMfaToken;\n  }\n\n  SFLoginInput setEnableClientRequestMfaToken(boolean enableClientRequestMfaToken) {\n    this.enableClientRequestMfaToken = enableClientRequestMfaToken;\n    return this;\n  }\n\n  public SFOauthLoginInput getOauthLoginInput() {\n    return oauthLoginInput;\n  }\n\n  public SFLoginInput setOauthLoginInput(SFOauthLoginInput oauthLoginInput) {\n    this.oauthLoginInput = oauthLoginInput;\n    return this;\n  }\n\n  void restoreOriginalAuthenticator() {\n    this.authenticator = this.originalAuthenticator;\n  }\n\n  String getOriginalAuthenticator() {\n    return this.originalAuthenticator;\n  }\n\n  SFLoginInput setOriginalAuthenticator(String originalAuthenticator) {\n    this.originalAuthenticator = originalAuthenticator;\n    return this;\n  }\n\n  public void setWorkloadIdentityAttestation(\n      WorkloadIdentityAttestation workloadIdentityAttestation) {\n    this.workloadIdentityAttestation = workloadIdentityAttestation;\n  }\n\n  public WorkloadIdentityAttestation getWorkloadIdentityAttestation() {\n    return workloadIdentityAttestation;\n  }\n\n  public String getWorkloadIdentityEntraResource() {\n    return this.workloadIdentityEntraResource;\n  }\n\n  public SFLoginInput setWorkloadIdentityEntraResource(String workloadIdentityEntraResource) {\n    this.workloadIdentityEntraResource = workloadIdentityEntraResource;\n    return this;\n  }\n\n  public List<String> getWorkloadIdentityImpersonationPath() {\n    return workloadIdentityImpersonationPath;\n  }\n\n  public SFLoginInput setWorkloadIdentityImpersonationPath(\n      String workloadIdentityImpersonationPath) {\n    if (!SnowflakeUtil.isNullOrEmpty(workloadIdentityImpersonationPath)) {\n      this.workloadIdentityImpersonationPath =\n          Arrays.stream(workloadIdentityImpersonationPath.split(\",\"))\n              .map(String::trim)\n              .filter(s -> !s.isEmpty())\n              .collect(Collectors.toList());\n    }\n    return this;\n  }\n\n  public String getWorkloadIdentityAwsExternalId() {\n    return workloadIdentityAwsExternalId;\n  }\n\n  public SFLoginInput setWorkloadIdentityAwsExternalId(String workloadIdentityAwsExternalId) {\n    this.workloadIdentityAwsExternalId = workloadIdentityAwsExternalId;\n    return this;\n  }\n\n  public SessionUtilExternalBrowser.AuthExternalBrowserHandlers getBrowserHandler() {\n    return browserHandler;\n  }\n\n  public void setBrowserHandler(\n      SessionUtilExternalBrowser.AuthExternalBrowserHandlers browserHandler) {\n    this.browserHandler = browserHandler;\n  }\n\n  public int getPlatformDetectionTimeoutMs() {\n    return platformDetectionTimeoutMs;\n  }\n\n  public SFLoginInput setPlatformDetectionTimeoutMs(int platformDetectionTimeoutMs) {\n    this.platformDetectionTimeoutMs = platformDetectionTimeoutMs;\n    return this;\n  }\n\n  public boolean isDisablePlatformDetection() {\n    return disablePlatformDetection;\n  }\n\n  public SFLoginInput setDisablePlatformDetection(boolean disablePlatformDetection) {\n    this.disablePlatformDetection = disablePlatformDetection;\n    return this;\n  }\n\n  public int getMaxRetryCount() {\n    return maxRetryCount;\n  }\n\n  public SFLoginInput setMaxRetryCount(int maxRetryCount) {\n    this.maxRetryCount = maxRetryCount;\n    return this;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFLoginOutput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.time.Duration;\nimport java.util.Map;\n\n/** Login output information including session tokens, database versions */\npublic class SFLoginOutput {\n  private String sessionToken;\n  private String masterToken;\n  private long masterTokenValidityInSeconds;\n  private String idToken;\n  private String mfaToken;\n  private String oauthAccessToken;\n  private String oauthRefreshToken;\n  private String databaseVersion;\n  private int databaseMajorVersion;\n  private int databaseMinorVersion;\n  private Duration httpClientSocketTimeout;\n  private Duration httpClientConnectionTimeout;\n  private String sessionDatabase;\n  private String sessionSchema;\n  private String sessionRole;\n  private String sessionWarehouse;\n  private Map<String, Object> commonParams;\n  private Map<String, String> loginResponseHeaders;\n\n  private String sessionId;\n\n  SFLoginOutput() {}\n\n  SFLoginOutput(\n      String sessionToken,\n      String masterToken,\n      long masterTokenValidityInSeconds,\n      String idToken,\n      String mfaToken,\n      String oauthAccessToken,\n      String oauthRefreshToken,\n      String databaseVersion,\n      int databaseMajorVersion,\n      int databaseMinorVersion,\n      int httpClientSocketTimeout,\n      int httpClientConnectionTimeout,\n      String sessionDatabase,\n      String sessionSchema,\n      String sessionRole,\n      String sessionWarehouse,\n      String sessionId,\n      Map<String, Object> commonParams,\n      Map<String, String> loginResponseHeaders) {\n    this.sessionToken = sessionToken;\n    this.masterToken = masterToken;\n    this.idToken = idToken;\n    this.mfaToken = mfaToken;\n    this.oauthAccessToken = oauthAccessToken;\n    this.oauthRefreshToken = oauthRefreshToken;\n    this.databaseVersion = databaseVersion;\n    this.databaseMajorVersion = databaseMajorVersion;\n    this.databaseMinorVersion = databaseMinorVersion;\n    this.httpClientSocketTimeout = Duration.ofMillis(httpClientSocketTimeout);\n    this.httpClientConnectionTimeout = Duration.ofMillis(httpClientConnectionTimeout);\n    this.sessionDatabase = sessionDatabase;\n    this.sessionSchema = sessionSchema;\n    this.sessionRole = sessionRole;\n    this.sessionWarehouse = sessionWarehouse;\n    this.commonParams = commonParams;\n    this.masterTokenValidityInSeconds = masterTokenValidityInSeconds;\n    this.sessionId = sessionId;\n    this.loginResponseHeaders = loginResponseHeaders;\n  }\n\n  public boolean getAutoCommit() {\n    return (Boolean) this.commonParams.get(\"AUTOCOMMIT\");\n  }\n\n  public String getSessionId() {\n    return sessionId;\n  }\n\n  public String getSessionToken() {\n    return sessionToken;\n  }\n\n  public SFLoginOutput setSessionToken(String sessionToken) {\n    this.sessionToken = sessionToken;\n    return this;\n  }\n\n  String getMasterToken() {\n    return masterToken;\n  }\n\n  SFLoginOutput setMasterToken(String masterToken) {\n    this.masterToken = masterToken;\n    return this;\n  }\n\n  String getIdToken() {\n    return idToken;\n  }\n\n  String getMfaToken() {\n    return mfaToken;\n  }\n\n  String getOauthAccessToken() {\n    return oauthAccessToken;\n  }\n\n  String getOauthRefreshToken() {\n    return oauthRefreshToken;\n  }\n\n  String getDatabaseVersion() {\n    return databaseVersion;\n  }\n\n  int getDatabaseMajorVersion() {\n    return databaseMajorVersion;\n  }\n\n  int getDatabaseMinorVersion() {\n    return databaseMinorVersion;\n  }\n\n  Duration getHttpClientSocketTimeout() {\n    return httpClientSocketTimeout;\n  }\n\n  Duration getHttpClientConnectionTimeout() {\n    return httpClientConnectionTimeout;\n  }\n\n  Map<String, Object> getCommonParams() {\n    return commonParams;\n  }\n\n  String getSessionDatabase() {\n    return sessionDatabase;\n  }\n\n  String getSessionSchema() {\n    return sessionSchema;\n  }\n\n  String getSessionRole() {\n    return sessionRole;\n  }\n\n  String getSessionWarehouse() {\n    return sessionWarehouse;\n  }\n\n  long getMasterTokenValidityInSeconds() {\n    return masterTokenValidityInSeconds;\n  }\n\n  public Map<String, String> getLoginResponseHeaders() {\n    return loginResponseHeaders;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFOCSPException.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport net.snowflake.client.internal.jdbc.OCSPErrorCode;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SFOCSPException extends Throwable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFOCSPException.class);\n\n  private static final long serialVersionUID = 1L;\n\n  private final OCSPErrorCode errorCode;\n\n  public SFOCSPException(OCSPErrorCode errorCode, String errorMsg) {\n    this(errorCode, errorMsg, null);\n  }\n\n  public SFOCSPException(OCSPErrorCode errorCode, String errorMsg, Throwable cause) {\n    super(errorMsg);\n    this.errorCode = errorCode;\n    if (cause != null) {\n      this.initCause(cause);\n    }\n  }\n\n  public OCSPErrorCode getErrorCode() {\n    return errorCode;\n  }\n\n  @Override\n  public String toString() {\n    return super.toString()\n        + (getErrorCode() != null ? \", errorCode = \" + getErrorCode() : \"\")\n        + (getMessage() != null ? \", errorMsg = \" + getMessage() : \"\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFOauthLoginInput.java",
    "content": "package net.snowflake.client.internal.core;\n\npublic class SFOauthLoginInput {\n\n  private static final String LOCAL_APPLICATION_CLIENT_CREDENTIAL = \"LOCAL_APPLICATION\";\n\n  private String clientId;\n  private String clientSecret;\n  private final String redirectUri;\n  private final String authorizationUrl;\n  private final String tokenRequestUrl;\n  private final String scope;\n  private final boolean enableSingleUseRefreshTokens;\n\n  public SFOauthLoginInput(\n      String clientId,\n      String clientSecret,\n      String redirectUri,\n      String authorizationUrl,\n      String tokenRequestUrl,\n      String scope) {\n    this(clientId, clientSecret, redirectUri, authorizationUrl, tokenRequestUrl, scope, false);\n  }\n\n  public SFOauthLoginInput(\n      String clientId,\n      String clientSecret,\n      String redirectUri,\n      String authorizationUrl,\n      String tokenRequestUrl,\n      String scope,\n      boolean enableSingleUseRefreshTokens) {\n    this.redirectUri = redirectUri;\n    this.clientId = clientId;\n    this.clientSecret = clientSecret;\n    this.authorizationUrl = authorizationUrl;\n    this.tokenRequestUrl = tokenRequestUrl;\n    this.scope = scope;\n    this.enableSingleUseRefreshTokens = enableSingleUseRefreshTokens;\n  }\n\n  public String getRedirectUri() {\n    return redirectUri;\n  }\n\n  public String getClientId() {\n    return clientId;\n  }\n\n  public String getClientSecret() {\n    return clientSecret;\n  }\n\n  public String getAuthorizationUrl() {\n    return authorizationUrl;\n  }\n\n  public String getTokenRequestUrl() {\n    return tokenRequestUrl;\n  }\n\n  public String getScope() {\n    return scope;\n  }\n\n  public boolean getEnableSingleUseRefreshTokens() {\n    return enableSingleUseRefreshTokens;\n  }\n\n  public void setLocalApplicationClientCredential() {\n    this.clientId = LOCAL_APPLICATION_CLIENT_CREDENTIAL;\n    this.clientSecret = LOCAL_APPLICATION_CLIENT_CREDENTIAL;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFPreparedStatementMetaData.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.common.core.SqlState;\n\n/** Statement metadata which includes the result metadata and bind information. */\npublic class SFPreparedStatementMetaData {\n  // result metadata\n  private SFResultSetMetaData resultSetMetaData;\n\n  // number of binds\n  private int numberOfBinds;\n\n  private final SFStatementType statementType;\n\n  private final boolean arrayBindSupported;\n\n  private List<MetaDataOfBinds> metaDataOfBinds;\n\n  private final boolean isValidMetaData;\n\n  public SFPreparedStatementMetaData(\n      SFResultSetMetaData resultSetMetaData,\n      SFStatementType statementType,\n      int numberOfBinds,\n      boolean arrayBindSupported,\n      List<MetaDataOfBinds> metaDataOfBinds,\n      boolean isValidMetaData) {\n    this.resultSetMetaData = resultSetMetaData;\n    this.statementType = statementType;\n    this.numberOfBinds = numberOfBinds;\n    this.arrayBindSupported = arrayBindSupported;\n    this.metaDataOfBinds = metaDataOfBinds;\n    this.isValidMetaData = isValidMetaData;\n  }\n\n  public SFResultSetMetaData getResultSetMetaData() {\n    return resultSetMetaData;\n  }\n\n  public void setResultSetMetaData(SFResultSetMetaData resultSetMetaData) {\n    this.resultSetMetaData = resultSetMetaData;\n  }\n\n  public int getNumberOfBinds() {\n    return numberOfBinds;\n  }\n\n  public MetaDataOfBinds getMetaDataForBindParam(int param) throws SQLException {\n    if (param < 1 || param > numberOfBinds) {\n      throw new SnowflakeSQLException(\n          SqlState.NUMERIC_VALUE_OUT_OF_RANGE,\n          ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE.getMessageCode(),\n          param,\n          numberOfBinds);\n    }\n    if (numberOfBinds != metaDataOfBinds.size() || metaDataOfBinds.size() == 0) {\n      throw new SnowflakeSQLException(\n          ((String) null), SqlState.NO_DATA, ErrorCode.NO_VALID_DATA.getMessageCode());\n    }\n    return metaDataOfBinds.get(param - 1);\n  }\n\n  public void setNumberOfBinds(int numberOfBinds) {\n    this.numberOfBinds = numberOfBinds;\n  }\n\n  /**\n   * Is a valid metadata or not. If true, this object is a valid metadata from describe. If false, a\n   * dummy/empty metadata generated because prepare statement fails.\n   *\n   * <p>This is used to determine if the content is valid or not, e.g., number of bind parameters.\n   *\n   * @return true or false\n   */\n  public boolean isValidMetaData() {\n    return isValidMetaData;\n  }\n\n  /**\n   * According to StatementType, to decide whether array binds supported or not\n   *\n   * <p>Currently, only INSERT supports array bind\n   *\n   * @return true if array binds is supported.\n   */\n  public boolean isArrayBindSupported() {\n    return this.arrayBindSupported;\n  }\n\n  public SFStatementType getStatementType() {\n    return this.statementType;\n  }\n\n  /**\n   * Generates an empty/invalid metadata for placeholder.\n   *\n   * @return statement metadata\n   */\n  public static SFPreparedStatementMetaData emptyMetaData() {\n    return new SFPreparedStatementMetaData(\n        new SFResultSetMetaData(\n            0,\n            Collections.<String>emptyList(),\n            Collections.<String>emptyList(),\n            Collections.<Integer>emptyList(),\n            null),\n        SFStatementType.UNKNOWN,\n        0,\n        false,\n        new ArrayList<>(),\n        false); // invalid metadata\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFPubKeysInternal.java",
    "content": "\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFResultSet.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.StmtUtil.eventHandler;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.sql.SQLException;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.BasicEvent.QueryState;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.JsonResultChunk;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultChunk;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\n/** Snowflake ResultSet implementation */\npublic class SFResultSet extends SFJsonResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFResultSet.class);\n\n  private int columnCount = 0;\n\n  private int currentChunkRowCount = 0;\n\n  private int currentChunkRowIndex = -1;\n\n  private JsonNode firstChunkRowset = null;\n\n  private JsonResultChunk currentChunk = null;\n\n  private String queryId;\n\n  private SFStatementType statementType;\n\n  private boolean totalRowCountTruncated;\n\n  private boolean sortResult = false;\n\n  private Object[][] firstChunkSortedRowSet;\n\n  // time the first chunk is consumed at (timestamp taken at object creation)\n  private final long firstChunkTime;\n\n  private long chunkCount = 0;\n\n  private long nextChunkIndex = 0;\n\n  private ChunkDownloader chunkDownloader;\n\n  protected SFBaseStatement statement;\n\n  private final boolean arrayBindSupported;\n\n  private Telemetry telemetryClient;\n\n  // If customer wants Timestamp_NTZ values to be stored in UTC time\n  // instead of a local/session timezone, set to true\n  private boolean treatNTZAsUTC;\n\n  private boolean formatDateWithTimezone;\n\n  /**\n   * Constructor takes a result from the API response that we get from executing a SQL statement.\n   *\n   * <p>The constructor will initialize the ResultSetMetaData.\n   *\n   * @param resultSetSerializable result data after parsing\n   * @param statement statement object\n   * @param sortResult true if sort results otherwise false\n   * @throws SQLException exception raised from general SQL layers\n   */\n  public SFResultSet(\n      SnowflakeResultSetSerializableV1 resultSetSerializable,\n      SFBaseStatement statement,\n      boolean sortResult)\n      throws SQLException {\n    this(\n        resultSetSerializable,\n        statement.getSFBaseSession(internalCallMarker()),\n        statement.getSFBaseSession(internalCallMarker()).getTelemetryClient(internalCallMarker()),\n        sortResult);\n\n    this.statement = statement;\n    SFBaseSession session = statement.getSFBaseSession(internalCallMarker());\n    session.setDatabase(resultSetSerializable.getFinalDatabaseName());\n    session.setSchema(resultSetSerializable.getFinalSchemaName());\n    session.setRole(resultSetSerializable.getFinalRoleName());\n    session.setWarehouse(resultSetSerializable.getFinalWarehouseName());\n    this.treatNTZAsUTC = resultSetSerializable.getTreatNTZAsUTC();\n    this.formatDateWithTimezone = resultSetSerializable.getFormatDateWithTimeZone();\n\n    // update the driver/session with common parameters from GS\n    SessionUtil.updateSfDriverParamValues(\n        this.parameters, statement.getSFBaseSession(internalCallMarker()));\n\n    // if server gives a send time, log time it took to arrive\n    if (resultSetSerializable.getSendResultTime() != 0) {\n      long timeConsumeFirstResult = this.firstChunkTime - resultSetSerializable.getSendResultTime();\n      logMetric(TelemetryField.TIME_CONSUME_FIRST_RESULT, timeConsumeFirstResult);\n    }\n\n    eventHandler.triggerStateTransition(\n        BasicEvent.QueryState.CONSUMING_RESULT,\n        String.format(QueryState.CONSUMING_RESULT.getArgString(), queryId, 0));\n  }\n\n  /**\n   * This is a minimum initialization for SFResultSet. Mainly used for testing purpose. However,\n   * real prod constructor will call this constructor as well\n   *\n   * @param resultSetSerializable data returned in query response\n   * @param telemetryClient telemetryClient\n   * @param sortResult should sorting take place\n   * @throws SQLException if exception is encountered\n   */\n  public SFResultSet(\n      SnowflakeResultSetSerializableV1 resultSetSerializable,\n      Telemetry telemetryClient,\n      boolean sortResult)\n      throws SQLException {\n    this(resultSetSerializable, new SFSession(), telemetryClient, sortResult);\n  }\n\n  /**\n   * This is a minimum initialization for SFResultSet. Mainly used for testing purpose. However,\n   * real prod constructor will call this constructor as well\n   *\n   * @param resultSetSerializable data returned in query response\n   * @param session snowflake session\n   * @param telemetryClient telemetryClient\n   * @param sortResult should sorting take place\n   * @throws SQLException if an exception is encountered.\n   */\n  public SFResultSet(\n      SnowflakeResultSetSerializableV1 resultSetSerializable,\n      SFBaseSession session,\n      Telemetry telemetryClient,\n      boolean sortResult)\n      throws SQLException {\n    super(resultSetSerializable.getTimeZone(), new Converters(session, resultSetSerializable));\n    this.resultSetSerializable = resultSetSerializable;\n    this.columnCount = 0;\n    this.sortResult = sortResult;\n    this.firstChunkTime = System.currentTimeMillis();\n\n    this.telemetryClient = telemetryClient;\n    this.queryId = resultSetSerializable.getQueryId();\n    this.statementType = resultSetSerializable.getStatementType();\n    this.totalRowCountTruncated = resultSetSerializable.isTotalRowCountTruncated();\n    this.parameters = resultSetSerializable.getParameters();\n    this.columnCount = resultSetSerializable.getColumnCount();\n    this.firstChunkRowset = resultSetSerializable.getAndClearFirstChunkRowset();\n    this.currentChunkRowCount = resultSetSerializable.getFirstChunkRowCount();\n    this.chunkCount = resultSetSerializable.getChunkFileCount();\n    this.chunkDownloader = resultSetSerializable.getChunkDownloader();\n    this.timestampNTZFormatter = resultSetSerializable.getTimestampNTZFormatter();\n    this.timestampLTZFormatter = resultSetSerializable.getTimestampLTZFormatter();\n    this.timestampTZFormatter = resultSetSerializable.getTimestampTZFormatter();\n    this.dateFormatter = resultSetSerializable.getDateFormatter();\n    this.timeFormatter = resultSetSerializable.getTimeFormatter();\n    this.honorClientTZForTimestampNTZ = resultSetSerializable.isHonorClientTZForTimestampNTZ();\n    this.binaryFormatter = resultSetSerializable.getBinaryFormatter();\n    this.resultVersion = resultSetSerializable.getResultVersion();\n    this.numberOfBinds = resultSetSerializable.getNumberOfBinds();\n    this.arrayBindSupported = resultSetSerializable.isArrayBindSupported();\n    this.metaDataOfBinds = resultSetSerializable.getMetaDataOfBinds();\n    this.resultSetMetaData = resultSetSerializable.getSFResultSetMetaData(internalCallMarker());\n    this.treatNTZAsUTC = resultSetSerializable.getTreatNTZAsUTC();\n    this.formatDateWithTimezone = resultSetSerializable.getFormatDateWithTimeZone();\n\n    // sort result set if needed\n    if (sortResult) {\n      // we don't support sort result when there are offline chunks\n      if (chunkCount > 0) {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            ErrorCode.CLIENT_SIDE_SORTING_NOT_SUPPORTED.getMessageCode(),\n            SqlState.FEATURE_NOT_SUPPORTED);\n      }\n\n      sortResultSet();\n    }\n  }\n\n  private boolean fetchNextRow() throws SFException, SnowflakeSQLException {\n    if (sortResult) {\n      return fetchNextRowSorted();\n    } else {\n      return fetchNextRowUnsorted();\n    }\n  }\n\n  private boolean fetchNextRowSorted() {\n    currentChunkRowIndex++;\n\n    if (currentChunkRowIndex < currentChunkRowCount) {\n      return true;\n    }\n\n    firstChunkSortedRowSet = null;\n\n    // no more chunks as sorted is only supported\n    // for one chunk\n    return false;\n  }\n\n  private boolean fetchNextRowUnsorted() throws SFException, SnowflakeSQLException {\n    currentChunkRowIndex++;\n\n    if (currentChunkRowIndex < currentChunkRowCount) {\n      return true;\n    }\n\n    // let GC collect first rowset\n    firstChunkRowset = null;\n\n    if (nextChunkIndex < chunkCount) {\n      try {\n        eventHandler.triggerStateTransition(\n            BasicEvent.QueryState.CONSUMING_RESULT,\n            String.format(QueryState.CONSUMING_RESULT.getArgString(), queryId, nextChunkIndex));\n\n        SnowflakeResultChunk nextChunk = chunkDownloader.getNextChunkToConsume();\n\n        if (nextChunk == null) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"Expect chunk but got null for chunk index \" + nextChunkIndex);\n        }\n\n        currentChunkRowIndex = 0;\n        currentChunkRowCount = nextChunk.getRowCount();\n        currentChunk = (JsonResultChunk) nextChunk;\n\n        logger.debug(\n            \"Moving to chunk index {}, row count={}\", nextChunkIndex, currentChunkRowCount);\n\n        nextChunkIndex++;\n\n        return true;\n      } catch (InterruptedException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryId, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n      }\n    } else if (chunkCount > 0) {\n      try {\n        logger.debug(\"End of chunks\", false);\n        DownloaderMetrics metrics = chunkDownloader.terminate();\n        logChunkDownloaderMetrics(metrics);\n      } catch (InterruptedException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryId, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n      }\n    }\n\n    return false;\n  }\n\n  private void logMetric(TelemetryField field, long value) {\n    TelemetryData data = TelemetryUtil.buildJobData(this.queryId, field, value);\n    this.telemetryClient.addLogToBatch(data);\n  }\n\n  private void logChunkDownloaderMetrics(DownloaderMetrics metrics) {\n    if (metrics != null) {\n      logMetric(TelemetryField.TIME_WAITING_FOR_CHUNKS, metrics.getMillisWaiting());\n      logMetric(TelemetryField.TIME_DOWNLOADING_CHUNKS, metrics.getMillisDownloading());\n      logMetric(TelemetryField.TIME_PARSING_CHUNKS, metrics.getMillisParsing());\n    }\n  }\n\n  /**\n   * Advance to next row\n   *\n   * @return true if next row exists, false otherwise\n   */\n  @Override\n  public boolean next() throws SFException, SnowflakeSQLException {\n    if (isClosed()) {\n      return false;\n    }\n\n    // otherwise try to fetch again\n    if (fetchNextRow()) {\n      row++;\n      if (isLast()) {\n        long timeConsumeLastResult = System.currentTimeMillis() - this.firstChunkTime;\n        logMetric(TelemetryField.TIME_CONSUME_LAST_RESULT, timeConsumeLastResult);\n      }\n      return true;\n    } else {\n      logger.debug(\"End of result\", false);\n\n      /*\n       * Here we check if the result has been truncated and throw exception if\n       * so.\n       */\n      if (totalRowCountTruncated\n          || Boolean.TRUE\n              .toString()\n              .equalsIgnoreCase(systemGetProperty(\"snowflake.enable_incident_test2\"))) {\n        throw new SFException(queryId, ErrorCode.MAX_RESULT_LIMIT_EXCEEDED);\n      }\n\n      // mark end of result\n      return false;\n    }\n  }\n\n  @Override\n  protected Object getObjectInternal(int columnIndex) throws SFException {\n    if (columnIndex <= 0 || columnIndex > resultSetMetaData.getColumnCount()) {\n      throw new SFException(queryId, ErrorCode.COLUMN_DOES_NOT_EXIST, columnIndex);\n    }\n\n    final int internalColumnIndex = columnIndex - 1;\n    Object retValue;\n    if (sortResult) {\n      retValue = firstChunkSortedRowSet[currentChunkRowIndex][internalColumnIndex];\n    } else if (firstChunkRowset != null) {\n      retValue =\n          JsonResultChunk.extractCell(firstChunkRowset, currentChunkRowIndex, internalColumnIndex);\n    } else if (currentChunk != null) {\n      retValue = currentChunk.getCell(currentChunkRowIndex, internalColumnIndex);\n    } else {\n      throw new SFException(queryId, ErrorCode.COLUMN_DOES_NOT_EXIST, columnIndex);\n    }\n    wasNull = retValue == null;\n    return retValue;\n  }\n\n  private void sortResultSet() {\n    // first fetch rows into firstChunkSortedRowSet\n    firstChunkSortedRowSet = new Object[currentChunkRowCount][];\n\n    for (int rowIdx = 0; rowIdx < currentChunkRowCount; rowIdx++) {\n      firstChunkSortedRowSet[rowIdx] = new Object[columnCount];\n      for (int colIdx = 0; colIdx < columnCount; colIdx++) {\n        firstChunkSortedRowSet[rowIdx][colIdx] =\n            JsonResultChunk.extractCell(firstChunkRowset, rowIdx, colIdx);\n      }\n    }\n\n    // now sort it\n    Arrays.sort(\n        firstChunkSortedRowSet,\n        new Comparator<Object[]>() {\n          public int compare(Object[] a, Object[] b) {\n            int numCols = a.length;\n\n            for (int colIdx = 0; colIdx < numCols; colIdx++) {\n              if (a[colIdx] == null && b[colIdx] == null) {\n                continue;\n              }\n\n              // null is considered bigger than all values\n              if (a[colIdx] == null) {\n                return 1;\n              }\n\n              if (b[colIdx] == null) {\n                return -1;\n              }\n\n              int res = a[colIdx].toString().compareTo(b[colIdx].toString());\n\n              // continue to next column if no difference\n              if (res == 0) {\n                continue;\n              }\n\n              return res;\n            }\n\n            // all columns are the same\n            return 0;\n          }\n        });\n  }\n\n  @Override\n  public boolean isLast() {\n    return nextChunkIndex == chunkCount && currentChunkRowIndex + 1 == currentChunkRowCount;\n  }\n\n  @Override\n  public boolean isAfterLast() {\n    return nextChunkIndex == chunkCount && currentChunkRowIndex >= currentChunkRowCount;\n  }\n\n  @Override\n  public void close() throws SnowflakeSQLException {\n    super.close();\n\n    try {\n      if (chunkDownloader != null) {\n        DownloaderMetrics metrics = chunkDownloader.terminate();\n        logChunkDownloaderMetrics(metrics);\n        firstChunkSortedRowSet = null;\n        firstChunkRowset = null;\n        currentChunk = null;\n      }\n    } catch (InterruptedException ex) {\n      throw new SnowflakeSQLLoggedException(\n          queryId, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n    }\n  }\n\n  @Override\n  public SFStatementType getStatementType() {\n    return statementType;\n  }\n\n  @Override\n  public void setStatementType(SFStatementType statementType) {\n    this.statementType = statementType;\n  }\n\n  @Override\n  public boolean isArrayBindSupported() {\n    return this.arrayBindSupported;\n  }\n\n  @Override\n  public String getQueryId() {\n    return queryId;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFResultSetFactory.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.sql.SQLException;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Factory class to create SFBaseResultSet class. Depending on result format, different instance\n * will be created\n */\nclass SFResultSetFactory {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFResultSetFactory.class);\n  /**\n   * Factory class used to generate ResultSet object according to query result format\n   *\n   * @param result raw response from server\n   * @param statement statement that created current resultset\n   * @param sortResult true if sort first chunk\n   * @return result set object\n   */\n  static SFBaseResultSet getResultSet(\n      JsonNode result,\n      SFStatement statement,\n      boolean sortResult,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException {\n\n    execTimeData.setProcessResultChunkStart();\n    SnowflakeResultSetSerializableV1 resultSetSerializable =\n        SnowflakeResultSetSerializableV1.create(\n            result,\n            statement.getSFBaseSession(internalCallMarker()),\n            statement,\n            internalCallMarker());\n    execTimeData.setProcessResultChunkEnd();\n    SFBaseResultSet rs;\n    execTimeData.setCreateResultSetStart();\n    switch (resultSetSerializable.getQueryResultFormat()) {\n      case ARROW:\n        logger.debug(\"Query result received in ARROW format. Processing with SFArrowResultSet.\");\n        rs =\n            new SFArrowResultSet(\n                resultSetSerializable,\n                statement.getSFBaseSession(internalCallMarker()),\n                statement,\n                sortResult);\n        break;\n      case JSON:\n        rs = new SFResultSet(resultSetSerializable, statement, sortResult);\n        break;\n      default:\n        rs = null;\n        break;\n    }\n    execTimeData.setCreateResultSetEnd();\n    if (rs == null) {\n      throw new SnowflakeSQLLoggedException(\n          statement.getSFBaseSession(internalCallMarker()),\n          ErrorCode.INTERNAL_ERROR,\n          \"Unsupported query result format: \"\n              + resultSetSerializable.getQueryResultFormat().name());\n    }\n    return rs;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFResultSetMetaData.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.Date;\nimport java.sql.ResultSetMetaData;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumnMetadata;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SFTime;\nimport net.snowflake.common.core.SFTimestamp;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\n/** Snowflake ResultSetMetaData */\npublic class SFResultSetMetaData {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFResultSetMetaData.class);\n\n  private int columnCount = 0;\n\n  private List<String> columnNames;\n\n  private List<String> columnTypeNames;\n\n  private List<Integer> columnTypes;\n\n  private List<Integer> precisions;\n\n  private List<Integer> dimensions;\n\n  private List<Integer> scales;\n\n  private List<Integer> nullables;\n\n  private List<String> columnSrcTables;\n\n  private List<String> columnSrcSchemas;\n\n  private List<String> columnSrcDatabases;\n\n  private List<Integer> columnDisplaySizes;\n\n  private List<SnowflakeColumnMetadata> columnMetadata = new ArrayList<>();\n  private String queryId;\n\n  private Map<String, Integer> columnNamePositionMap = new HashMap<>();\n\n  private Map<String, Integer> columnNameUpperCasePositionMap = new HashMap<>();\n\n  // For creating incidents\n  private SFBaseSession session;\n\n  // Date time formatter for calculating the display size\n  private SnowflakeDateTimeFormat timestampNTZFormatter;\n\n  private SnowflakeDateTimeFormat timestampLTZFormatter;\n\n  private SnowflakeDateTimeFormat timestampTZFormatter;\n\n  private SnowflakeDateTimeFormat timeFormatter;\n\n  private SnowflakeDateTimeFormat dateFormatter;\n\n  // provide default display size for databasemetadata result set.\n  // i.e. result set returned calling getTables etc\n  private int timestampNTZStringLength = 30;\n\n  private int timestampLTZStringLength = 30;\n\n  private int timestampTZStringLength = 30;\n\n  private int timeStringLength = 18;\n\n  private int dateStringLength = 10;\n\n  private boolean isResultColumnCaseInsensitive = false;\n\n  private List<Boolean> isAutoIncrementList;\n\n  public SFResultSetMetaData(\n      int columnCount,\n      List<String> columnNames,\n      List<String> columnTypeNames,\n      List<Integer> columnTypes,\n      SFBaseSession session) {\n    this.columnCount = columnCount;\n    this.columnNames = columnNames;\n    this.columnTypeNames = columnTypeNames;\n    this.columnTypes = columnTypes;\n    this.session = session;\n  }\n\n  public SFResultSetMetaData(\n      List<SnowflakeColumnMetadata> columnMetadata,\n      SFBaseSession session,\n      SnowflakeDateTimeFormat timestampNTZFormatter,\n      SnowflakeDateTimeFormat timestampLTZFormatter,\n      SnowflakeDateTimeFormat timestampTZFormatter,\n      SnowflakeDateTimeFormat dateFormatter,\n      SnowflakeDateTimeFormat timeFormatter) {\n    this(\n        columnMetadata,\n        \"none\",\n        session,\n        (session != null) && session.isResultColumnCaseInsensitive(),\n        timestampNTZFormatter,\n        timestampLTZFormatter,\n        timestampTZFormatter,\n        dateFormatter,\n        timeFormatter);\n  }\n\n  public SFResultSetMetaData(\n      List<SnowflakeColumnMetadata> columnMetadata,\n      String queryId,\n      SFBaseSession session,\n      boolean isResultColumnCaseInsensitive,\n      SnowflakeDateTimeFormat timestampNTZFormatter,\n      SnowflakeDateTimeFormat timestampLTZFormatter,\n      SnowflakeDateTimeFormat timestampTZFormatter,\n      SnowflakeDateTimeFormat dateFormatter,\n      SnowflakeDateTimeFormat timeFormatter) {\n    this.columnCount = columnMetadata.size();\n    this.columnMetadata = columnMetadata;\n    this.queryId = queryId;\n    this.timestampNTZFormatter = timestampNTZFormatter;\n    this.timestampLTZFormatter = timestampLTZFormatter;\n    this.timestampTZFormatter = timestampTZFormatter;\n    this.dateFormatter = dateFormatter;\n    this.timeFormatter = timeFormatter;\n    calculateDateTimeStringLength();\n\n    this.columnNames = new ArrayList<>(this.columnCount);\n    this.columnTypeNames = new ArrayList<>(this.columnCount);\n    this.columnTypes = new ArrayList<>(this.columnCount);\n    this.precisions = new ArrayList<>(this.columnCount);\n    this.dimensions = new ArrayList<>(this.columnCount);\n    this.scales = new ArrayList<>(this.columnCount);\n    this.nullables = new ArrayList<>(this.columnCount);\n    this.columnSrcDatabases = new ArrayList<>(this.columnCount);\n    this.columnSrcSchemas = new ArrayList<>(this.columnCount);\n    this.columnSrcTables = new ArrayList<>(this.columnCount);\n    this.columnDisplaySizes = new ArrayList<>(this.columnCount);\n    this.isAutoIncrementList = new ArrayList<>(this.columnCount);\n    this.isResultColumnCaseInsensitive = isResultColumnCaseInsensitive;\n\n    for (int colIdx = 0; colIdx < columnCount; colIdx++) {\n      columnNames.add(columnMetadata.get(colIdx).getName());\n      columnTypeNames.add(columnMetadata.get(colIdx).getTypeName());\n      precisions.add(calculatePrecision(columnMetadata.get(colIdx)));\n      dimensions.add(calculateDimension(columnMetadata.get(colIdx)));\n      columnTypes.add(columnMetadata.get(colIdx).getType());\n      scales.add(columnMetadata.get(colIdx).getScale());\n      nullables.add(\n          columnMetadata.get(colIdx).isNullable()\n              ? ResultSetMetaData.columnNullable\n              : ResultSetMetaData.columnNoNulls);\n      columnSrcDatabases.add(columnMetadata.get(colIdx).getColumnSrcDatabase());\n      columnSrcSchemas.add(columnMetadata.get(colIdx).getColumnSrcSchema());\n      columnSrcTables.add(columnMetadata.get(colIdx).getColumnSrcTable());\n      columnDisplaySizes.add(calculateDisplaySize(columnMetadata.get(colIdx)));\n      isAutoIncrementList.add(columnMetadata.get(colIdx).isAutoIncrement());\n    }\n\n    this.session = session;\n  }\n\n  private Integer calculatePrecision(SnowflakeColumnMetadata columnMetadata) {\n    int columnType = columnMetadata.getType();\n    switch (columnType) {\n      case Types.CHAR:\n      case Types.VARCHAR:\n      case Types.BINARY:\n        return columnMetadata.getLength();\n      case Types.INTEGER:\n      case Types.DECIMAL:\n      case Types.BIGINT:\n        return columnMetadata.getPrecision();\n      case Types.DATE:\n        return dateStringLength;\n      case Types.TIME:\n        return timeStringLength;\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ:\n        return timestampLTZStringLength;\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ:\n        return timestampTZStringLength;\n      case Types.TIMESTAMP:\n        return timestampNTZStringLength;\n        // for double and boolean\n        // Precision is not applicable hence return 0\n      default:\n        return 0;\n    }\n  }\n\n  private Integer calculateDimension(SnowflakeColumnMetadata columnMetadata) {\n    int columnType = columnMetadata.getType();\n    if (columnType == SnowflakeType.EXTRA_TYPES_VECTOR) {\n      return columnMetadata.getDimension();\n    }\n    return 0;\n  }\n\n  private Integer calculateDisplaySize(SnowflakeColumnMetadata columnMetadata) {\n    int columnType = columnMetadata.getType();\n    switch (columnType) {\n      case Types.CHAR:\n      case Types.VARCHAR:\n      case Types.BINARY:\n        return columnMetadata.getLength();\n      case Types.INTEGER:\n      case Types.BIGINT:\n        // + 1 because number can be negative, it could be -20 for number(2,0)\n        return columnMetadata.getPrecision() + 1;\n      case Types.DECIMAL:\n        // first + 1 because number can be negative, second + 1 because it always\n        // include decimal point.\n        // i.e. number(2, 1) could be -1.3\n        return columnMetadata.getPrecision() + 1 + 1;\n      case Types.DOUBLE:\n        // Hard code as 24 since the longest float\n        // represented in char is\n        // -2.2250738585072020E−308\n        return 24;\n      case Types.DATE:\n        return dateStringLength;\n      case Types.TIME:\n        return timeStringLength;\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ:\n        return timestampLTZStringLength;\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ:\n        return timestampTZStringLength;\n      case Types.TIMESTAMP:\n        return timestampNTZStringLength;\n      case Types.BOOLEAN:\n        // Hard code as 5 since the longest char to represent\n        // a boolean would be false, which is 5.\n        return 5;\n      default:\n        return 25;\n    }\n  }\n\n  private void calculateDateTimeStringLength() {\n    SFTimestamp ts =\n        SFTimestamp.fromMilliseconds(System.currentTimeMillis(), TimeZone.getDefault());\n    try {\n      if (timestampNTZFormatter != null) {\n        String tsNTZStr =\n            ResultUtil.getSFTimestampAsString(\n                ts,\n                Types.TIMESTAMP,\n                9,\n                timestampNTZFormatter,\n                timestampLTZFormatter,\n                timestampTZFormatter,\n                session);\n        timestampNTZStringLength = tsNTZStr.length();\n      }\n      if (timestampLTZFormatter != null) {\n        String tsLTZStr =\n            ResultUtil.getSFTimestampAsString(\n                ts,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                9,\n                timestampNTZFormatter,\n                timestampLTZFormatter,\n                timestampTZFormatter,\n                session);\n        timestampLTZStringLength = tsLTZStr.length();\n      }\n      if (timestampTZFormatter != null) {\n        String tsTZStr =\n            ResultUtil.getSFTimestampAsString(\n                ts,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                9,\n                timestampNTZFormatter,\n                timestampLTZFormatter,\n                timestampTZFormatter,\n                session);\n        timestampTZStringLength = tsTZStr.length();\n      }\n\n      SFTime time = SFTime.fromTimestamp(ts);\n      if (timeFormatter != null) {\n        timeStringLength = ResultUtil.getSFTimeAsString(time, 9, timeFormatter).length();\n      }\n      if (dateFormatter != null) {\n        final Calendar calendar = Calendar.getInstance();\n        calendar.set(2015, Calendar.DECEMBER, 11);\n        dateStringLength =\n            ResultUtil.getDateAsString(new Date(calendar.getTimeInMillis()), dateFormatter)\n                .length();\n      }\n    } catch (SFException e) {\n      logger.debug(\"Failed to calculate the display size. Use default one.\", false);\n    }\n  }\n\n  /**\n   * get the query id\n   *\n   * @return query id\n   */\n  public String getQueryId() {\n    return queryId;\n  }\n\n  /**\n   * get the session\n   *\n   * @return session object\n   */\n  public SFBaseSession getSession() {\n    return session;\n  }\n\n  /**\n   * Get the list of column names\n   *\n   * @return column names in list\n   */\n  public List<String> getColumnNames() {\n    return columnNames;\n  }\n\n  /**\n   * Get the index of the column by name\n   *\n   * @param columnName column name\n   * @return index of the column that names matches the column name\n   */\n  public int getColumnIndex(String columnName) {\n    columnName = isResultColumnCaseInsensitive ? columnName.toUpperCase() : columnName;\n    Map<String, Integer> nameToIndexMap =\n        isResultColumnCaseInsensitive ? columnNameUpperCasePositionMap : columnNamePositionMap;\n\n    if (nameToIndexMap.get(columnName) != null) {\n      return nameToIndexMap.get(columnName);\n    } else {\n      int columnIndex =\n          isResultColumnCaseInsensitive\n              ? ResultUtil.listSearchCaseInsensitive(columnNames, columnName)\n              : columnNames.indexOf(columnName);\n      nameToIndexMap.put(columnName, columnIndex);\n      return columnIndex;\n    }\n  }\n\n  /**\n   * Get number of columns\n   *\n   * @return column count\n   */\n  public int getColumnCount() {\n    return columnCount;\n  }\n\n  public int getColumnType(int column) throws SFException {\n    return ColumnTypeHelper.getColumnType(getInternalColumnType(column), session);\n  }\n\n  public int getInternalColumnType(int column) throws SFException {\n    int columnIdx = column - 1;\n    if (column < 1 || column > columnTypes.size()) {\n      throw new SFException(queryId, ErrorCode.COLUMN_DOES_NOT_EXIST, column);\n    }\n\n    if (columnTypes.get(columnIdx) == null) {\n      throw new SFException(\n          queryId, ErrorCode.INTERNAL_ERROR, \"Missing column type for column \" + column);\n    }\n\n    return columnTypes.get(columnIdx);\n  }\n\n  public String getColumnTypeName(int column) throws SFException {\n    if (column < 1 || column > columnTypeNames.size()) {\n      throw new SFException(queryId, ErrorCode.COLUMN_DOES_NOT_EXIST, column);\n    }\n\n    if (columnTypeNames.get(column - 1) == null) {\n      throw new SFException(\n          queryId, ErrorCode.INTERNAL_ERROR, \"Missing column type for column \" + column);\n    }\n\n    return columnTypeNames.get(column - 1);\n  }\n\n  public int getScale(int column) {\n    if (scales != null && scales.size() >= column) {\n      return scales.get(column - 1);\n    } else {\n      // TODO: fix this later to use different defaults for number or timestamp\n      return 9;\n    }\n  }\n\n  public int getPrecision(int column) {\n    if (precisions != null && precisions.size() >= column) {\n      return precisions.get(column - 1);\n    } else {\n      // TODO: fix this later to use different defaults for number or timestamp\n      return 9;\n    }\n  }\n\n  public int getDimension(int column) {\n    if (dimensions != null && dimensions.size() >= column && column > 0) {\n      return dimensions.get(column - 1);\n    } else {\n      return 0;\n    }\n  }\n\n  public boolean isSigned(int column) {\n    return (columnTypes.get(column - 1) == Types.INTEGER\n        || columnTypes.get(column - 1) == Types.DECIMAL\n        || columnTypes.get(column - 1) == Types.BIGINT\n        || columnTypes.get(column - 1) == Types.DOUBLE);\n  }\n\n  public String getColumnLabel(int column) {\n    if (columnNames != null) {\n      return columnNames.get(column - 1);\n    } else {\n      return \"C\" + Integer.toString(column - 1);\n    }\n  }\n\n  public String getColumnName(int column) {\n    if (columnNames != null) {\n      return columnNames.get(column - 1);\n    } else {\n      return \"C\" + Integer.toString(column - 1);\n    }\n  }\n\n  public int isNullable(int column) {\n    if (nullables != null) {\n      return nullables.get(column - 1);\n    } else {\n      return ResultSetMetaData.columnNullableUnknown;\n    }\n  }\n\n  public String getCatalogName(int column) {\n    if (columnSrcDatabases == null) {\n      return \"\";\n    }\n    return columnSrcDatabases.get(column - 1);\n  }\n\n  public String getSchemaName(int column) {\n    if (columnSrcDatabases == null) {\n      return \"\";\n    }\n    return columnSrcSchemas.get(column - 1);\n  }\n\n  public String getTableName(int column) {\n    if (columnSrcDatabases == null) {\n      return \"T\";\n    }\n    return columnSrcTables.get(column - 1);\n  }\n\n  public Integer getColumnDisplaySize(int column) {\n    if (columnDisplaySizes == null) {\n      return 25;\n    }\n    return columnDisplaySizes.get(column - 1);\n  }\n\n  public boolean getIsAutoIncrement(int column) {\n    if (isAutoIncrementList == null || isAutoIncrementList.size() == 0) {\n      return false;\n    }\n\n    return isAutoIncrementList.get(column - 1);\n  }\n\n  public List<Boolean> getIsAutoIncrementList() {\n    return isAutoIncrementList;\n  }\n\n  public List<FieldMetadata> getColumnFields(int column) throws SFException {\n    if (column < 1 || column > columnMetadata.size()) {\n      throw new SFException(queryId, ErrorCode.COLUMN_DOES_NOT_EXIST, column);\n    }\n\n    if (columnMetadata.get(column - 1) == null) {\n      throw new SFException(\n          queryId, ErrorCode.INTERNAL_ERROR, \"Missing column fields for column \" + column);\n    }\n\n    return columnMetadata.get(column - 1).getFields();\n  }\n\n  public boolean isStructuredTypeColumn(int columnIndex) {\n    return columnMetadata.get(columnIndex - 1).getFields() != null\n        && !columnMetadata.get(columnIndex - 1).getFields().isEmpty();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFSSLConnectionSocketFactory.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.IOException;\nimport java.net.Proxy;\nimport java.net.Socket;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLServerSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.conn.ssl.SSLConnectionSocketFactory;\nimport org.apache.http.protocol.HttpContext;\n\n/** Snowflake custom SSLConnectionSocketFactory */\npublic class SFSSLConnectionSocketFactory extends SSLConnectionSocketFactory {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SFSSLConnectionSocketFactory.class);\n  private static TlsVersion minTlsVersion = TlsVersion.TLS_1_2;\n  private static TlsVersion maxTlsVersion = TlsVersion.TLS_1_3;\n  private final boolean socksProxyDisabled;\n\n  public SFSSLConnectionSocketFactory(TrustManager[] trustManagers, boolean socksProxyDisabled)\n      throws NoSuchAlgorithmException, KeyManagementException {\n    super(\n        initSSLContext(trustManagers),\n        getSupportedTlsVersions(),\n        decideCipherSuites(),\n        SSLConnectionSocketFactory.getDefaultHostnameVerifier());\n    this.socksProxyDisabled = socksProxyDisabled;\n  }\n\n  private static String[] getSupportedTlsVersions() {\n    if (minTlsVersion.compareTo(maxTlsVersion) > 0) {\n      throw new IllegalArgumentException(\n          String.format(\n              \"Minimum TLS version %s cannot be greater than the maximum TLS version %s\",\n              minTlsVersion.getProtocolName(), maxTlsVersion.getProtocolName()));\n    }\n    List<String> supported =\n        Arrays.stream(TlsVersion.values())\n            .filter(TlsVersion::isAvailable)\n            .filter(v -> v.compareTo(minTlsVersion) >= 0)\n            .filter(v -> v.compareTo(maxTlsVersion) <= 0)\n            .map(TlsVersion::getProtocolName)\n            .collect(Collectors.toList());\n\n    if (supported.isEmpty()) {\n      throw new IllegalStateException(\n          String.format(\n              \"No TLS versions match constraints: min=%s, max=%s\",\n              minTlsVersion.getProtocolName(), maxTlsVersion.getProtocolName()));\n    }\n    return supported.toArray(new String[0]);\n  }\n\n  private static SSLContext initSSLContext(TrustManager[] trustManagers)\n      throws NoSuchAlgorithmException, KeyManagementException {\n    // Use generic TLS context to support multiple versions\n    SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n    sslContext.init(\n        null, // key manager\n        trustManagers, // trust manager\n        null); // secure random\n    return sslContext;\n  }\n\n  @Override\n  public Socket createSocket(HttpContext ctx) throws IOException {\n    return socksProxyDisabled ? new Socket(Proxy.NO_PROXY) : super.createSocket(ctx);\n  }\n\n  /**\n   * Decide cipher suites that will be passed into the SSLConnectionSocketFactory\n   *\n   * @return List of cipher suites.\n   */\n  private static String[] decideCipherSuites() {\n    String sysCipherSuites = systemGetProperty(\"https.cipherSuites\");\n\n    String[] cipherSuites =\n        sysCipherSuites != null\n            ? sysCipherSuites.split(\",\")\n            :\n            // use jdk default cipher suites\n            ((SSLServerSocketFactory) SSLServerSocketFactory.getDefault()).getDefaultCipherSuites();\n\n    // cipher suites need to be picked up in code explicitly for jdk 1.7\n    // https://stackoverflow.com/questions/44378970/\n    logger.trace(\"Cipher suites used: {}\", (ArgSupplier) () -> Arrays.toString(cipherSuites));\n\n    return cipherSuites;\n  }\n\n  public static void setMinTlsVersion(String minTlsVersion) {\n    logger.debug(\"Setting minimum TLS version to: {}\", minTlsVersion);\n    SFSSLConnectionSocketFactory.minTlsVersion = TlsVersion.fromString(minTlsVersion);\n  }\n\n  public static void setMaxTlsVersion(String maxTlsVersion) {\n    logger.debug(\"Setting maximum TLS version to: {}\", maxTlsVersion);\n    SFSSLConnectionSocketFactory.maxTlsVersion = TlsVersion.fromString(maxTlsVersion);\n  }\n\n  private enum TlsVersion {\n    TLS_1_2(\"TLSv1.2\"),\n    TLS_1_3(\"TLSv1.3\");\n\n    private final String protocolName;\n\n    TlsVersion(String protocolName) {\n      this.protocolName = protocolName;\n    }\n\n    String getProtocolName() {\n      return protocolName;\n    }\n\n    boolean isAvailable() {\n      try {\n        SSLContext.getInstance(this.protocolName);\n        return true;\n      } catch (NoSuchAlgorithmException e) {\n        logger.debug(\"TLS protocol {} is not available\", this.protocolName);\n        return false;\n      }\n    }\n\n    static TlsVersion fromString(String text) {\n      if (text == null) {\n        return null;\n      }\n      for (TlsVersion v : TlsVersion.values()) {\n        if (v.protocolName.equalsIgnoreCase(text)) {\n          return v;\n        }\n      }\n      throw new IllegalArgumentException(\"Unsupported TLS version: \" + text);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFSession.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SFLoginInput.getBooleanValue;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.recordIfExternal;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport java.security.PrivateKey;\nimport java.sql.DriverPropertyInfo;\nimport java.sql.SQLException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.logging.Level;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.internal.config.SFClientConfig;\nimport net.snowflake.client.internal.core.crl.CRLValidator;\nimport net.snowflake.client.internal.core.minicore.MinicoreTelemetry;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.DefaultSFConnectionHandler;\nimport net.snowflake.client.internal.jdbc.SnowflakeConnectString;\nimport net.snowflake.client.internal.jdbc.SnowflakeReauthenticationRequest;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.diagnostic.DiagnosticContext;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetry.NoOpTelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.JDK14Logger;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.log.SFLoggerUtil;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.HttpHeaders;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.utils.URIBuilder;\n\n/** Snowflake session implementation */\npublic class SFSession extends SFBaseSession {\n  public static final String SF_QUERY_REQUEST_ID = \"requestId\";\n  public static final String SF_HEADER_AUTHORIZATION = HttpHeaders.AUTHORIZATION;\n  public static final String SF_HEADER_SNOWFLAKE_AUTHTYPE = \"Snowflake\";\n  public static final String SF_HEADER_TOKEN_TAG = \"Token\";\n  private static final String SF_ENABLE_WIF_AWS_EXTERNAL_ID = \"SF_ENABLE_WIF_AWS_EXTERNAL_ID\";\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFSession.class);\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n  private static final String SF_PATH_SESSION_HEARTBEAT = \"/session/heartbeat\";\n  private static final String SF_PATH_QUERY_MONITOR = \"/monitoring/queries/\";\n  private static final int MAX_SESSION_PARAMETERS = 1000;\n  // this constant was public - let's not change it\n  public static final int DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT =\n      HttpUtil.DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT_IN_MS;\n  private final AtomicInteger sequenceId = new AtomicInteger(0);\n  private final List<DriverPropertyInfo> missingProperties = new ArrayList<>();\n  // list of active asynchronous queries. Used to see if session should be closed when connection\n  // closes\n  private Set<String> activeAsyncQueries = ConcurrentHashMap.newKeySet();\n  private boolean isClosed = true;\n  private String sessionToken;\n  private String masterToken;\n  private long masterTokenValidityInSeconds;\n  private String idToken;\n  private String mfaToken;\n  private String oauthAccessToken;\n  private String oauthRefreshToken;\n  private String privateKeyFileLocation;\n  private String privateKeyBase64;\n  private String privateKeyPassword;\n  private PrivateKey privateKey;\n\n  private SFClientConfig sfClientConfig;\n\n  /**\n   * Amount of seconds a user is willing to tolerate for establishing the connection with database.\n   * In our case, it means the first login request to get authorization token.\n   *\n   * <p>Default:300 seconds\n   */\n  private int loginTimeout = 300;\n\n  /**\n   * Amount of milliseconds a user is willing to tolerate for network related issues (e.g. HTTP\n   * 503/504) or database transient issues (e.g. GS not responding)\n   *\n   * <p>A value of 0 means no timeout\n   *\n   * <p>Default: 0\n   */\n  private int networkTimeoutInMilli = 0; // in milliseconds\n\n  @Deprecated private int authTimeout = 0;\n  private boolean enableCombineDescribe = false;\n  private Duration httpClientConnectionTimeout = HttpUtil.getConnectionTimeout();\n  private Duration httpClientSocketTimeout = HttpUtil.getSocketTimeout();\n  // whether we try to simulate a socket timeout (a default value of 0 means\n  // no simulation). The value is in milliseconds\n  private int injectSocketTimeout = 0;\n  // simulate client pause after initial execute and before first get-result\n  // call ( a default value of 0 means no pause). The value is in seconds\n  private int injectClientPause = 0;\n  // session parameters\n  private Map<String, Object> sessionParametersMap = new HashMap<>();\n  private boolean passcodeInPassword = false;\n\n  // deprecated\n  private Level tracingLevel = Level.INFO;\n  // client to log session metrics to telemetry in GS\n  private Telemetry telemetryClient;\n  private SnowflakeConnectString sfConnStr;\n  // The cache of query context sent from Cloud Service.\n  QueryContextCache qcc;\n\n  // Max retries for outgoing http requests.\n  private int maxHttpRetries = 7;\n\n  /**\n   * Retry timeout in seconds. Cannot be less than 300.\n   *\n   * <p>Default: 300\n   */\n  private int retryTimeout = 300;\n\n  private int defaultPlatformDetectionTimeoutMs = 200;\n\n  private boolean enableClientStoreTemporaryCredential = true;\n  private boolean enableClientRequestMfaToken = true;\n\n  /**\n   * Max timeout for external browser authentication in seconds\n   *\n   * <p>Default: 120\n   */\n  private Duration browserResponseTimeout = Duration.ofSeconds(120);\n\n  private boolean javaUtilLoggingConsoleOut = false;\n  private String javaUtilLoggingConsoleOutThreshold = null;\n\n  private List<HttpHeadersCustomizer> httpHeadersCustomizers;\n\n  // This constructor is used only by tests with no real connection.\n  // For real connections, the other constructor is always used.\n  @VisibleForTesting\n  public SFSession() {\n    this(new DefaultSFConnectionHandler(null));\n  }\n\n  public SFSession(DefaultSFConnectionHandler sfConnectionHandler) {\n    super(sfConnectionHandler);\n  }\n\n  /**\n   * Function that checks if the active session can be closed when the connection is closed. If\n   * there are active asynchronous queries running, the session should stay open even if the\n   * connection closes so that the queries can finish running.\n   *\n   * @return true if it is safe to close this session, false if not\n   */\n  @Override\n  public boolean isSafeToClose() {\n    boolean canClose = true;\n    // if the set of asynchronous queries is empty, return true\n    if (this.activeAsyncQueries.isEmpty()) {\n      return canClose;\n    }\n    // if the set is not empty, iterate through each query and check its status\n    for (String query : this.activeAsyncQueries) {\n      try {\n        QueryStatus queryStatus = getQueryStatus(query);\n        //  if any query is still running, it is not safe to close.\n        if (queryStatus.isStillRunning()) {\n          canClose = false;\n        }\n      } catch (SQLException e) {\n        logger.error(e.getMessage(), true);\n      }\n    }\n    return canClose;\n  }\n\n  /**\n   * Add async query to list of active async queries based on its query ID\n   *\n   * @param queryID query ID\n   */\n  public void addQueryToActiveQueryList(String queryID) {\n    activeAsyncQueries.add(queryID);\n  }\n\n  private JsonNode getQueryMetadata(String queryID) throws SQLException {\n    // create the URL to check the query monitoring endpoint\n    String statusUrl = \"\";\n    String sessionUrl = getUrl();\n    if (sessionUrl.endsWith(\"/\")) {\n      statusUrl =\n          sessionUrl.substring(0, sessionUrl.length() - 1) + SF_PATH_QUERY_MONITOR + queryID;\n    } else {\n      statusUrl = sessionUrl + SF_PATH_QUERY_MONITOR + queryID;\n    }\n    // Create a new HTTP GET object and set appropriate headers\n\n    HttpGet get = new HttpGet(statusUrl);\n    String response = null;\n    JsonNode jsonNode = null;\n    boolean sessionRenewed;\n    // Do this while the session hasn't been renewed\n    do {\n      sessionRenewed = false;\n      try {\n        get.setHeader(\"Content-type\", \"application/json\");\n        get.setHeader(\"Authorization\", \"Snowflake Token=\\\"\" + this.sessionToken + \"\\\"\");\n        response =\n            HttpUtil.executeGeneralRequest(\n                get,\n                loginTimeout,\n                0,\n                (int) httpClientSocketTimeout.toMillis(),\n                maxHttpRetries,\n                getHttpClientKey(),\n                this);\n        jsonNode = OBJECT_MAPPER.readTree(response);\n      } catch (Exception e) {\n        throw new SnowflakeSQLLoggedException(\n            queryID,\n            this,\n            e.getMessage(),\n            \"No response or invalid response from GET request. Error: \" + e.getMessage(),\n            e);\n      }\n\n      // Get response as JSON and parse it to get the query status\n      // check the success field first\n      if (!jsonNode.path(\"success\").asBoolean()) {\n        logger.debug(\"Response: {}\", response);\n\n        int errorCode = jsonNode.path(\"code\").asInt();\n        // If the error is due to an expired session token, try renewing the session and trying\n        // again\n        if (errorCode == Constants.SESSION_EXPIRED_GS_CODE) {\n          try {\n            this.renewSession(this.sessionToken);\n          } catch (SnowflakeReauthenticationRequest | SFException ex) {\n            // If we fail to renew the session based on a re-authentication error, try to\n            // re-authenticate the session first\n            if (ex instanceof SnowflakeReauthenticationRequest\n                && this.isExternalbrowserOrOAuthFullFlowAuthenticator()) {\n              try {\n                this.open(internalCallMarker());\n              } catch (SFException e) {\n                throw new SnowflakeSQLException(e);\n              }\n            }\n            // If we reach a re-authentication error but cannot re-authenticate, throw an exception\n            else if (ex instanceof SnowflakeReauthenticationRequest) {\n              throw (SnowflakeSQLException) ex;\n            }\n            // If trying to renew the session results in an error for any other reason, throw an\n            // exception\n            else if (ex instanceof SFException) {\n              throw new SnowflakeSQLException((SFException) ex);\n            }\n            throw new SnowflakeSQLException(null, queryID, ex.getMessage());\n          }\n          sessionRenewed = true;\n          // If the error code was not due to session renewal issues, throw an exception\n        } else {\n          throw new SnowflakeSQLException(\n              queryID,\n              jsonNode.path(\"message\").asText(),\n              SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n              errorCode);\n        }\n      }\n    } while (sessionRenewed);\n    return jsonNode.path(\"data\").path(\"queries\");\n  }\n\n  /**\n   * @param queryID query ID of the query whose status is being investigated\n   * @return a QueryStatus instance indicating the query's status\n   * @throws SQLException if an error is encountered\n   */\n  @Override\n  public QueryStatus getQueryStatus(String queryID) throws SQLException {\n    JsonNode queryNode = getQueryMetadata(queryID);\n    logger.debug(\"Query status: {}\", queryNode.asText());\n    if (queryNode.isEmpty()) {\n      return QueryStatus.empty();\n    }\n    JsonNode node = queryNode.get(0);\n    long endTime = node.path(\"endTime\").asLong(0);\n    int errorCode = node.path(\"errorCode\").asInt(0);\n    String errorMessage = node.path(\"errorMessage\").asText(\"No error reported\");\n    String id = node.path(\"id\").asText(\"\");\n    String name = node.path(\"status\").asText(\"\");\n    long sessionId = node.path(\"sessionId\").asLong(0);\n    String sqlText = node.path(\"sqlText\").asText(\"\");\n    long startTime = node.path(\"startTime\").asLong(0);\n    String state = node.path(\"state\").asText(\"\");\n    int totalDuration = node.path(\"totalDuration\").asInt(0);\n    String warehouseExternalSize = node.path(\"warehouseExternalSize\").asText(null);\n    int warehouseId = node.path(\"warehouseId\").asInt(0);\n    String warehouseName = node.path(\"warehouseName\").asText(null);\n    String warehouseServerType = node.path(\"warehouseServerType\").asText(null);\n    QueryStatus result =\n        new QueryStatus(\n            endTime,\n            errorCode,\n            errorMessage,\n            id,\n            name,\n            sessionId,\n            sqlText,\n            startTime,\n            state,\n            totalDuration,\n            warehouseExternalSize,\n            warehouseId,\n            warehouseName,\n            warehouseServerType);\n    if (!result.isStillRunning()) {\n      activeAsyncQueries.remove(queryID);\n    }\n    return result;\n  }\n\n  /**\n   * Add a property If a property is known for connection, add it to connection properties If not,\n   * add it as a dynamic session parameters\n   *\n   * <p>Make sure a property is not added more than once and the number of properties does not\n   * exceed limit.\n   *\n   * @param propertyName property name\n   * @param propertyValue property value\n   * @throws SFException exception raised from Snowflake components\n   */\n  public void addSFSessionProperty(String propertyName, Object propertyValue) throws SFException {\n    SFSessionProperty connectionProperty = SFSessionProperty.lookupByKey(propertyName);\n\n    if (connectionProperty != null) {\n      addProperty(propertyName, propertyValue);\n      // check if the value type is as expected\n      propertyValue = SFSessionProperty.checkPropertyValue(connectionProperty, propertyValue);\n\n      switch (connectionProperty) {\n        case LOGIN_TIMEOUT:\n          if (propertyValue != null) {\n            loginTimeout = (Integer) propertyValue;\n          }\n          break;\n\n        case NETWORK_TIMEOUT:\n          if (propertyValue != null) {\n            networkTimeoutInMilli = (Integer) propertyValue;\n          }\n          break;\n\n        case INJECT_CLIENT_PAUSE:\n          if (propertyValue != null) {\n            injectClientPause = (Integer) propertyValue;\n          }\n          break;\n\n        case INJECT_SOCKET_TIMEOUT:\n          if (propertyValue != null) {\n            injectSocketTimeout = (Integer) propertyValue;\n          }\n          break;\n\n        case PASSCODE_IN_PASSWORD:\n          passcodeInPassword = (propertyValue != null && (Boolean) propertyValue);\n          break;\n\n        case TRACING:\n          if (propertyValue != null) {\n            tracingLevel = Level.parse(((String) propertyValue).toUpperCase());\n          }\n          break;\n        case JAVA_LOGGING_CONSOLE_STD_OUT:\n          if (propertyValue != null) {\n            javaUtilLoggingConsoleOut = (Boolean) propertyValue;\n          }\n          break;\n        case JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD:\n          if (propertyValue != null) {\n            javaUtilLoggingConsoleOutThreshold = (String) propertyValue;\n          }\n          break;\n\n        case DISABLE_SOCKS_PROXY:\n          // note: if any session has this parameter, it will be used for all\n          // sessions on the current JVM.\n          if (propertyValue != null) {\n            HttpUtil.setSocksProxyDisabled((Boolean) propertyValue);\n          }\n          break;\n\n        case VALIDATE_DEFAULT_PARAMETERS:\n          if (propertyValue != null) {\n            setValidateDefaultParameters(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case PRIVATE_KEY_FILE:\n          if (propertyValue != null) {\n            privateKeyFileLocation = (String) propertyValue;\n          }\n          break;\n\n        case PRIVATE_KEY_BASE64:\n          if (propertyValue != null) {\n            privateKeyBase64 = (String) propertyValue;\n          }\n          break;\n\n        case PRIVATE_KEY_FILE_PWD:\n        case PRIVATE_KEY_PWD:\n          if (propertyValue != null) {\n            privateKeyPassword = (String) propertyValue;\n          }\n          break;\n\n        case MAX_HTTP_RETRIES:\n          if (propertyValue != null) {\n            maxHttpRetries = (Integer) propertyValue;\n          }\n          break;\n\n        case ENABLE_PUT_GET:\n          if (propertyValue != null) {\n            setEnablePutGet(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case ENABLE_COPY_RESULT_SET:\n          if (propertyValue != null) {\n            setEnableCopyResultSet(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case RETRY_TIMEOUT:\n          if (propertyValue != null) {\n            int timeoutValue = (Integer) propertyValue;\n            if (timeoutValue >= 300 || timeoutValue == 0) {\n              retryTimeout = timeoutValue;\n            }\n          }\n          break;\n\n        case ENABLE_PATTERN_SEARCH:\n          if (propertyValue != null) {\n            setEnablePatternSearch(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case ENABLE_EXACT_SCHEMA_SEARCH_ENABLED:\n          if (propertyValue != null) {\n            setEnableExactSchemaSearch(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case ENABLE_WILDCARDS_IN_SHOW_METADATA_COMMANDS:\n          if (propertyValue != null) {\n            setEnableWildcardsInShowMetadataCommands(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case DISABLE_GCS_DEFAULT_CREDENTIALS:\n          if (propertyValue != null) {\n            setDisableGcsDefaultCredentials(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case JDBC_ARROW_TREAT_DECIMAL_AS_INT:\n          if (propertyValue != null) {\n            setJdbcArrowTreatDecimalAsInt(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case BROWSER_RESPONSE_TIMEOUT:\n          if (propertyValue != null) {\n            browserResponseTimeout = Duration.ofSeconds((Integer) propertyValue);\n          }\n          break;\n\n        case JDBC_DEFAULT_FORMAT_DATE_WITH_TIMEZONE:\n          if (propertyValue != null) {\n            setDefaultFormatDateWithTimezone(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case JDBC_GET_DATE_USE_NULL_TIMEZONE:\n          if (propertyValue != null) {\n            setGetDateUseNullTimezone(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case ENABLE_CLIENT_STORE_TEMPORARY_CREDENTIAL:\n          if (propertyValue != null) {\n            enableClientStoreTemporaryCredential = getBooleanValue(propertyValue);\n          }\n          break;\n\n        case ENABLE_CLIENT_REQUEST_MFA_TOKEN:\n          if (propertyValue != null) {\n            enableClientRequestMfaToken = getBooleanValue(propertyValue);\n          }\n          break;\n\n        case IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT:\n          if (propertyValue != null) {\n            setImplicitServerSideQueryTimeout(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case CLEAR_BATCH_ONLY_AFTER_SUCCESSFUL_EXECUTION:\n          if (propertyValue != null) {\n            setClearBatchOnlyAfterSuccessfulExecution(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME:\n          if (propertyValue != null) {\n            setTreatTimeAsWallClockTime(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case OWNER_ONLY_STAGE_FILE_PERMISSIONS_ENABLED:\n          if (propertyValue != null) {\n            setOwnerOnlyStageFilePermissionsEnabled(getBooleanValue(propertyValue));\n          }\n          break;\n\n        case MIN_TLS_VERSION:\n          if (propertyValue != null) {\n            SFSSLConnectionSocketFactory.setMinTlsVersion((String) propertyValue);\n          }\n          break;\n\n        case MAX_TLS_VERSION:\n          if (propertyValue != null) {\n            SFSSLConnectionSocketFactory.setMaxTlsVersion((String) propertyValue);\n          }\n          break;\n\n        case ALLOW_CERTIFICATES_WITHOUT_CRL_URL:\n          if (propertyValue != null) {\n            setAllowCertificatesWithoutCrlUrl(getBooleanValue(propertyValue));\n          }\n          break;\n\n        default:\n          break;\n      }\n    } else {\n      // this property does not match any predefined property, treat it as\n      // session parameter\n      if (sessionParametersMap.containsKey(propertyName)) {\n        throw new SFException(ErrorCode.DUPLICATE_CONNECTION_PROPERTY_SPECIFIED, propertyName);\n      } else {\n        sessionParametersMap.put(propertyName, propertyValue);\n      }\n\n      // check if the number of session properties exceed limit\n      if (sessionParametersMap.size() > MAX_SESSION_PARAMETERS) {\n        throw new SFException(ErrorCode.TOO_MANY_SESSION_PARAMETERS, MAX_SESSION_PARAMETERS);\n      }\n    }\n  }\n\n  public void overrideConsoleHandlerWhenNecessary() {\n    if (javaUtilLoggingConsoleOut) {\n      JDK14Logger.useStdOutConsoleHandler(javaUtilLoggingConsoleOutThreshold);\n    }\n  }\n\n  public boolean containProperty(String key) {\n    return sessionParametersMap.containsKey(key);\n  }\n\n  /**\n   * Open a new database session\n   *\n   * @throws SFException this is a runtime exception\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  @VisibleForTesting\n  static void checkAwsExternalIdEnabled(Map<SFSessionProperty, Object> props) throws SFException {\n    if (!AuthenticatorType.WORKLOAD_IDENTITY\n            .name()\n            .equalsIgnoreCase((String) props.get(SFSessionProperty.AUTHENTICATOR))\n        || !\"aws\"\n            .equalsIgnoreCase((String) props.get(SFSessionProperty.WORKLOAD_IDENTITY_PROVIDER))) {\n      return;\n    }\n    String awsExternalId = (String) props.get(SFSessionProperty.WORKLOAD_IDENTITY_AWS_EXTERNAL_ID);\n    if (!SnowflakeUtil.convertSystemGetEnvToBooleanValue(SF_ENABLE_WIF_AWS_EXTERNAL_ID, false)\n        && awsExternalId != null\n        && !awsExternalId.isEmpty()) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Connection property workloadIdentityAwsExternalId is not enabled\");\n    }\n  }\n\n  public synchronized void open() throws SFException, SnowflakeSQLException {\n    open(null);\n  }\n\n  public synchronized void open(InternalCallMarker internalCallMarker)\n      throws SFException, SnowflakeSQLException {\n    recordIfExternal(\"SFSession\", \"open\", internalCallMarker);\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    performSanityCheckOnProperties();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    logger.info(\n        \"Opening session with server: {}, account: {}, user: {}, password is {}, role: {}, database: {}, schema: {},\"\n            + \" warehouse: {}, validate default parameters: {}, authenticator: {}, ocsp mode: {},\"\n            + \" passcode in password: {}, passcode is {}, private key is {}, disable socks proxy: {},\"\n            + \" application: {}, app id: {}, app version: {}, login timeout: {}, retry timeout: {}, network timeout: {},\"\n            + \" query timeout: {}, connection timeout: {}, socket timeout: {}, tracing: {},\"\n            + \" private key file: {}, private key base 64: {}, private key pwd is {},\"\n            + \" enable_diagnostics: {}, diagnostics_allowlist_path: {},\"\n            + \" session parameters: client store temporary credential: {}, gzip disabled: {}, browser response timeout: {}\",\n        connectionPropertiesMap.get(SFSessionProperty.SERVER_URL),\n        connectionPropertiesMap.get(SFSessionProperty.ACCOUNT),\n        connectionPropertiesMap.get(SFSessionProperty.USER),\n        SFLoggerUtil.isVariableProvided(\n            (String) connectionPropertiesMap.get(SFSessionProperty.PASSWORD)),\n        connectionPropertiesMap.get(SFSessionProperty.ROLE),\n        connectionPropertiesMap.get(SFSessionProperty.DATABASE),\n        connectionPropertiesMap.get(SFSessionProperty.SCHEMA),\n        connectionPropertiesMap.get(SFSessionProperty.WAREHOUSE),\n        connectionPropertiesMap.get(SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS),\n        connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR),\n        getOCSPMode().name(),\n        connectionPropertiesMap.get(SFSessionProperty.PASSCODE_IN_PASSWORD),\n        SFLoggerUtil.isVariableProvided(\n            (String) connectionPropertiesMap.get(SFSessionProperty.PASSCODE)),\n        SFLoggerUtil.isVariableProvided(connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY)),\n        connectionPropertiesMap.get(SFSessionProperty.DISABLE_SOCKS_PROXY),\n        connectionPropertiesMap.get(SFSessionProperty.APPLICATION),\n        connectionPropertiesMap.get(SFSessionProperty.APP_ID),\n        connectionPropertiesMap.get(SFSessionProperty.APP_VERSION),\n        connectionPropertiesMap.get(SFSessionProperty.LOGIN_TIMEOUT),\n        connectionPropertiesMap.get(SFSessionProperty.RETRY_TIMEOUT),\n        connectionPropertiesMap.get(SFSessionProperty.NETWORK_TIMEOUT),\n        connectionPropertiesMap.get(SFSessionProperty.QUERY_TIMEOUT),\n        connectionPropertiesMap.get(SFSessionProperty.HTTP_CLIENT_CONNECTION_TIMEOUT),\n        connectionPropertiesMap.get(SFSessionProperty.HTTP_CLIENT_SOCKET_TIMEOUT),\n        connectionPropertiesMap.get(SFSessionProperty.TRACING),\n        connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY_FILE),\n        SFLoggerUtil.isVariableProvided(\n            (String) connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY_BASE64)),\n        SFLoggerUtil.isVariableProvided(\n            (String)\n                connectionPropertiesMap.getOrDefault(\n                    SFSessionProperty.PRIVATE_KEY_PWD,\n                    connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY_FILE_PWD))),\n        connectionPropertiesMap.get(SFSessionProperty.ENABLE_DIAGNOSTICS),\n        connectionPropertiesMap.get(SFSessionProperty.DIAGNOSTICS_ALLOWLIST_FILE),\n        sessionParametersMap.get(SessionUtil.CLIENT_STORE_TEMPORARY_CREDENTIAL),\n        connectionPropertiesMap.get(SFSessionProperty.GZIP_DISABLED),\n        connectionPropertiesMap.get(SFSessionProperty.BROWSER_RESPONSE_TIMEOUT));\n\n    HttpClientSettingsKey httpClientSettingsKey = getHttpClientKey();\n    logger.debug(\n        \"Connection proxy parameters: use proxy: {}, proxy host: {}, proxy port: {}, proxy user: {},\"\n            + \" proxy password is {}, non proxy hosts: {}, proxy protocol: {}\",\n        httpClientSettingsKey.usesProxy(),\n        httpClientSettingsKey.getProxyHost(),\n        httpClientSettingsKey.getProxyPort(),\n        httpClientSettingsKey.getProxyUser(),\n        SFLoggerUtil.isVariableProvided(httpClientSettingsKey.getProxyPassword()),\n        httpClientSettingsKey.getNonProxyHosts(),\n        httpClientSettingsKey.getProxyHttpProtocol());\n\n    checkAwsExternalIdEnabled(connectionPropertiesMap);\n\n    // TODO: temporarily hardcode sessionParameter debug info. will be changed in the future\n    SFLoginInput loginInput = new SFLoginInput();\n    SFOauthLoginInput oauthLoginInput =\n        new SFOauthLoginInput(\n            (String) connectionPropertiesMap.get(SFSessionProperty.OAUTH_CLIENT_ID),\n            (String) connectionPropertiesMap.get(SFSessionProperty.OAUTH_CLIENT_SECRET),\n            (String) connectionPropertiesMap.get(SFSessionProperty.OAUTH_REDIRECT_URI),\n            (String) connectionPropertiesMap.get(SFSessionProperty.OAUTH_AUTHORIZATION_URL),\n            (String) connectionPropertiesMap.get(SFSessionProperty.OAUTH_TOKEN_REQUEST_URL),\n            (String) connectionPropertiesMap.get(SFSessionProperty.OAUTH_SCOPE),\n            getBooleanValue(\n                connectionPropertiesMap.get(\n                    SFSessionProperty.OAUTH_ENABLE_SINGLE_USE_REFRESH_TOKENS)));\n\n    loginInput\n        .setServerUrl((String) connectionPropertiesMap.get(SFSessionProperty.SERVER_URL))\n        .setDatabaseName((String) connectionPropertiesMap.get(SFSessionProperty.DATABASE))\n        .setSchemaName((String) connectionPropertiesMap.get(SFSessionProperty.SCHEMA))\n        .setWarehouse((String) connectionPropertiesMap.get(SFSessionProperty.WAREHOUSE))\n        .setRole((String) connectionPropertiesMap.get(SFSessionProperty.ROLE))\n        .setValidateDefaultParameters(\n            connectionPropertiesMap.get(SFSessionProperty.VALIDATE_DEFAULT_PARAMETERS))\n        .setAuthenticator((String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR))\n        .setOriginalAuthenticator(\n            (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR))\n        .setOKTAUserName((String) connectionPropertiesMap.get(SFSessionProperty.OKTA_USERNAME))\n        .setAccountName((String) connectionPropertiesMap.get(SFSessionProperty.ACCOUNT))\n        .setLoginTimeout(loginTimeout)\n        .setRetryTimeout(retryTimeout)\n        .setAuthTimeout(authTimeout)\n        .setUserName((String) connectionPropertiesMap.get(SFSessionProperty.USER))\n        .setPassword((String) connectionPropertiesMap.get(SFSessionProperty.PASSWORD))\n        .setToken((String) connectionPropertiesMap.get(SFSessionProperty.TOKEN))\n        .setPasscodeInPassword(passcodeInPassword)\n        .setPasscode((String) connectionPropertiesMap.get(SFSessionProperty.PASSCODE))\n        .setConnectionTimeout(\n            connectionPropertiesMap.get(SFSessionProperty.HTTP_CLIENT_CONNECTION_TIMEOUT) != null\n                ? Duration.ofMillis(\n                    (int)\n                        connectionPropertiesMap.get(\n                            SFSessionProperty.HTTP_CLIENT_CONNECTION_TIMEOUT))\n                : httpClientConnectionTimeout)\n        .setSocketTimeout(\n            connectionPropertiesMap.get(SFSessionProperty.HTTP_CLIENT_SOCKET_TIMEOUT) != null\n                ? Duration.ofMillis(\n                    (int) connectionPropertiesMap.get(SFSessionProperty.HTTP_CLIENT_SOCKET_TIMEOUT))\n                : httpClientSocketTimeout)\n        .setAppId((String) connectionPropertiesMap.get(SFSessionProperty.APP_ID))\n        .setAppVersion((String) connectionPropertiesMap.get(SFSessionProperty.APP_VERSION))\n        .setSessionParameters(sessionParametersMap)\n        .setPrivateKey((PrivateKey) connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY))\n        .setPrivateKeyFile((String) connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY_FILE))\n        .setOauthLoginInput(oauthLoginInput)\n        .setWorkloadIdentityProvider(\n            (String) connectionPropertiesMap.get(SFSessionProperty.WORKLOAD_IDENTITY_PROVIDER))\n        .setWorkloadIdentityEntraResource(\n            (String)\n                connectionPropertiesMap.get(SFSessionProperty.WORKLOAD_IDENTITY_ENTRA_RESOURCE))\n        .setWorkloadIdentityImpersonationPath(\n            (String)\n                connectionPropertiesMap.get(SFSessionProperty.WORKLOAD_IDENTITY_IMPERSONATION_PATH))\n        .setWorkloadIdentityAwsExternalId(\n            (String)\n                connectionPropertiesMap.get(SFSessionProperty.WORKLOAD_IDENTITY_AWS_EXTERNAL_ID))\n        .setPrivateKeyBase64(\n            (String) connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY_BASE64))\n        .setPrivateKeyPwd(\n            (String)\n                connectionPropertiesMap.getOrDefault(\n                    SFSessionProperty.PRIVATE_KEY_PWD,\n                    connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY_FILE_PWD)))\n        .setApplication((String) connectionPropertiesMap.get(SFSessionProperty.APPLICATION))\n        .setServiceName(getServiceName())\n        .setOCSPMode(getOCSPMode())\n        .setHttpClientSettingsKey(httpClientSettingsKey)\n        .setDisableConsoleLogin(\n            connectionPropertiesMap.get(SFSessionProperty.DISABLE_CONSOLE_LOGIN) != null\n                ? getBooleanValue(\n                    connectionPropertiesMap.get(SFSessionProperty.DISABLE_CONSOLE_LOGIN))\n                : true)\n        .setDisableSamlURLCheck(\n            connectionPropertiesMap.get(SFSessionProperty.DISABLE_SAML_URL_CHECK) != null\n                ? getBooleanValue(\n                    connectionPropertiesMap.get(SFSessionProperty.DISABLE_SAML_URL_CHECK))\n                : false)\n        .setEnableClientStoreTemporaryCredential(enableClientStoreTemporaryCredential)\n        .setEnableClientRequestMfaToken(enableClientRequestMfaToken)\n        .setBrowserResponseTimeout(browserResponseTimeout)\n        .setPlatformDetectionTimeoutMs(\n            connectionPropertiesMap.get(SFSessionProperty.PLATFORM_DETECTION_TIMEOUT_MS) != null\n                ? (int) connectionPropertiesMap.get(SFSessionProperty.PLATFORM_DETECTION_TIMEOUT_MS)\n                : defaultPlatformDetectionTimeoutMs)\n        .setDisablePlatformDetection(\n            connectionPropertiesMap.get(SFSessionProperty.DISABLE_PLATFORM_DETECTION) != null\n                ? getBooleanValue(\n                    connectionPropertiesMap.get(SFSessionProperty.DISABLE_PLATFORM_DETECTION))\n                : false) // Default to false (platform detection enabled)\n        .setMaxRetryCount(maxHttpRetries);\n\n    logger.info(\n        \"Connecting to {} Snowflake domain\",\n        loginInput.getHostFromServerUrl().toLowerCase().endsWith(\".cn\") ? \"CHINA\" : \"GLOBAL\");\n\n    // we ignore the parameters CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED and htapOOBTelemetryEnabled\n    // OOB telemetry is disabled\n    TelemetryService.disableOOBTelemetry();\n\n    // propagate OCSP mode to SFTrustManager. Note OCSP setting is global on JVM.\n    HttpUtil.setConnectionTimeout(loginInput.getConnectionTimeoutInMillis());\n    HttpUtil.setSocketTimeout(loginInput.getSocketTimeoutInMillis());\n    HttpUtil.initHttpClient(httpClientSettingsKey, null, httpHeadersCustomizers);\n    runDiagnosticsIfEnabled();\n\n    SFLoginOutput loginOutput =\n        SessionUtil.openSession(loginInput, connectionPropertiesMap, tracingLevel.toString());\n    isClosed = false;\n\n    authTimeout = loginInput.getAuthTimeout();\n    sessionToken = loginOutput.getSessionToken();\n    masterToken = loginOutput.getMasterToken();\n    idToken = loginOutput.getIdToken();\n    mfaToken = loginOutput.getMfaToken();\n    oauthAccessToken = loginOutput.getOauthAccessToken();\n    oauthRefreshToken = loginOutput.getOauthRefreshToken();\n    setDatabaseVersion(loginOutput.getDatabaseVersion());\n    setDatabaseMajorVersion(loginOutput.getDatabaseMajorVersion());\n    setDatabaseMinorVersion(loginOutput.getDatabaseMinorVersion());\n    httpClientSocketTimeout = loginOutput.getHttpClientSocketTimeout();\n    httpClientConnectionTimeout = loginOutput.getHttpClientConnectionTimeout();\n    masterTokenValidityInSeconds = loginOutput.getMasterTokenValidityInSeconds();\n    setDatabase(loginOutput.getSessionDatabase());\n    setSchema(loginOutput.getSessionSchema());\n    setRole(loginOutput.getSessionRole());\n    setWarehouse(loginOutput.getSessionWarehouse());\n    setSessionId(loginOutput.getSessionId());\n    setAutoCommit(loginOutput.getAutoCommit());\n    extractAndUpdateStickyHttpHeaders(loginOutput.getLoginResponseHeaders());\n\n    // Update common parameter values for this session\n    SessionUtil.updateSfDriverParamValues(loginOutput.getCommonParams(), this);\n\n    String loginDatabaseName = (String) connectionPropertiesMap.get(SFSessionProperty.DATABASE);\n    String loginSchemaName = (String) connectionPropertiesMap.get(SFSessionProperty.SCHEMA);\n    String loginRole = (String) connectionPropertiesMap.get(SFSessionProperty.ROLE);\n    String loginWarehouse = (String) connectionPropertiesMap.get(SFSessionProperty.WAREHOUSE);\n\n    if (loginDatabaseName != null && !loginDatabaseName.equalsIgnoreCase(getDatabase())) {\n      sqlWarnings.add(\n          new SFException(\n              ErrorCode.CONNECTION_ESTABLISHED_WITH_DIFFERENT_PROP,\n              \"Database\",\n              loginDatabaseName,\n              getDatabase()));\n    }\n\n    if (loginSchemaName != null && !loginSchemaName.equalsIgnoreCase(getSchema())) {\n      sqlWarnings.add(\n          new SFException(\n              ErrorCode.CONNECTION_ESTABLISHED_WITH_DIFFERENT_PROP,\n              \"Schema\",\n              loginSchemaName,\n              getSchema()));\n    }\n\n    if (loginRole != null && !loginRole.equalsIgnoreCase(getRole())) {\n      sqlWarnings.add(\n          new SFException(\n              ErrorCode.CONNECTION_ESTABLISHED_WITH_DIFFERENT_PROP, \"Role\", loginRole, getRole()));\n    }\n\n    if (loginWarehouse != null && !loginWarehouse.equalsIgnoreCase(getWarehouse())) {\n      sqlWarnings.add(\n          new SFException(\n              ErrorCode.CONNECTION_ESTABLISHED_WITH_DIFFERENT_PROP,\n              \"Warehouse\",\n              loginWarehouse,\n              getWarehouse()));\n    }\n\n    boolean disableQueryContextCache = getDisableQueryContextCacheOption();\n    logger.debug(\n        \"Query context cache is {}\", ((disableQueryContextCache) ? \"disabled\" : \"enabled\"));\n\n    // Initialize QCC\n    if (!disableQueryContextCache) {\n      qcc = new QueryContextCache(this.getQueryContextCacheSize());\n    } else {\n      qcc = null;\n    }\n\n    // start heartbeat for this session so that the master token will not expire\n    startHeartbeatForThisSession();\n    this.getTelemetryClient(internalCallMarker());\n\n    // Flush any internal API usage telemetry that accumulated before session login\n    InternalApiTelemetryTracker.flush(getTelemetryClient(internalCallMarker()));\n\n    // Send minicore telemetry after session is established\n    sendMinicoreTelemetry();\n\n    stopwatch.stop();\n    logger.debug(\"Session {} opened in {} ms.\", getSessionId(), stopwatch.elapsedMillis());\n  }\n\n  private void sendMinicoreTelemetry() {\n    try {\n      Telemetry telemetry = getTelemetryClient(internalCallMarker());\n      if (!(telemetry instanceof TelemetryClient)) {\n        logger.trace(\"Telemetry client not available, skipping minicore telemetry\");\n        return;\n      }\n      TelemetryClient telemetryClient = (TelemetryClient) telemetry;\n\n      MinicoreTelemetry minicoreTelemetry = MinicoreTelemetry.create();\n      telemetryClient.addLogToBatch(\n          minicoreTelemetry.toInBandTelemetryNode(), System.currentTimeMillis());\n      logger.trace(\"Queued minicore telemetry for sending\");\n\n    } catch (Exception e) {\n      // Never fail the session due to telemetry\n      logger.trace(\"Failed to send minicore telemetry: {}\", e.getMessage());\n    }\n  }\n\n  /**\n   * If authenticator is null and private key is specified, jdbc will assume key pair authentication\n   *\n   * @return true if authenticator type is SNOWFLAKE (meaning password)\n   */\n  private boolean isSnowflakeAuthenticator() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    String authenticator = (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR);\n    PrivateKey privateKey = (PrivateKey) connectionPropertiesMap.get(SFSessionProperty.PRIVATE_KEY);\n    return (authenticator == null\n            && privateKey == null\n            && privateKeyFileLocation == null\n            && privateKeyBase64 == null)\n        || AuthenticatorType.SNOWFLAKE.name().equalsIgnoreCase(authenticator);\n  }\n\n  boolean isExternalbrowserOrOAuthFullFlowAuthenticator() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    String authenticator = (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR);\n    return AuthenticatorType.EXTERNALBROWSER.name().equalsIgnoreCase(authenticator)\n        || AuthenticatorType.OAUTH_AUTHORIZATION_CODE.name().equalsIgnoreCase(authenticator)\n        || AuthenticatorType.OAUTH_CLIENT_CREDENTIALS.name().equalsIgnoreCase(authenticator);\n  }\n\n  /**\n   * Returns true if authenticator is OKTA native\n   *\n   * @return true or false\n   */\n  boolean isOKTAAuthenticator() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    String authenticator = (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR);\n    return !isNullOrEmpty(authenticator) && authenticator.startsWith(\"https://\");\n  }\n\n  /**\n   * Returns true if authenticator is UsernamePasswordMFA native\n   *\n   * @return true or false\n   */\n  boolean isUsernamePasswordMFAAuthenticator() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    String authenticator = (String) connectionPropertiesMap.get(SFSessionProperty.AUTHENTICATOR);\n    return AuthenticatorType.USERNAME_PASSWORD_MFA.name().equalsIgnoreCase(authenticator);\n  }\n\n  /**\n   * A helper function to call global service and renew session.\n   *\n   * @param prevSessionToken the session token that has expired\n   * @throws SnowflakeSQLException if failed to renew the session\n   * @throws SFException if failed to renew the session\n   */\n  synchronized void renewSession(String prevSessionToken)\n      throws SFException, SnowflakeSQLException {\n    if (sessionToken != null && !sessionToken.equals(prevSessionToken)) {\n      logger.debug(\n          \"Not renewing session {} because session token has not been updated.\", getSessionId());\n      return;\n    }\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n\n    logger.debug(\"Renewing session {}\", getSessionId());\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput\n        .setServerUrl(getServerUrl())\n        .setSessionToken(sessionToken)\n        .setMasterToken(masterToken)\n        .setIdToken(idToken)\n        .setMfaToken(mfaToken)\n        .setOauthAccessToken(oauthAccessToken)\n        .setOauthRefreshToken(oauthRefreshToken)\n        .setLoginTimeout(loginTimeout)\n        .setRetryTimeout(retryTimeout)\n        .setDatabaseName(getDatabase())\n        .setSchemaName(getSchema())\n        .setRole(getRole())\n        .setWarehouse(getWarehouse())\n        .setOCSPMode(getOCSPMode())\n        .setHttpClientSettingsKey(getHttpClientKey());\n\n    SFLoginOutput loginOutput = SessionUtil.renewSession(loginInput, this);\n\n    sessionToken = loginOutput.getSessionToken();\n    masterToken = loginOutput.getMasterToken();\n    stopwatch.stop();\n    logger.debug(\n        \"Session {} renewed successfully in {} ms\", getSessionId(), stopwatch.elapsedMillis());\n  }\n\n  /**\n   * get session token\n   *\n   * @return session token\n   */\n  public String getSessionToken() {\n    return getSessionToken(null);\n  }\n\n  public String getSessionToken(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SFSession\", \"getSessionToken\", internalCallMarker);\n    return sessionToken;\n  }\n\n  public void close(InternalCallMarker internalCallMarker)\n      throws SFException, SnowflakeSQLException {\n    recordIfExternal(\"SFSession\", \"close\", internalCallMarker);\n    logger.debug(\"Closing session {}\", getSessionId());\n\n    // stop heartbeat for this session\n    stopHeartbeatForThisSession();\n\n    if (isClosed) {\n      logger.debug(\"Session {} is already closed\", getSessionId());\n      return;\n    }\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput\n        .setServerUrl(getServerUrl())\n        .setSessionToken(sessionToken)\n        .setLoginTimeout(loginTimeout)\n        .setRetryTimeout(retryTimeout)\n        .setOCSPMode(getOCSPMode())\n        .setHttpClientSettingsKey(getHttpClientKey());\n\n    SessionUtil.closeSession(loginInput, this);\n    InternalApiTelemetryTracker.flush(getTelemetryClient(internalCallMarker()));\n    closeTelemetryClient();\n    getClientInfo().clear();\n\n    // qcc can be null, if disabled.\n    if (qcc != null) {\n      qcc.clearCache();\n    }\n\n    stopwatch.stop();\n    logger.debug(\n        \"Session {} has been successfully closed in {} ms\",\n        getSessionId(),\n        stopwatch.elapsedMillis());\n    isClosed = true;\n  }\n\n  /**\n   * Makes a heartbeat call to check for session validity.\n   *\n   * @param timeout the query timeout\n   * @throws Exception if an error occurs\n   * @throws SFException exception raised from Snowflake\n   */\n  public void callHeartBeat(int timeout) throws Exception, SFException {\n    if (timeout > 0) {\n      callHeartBeatWithQueryTimeout(timeout);\n    } else {\n      heartbeat();\n    }\n  }\n\n  /**\n   * Makes a heartbeat call with query timeout to check for session validity.\n   *\n   * @param timeout the query timeout\n   * @throws Exception if an error occurs\n   * @throws SFException exception raised from Snowflake\n   */\n  private void callHeartBeatWithQueryTimeout(int timeout) throws Exception, SFException {\n    class HeartbeatTask implements Callable<Void> {\n\n      @Override\n      public Void call() throws SQLException {\n        try {\n          heartbeat();\n        } catch (SFException e) {\n          throw new SnowflakeSQLException(e, e.getSqlState(), e.getVendorCode(), e.getParams());\n        }\n        return null;\n      }\n    }\n    ExecutorService executor = Executors.newSingleThreadExecutor();\n    Future<Void> future = executor.submit(new HeartbeatTask());\n\n    // Cancel the heartbeat call when timeout is reached\n    try {\n      future.get(timeout, TimeUnit.SECONDS);\n    } catch (TimeoutException e) {\n      future.cancel(true);\n      throw new SFException(ErrorCode.QUERY_CANCELED);\n    } finally {\n      executor.shutdownNow();\n    }\n  }\n\n  /** Start heartbeat for this session */\n  protected void startHeartbeatForThisSession() {\n    if (getEnableHeartbeat() && !isNullOrEmpty(masterToken)) {\n      logger.debug(\n          \"Session {} start heartbeat, master token validity: {} s\",\n          getSessionId(),\n          masterTokenValidityInSeconds);\n\n      HeartbeatRegistry.getInstance()\n          .addSession(this, masterTokenValidityInSeconds, heartbeatFrequency);\n    } else {\n      logger.debug(\"Heartbeat not enabled for the session {}\", getSessionId());\n    }\n  }\n\n  /** Stop heartbeat for this session */\n  protected void stopHeartbeatForThisSession() {\n    if (getEnableHeartbeat() && !isNullOrEmpty(masterToken)) {\n      logger.debug(\"Session {} stop heartbeat\", getSessionId());\n\n      HeartbeatRegistry.getInstance().removeSession(this);\n    } else {\n      logger.debug(\"Heartbeat not enabled for the session {}\", getSessionId());\n    }\n  }\n\n  /**\n   * Send heartbeat for the session\n   *\n   * @throws SFException exception raised from Snowflake\n   * @throws SQLException exception raised from SQL generic layers\n   */\n  protected void heartbeat() throws SFException, SQLException {\n    logger.debug(\"Session {} heartbeat\", getSessionId());\n\n    if (isClosed) {\n      return;\n    }\n\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n\n    HttpPost postRequest = null;\n\n    String requestId = UUIDUtils.getUUID().toString();\n\n    boolean retry = false;\n\n    // the loop for retrying if it runs into session expiration\n    do {\n      try {\n        URIBuilder uriBuilder;\n\n        uriBuilder = new URIBuilder(getServerUrl());\n\n        uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, requestId);\n\n        uriBuilder.setPath(SF_PATH_SESSION_HEARTBEAT);\n\n        postRequest = new HttpPost(uriBuilder.build());\n\n        // remember the session token in case it expires we need to renew\n        // the session only when no other thread has renewed it\n        String prevSessionToken = sessionToken;\n\n        postRequest.setHeader(\n            SF_HEADER_AUTHORIZATION,\n            SF_HEADER_SNOWFLAKE_AUTHTYPE\n                + \" \"\n                + SF_HEADER_TOKEN_TAG\n                + \"=\\\"\"\n                + prevSessionToken\n                + \"\\\"\");\n\n        logger.debug(\"Executing heartbeat request: {}\", postRequest.toString());\n\n        // the following will retry transient network issues\n        // increase heartbeat timeout from 60 sec to 300 sec\n        // per https://support-snowflake.zendesk.com/agent/tickets/6629\n        int SF_HEARTBEAT_TIMEOUT = 300;\n        String theResponse =\n            HttpUtil.executeGeneralRequest(\n                postRequest,\n                SF_HEARTBEAT_TIMEOUT,\n                0,\n                (int) httpClientSocketTimeout.toMillis(),\n                0,\n                getHttpClientKey(),\n                this);\n\n        JsonNode rootNode;\n\n        logger.debug(\"Connection heartbeat response: {}\", theResponse);\n\n        rootNode = OBJECT_MAPPER.readTree(theResponse);\n\n        // check the response to see if it is session expiration response\n        if (rootNode != null\n            && (Constants.SESSION_EXPIRED_GS_CODE == rootNode.path(\"code\").asInt())) {\n          logger.debug(\"Renew session and retry\", false);\n          this.renewSession(prevSessionToken);\n          retry = true;\n          continue;\n        }\n\n        SnowflakeUtil.checkErrorAndThrowException(rootNode);\n\n        // success\n        retry = false;\n      } catch (Throwable ex) {\n        // for snowflake exception, just rethrow it\n        if (ex instanceof SnowflakeSQLException) {\n          throw (SnowflakeSQLException) ex;\n        }\n\n        logger.error(\"Unexpected exception\", ex);\n\n        throw new SFException(\n            ErrorCode.INTERNAL_ERROR, SFException.oneLiner(\"unexpected exception\", ex));\n      }\n    } while (retry);\n    stopwatch.stop();\n    logger.debug(\n        \"Session {} heartbeat successful in {} ms\", getSessionId(), stopwatch.elapsedMillis());\n  }\n\n  void injectedDelay() {\n\n    AtomicInteger injectedDelay = getInjectedDelay();\n    int d = injectedDelay.get();\n\n    if (d != 0) {\n      injectedDelay.set(0);\n      try {\n        logger.trace(\"delayed for {}\", d);\n\n        Thread.sleep(d);\n      } catch (InterruptedException ex) {\n      }\n    }\n  }\n\n  public int getInjectSocketTimeout() {\n    return injectSocketTimeout;\n  }\n\n  public void setInjectSocketTimeout(int injectSocketTimeout) {\n    this.injectSocketTimeout = injectSocketTimeout;\n  }\n\n  public int getNetworkTimeoutInMilli() {\n    return networkTimeoutInMilli;\n  }\n\n  public int getAuthTimeout() {\n    return authTimeout;\n  }\n\n  public int getHttpClientSocketTimeout() {\n    return (int) httpClientSocketTimeout.toMillis();\n  }\n\n  public int getHttpClientConnectionTimeout() {\n    return (int) httpClientConnectionTimeout.toMillis();\n  }\n\n  public boolean isClosed() {\n    return isClosed;\n  }\n\n  public int getInjectClientPause() {\n    return injectClientPause;\n  }\n\n  public int getMaxHttpRetries() {\n    return maxHttpRetries;\n  }\n\n  public void setInjectClientPause(int injectClientPause) {\n    this.injectClientPause = injectClientPause;\n  }\n\n  protected int getAndIncrementSequenceId() {\n    return sequenceId.getAndIncrement();\n  }\n\n  public boolean getEnableCombineDescribe() {\n    return this.enableCombineDescribe;\n  }\n\n  public void setEnableCombineDescribe(boolean enable) {\n    this.enableCombineDescribe = enable;\n  }\n\n  public synchronized Telemetry getTelemetryClient(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SFSession\", \"getTelemetryClient\", internalCallMarker);\n    // initialize for the first time. this should only be done after session\n    // properties have been set, else the client won't properly resolve the URL.\n    if (telemetryClient == null) {\n      if (getUrl() == null) {\n        logger.debug(\n            \"Telemetry client requested before session properties set; returning no-op client\");\n        return new NoOpTelemetryClient();\n      }\n      telemetryClient = TelemetryClient.createTelemetry(this);\n\n      // Provide the real telemetry client to the CRL validator for this session's\n      // HttpClientSettingsKey\n      try {\n        CRLValidator.setTelemetryClientForKey(getHttpClientKey(), telemetryClient);\n      } catch (Exception e) {\n        logger.warn(\"Failed to provide telemetry client to CRL trust manager: {}\", e.getMessage());\n      }\n    }\n    return telemetryClient;\n  }\n\n  public void closeTelemetryClient() {\n    if (telemetryClient != null) {\n      telemetryClient.close();\n    }\n  }\n\n  public String getIdToken() {\n    return getIdToken(null);\n  }\n\n  public String getIdToken(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SFSession\", \"getIdToken\", internalCallMarker);\n    return idToken;\n  }\n\n  public String getAccessToken() {\n    return getAccessToken(null);\n  }\n\n  public String getAccessToken(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SFSession\", \"getAccessToken\", internalCallMarker);\n    return oauthAccessToken;\n  }\n\n  public String getMfaToken() {\n    return getMfaToken(null);\n  }\n\n  public String getMfaToken(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SFSession\", \"getMfaToken\", internalCallMarker);\n    return mfaToken;\n  }\n\n  public SnowflakeConnectString getSnowflakeConnectionString() {\n    return sfConnStr;\n  }\n\n  public void setSnowflakeConnectionString(SnowflakeConnectString connStr) {\n    sfConnStr = connStr;\n  }\n\n  /**\n   * Performs a sanity check on properties. Sanity checking includes: - verifying that a server url\n   * is present - verifying various combinations of properties given the authenticator\n   *\n   * @throws SFException Will be thrown if any of the necessary properties are missing\n   */\n  private void performSanityCheckOnProperties() throws SFException {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n\n    for (SFSessionProperty property : SFSessionProperty.values()) {\n      if (property.isRequired() && !connectionPropertiesMap.containsKey(property)) {\n        switch (property) {\n          case SERVER_URL:\n            throw new SFException(ErrorCode.MISSING_SERVER_URL);\n\n          default:\n            throw new SFException(ErrorCode.MISSING_CONNECTION_PROPERTY, property.getPropertyKey());\n        }\n      }\n    }\n\n    if (isSnowflakeAuthenticator()\n        || isOKTAAuthenticator()\n        || isUsernamePasswordMFAAuthenticator()) {\n      // userName and password are expected for both Snowflake and Okta.\n      String userName = (String) connectionPropertiesMap.get(SFSessionProperty.USER);\n      if (isNullOrEmpty(userName)) {\n        throw new SFException(ErrorCode.MISSING_USERNAME);\n      }\n\n      String password = (String) connectionPropertiesMap.get(SFSessionProperty.PASSWORD);\n      if (isNullOrEmpty(password)) {\n\n        throw new SFException(ErrorCode.MISSING_PASSWORD);\n      }\n    }\n\n    // perform sanity check on proxy settings\n    boolean useProxy =\n        (boolean) connectionPropertiesMap.getOrDefault(SFSessionProperty.USE_PROXY, false);\n    if (useProxy) {\n      if (!connectionPropertiesMap.containsKey(SFSessionProperty.PROXY_HOST)\n          || connectionPropertiesMap.get(SFSessionProperty.PROXY_HOST) == null\n          || ((String) connectionPropertiesMap.get(SFSessionProperty.PROXY_HOST)).isEmpty()\n          || !connectionPropertiesMap.containsKey(SFSessionProperty.PROXY_PORT)\n          || connectionPropertiesMap.get(SFSessionProperty.PROXY_HOST) == null) {\n        throw new SFException(\n            ErrorCode.INVALID_PROXY_PROPERTIES, \"Both proxy host and port values are needed.\");\n      }\n    }\n  }\n\n  @Override\n  public List<DriverPropertyInfo> checkProperties() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    for (SFSessionProperty property : SFSessionProperty.values()) {\n      if (property.isRequired() && !connectionPropertiesMap.containsKey(property)) {\n        missingProperties.add(addNewDriverProperty(property.getPropertyKey(), null));\n      }\n    }\n    if (isSnowflakeAuthenticator() || isOKTAAuthenticator()) {\n      // userName and password are expected for both Snowflake and Okta.\n      String userName = (String) connectionPropertiesMap.get(SFSessionProperty.USER);\n      if (isNullOrEmpty(userName)) {\n        missingProperties.add(\n            addNewDriverProperty(SFSessionProperty.USER.getPropertyKey(), \"username for account\"));\n      }\n\n      String password = (String) connectionPropertiesMap.get(SFSessionProperty.PASSWORD);\n      if (isNullOrEmpty(password)) {\n        missingProperties.add(\n            addNewDriverProperty(\n                SFSessionProperty.PASSWORD.getPropertyKey(), \"password for \" + \"account\"));\n      }\n    }\n\n    boolean useProxy =\n        (boolean) connectionPropertiesMap.getOrDefault(SFSessionProperty.USE_PROXY, false);\n    if (useProxy) {\n      if (!connectionPropertiesMap.containsKey(SFSessionProperty.PROXY_HOST)) {\n        missingProperties.add(\n            addNewDriverProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy host name\"));\n      }\n      if (!connectionPropertiesMap.containsKey(SFSessionProperty.PROXY_PORT)) {\n        missingProperties.add(\n            addNewDriverProperty(\n                SFSessionProperty.PROXY_PORT.getPropertyKey(),\n                \"proxy port; \" + \"should be an integer\"));\n      }\n    }\n    return missingProperties;\n  }\n\n  private DriverPropertyInfo addNewDriverProperty(String name, String description) {\n    DriverPropertyInfo info = new DriverPropertyInfo(name, null);\n    info.description = description;\n    return info;\n  }\n\n  /**\n   * @return whether this session uses async queries\n   */\n  public boolean isAsyncSession() {\n    return !activeAsyncQueries.isEmpty();\n  }\n\n  private boolean getDisableQueryContextCacheOption() {\n    Boolean disableQueryContextCache = false;\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.DISABLE_QUERY_CONTEXT_CACHE)) {\n      disableQueryContextCache =\n          (Boolean) connectionPropertiesMap.get(SFSessionProperty.DISABLE_QUERY_CONTEXT_CACHE);\n    }\n\n    return disableQueryContextCache;\n  }\n\n  @Override\n  public void setQueryContext(String queryContext) {\n    boolean disableQueryContextCache = getDisableQueryContextCacheOption();\n    if (!disableQueryContextCache) {\n      qcc.deserializeQueryContextJson(queryContext);\n    }\n  }\n\n  @Override\n  public QueryContextDTO getQueryContextDTO() {\n    boolean disableQueryContextCache = getDisableQueryContextCacheOption();\n\n    if (!disableQueryContextCache) {\n      QueryContextDTO res = qcc.serializeQueryContextDTO();\n      return res;\n    } else {\n      return null;\n    }\n  }\n\n  public SFClientConfig getSfClientConfig() {\n    return sfClientConfig;\n  }\n\n  public void setSfClientConfig(SFClientConfig sfClientConfig) {\n    this.sfClientConfig = sfClientConfig;\n  }\n\n  /**\n   * If the JDBC driver starts in diagnostics mode then the method prints results of the\n   * connectivity tests it performs in the logs. A SQLException is thrown with a message indicating\n   * that the driver is in diagnostics mode, and that a connection was not created.\n   */\n  private void runDiagnosticsIfEnabled() throws SnowflakeSQLException {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = getConnectionPropertiesMap();\n    boolean isDiagnosticsEnabled =\n        Optional.ofNullable(connectionPropertiesMap.get(SFSessionProperty.ENABLE_DIAGNOSTICS))\n            .map(b -> (Boolean) b)\n            .orElse(false);\n\n    if (!isDiagnosticsEnabled) {\n      return;\n    }\n    logger.info(\"Running diagnostics tests\");\n    String allowListFile =\n        (String) connectionPropertiesMap.get(SFSessionProperty.DIAGNOSTICS_ALLOWLIST_FILE);\n\n    if (allowListFile == null || allowListFile.isEmpty()) {\n      logger.error(\n          \"Diagnostics was enabled but an allowlist file was not provided.\"\n              + \" Please provide an allowlist JSON file using the connection parameter {}\",\n          SFSessionProperty.DIAGNOSTICS_ALLOWLIST_FILE);\n      throw new SnowflakeSQLException(\n          \"Diagnostics was enabled but an allowlist file was not provided. \"\n              + \"Please provide an allowlist JSON file using the connection parameter \"\n              + SFSessionProperty.DIAGNOSTICS_ALLOWLIST_FILE);\n    } else {\n      DiagnosticContext diagnosticContext =\n          new DiagnosticContext(allowListFile, connectionPropertiesMap);\n      diagnosticContext.runDiagnostics();\n    }\n\n    throw new SnowflakeSQLException(\n        \"A connection was not created because the driver is running in diagnostics mode.\"\n            + \" If this is unintended then disable diagnostics check by removing the \"\n            + SFSessionProperty.ENABLE_DIAGNOSTICS\n            + \" connection parameter\");\n  }\n\n  public void setHttpHeadersCustomizers(List<HttpHeadersCustomizer> httpHeadersCustomizers) {\n    this.httpHeadersCustomizers = httpHeadersCustomizers;\n  }\n\n  public List<HttpHeadersCustomizer> getHttpHeadersCustomizers() {\n    return httpHeadersCustomizers;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFSessionProperty.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.security.PrivateKey;\nimport java.util.List;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\n\n/** session properties accepted for opening a new session. */\npublic enum SFSessionProperty {\n  SERVER_URL(\"serverURL\", true, String.class),\n  USER(\"user\", false, String.class),\n  PASSWORD(\"password\", false, String.class),\n  ACCOUNT(\"account\", true, String.class),\n  DATABASE(\"database\", false, String.class, \"db\"),\n  SCHEMA(\"schema\", false, String.class),\n  PASSCODE_IN_PASSWORD(\"passcodeInPassword\", false, Boolean.class),\n  PASSCODE(\"passcode\", false, String.class),\n  TOKEN(\"token\", false, String.class),\n  ID_TOKEN_PASSWORD(\"id_token_password\", false, String.class),\n  ROLE(\"role\", false, String.class),\n  AUTHENTICATOR(\"authenticator\", false, String.class),\n  OKTA_USERNAME(\"oktausername\", false, String.class),\n  PRIVATE_KEY(\"privateKey\", false, PrivateKey.class),\n  OAUTH_REDIRECT_URI(\"oauthRedirectUri\", false, String.class),\n  OAUTH_CLIENT_ID(\"oauthClientID\", false, String.class),\n  OAUTH_CLIENT_SECRET(\"oauthClientSecret\", false, String.class),\n  OAUTH_SCOPE(\"oauthScope\", false, String.class),\n  OAUTH_AUTHORIZATION_URL(\"oauthAuthorizationUrl\", false, String.class),\n  OAUTH_TOKEN_REQUEST_URL(\"oauthTokenRequestUrl\", false, String.class),\n  OAUTH_ENABLE_SINGLE_USE_REFRESH_TOKENS(\"oauthEnableSingleUseRefreshTokens\", false, Boolean.class),\n  WORKLOAD_IDENTITY_PROVIDER(\"workloadIdentityProvider\", false, String.class),\n  WORKLOAD_IDENTITY_ENTRA_RESOURCE(\"workloadIdentityEntraResource\", false, String.class),\n  WORKLOAD_IDENTITY_IMPERSONATION_PATH(\"workloadIdentityImpersonationPath\", false, String.class),\n  WORKLOAD_IDENTITY_AWS_EXTERNAL_ID(\"workloadIdentityAwsExternalId\", false, String.class),\n  WAREHOUSE(\"warehouse\", false, String.class),\n  LOGIN_TIMEOUT(\"loginTimeout\", false, Integer.class),\n  NETWORK_TIMEOUT(\"networkTimeout\", false, Integer.class),\n  INJECT_SOCKET_TIMEOUT(\"injectSocketTimeout\", false, Integer.class),\n  INJECT_CLIENT_PAUSE(\"injectClientPause\", false, Integer.class),\n  APP_ID(\"appId\", false, String.class),\n  APP_VERSION(\"appVersion\", false, String.class),\n  OCSP_FAIL_OPEN(\"ocspFailOpen\", false, Boolean.class),\n  /**\n   * @deprecated Use {@link #DISABLE_OCSP_CHECKS} for clarity. This configuration option is used to\n   *     disable OCSP verification.\n   */\n  @Deprecated\n  INSECURE_MODE(\"insecureMode\", false, Boolean.class),\n  DISABLE_OCSP_CHECKS(\"disableOCSPChecks\", false, Boolean.class),\n  QUERY_TIMEOUT(\"queryTimeout\", false, Integer.class),\n  STRINGS_QUOTED(\"stringsQuotedForColumnDef\", false, Boolean.class),\n  APPLICATION(\"application\", false, String.class),\n  TRACING(\"tracing\", false, String.class),\n  DISABLE_SOCKS_PROXY(\"disableSocksProxy\", false, Boolean.class),\n  // connection proxy\n  USE_PROXY(\"useProxy\", false, Boolean.class),\n  PROXY_HOST(\"proxyHost\", false, String.class),\n  PROXY_PORT(\"proxyPort\", false, String.class),\n  PROXY_USER(\"proxyUser\", false, String.class),\n  PROXY_PASSWORD(\"proxyPassword\", false, String.class),\n  NON_PROXY_HOSTS(\"nonProxyHosts\", false, String.class),\n  PROXY_PROTOCOL(\"proxyProtocol\", false, String.class),\n  VALIDATE_DEFAULT_PARAMETERS(\"validateDefaultParameters\", false, Boolean.class),\n  INJECT_WAIT_IN_PUT(\"inject_wait_in_put\", false, Integer.class),\n  PRIVATE_KEY_FILE(\"private_key_file\", false, String.class),\n  PRIVATE_KEY_BASE64(\"private_key_base64\", false, String.class),\n  /**\n   * @deprecated Use {@link #PRIVATE_KEY_PWD} for clarity. The given password will be used to\n   *     decrypt the private key value independent of whether that value is supplied as a file or\n   *     base64 string\n   */\n  @Deprecated\n  PRIVATE_KEY_FILE_PWD(\"private_key_file_pwd\", false, String.class),\n  PRIVATE_KEY_PWD(\"private_key_pwd\", false, String.class),\n  CLIENT_INFO(\"snowflakeClientInfo\", false, String.class),\n  ALLOW_UNDERSCORES_IN_HOST(\"allowUnderscoresInHost\", false, Boolean.class),\n\n  // Adds a suffix to the user agent header in the http requests made by the jdbc driver\n  USER_AGENT_SUFFIX(\"user_agent_suffix\", false, String.class),\n\n  CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED(\n      \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\", false, Boolean.class),\n  GZIP_DISABLED(\"gzipDisabled\", false, Boolean.class),\n  DISABLE_QUERY_CONTEXT_CACHE(\"disableQueryContextCache\", false, Boolean.class),\n  HTAP_OOB_TELEMETRY_ENABLED(\"htapOOBTelemetryEnabled\", false, Boolean.class),\n\n  CLIENT_CONFIG_FILE(\"client_config_file\", false, String.class),\n\n  MAX_HTTP_RETRIES(\"maxHttpRetries\", false, Integer.class),\n\n  ENABLE_PUT_GET(\"enablePutGet\", false, Boolean.class),\n  ENABLE_COPY_RESULT_SET(\"enableCopyResultSet\", false, Boolean.class),\n  DISABLE_CONSOLE_LOGIN(\"disableConsoleLogin\", false, Boolean.class),\n\n  PUT_GET_MAX_RETRIES(\"putGetMaxRetries\", false, Integer.class),\n\n  RETRY_TIMEOUT(\"retryTimeout\", false, Integer.class),\n  ENABLE_DIAGNOSTICS(\"ENABLE_DIAGNOSTICS\", false, Boolean.class),\n  DIAGNOSTICS_ALLOWLIST_FILE(\"DIAGNOSTICS_ALLOWLIST_FILE\", false, String.class),\n\n  ENABLE_PATTERN_SEARCH(\"enablePatternSearch\", false, Boolean.class),\n  ENABLE_EXACT_SCHEMA_SEARCH_ENABLED(\"ENABLE_EXACT_SCHEMA_SEARCH_ENABLED\", false, Boolean.class),\n\n  DISABLE_GCS_DEFAULT_CREDENTIALS(\"disableGcsDefaultCredentials\", false, Boolean.class),\n\n  JDBC_ARROW_TREAT_DECIMAL_AS_INT(\"JDBC_ARROW_TREAT_DECIMAL_AS_INT\", false, Boolean.class),\n\n  DISABLE_SAML_URL_CHECK(\"disableSamlURLCheck\", false, Boolean.class),\n\n  // Used to determine whether to use the previously hardcoded value for the formatter (for\n  // backwards compatibility) or use the value of JDBC_FORMAT_DATE_WITH_TIMEZONE\n  JDBC_DEFAULT_FORMAT_DATE_WITH_TIMEZONE(\n      \"JDBC_DEFAULT_FORMAT_DATE_WITH_TIMEZONE\", false, Boolean.class),\n\n  // Used as a fix for issue SNOW-354859. Remove with snowflake-jdbc version 4.x with BCR changes.\n  JDBC_GET_DATE_USE_NULL_TIMEZONE(\"JDBC_GET_DATE_USE_NULL_TIMEZONE\", false, Boolean.class),\n\n  BROWSER_RESPONSE_TIMEOUT(\"BROWSER_RESPONSE_TIMEOUT\", false, Integer.class),\n\n  ENABLE_CLIENT_STORE_TEMPORARY_CREDENTIAL(\"clientStoreTemporaryCredential\", false, Boolean.class),\n\n  ENABLE_CLIENT_REQUEST_MFA_TOKEN(\"clientRequestMfaToken\", false, Boolean.class),\n\n  HTTP_CLIENT_CONNECTION_TIMEOUT(\"HTTP_CLIENT_CONNECTION_TIMEOUT\", false, Integer.class),\n\n  HTTP_CLIENT_SOCKET_TIMEOUT(\"HTTP_CLIENT_SOCKET_TIMEOUT\", false, Integer.class),\n\n  JAVA_LOGGING_CONSOLE_STD_OUT(\"JAVA_LOGGING_CONSOLE_STD_OUT\", false, Boolean.class),\n\n  JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD(\n      \"JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD\", false, String.class),\n\n  IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT(\"IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT\", false, Boolean.class),\n\n  CLEAR_BATCH_ONLY_AFTER_SUCCESSFUL_EXECUTION(\n      \"CLEAR_BATCH_ONLY_AFTER_SUCCESSFUL_EXECUTION\", false, Boolean.class),\n\n  CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME(\n      \"CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME\", false, Boolean.class),\n\n  HTTP_HEADER_CUSTOMIZERS(\n      HttpHeadersCustomizer.HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, false, List.class),\n\n  // Used to enable the owner-only stage file permissions feature. This feature will be enabled by\n  // default in the next major release.\n  OWNER_ONLY_STAGE_FILE_PERMISSIONS_ENABLED(\n      \"ownerOnlyStageFilePermissionsEnabled\", false, Boolean.class),\n\n  ENABLE_WILDCARDS_IN_SHOW_METADATA_COMMANDS(\n      \"ENABLE_WILDCARDS_IN_SHOW_METADATA_COMMANDS\", false, Boolean.class),\n\n  MIN_TLS_VERSION(\"MIN_TLS_VERSION\", false, String.class),\n\n  MAX_TLS_VERSION(\"MAX_TLS_VERSION\", false, String.class),\n\n  CERT_REVOCATION_CHECK_MODE(\"CERT_REVOCATION_CHECK_MODE\", false, String.class),\n\n  ALLOW_CERTIFICATES_WITHOUT_CRL_URL(\"ALLOW_CERTIFICATES_WITHOUT_CRL_URL\", false, Boolean.class),\n\n  PLATFORM_DETECTION_TIMEOUT_MS(\"platformDetectionTimeoutMs\", false, Integer.class),\n\n  DISABLE_PLATFORM_DETECTION(\"disablePlatformDetection\", false, Boolean.class);\n\n  // property key in string\n  private String propertyKey;\n\n  // if required  when establishing connection\n  private boolean required;\n\n  // value type\n  private Class<?> valueType;\n\n  // alias to property key\n  private String[] aliases;\n\n  // application name matcher\n  public static Pattern APPLICATION_REGEX = Pattern.compile(\"^[A-Za-z][A-Za-z0-9\\\\.\\\\-_]{1,50}$\");\n\n  public boolean isRequired() {\n    return required;\n  }\n\n  public String getPropertyKey() {\n    return propertyKey;\n  }\n\n  public Class<?> getValueType() {\n    return valueType;\n  }\n\n  SFSessionProperty(String propertyKey, boolean required, Class<?> valueType, String... aliases) {\n    this.propertyKey = propertyKey;\n    this.required = required;\n    this.valueType = valueType;\n    this.aliases = aliases;\n  }\n\n  static SFSessionProperty lookupByKey(String propertyKey) {\n    for (SFSessionProperty property : SFSessionProperty.values()) {\n      if (property.propertyKey.equalsIgnoreCase(propertyKey)) {\n        return property;\n      } else {\n        for (String alias : property.aliases) {\n          if (alias.equalsIgnoreCase(propertyKey)) {\n            return property;\n          }\n        }\n      }\n    }\n    return null;\n  }\n\n  /**\n   * Check if property value is desired class. Convert if possible\n   *\n   * @param property The session property to check\n   * @param propertyValue The property value to check\n   * @return The checked property value\n   * @throws SFException Will be thrown if an invalid property value is passed in\n   */\n  static Object checkPropertyValue(SFSessionProperty property, Object propertyValue)\n      throws SFException {\n    if (propertyValue == null) {\n      return null;\n    }\n\n    if (property.getValueType().isAssignableFrom(propertyValue.getClass())) {\n      switch (property) {\n        case APPLICATION:\n          if (APPLICATION_REGEX.matcher((String) propertyValue).find()) {\n            return propertyValue;\n          } else {\n            throw new SFException(ErrorCode.INVALID_PARAMETER_VALUE, propertyValue, property);\n          }\n        default:\n          return propertyValue;\n      }\n    } else {\n      if (property.getValueType() == Boolean.class && propertyValue instanceof String) {\n        return SFLoginInput.getBooleanValue(propertyValue);\n      } else if (property.getValueType() == Integer.class && propertyValue instanceof String) {\n        try {\n          return Integer.valueOf((String) propertyValue);\n        } catch (NumberFormatException e) {\n          throw new SFException(\n              ErrorCode.INVALID_PARAMETER_VALUE,\n              propertyValue.getClass().getName(),\n              property.getValueType().getName());\n        }\n      }\n    }\n\n    throw new SFException(\n        ErrorCode.INVALID_PARAMETER_TYPE,\n        propertyValue.getClass().getName(),\n        property.getValueType().getName());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFSqlInput.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\n\n/** This interface extends the standard {@link SQLInput} interface to provide additional methods. */\npublic interface SFSqlInput extends SQLInput {\n\n  /**\n   * Method unwrapping object of class SQLInput to object of class SfSqlInput.\n   *\n   * @param sqlInput SQLInput to consider.\n   * @return Object unwrapped to SFSqlInput class.\n   */\n  static SFSqlInput unwrap(SQLInput sqlInput) {\n    return (SFSqlInput) sqlInput;\n  }\n\n  /**\n   * Reads the next attribute in the stream and returns it as a <code>java.sql.Timestamp</code>\n   * object.\n   *\n   * @param tz timezone to consider.\n   * @return the attribute; if the value is SQL <code>NULL</code>, returns <code>null</code>\n   * @exception SQLException if a database access error occurs\n   */\n  java.sql.Timestamp readTimestamp(TimeZone tz) throws SQLException;\n  /**\n   * Reads the next attribute in the stream and returns it as a <code>Object</code> object.\n   *\n   * @param <T> the type of the class modeled by this Class object\n   * @param type Class representing the Java data type to convert the attribute to.\n   * @param tz timezone to consider.\n   * @return the attribute at the head of the stream as an {@code Object} in the Java programming\n   *     language;{@code null} if the attribute is SQL {@code NULL}\n   * @exception SQLException if a database access error occurs\n   */\n  <T> T readObject(Class<T> type, TimeZone tz) throws SQLException;\n  /**\n   * Reads the next attribute in the stream and returns it as a <code>List</code> object.\n   *\n   * @param <T> the type of the class modeled by this Class object\n   * @param type Class representing the Java data type to convert the attribute to.\n   * @return the attribute at the head of the stream as an {@code List} in the Java programming\n   *     language;{@code null} if the attribute is SQL {@code NULL}\n   * @exception SQLException if a database access error occurs\n   */\n  <T> List<T> readList(Class<T> type) throws SQLException;\n\n  /**\n   * Reads the next attribute in the stream and returns it as a <code>Map</code> object.\n   *\n   * @param <T> the type of the class modeled by this Class object\n   * @param type Class representing the Java data type to convert the attribute to.\n   * @return the attribute at the head of the stream as an {@code Map} in the Java programming\n   *     language;{@code null} if the attribute is SQL {@code NULL}\n   * @exception SQLException if a database access error occurs\n   */\n  <T> Map<String, T> readMap(Class<T> type) throws SQLException;\n  /**\n   * Reads the next attribute in the stream and returns it as a <code>Array</code> object.\n   *\n   * @param <T> the type of the class modeled by this Class object\n   * @param type Class representing the Java data type to convert the attribute to.\n   * @return the attribute at the head of the stream as an {@code Array} in the Java programming\n   *     language;{@code null} if the attribute is SQL {@code NULL}\n   * @exception SQLException if a database access error occurs\n   */\n  <T> T[] readArray(Class<T> type) throws SQLException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFStatement.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SessionUtil.DEFAULT_CLIENT_MEMORY_LIMIT;\nimport static net.snowflake.client.internal.core.SessionUtil.DEFAULT_CLIENT_PREFETCH_THREADS;\nimport static net.snowflake.client.internal.core.SessionUtil.MAX_CLIENT_CHUNK_SIZE;\nimport static net.snowflake.client.internal.core.SessionUtil.MIN_CLIENT_CHUNK_SIZE;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.recordIfExternal;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.internal.core.BasicEvent.QueryState;\nimport net.snowflake.client.internal.core.bind.BindException;\nimport net.snowflake.client.internal.core.bind.BindUploader;\nimport net.snowflake.client.internal.driver.DriverInitializer;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SnowflakeReauthenticationRequest;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.client.methods.HttpRequestBase;\n\n/** Snowflake statement */\npublic class SFStatement extends SFBaseStatement {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFStatement.class);\n\n  private SFSession session;\n\n  private SFBaseResultSet resultSet = null;\n\n  private HttpRequestBase httpRequest;\n\n  private boolean isClosed = false;\n\n  private int sequenceId = -1;\n\n  private String requestId = null;\n\n  private String sqlText = null;\n\n  private final AtomicBoolean canceling = new AtomicBoolean(false);\n\n  private boolean isFileTransfer = false;\n\n  private SnowflakeFileTransferAgent transferAgent = null;\n\n  private static final int MAX_BINDING_PARAMS_FOR_LOGGING = 1000;\n\n  /** id used in combine describe and execute */\n  private String describeJobUUID;\n\n  // list of child result objects for queries called by the current query, if any\n  private List<SFChildResult> childResults = null;\n\n  // Three parameters adjusted in conservative memory usage mode\n  private int conservativePrefetchThreads;\n  private int conservativeResultChunkSize;\n  private long conservativeMemoryLimit; // in bytes\n\n  public SFStatement(SFSession session) {\n    logger.trace(\"SFStatement(SFSession session)\", false);\n\n    this.session = session;\n    Integer queryTimeout = session == null ? null : session.getQueryTimeout();\n    this.queryTimeout = queryTimeout != null ? queryTimeout : this.queryTimeout;\n    verifyArrowSupport();\n  }\n\n  private void verifyArrowSupport() {\n    if (!DriverInitializer.isArrowEnabled()) {\n      logger.debug(\"Disable arrow support: {}\", DriverInitializer.getArrowDisableReason());\n      statementParametersMap.put(\"JDBC_QUERY_RESULT_FORMAT\", \"JSON\");\n    }\n  }\n\n  /**\n   * Sanity check query text\n   *\n   * @param sql The SQL statement to check\n   * @throws SQLException Will be thrown if sql is null or empty\n   */\n  private void sanityCheckQuery(String sql) throws SQLException {\n    if (sql == null || sql.isEmpty()) {\n      throw new SnowflakeSQLException(\n          SqlState.SQL_STATEMENT_NOT_YET_COMPLETE, ErrorCode.INVALID_SQL.getMessageCode(), sql);\n    }\n  }\n\n  /**\n   * Execute SQL query with an option for describe only\n   *\n   * @param sql sql statement\n   * @param describeOnly true if describe only\n   * @return query result set\n   * @throws SQLException if connection is already closed\n   * @throws SFException if result set is null\n   */\n  private SFBaseResultSet executeQuery(\n      String sql,\n      Map<String, ParameterBindingDTO> parametersBinding,\n      boolean describeOnly,\n      boolean asyncExec,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException {\n    sanityCheckQuery(sql);\n\n    String trimmedSql = sql.trim();\n\n    // snowflake specific client side commands\n    if (isFileTransfer(trimmedSql)) {\n      // Server side value or Connection string value is false then disable the PUT/GET command\n      if ((session != null && !(session.getJdbcEnablePutGet() && session.getEnablePutGet()))) {\n        // PUT/GET command disabled either on server side or in the client connection string\n        logger.debug(\"Executing file transfer locally is disabled: {}\", sql);\n        throw new SnowflakeSQLException(\"File transfers have been disabled.\");\n      }\n\n      // PUT/GET command\n      logger.debug(\"Executing file transfer locally: {}\", sql);\n\n      return executeFileTransfer(sql);\n    }\n\n    // NOTE: It is intentional two describeOnly parameters are specified.\n    return executeQueryInternal(\n        sql,\n        parametersBinding,\n        describeOnly,\n        describeOnly, // internal query if describeOnly is true\n        asyncExec,\n        caller,\n        execTimeData);\n  }\n\n  /**\n   * Describe a statement\n   *\n   * @param sql statement\n   * @return metadata of statement including result set metadata and binding information\n   * @throws SQLException if connection is already closed\n   * @throws SFException if result set is null\n   */\n  @Override\n  public SFPreparedStatementMetaData describe(String sql) throws SFException, SQLException {\n    SFBaseResultSet baseResultSet =\n        executeQuery(sql, null, true, false, null, new ExecTimeTelemetryData());\n\n    describeJobUUID = baseResultSet.getQueryId();\n\n    return new SFPreparedStatementMetaData(\n        baseResultSet.getMetaData(),\n        baseResultSet.getStatementType(),\n        baseResultSet.getNumberOfBinds(),\n        baseResultSet.isArrayBindSupported(),\n        baseResultSet.getMetaDataOfBinds(),\n        true); // valid metadata\n  }\n\n  /**\n   * Internal method for executing a query with bindings accepted.\n   *\n   * <p>\n   *\n   * @param sql sql statement\n   * @param parameterBindings binding information\n   * @param describeOnly true if just showing result set metadata\n   * @param internal true if internal command not showing up in the history\n   * @param caller the JDBC method that called this function, null if none\n   * @return snowflake query result set\n   * @throws SQLException if connection is already closed\n   * @throws SFException if result set is null\n   */\n  SFBaseResultSet executeQueryInternal(\n      String sql,\n      Map<String, ParameterBindingDTO> parameterBindings,\n      boolean describeOnly,\n      boolean internal,\n      boolean asyncExec,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException {\n    resetState();\n\n    logger.debug(\"ExecuteQuery: {}\", sql);\n\n    if (session == null || session.isClosed()) {\n      throw new SQLException(\"connection is closed\");\n    }\n\n    Object result =\n        executeHelper(\n            sql,\n            StmtUtil.SF_MEDIA_TYPE,\n            parameterBindings,\n            describeOnly,\n            internal,\n            asyncExec,\n            execTimeData);\n\n    if (result == null) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"got null result\");\n    }\n\n    /*\n     * we sort the result if the connection is in sorting mode\n     */\n    Object sortProperty = session.getSessionPropertyByKey(\"sort\");\n\n    boolean sortResult = sortProperty != null && (Boolean) sortProperty;\n\n    logger.debug(\"Creating result set\", false);\n\n    try {\n      JsonNode jsonResult = (JsonNode) result;\n      resultSet = SFResultSetFactory.getResultSet(jsonResult, this, sortResult, execTimeData);\n      childResults = ResultUtil.getChildResults(session, requestId, jsonResult);\n\n      // if child results are available, skip over this result set and set the\n      // current result to the first child's result.\n      // we still construct the first result set for its side effects.\n      if (!childResults.isEmpty()) {\n        SFStatementType type = childResults.get(0).getType();\n\n        // ensure first query type matches the calling JDBC method, if exists\n        if (caller == CallingMethod.EXECUTE_QUERY && !type.isGenerateResultSet()) {\n          throw new SnowflakeSQLLoggedException(\n              session, ErrorCode.QUERY_FIRST_RESULT_NOT_RESULT_SET);\n        } else if (caller == CallingMethod.EXECUTE_UPDATE && type.isGenerateResultSet()) {\n          throw new SnowflakeSQLLoggedException(\n              session, ErrorCode.UPDATE_FIRST_RESULT_NOT_UPDATE_COUNT);\n        }\n\n        // this will update resultSet to point to the first child result before we return it\n        getMoreResults();\n      }\n    } catch (SnowflakeSQLException | OutOfMemoryError ex) {\n      // snow-24428: no need to generate incident for exceptions we generate\n      // snow-29403: or client OOM\n      throw ex;\n    } catch (Throwable ex) {\n      // SNOW-22813 log exception\n      logger.error(\"Exception creating result\", ex);\n\n      throw new SFException(\n          ErrorCode.INTERNAL_ERROR, SFException.oneLiner(\"exception creating result\", ex));\n    }\n    logger.debug(\"Done creating result set\", false);\n\n    if (asyncExec) {\n      session.addQueryToActiveQueryList(resultSet.getQueryId());\n    }\n    execTimeData.setQueryId(resultSet.getQueryId());\n    return resultSet;\n  }\n\n  /**\n   * Set a time bomb to cancel the outstanding query when timeout is reached.\n   *\n   * @param executor object to execute statement cancel request\n   */\n  private void setTimeBomb(ScheduledExecutorService executor) {\n    class TimeBombTask implements Callable<Void> {\n\n      private final SFStatement statement;\n\n      private TimeBombTask(SFStatement statement) {\n        this.statement = statement;\n      }\n\n      @Override\n      public Void call() throws SQLException {\n        try {\n          statement.cancel(CancellationReason.TIMEOUT);\n        } catch (SFException ex) {\n          throw new SnowflakeSQLLoggedException(\n              session, ex.getSqlState(), ex.getVendorCode(), ex, ex.getParams());\n        }\n        return null;\n      }\n    }\n\n    executor.schedule(new TimeBombTask(this), this.queryTimeout, TimeUnit.SECONDS);\n  }\n\n  /**\n   * A helper method to build URL and submit the SQL to snowflake for exec\n   *\n   * @param sql sql statement\n   * @param mediaType media type\n   * @param bindValues map of binding values\n   * @param describeOnly whether only show the result set metadata\n   * @param internal run internal query not showing up in history\n   * @param asyncExec is async execute\n   * @param execTimeData ExecTimeTelemetryData\n   * @return raw json response\n   * @throws SFException if query is canceled\n   * @throws SnowflakeSQLException if query is already running\n   */\n  public Object executeHelper(\n      String sql,\n      String mediaType,\n      Map<String, ParameterBindingDTO> bindValues,\n      boolean describeOnly,\n      boolean internal,\n      boolean asyncExec,\n      ExecTimeTelemetryData execTimeData)\n      throws SnowflakeSQLException, SFException {\n    ScheduledExecutorService executor = null;\n\n    try {\n      synchronized (this) {\n        if (isClosed) {\n          throw new SFException(ErrorCode.STATEMENT_CLOSED);\n        }\n\n        // initialize a sequence id if not closed or not for aborting\n        if (canceling.get()) {\n          // nothing to do if canceled\n          throw new SFException(ErrorCode.QUERY_CANCELED);\n        }\n\n        if (this.requestId != null) {\n          throw new SnowflakeSQLLoggedException(\n              session,\n              ErrorCode.STATEMENT_ALREADY_RUNNING_QUERY.getMessageCode(),\n              SqlState.FEATURE_NOT_SUPPORTED);\n        }\n\n        this.requestId = UUIDUtils.getUUID().toString();\n        execTimeData.setRequestId(requestId);\n        this.sequenceId = session.getAndIncrementSequenceId();\n\n        this.sqlText = sql;\n      }\n\n      EventUtil.triggerStateTransition(\n          BasicEvent.QueryState.QUERY_STARTED,\n          String.format(QueryState.QUERY_STARTED.getArgString(), requestId));\n\n      // if there are a large number of bind values, we should upload them to stage\n      // instead of passing them in the payload (if enabled)\n      execTimeData.setBindStart();\n      int numBinds = BindUploader.arrayBindValueCount(bindValues);\n      String bindStagePath = null;\n      if (0 < session.getArrayBindStageThreshold()\n          && session.getArrayBindStageThreshold() <= numBinds\n          && !describeOnly\n          && BindUploader.isArrayBind(bindValues)) {\n        try (BindUploader uploader = BindUploader.newInstance(session, requestId)) {\n          uploader.upload(bindValues);\n          bindStagePath = uploader.getStagePath();\n        } catch (BindException ex) {\n          logger.debug(\n              \"Exception encountered trying to upload binds to stage with input stream. Attaching\"\n                  + \" binds in payload instead. \",\n              ex);\n          TelemetryData errorLog = TelemetryUtil.buildJobData(this.requestId, ex.type.field, 1);\n          this.session.getTelemetryClient(internalCallMarker()).addLogToBatch(errorLog);\n        } catch (SQLException ex) {\n          logger.debug(\n              \"Exception encountered trying to upload binds to stage with input stream. Attaching\"\n                  + \" binds in payload instead. \",\n              ex);\n          TelemetryData errorLog =\n              TelemetryUtil.buildJobData(this.requestId, TelemetryField.FAILED_BIND_UPLOAD, 1);\n          this.session.getTelemetryClient(internalCallMarker()).addLogToBatch(errorLog);\n        }\n      }\n\n      if (session.isConservativeMemoryUsageEnabled()) {\n        logger.debug(\"JDBC conservative memory usage is enabled.\", false);\n        calculateConservativeMemoryUsage();\n      }\n\n      StmtUtil.StmtInput stmtInput = new StmtUtil.StmtInput();\n      stmtInput\n          .setSql(sql)\n          .setMediaType(mediaType)\n          .setInternal(internal)\n          .setDescribeOnly(describeOnly)\n          .setAsync(asyncExec)\n          .setServerUrl(session.getServerUrl())\n          .setRequestId(requestId)\n          .setSequenceId(sequenceId)\n          .setParametersMap(statementParametersMap)\n          .setSessionToken(session.getSessionToken(internalCallMarker()))\n          .setNetworkTimeoutInMillis(session.getNetworkTimeoutInMilli())\n          .setInjectSocketTimeout(session.getInjectSocketTimeout())\n          .setInjectClientPause(session.getInjectClientPause())\n          .setCanceling(canceling)\n          .setRetry(false)\n          .setDescribedJobId(describeJobUUID)\n          .setCombineDescribe(session.getEnableCombineDescribe())\n          .setQuerySubmissionTime(System.currentTimeMillis())\n          .setServiceName(session.getServiceName())\n          .setOCSPMode(session.getOCSPMode())\n          .setHttpClientSettingsKey(session.getHttpClientKey())\n          .setMaxRetries(session.getMaxHttpRetries())\n          .setQueryContextDTO(session.isAsyncSession() ? null : session.getQueryContextDTO());\n      if (bindStagePath != null) {\n        stmtInput.setBindValues(null).setBindStage(bindStagePath);\n        // use the new SQL format for this query so dates/timestamps are parsed correctly\n        setUseNewSqlFormat(true);\n        statementParametersMap.put(\"TIMESTAMP_INPUT_FORMAT\", \"AUTO\");\n      } else {\n        stmtInput.setBindValues(bindValues).setBindStage(null);\n      }\n      if (numBinds > 0 && session.getPreparedStatementLogging()) {\n        if (numBinds > MAX_BINDING_PARAMS_FOR_LOGGING) {\n          logger.debug(\n              \"Number of binds exceeds logging limit. Printing off {} binding parameters.\",\n              MAX_BINDING_PARAMS_FOR_LOGGING);\n        } else {\n          logger.debug(\"Printing off {} binding parameters.\", numBinds);\n        }\n        int counter = 0;\n        // if it's an array bind, print off the first few rows from each column.\n        if (BindUploader.isArrayBind(bindValues)) {\n          int numRowsPrinted = MAX_BINDING_PARAMS_FOR_LOGGING / bindValues.size();\n          if (numRowsPrinted <= 0) {\n            numRowsPrinted = 1;\n          }\n          for (Map.Entry<String, ParameterBindingDTO> entry : bindValues.entrySet()) {\n            List<String> bindRows = (List<String>) entry.getValue().getValue();\n            if (numRowsPrinted >= bindRows.size()) {\n              numRowsPrinted = bindRows.size();\n            }\n            String rows = \"[\";\n            for (int i = 0; i < numRowsPrinted; i++) {\n              rows += bindRows.get(i) + \", \";\n            }\n            rows += \"]\";\n            logger.debug(\"Column {}: {}\", entry.getKey(), rows);\n            counter += numRowsPrinted;\n            if (counter >= MAX_BINDING_PARAMS_FOR_LOGGING) {\n              break;\n            }\n          }\n        }\n        // not an array, just a bunch of columns\n        else {\n          for (Map.Entry<String, ParameterBindingDTO> entry : bindValues.entrySet()) {\n            if (counter >= MAX_BINDING_PARAMS_FOR_LOGGING) {\n              break;\n            }\n            counter++;\n            logger.debug(\"Column {}: {}\", entry.getKey(), entry.getValue().getValue());\n          }\n        }\n      }\n      execTimeData.setBindEnd();\n\n      if (canceling.get()) {\n        logger.debug(\"Query cancelled\", false);\n\n        throw new SFException(ErrorCode.QUERY_CANCELED);\n      }\n\n      // if timeout is set, start a thread to cancel the request after timeout\n      // reached.\n      if (this.queryTimeout > 0) {\n        if (session.getImplicitServerSideQueryTimeout()) {\n          // Server side only query timeout\n          statementParametersMap.put(\"STATEMENT_TIMEOUT_IN_SECONDS\", this.queryTimeout);\n        } else {\n          // client side only query timeout\n          executor = Executors.newScheduledThreadPool(1);\n          setTimeBomb(executor);\n        }\n      }\n\n      StmtUtil.StmtOutput stmtOutput = null;\n      boolean sessionRenewed;\n\n      do {\n        sessionRenewed = false;\n        try {\n          stmtOutput = StmtUtil.execute(stmtInput, execTimeData, session);\n          break;\n        } catch (SnowflakeSQLException ex) {\n          renewSessionOnExpiry(ex, stmtInput.sessionToken);\n          // SNOW-18822: reset session token for the statement\n          stmtInput.setSessionToken(session.getSessionToken(internalCallMarker()));\n          stmtInput.setRetry(true);\n          sessionRenewed = true;\n          execTimeData.incrementRetryCount();\n          execTimeData.addRetryLocation(\"renewSession\");\n          logger.debug(\"Session got renewed, will retry\", false);\n        }\n      } while (sessionRenewed && !canceling.get());\n\n      // Debugging/Testing for incidents\n      if (Boolean.TRUE\n          .toString()\n          .equalsIgnoreCase(systemGetProperty(\"snowflake.enable_incident_test1\"))) {\n        throw new SFException(ErrorCode.STATEMENT_CLOSED);\n      }\n\n      synchronized (this) {\n        /*\n         * done with the remote execution of the query. set sequenceId to -1\n         * and request id to null so that we don't try to abort it upon canceling.\n         */\n        this.sequenceId = -1;\n        this.requestId = null;\n      }\n\n      if (canceling.get()) {\n        // If we are here, this is the context for the initial query that\n        // is being canceled. Raise an exception anyway here even if\n        // the server fails to abort it.\n        throw new SFException(ErrorCode.QUERY_CANCELED);\n      }\n\n      logger.debug(\"Returning from executeHelper\", false);\n\n      if (stmtOutput != null) {\n        return stmtOutput.getResult();\n      }\n      throw new SFException(ErrorCode.INTERNAL_ERROR);\n    } catch (SFException | SnowflakeSQLException ex) {\n      isClosed = true;\n      throw ex;\n    } finally {\n      if (executor != null) {\n        executor.shutdownNow();\n      }\n      // if this query enabled the new SQL format, re-disable it now\n      setUseNewSqlFormat(false);\n    }\n  }\n\n  /**\n   * calculate conservative memory limit and the number of prefetch threads before query execution\n   */\n  private void calculateConservativeMemoryUsage() {\n    int clientMemoryLimit = session.getClientMemoryLimit();\n    int clientPrefetchThread = session.getClientPrefetchThreads();\n    int clientChunkSize = session.getClientResultChunkSize();\n\n    long memoryLimitInBytes;\n    if (clientMemoryLimit == DEFAULT_CLIENT_MEMORY_LIMIT) {\n      // this is all default scenario\n      // only allows JDBC use at most 80% of free memory\n      long freeMemoryToUse = Runtime.getRuntime().freeMemory() * 8 / 10;\n      memoryLimitInBytes =\n          Math.min(\n              (long) 2 * clientPrefetchThread * clientChunkSize * 1024 * 1024, freeMemoryToUse);\n    } else {\n      memoryLimitInBytes = (long) clientMemoryLimit * 1024 * 1024;\n    }\n    conservativeMemoryLimit = memoryLimitInBytes;\n    reducePrefetchThreadsAndChunkSizeToFitMemoryLimit(\n        conservativeMemoryLimit, clientPrefetchThread, clientChunkSize);\n  }\n\n  private void updateConservativeResultChunkSize(int clientChunkSize) {\n    if (clientChunkSize != conservativeResultChunkSize) {\n      logger.debug(\n          \"conservativeResultChunkSize changed from {} to {}\",\n          conservativeResultChunkSize,\n          clientChunkSize);\n      conservativeResultChunkSize = clientChunkSize;\n      statementParametersMap.put(\"CLIENT_RESULT_CHUNK_SIZE\", conservativeResultChunkSize);\n    }\n  }\n\n  private void reducePrefetchThreadsAndChunkSizeToFitMemoryLimit(\n      long clientMemoryLimit, int clientPrefetchThread, int clientChunkSize) {\n    if (clientPrefetchThread != DEFAULT_CLIENT_PREFETCH_THREADS) {\n      // prefetch threads are configured so only reduce chunk size\n      conservativePrefetchThreads = clientPrefetchThread;\n      for (;\n          clientChunkSize >= MIN_CLIENT_CHUNK_SIZE;\n          clientChunkSize -= session.getConservativeMemoryAdjustStep()) {\n        if (clientMemoryLimit >= (long) 2 * clientPrefetchThread * clientChunkSize * 1024 * 1024) {\n          updateConservativeResultChunkSize(clientChunkSize);\n          return;\n        }\n      }\n      updateConservativeResultChunkSize(MIN_CLIENT_CHUNK_SIZE);\n    } else {\n      // reduce both prefetch threads and chunk size\n      while (clientPrefetchThread > 1) {\n        for (clientChunkSize = MAX_CLIENT_CHUNK_SIZE;\n            clientChunkSize >= MIN_CLIENT_CHUNK_SIZE;\n            clientChunkSize -= session.getConservativeMemoryAdjustStep()) {\n          if (clientMemoryLimit\n              >= (long) 2 * clientPrefetchThread * clientChunkSize * 1024 * 1024) {\n            conservativePrefetchThreads = clientPrefetchThread;\n            updateConservativeResultChunkSize(clientChunkSize);\n            return;\n          }\n        }\n        clientPrefetchThread--;\n      }\n      conservativePrefetchThreads = clientPrefetchThread;\n      updateConservativeResultChunkSize(MIN_CLIENT_CHUNK_SIZE);\n    }\n  }\n\n  /**\n   * @return conservative prefetch threads before fetching results\n   */\n  public int getConservativePrefetchThreads() {\n    return conservativePrefetchThreads;\n  }\n\n  /**\n   * @return conservative memory limit before fetching results\n   */\n  public long getConservativeMemoryLimit() {\n    return conservativeMemoryLimit;\n  }\n\n  /**\n   * Return an array of child query ID for the given query ID.\n   *\n   * <p>If the given query ID is for a multiple statements query, it returns an array of its child\n   * statements, otherwise, it returns an array to include the given query ID.\n   *\n   * @param queryID The given query ID\n   * @return An array of child query IDs\n   * @throws SQLException If the query is running or the corresponding query is FAILED.\n   */\n  @Override\n  public String[] getChildQueryIds(String queryID) throws SQLException {\n    QueryStatus queryStatus = session.getQueryStatus(queryID);\n    if (queryStatus.isStillRunning()) {\n      throw new SQLException(\n          \"Status of query associated with resultSet is \"\n              + queryStatus.getDescription()\n              + \". Results not generated.\");\n    }\n    try {\n      JsonNode jsonResult;\n      try {\n        jsonResult = StmtUtil.getQueryResultJSON(queryID, session);\n      } catch (SnowflakeSQLException ex) {\n        renewSessionOnExpiry(ex, session.getSessionToken(internalCallMarker()));\n        logger.debug(\"Session renewed during getChildQueryIds, retrying\", false);\n        jsonResult = StmtUtil.getQueryResultJSON(queryID, session);\n      }\n\n      List<SFChildResult> childResults = ResultUtil.getChildResults(session, requestId, jsonResult);\n      List<String> resultList = new ArrayList<>();\n      for (int i = 0; i < childResults.size(); i++) {\n        resultList.add(childResults.get(i).getId());\n      }\n      if (resultList.isEmpty()) {\n        resultList.add(queryID);\n      }\n      String[] result = new String[resultList.size()];\n      resultList.toArray(result);\n      return result;\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(ex);\n    }\n  }\n\n  @Override\n  public SFBaseResultSet execute(\n      String sql,\n      Map<String, ParameterBindingDTO> parametersBinding,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException {\n    return execute(sql, false, parametersBinding, caller, execTimeData);\n  }\n\n  /**\n   * A helper method to build URL and cancel the SQL for exec\n   *\n   * @param sql sql statement\n   * @param mediaType media type\n   * @param cancellationReason reason for the cancellation\n   * @throws SnowflakeSQLException if failed to cancel the statement\n   * @throws SFException if statement is already closed\n   */\n  private void cancelHelper(String sql, String mediaType, CancellationReason cancellationReason)\n      throws SnowflakeSQLException, SFException {\n    synchronized (this) {\n      if (isClosed) {\n        throw new SFException(ErrorCode.INTERNAL_ERROR, \"statement already closed\");\n      }\n    }\n\n    StmtUtil.StmtInput stmtInput = new StmtUtil.StmtInput();\n    stmtInput\n        .setServerUrl(session.getServerUrl())\n        .setSql(sql)\n        .setMediaType(mediaType)\n        .setRequestId(requestId)\n        .setSessionToken(session.getSessionToken(internalCallMarker()))\n        .setServiceName(session.getServiceName())\n        .setOCSPMode(session.getOCSPMode())\n        .setMaxRetries(session.getMaxHttpRetries())\n        .setHttpClientSettingsKey(session.getHttpClientKey());\n\n    StmtUtil.cancel(stmtInput, cancellationReason, session);\n\n    synchronized (this) {\n      /*\n       * done with the remote execution of the query. set sequenceId to -1\n       * and request id to null so that we don't try to abort it again upon\n       * canceling.\n       */\n      this.sequenceId = -1;\n      this.requestId = null;\n    }\n  }\n\n  /**\n   * Execute sql\n   *\n   * @param sql sql statement.\n   * @param asyncExec is async exec\n   * @param parametersBinding parameters to bind\n   * @param caller the JDBC interface method that called this method, if any\n   * @param execTimeData ExecTimeTelemetryData\n   * @return whether there is result set or not\n   * @throws SQLException if failed to execute sql\n   * @throws SFException exception raised from Snowflake components\n   * @throws SQLException if SQL error occurs\n   */\n  public SFBaseResultSet execute(\n      String sql,\n      boolean asyncExec,\n      Map<String, ParameterBindingDTO> parametersBinding,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException {\n    TelemetryService.getInstance().updateContext(session.getSnowflakeConnectionString());\n    sanityCheckQuery(sql);\n\n    session.injectedDelay();\n\n    if (session.getPreparedStatementLogging()) {\n      logger.info(\"Execute: {}\", sql);\n    } else {\n      logger.debug(\"Execute: {}\", sql);\n    }\n\n    String trimmedSql = sql.trim();\n\n    if (trimmedSql.length() >= 20 && trimmedSql.toLowerCase().startsWith(\"set-sf-property\")) {\n      executeSetProperty(sql);\n      return null;\n    }\n    return executeQuery(sql, parametersBinding, false, asyncExec, caller, execTimeData);\n  }\n\n  private SFBaseResultSet executeFileTransfer(String sql) throws SQLException, SFException {\n    session.injectedDelay();\n\n    resetState();\n\n    logger.debug(\"Entering executeFileTransfer\", false);\n\n    isFileTransfer = true;\n    transferAgent = new SnowflakeFileTransferAgent(sql, session, this, internalCallMarker());\n\n    try {\n      transferAgent.execute();\n\n      logger.debug(\"Setting result set\", false);\n\n      resultSet = (SFFixedViewResultSet) transferAgent.getResultSet();\n      childResults = Collections.emptyList();\n\n      logger.debug(\"Number of cols: {}\", resultSet.getMetaData().getColumnCount());\n      logger.debug(\"Completed transferring data\", false);\n      return resultSet;\n    } catch (SQLException ex) {\n      logger.debug(\"Exception: {}\", ex.getMessage());\n      throw ex;\n    }\n  }\n\n  @Override\n  public void close() {\n    logger.trace(\"void close()\", false);\n\n    if (requestId != null) {\n      EventUtil.triggerStateTransition(\n          BasicEvent.QueryState.QUERY_ENDED,\n          String.format(QueryState.QUERY_ENDED.getArgString(), requestId));\n    }\n\n    resultSet = null;\n    childResults = null;\n    isClosed = true;\n\n    if (httpRequest != null) {\n      logger.debug(\"Releasing connection for the http request\", false);\n\n      httpRequest.releaseConnection();\n      httpRequest = null;\n    }\n\n    session.getTelemetryClient(internalCallMarker()).sendBatchAsync();\n\n    isFileTransfer = false;\n    transferAgent = null;\n  }\n\n  @Override\n  public void cancel() throws SFException, SQLException {\n    logger.trace(\"void cancel()\", false);\n    cancel(CancellationReason.UNKNOWN);\n  }\n\n  @Override\n  public void cancel(CancellationReason cancellationReason) throws SFException, SQLException {\n    logger.trace(\"void cancel(CancellationReason)\", false);\n\n    if (canceling.get()) {\n      logger.debug(\"Query is already cancelled\", false);\n      return;\n    }\n\n    canceling.set(true);\n\n    if (isFileTransfer) {\n      if (transferAgent != null) {\n        logger.debug(\"Cancel file transferring ... \", false);\n        transferAgent.cancel();\n      }\n    } else {\n      synchronized (this) {\n        // the query hasn't been sent to GS yet, just mark the stmt closed\n        if (requestId == null) {\n          logger.debug(\"No remote query outstanding\", false);\n\n          return;\n        }\n      }\n\n      // cancel the query on the server side if it has been issued\n      cancelHelper(this.sqlText, StmtUtil.SF_MEDIA_TYPE, cancellationReason);\n    }\n  }\n\n  private void resetState() {\n    resultSet = null;\n    childResults = null;\n\n    if (httpRequest != null) {\n      httpRequest.releaseConnection();\n      httpRequest = null;\n    }\n\n    isClosed = false;\n    sequenceId = -1;\n    requestId = null;\n    sqlText = null;\n    canceling.set(false);\n\n    isFileTransfer = false;\n    transferAgent = null;\n  }\n\n  @Override\n  public SFBaseSession getSFBaseSession() {\n    return getSFBaseSession(null);\n  }\n\n  public SFBaseSession getSFBaseSession(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SFStatement\", \"getSFBaseSession\", internalCallMarker);\n    return session;\n  }\n\n  // *NOTE* this new SQL format is incomplete. It should only be used under certain circumstances.\n  private void setUseNewSqlFormat(boolean useNewSqlFormat) throws SFException {\n    this.addProperty(\"NEW_SQL_FORMAT\", useNewSqlFormat);\n  }\n\n  /**\n   * Renew the session if the given exception indicates session token expiry (error code 390112). If\n   * the error is not session expiry, the original exception is re-thrown.\n   */\n  void renewSessionOnExpiry(SnowflakeSQLException ex, String prevSessionToken)\n      throws SFException, SnowflakeSQLException {\n    if (ex.getErrorCode() != Constants.SESSION_EXPIRED_GS_CODE) {\n      throw ex;\n    }\n    try {\n      session.renewSession(prevSessionToken);\n    } catch (SnowflakeReauthenticationRequest ex0) {\n      if (session.isExternalbrowserOrOAuthFullFlowAuthenticator()) {\n        session.open(internalCallMarker());\n      } else {\n        throw ex0;\n      }\n    }\n  }\n\n  public boolean getMoreResults() throws SQLException {\n    return getMoreResults(Statement.CLOSE_CURRENT_RESULT);\n  }\n\n  @Override\n  public boolean getMoreResults(int current) throws SQLException {\n    // clean up current result, if exists\n    if (resultSet != null\n        && (current == Statement.CLOSE_CURRENT_RESULT || current == Statement.CLOSE_ALL_RESULTS)) {\n      resultSet.close();\n    }\n    resultSet = null;\n\n    // verify if more results exist\n    if (childResults == null || childResults.isEmpty()) {\n      return false;\n    }\n\n    // fetch next result using the query id\n    SFChildResult nextResult = childResults.remove(0);\n    try {\n      JsonNode result;\n      try {\n        result = StmtUtil.getQueryResultJSON(nextResult.getId(), session);\n      } catch (SnowflakeSQLException ex) {\n        renewSessionOnExpiry(ex, session.getSessionToken(internalCallMarker()));\n        logger.debug(\"Session renewed during getMoreResults, retrying child result fetch\", false);\n        result = StmtUtil.getQueryResultJSON(nextResult.getId(), session);\n      }\n\n      Object sortProperty = session.getSessionPropertyByKey(\"sort\");\n      boolean sortResult = sortProperty != null && (Boolean) sortProperty;\n      resultSet =\n          SFResultSetFactory.getResultSet(result, this, sortResult, new ExecTimeTelemetryData());\n      // override statement type so we can treat the result set like a result of\n      // the original statement called (and not the result scan)\n      resultSet.setStatementType(nextResult.getType());\n\n      return nextResult.getType().isGenerateResultSet();\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(ex);\n    }\n  }\n\n  @Override\n  public SFBaseResultSet getResultSet() {\n    return resultSet;\n  }\n\n  @Override\n  public boolean hasChildren() {\n    return !childResults.isEmpty();\n  }\n\n  @Override\n  public SFBaseResultSet asyncExecute(\n      String sql,\n      Map<String, ParameterBindingDTO> parametersBinding,\n      CallingMethod caller,\n      ExecTimeTelemetryData execTimeData)\n      throws SQLException, SFException {\n    return execute(sql, true, parametersBinding, caller, execTimeData);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFStatementType.java",
    "content": "/*\n * To change this license header, choose License Headers in Project Properties.\n * To change this template file, choose Tools | Templates\n * and open the template in the editor.\n */\npackage net.snowflake.client.internal.core;\n\n/**\n * Used to check if the statementType belongs to DDL or DML The enum of each statement type is\n * defined in com.snowflake.core.Statement.java\n */\npublic enum SFStatementType {\n  /**\n   * By default we set query will generate result set, which means executeUpdate will throw\n   * exception. In that way, we have a clear control of what statements can be executed by\n   * executeUpdate\n   */\n  UNKNOWN(0x0000, true),\n\n  SELECT(0x1000, true),\n\n  /** Data Manipulation Language */\n  DML(0x3000, false),\n  INSERT(0x3000 + 0x100, false),\n  UPDATE(0x3000 + 0x200, false),\n  DELETE(0x3000 + 0x300, false),\n  MERGE(0x3000 + 0x400, false),\n  MULTI_INSERT(0x3000 + 0x500, false),\n  COPY(0x3000 + 0x600, false),\n  UNLOAD(0x3000 + 0x700, false),\n  RECLUSTER(0x3000 + 0x800, false),\n\n  /** System Command Language (USE, DESCRIBE etc) */\n  SCL(0x4000, false),\n  ALTER_SESSION(0x4000 + 0x100, false),\n  USE(0x4000 + 0x300, false),\n  USE_DATABASE(0x4000 + 0x300 + 0x01, false),\n  USE_SCHEMA(0x4000 + 0x300 + 0x02, false),\n  USE_WAREHOUSE(0x4000 + 0x300 + 0x03, false),\n  SHOW(0x4000 + 0x400, true),\n  DESCRIBE(0x4000 + 0x500, true),\n  LIST(0x4000 + 0x700 + 0x01, true),\n\n  /** Transaction Command Language (COMMIT, ROLLBACK) */\n  TCL(0x5000, false),\n\n  /** Data Definition Language */\n  DDL(0x6000, false),\n  ALTER_USER_MANAGE_PATS(0x6000 + 0x200 + 0x44, true),\n\n  /** Stage-related commands (other than LIST) */\n  GET(0x7000 + 0x100 + 0x01, true),\n  PUT(0x7000 + 0x100 + 0x02, true),\n  REMOVE(0x7000 + 0x100 + 0x03, true),\n  ;\n\n  private final long statementTypeId;\n\n  /**\n   * Used by Statement.executeUpdate to determine if we should return update counts or throw\n   * exception. In general, JDBC should only throw exception if a result set object is required.\n   */\n  private final boolean generateResultSet;\n\n  private static final long LEVEL_3_RANGE = 0x1000;\n\n  SFStatementType(long id, boolean generateResultSet) {\n    this.statementTypeId = id;\n    this.generateResultSet = generateResultSet;\n  }\n\n  public static SFStatementType lookUpTypeById(long id) {\n    for (SFStatementType type : SFStatementType.values()) {\n      if (type.getStatementTypeId() == id) {\n        return type;\n      }\n    }\n\n    // if not specific type is found, then return category of statement\n    if (id >= SCL.getStatementTypeId() && id < SCL.getStatementTypeId() + LEVEL_3_RANGE) {\n      return SCL;\n    } else if (id >= TCL.getStatementTypeId() && id < TCL.getStatementTypeId() + LEVEL_3_RANGE) {\n      return TCL;\n    } else if (id >= DDL.getStatementTypeId() && id < DDL.getStatementTypeId() + LEVEL_3_RANGE) {\n      return DDL;\n    } else {\n      return UNKNOWN;\n    }\n  }\n\n  public long getStatementTypeId() {\n    return statementTypeId;\n  }\n\n  public boolean isDDL() {\n    return this == DDL;\n  }\n\n  public boolean isDML() {\n    return statementTypeId >= DML.getStatementTypeId()\n        && statementTypeId < DML.getStatementTypeId() + LEVEL_3_RANGE;\n  }\n\n  public boolean isTCL() {\n    return this == TCL;\n  }\n\n  public boolean isSCL() {\n    return this == SCL;\n  }\n\n  public boolean isGenerateResultSet() {\n    return this.generateResultSet;\n  }\n\n  public boolean isSelect() {\n    return this.statementTypeId == SELECT.statementTypeId;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SFTrustManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.JsonNodeType;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.math.BigInteger;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.security.InvalidKeyException;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.Signature;\nimport java.security.SignatureException;\nimport java.security.cert.CertificateEncodingException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.text.MessageFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.Enumeration;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.jdbc.OCSPErrorCode;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.DecorrelatedJitterBackoff;\nimport net.snowflake.client.internal.util.SFPair;\nimport org.apache.commons.codec.binary.Base64;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.ssl.SSLInitializationException;\nimport org.bouncycastle.asn1.ASN1Encodable;\nimport org.bouncycastle.asn1.ASN1Integer;\nimport org.bouncycastle.asn1.ASN1ObjectIdentifier;\nimport org.bouncycastle.asn1.ASN1OctetString;\nimport org.bouncycastle.asn1.DEROctetString;\nimport org.bouncycastle.asn1.DLSequence;\nimport org.bouncycastle.asn1.ocsp.CertID;\nimport org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;\nimport org.bouncycastle.asn1.x509.AlgorithmIdentifier;\nimport org.bouncycastle.asn1.x509.Certificate;\nimport org.bouncycastle.asn1.x509.Extension;\nimport org.bouncycastle.asn1.x509.Extensions;\nimport org.bouncycastle.asn1.x509.GeneralName;\nimport org.bouncycastle.asn1.x509.TBSCertificate;\nimport org.bouncycastle.cert.X509CertificateHolder;\nimport org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;\nimport org.bouncycastle.cert.ocsp.BasicOCSPResp;\nimport org.bouncycastle.cert.ocsp.CertificateID;\nimport org.bouncycastle.cert.ocsp.CertificateStatus;\nimport org.bouncycastle.cert.ocsp.OCSPException;\nimport org.bouncycastle.cert.ocsp.OCSPReq;\nimport org.bouncycastle.cert.ocsp.OCSPReqBuilder;\nimport org.bouncycastle.cert.ocsp.OCSPResp;\nimport org.bouncycastle.cert.ocsp.RevokedStatus;\nimport org.bouncycastle.cert.ocsp.SingleResp;\nimport org.bouncycastle.operator.DigestCalculator;\n\n/**\n * SFTrustManager is a composite of TrustManager of the default JVM TrustManager and Snowflake OCSP\n * revocation status checker. Use this when initializing SSLContext object.\n *\n * <pre>{@code\n * TrustManager[] trustManagers = {new SFTrustManager()};\n * SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n * sslContext.init(null, trustManagers, null);\n * }</pre>\n */\npublic class SFTrustManager extends X509ExtendedTrustManager {\n  /** Test System Parameters. Not used in the production */\n  public static final String SF_OCSP_RESPONSE_CACHE_SERVER_URL =\n      \"SF_OCSP_RESPONSE_CACHE_SERVER_URL\";\n\n  public static final String SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED =\n      \"SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED\";\n  public static final String SF_OCSP_TEST_INJECT_VALIDITY_ERROR =\n      \"SF_OCSP_TEST_INJECT_VALIDITY_ERROR\";\n  public static final String SF_OCSP_TEST_INJECT_UNKNOWN_STATUS =\n      \"SF_OCSP_TEST_INJECT_UNKNOWN_STATUS\";\n  public static final String SF_OCSP_TEST_RESPONDER_URL = \"SF_OCSP_TEST_RESPONDER_URL\";\n  public static final String SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT =\n      \"SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT\";\n  public static final String SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT =\n      \"SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT\";\n  public static final String SF_OCSP_TEST_INVALID_SIGNING_CERT =\n      \"SF_OCSP_TEST_INVALID_SIGNING_CERT\";\n  public static final String SF_OCSP_TEST_NO_OCSP_RESPONDER_URL =\n      \"SF_OCSP_TEST_NO_OCSP_RESPONDER_URL\";\n  /** OCSP response cache file name. Should be identical to other driver's cache file name. */\n  static final String CACHE_FILE_NAME = \"ocsp_response_cache.json\";\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFTrustManager.class);\n  private static final ASN1ObjectIdentifier OIDocsp =\n      new ASN1ObjectIdentifier(\"1.3.6.1.5.5.7.48.1\").intern();\n  private static final ASN1ObjectIdentifier SHA1RSA =\n      new ASN1ObjectIdentifier(\"1.2.840.113549.1.1.5\").intern();\n  private static final ASN1ObjectIdentifier SHA256RSA =\n      new ASN1ObjectIdentifier(\"1.2.840.113549.1.1.11\").intern();\n  private static final ASN1ObjectIdentifier SHA384RSA =\n      new ASN1ObjectIdentifier(\"1.2.840.113549.1.1.12\").intern();\n  private static final ASN1ObjectIdentifier SHA512RSA =\n      new ASN1ObjectIdentifier(\"1.2.840.113549.1.1.13\").intern();\n\n  private static final String ALGORITHM_SHA1_NAME = \"SHA-1\";\n  /** Object mapper for JSON encoding and decoding */\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n  /** System property name to specify cache directory. */\n  private static final String CACHE_DIR_PROP = \"net.snowflake.jdbc.ocspResponseCacheDir\";\n  /** Environment name to specify the cache directory. Used if system property not set. */\n  private static final String CACHE_DIR_ENV = \"SF_OCSP_RESPONSE_CACHE_DIR\";\n  /** OCSP response cache entry expiration time (s) */\n  private static final long CACHE_EXPIRATION_IN_SECONDS = 432000L;\n  /** OCSP response cache lock file expiration time (s) */\n  private static final long CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS = 60L;\n  /** Default OCSP Cache server connection timeout */\n  private static final int DEFAULT_OCSP_CACHE_SERVER_CONNECTION_TIMEOUT = 5000;\n  /** Default OCSP responder connection timeout */\n  private static final int DEFAULT_OCSP_RESPONDER_CONNECTION_TIMEOUT = 10000;\n  /** Default OCSP Cache server host name prefix */\n  private static final String DEFAULT_OCSP_CACHE_HOST_PREFIX = \"http://ocsp.snowflakecomputing.\";\n  /** Default domain for OCSP cache host */\n  private static final String DEFAULT_OCSP_CACHE_HOST_DOMAIN = \"com\";\n\n  /** OCSP response file cache directory */\n  private static final FileCacheManager fileCacheManager;\n  /** Tolerable validity date range ratio. */\n  private static final float TOLERABLE_VALIDITY_RANGE_RATIO = 0.01f;\n  /** Maximum clocktime skew (ms) */\n  private static final long MAX_CLOCK_SKEW_IN_MILLISECONDS = 900000L;\n  /** Minimum cache warm up time (ms) */\n  private static final long MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS = 18000000L;\n  /** Initial sleeping time in retry (ms) */\n  private static final long INITIAL_SLEEPING_TIME_IN_MILLISECONDS = 1000L;\n  /** Maximum sleeping time in retry (ms) */\n  private static final long MAX_SLEEPING_TIME_IN_MILLISECONDS = 16000L;\n  /** Map from signature algorithm ASN1 object to the name. */\n  private static final Map<ASN1ObjectIdentifier, String> SIGNATURE_OID_TO_STRING =\n      new ConcurrentHashMap<>();\n  /** Map from OCSP response code to a string representation. */\n  private static final Map<Integer, String> OCSP_RESPONSE_CODE_TO_STRING =\n      new ConcurrentHashMap<>();\n\n  private static final Object ROOT_CA_LOCK = new Object();\n  /** OCSP Response cache */\n  private static final Map<OcspResponseCacheKey, SFPair<Long, String>> OCSP_RESPONSE_CACHE =\n      new ConcurrentHashMap<>();\n  /** Date and timestamp format */\n  private static final SimpleDateFormat DATE_FORMAT_UTC =\n      new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n\n  /** OCSP Response Cache server Retry URL pattern */\n  static String SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN;\n  /** OCSP response cache server URL. */\n  static String SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE;\n\n  private static JcaX509CertificateConverter CONVERTER_X509 = new JcaX509CertificateConverter();\n  /** RootCA cache */\n  private static Map<Integer, Certificate> ROOT_CA = new ConcurrentHashMap<>();\n\n  private static final AtomicBoolean WAS_CACHE_UPDATED = new AtomicBoolean();\n  private static final AtomicBoolean WAS_CACHE_READ = new AtomicBoolean();\n  /** OCSP HTTP client */\n  private static final Map<HttpClientSettingsKey, CloseableHttpClient> ocspCacheServerClient =\n      new ConcurrentHashMap<>();\n  /** OCSP event types */\n  public static String SF_OCSP_EVENT_TYPE_REVOKED_CERTIFICATE_ERROR = \"RevokedCertificateError\";\n\n  public static String SF_OCSP_EVENT_TYPE_VALIDATION_ERROR = \"OCSPValidationError\";\n\n  private final HttpClientSettingsKey proxySettingsKey;\n\n  static {\n    // init OCSP response cache file manager\n    fileCacheManager =\n        new FileCacheManagerBuilder()\n            .setCacheDirectorySystemProperty(CACHE_DIR_PROP)\n            .setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV)\n            .setBaseCacheFileName(CACHE_FILE_NAME)\n            .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS)\n            .setOnlyOwnerPermissions(false)\n            .build();\n  }\n\n  static {\n    SIGNATURE_OID_TO_STRING.put(SHA1RSA, \"SHA1withRSA\");\n    SIGNATURE_OID_TO_STRING.put(SHA256RSA, \"SHA256withRSA\");\n    SIGNATURE_OID_TO_STRING.put(SHA384RSA, \"SHA384withRSA\");\n    SIGNATURE_OID_TO_STRING.put(SHA512RSA, \"SHA512withRSA\");\n  }\n\n  static {\n    OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.SUCCESSFUL, \"successful\");\n    OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.MALFORMED_REQUEST, \"malformedRequest\");\n    OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.INTERNAL_ERROR, \"internalError\");\n    OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.TRY_LATER, \"tryLater\");\n    OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.SIG_REQUIRED, \"sigRequired\");\n    OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.UNAUTHORIZED, \"unauthorized\");\n  }\n\n  static {\n    DATE_FORMAT_UTC.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  /** The default JVM Trust manager. */\n  private final X509TrustManager trustManager;\n  /** The default JVM Extended Trust Manager */\n  private final X509ExtendedTrustManager exTrustManager;\n\n  OCSPCacheServer ocspCacheServer = new OCSPCacheServer();\n  /** OCSP mode */\n  private OCSPMode ocspMode;\n\n  /**\n   * Constructor with the cache file. If not specified, the default cachefile is used.\n   *\n   * @param key HttpClientSettingsKey\n   * @param cacheFile cache file.\n   */\n  SFTrustManager(HttpClientSettingsKey key, File cacheFile) {\n    this.ocspMode = key.getOcspMode();\n    this.proxySettingsKey = key;\n    this.trustManager = getTrustManager(TrustManagerFactory.getDefaultAlgorithm());\n\n    if (trustManager instanceof X509ExtendedTrustManager) {\n      this.exTrustManager = (X509ExtendedTrustManager) trustManager;\n    } else {\n      logger.debug(\"Standard X509TrustManager is used instead of X509ExtendedTrustManager.\");\n      this.exTrustManager = null;\n    }\n\n    checkNewOCSPEndpointAvailability();\n\n    if (cacheFile != null) {\n      fileCacheManager.overrideCacheFile(cacheFile);\n    }\n    if (!WAS_CACHE_READ.getAndSet(true)) {\n      // read cache file once\n      JsonNode res = fileCacheManager.readCacheFile();\n      readJsonStoreCache(res);\n    }\n\n    logger.debug(\n        \"Initializing trust manager with OCSP mode: {}, cache file: {}\", ocspMode, cacheFile);\n  }\n\n  /** Deletes OCSP response cache file from disk. */\n  public static void deleteCache() {\n    fileCacheManager.deleteCacheFile();\n  }\n\n  public static void cleanTestSystemParameters() {\n    System.clearProperty(SF_OCSP_RESPONSE_CACHE_SERVER_URL);\n    System.clearProperty(SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED);\n    System.clearProperty(SF_OCSP_TEST_INJECT_VALIDITY_ERROR);\n    System.clearProperty(SF_OCSP_TEST_INJECT_UNKNOWN_STATUS);\n    System.clearProperty(SF_OCSP_TEST_RESPONDER_URL);\n    System.clearProperty(SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT);\n    System.clearProperty(SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT);\n    System.clearProperty(SF_OCSP_TEST_INVALID_SIGNING_CERT);\n    System.clearProperty(SF_OCSP_TEST_NO_OCSP_RESPONDER_URL);\n  }\n\n  /**\n   * Reset OCSP Cache server URL\n   *\n   * @param ocspCacheServerUrl OCSP Cache server URL\n   */\n  static void resetOCSPResponseCacherServerURL(String ocspCacheServerUrl) throws IOException {\n    if (ocspCacheServerUrl == null || SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN != null) {\n      return;\n    }\n    SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = ocspCacheServerUrl;\n    if (!SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE.startsWith(DEFAULT_OCSP_CACHE_HOST_PREFIX)) {\n      URL url = new URL(SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);\n      if (url.getPort() > 0) {\n        SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN =\n            String.format(\n                \"%s://%s:%d/retry/%s\", url.getProtocol(), url.getHost(), url.getPort(), \"%s/%s\");\n      } else {\n        SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN =\n            String.format(\"%s://%s/retry/%s\", url.getProtocol(), url.getHost(), \"%s/%s\");\n      }\n      logger.debug(\n          \"Reset OCSP response cache server URL to: {}\",\n          SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);\n    }\n  }\n\n  static void setOCSPResponseCacheServerURL(String serverURL) {\n    String ocspCacheUrl = systemGetProperty(SF_OCSP_RESPONSE_CACHE_SERVER_URL);\n    if (ocspCacheUrl != null) {\n      SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = ocspCacheUrl;\n    }\n    try {\n      ocspCacheUrl = systemGetEnv(SF_OCSP_RESPONSE_CACHE_SERVER_URL);\n      if (ocspCacheUrl != null) {\n        SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = ocspCacheUrl;\n      }\n    } catch (Throwable ex) {\n      logger.debug(\n          \"Failed to get environment variable \" + SF_OCSP_RESPONSE_CACHE_SERVER_URL + \". Ignored\",\n          true);\n    }\n    if (SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE == null) {\n      String topLevelDomain = DEFAULT_OCSP_CACHE_HOST_DOMAIN;\n      try {\n        URL url = new URL(serverURL);\n        int domainIndex = url.getHost().lastIndexOf(\".\") + 1;\n        topLevelDomain = url.getHost().substring(domainIndex);\n      } catch (Exception e) {\n        logger.debug(\"Exception while setting top level domain (for OCSP)\", e);\n      }\n      SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE =\n          String.format(\"%s%s/%s\", DEFAULT_OCSP_CACHE_HOST_PREFIX, topLevelDomain, CACHE_FILE_NAME);\n    }\n    logger.debug(\"Set OCSP response cache server to: {}\", SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);\n  }\n\n  private static boolean useOCSPResponseCacheServer() {\n    String ocspCacheServerEnabled = systemGetProperty(SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED);\n    if (Boolean.FALSE.toString().equalsIgnoreCase(ocspCacheServerEnabled)) {\n      logger.debug(\"No OCSP Response Cache Server is used.\", false);\n      return false;\n    }\n    try {\n      ocspCacheServerEnabled = systemGetEnv(SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED);\n      if (Boolean.FALSE.toString().equalsIgnoreCase(ocspCacheServerEnabled)) {\n        logger.debug(\"No OCSP Response Cache Server is used.\", false);\n        return false;\n      }\n    } catch (Throwable ex) {\n      logger.debug(\n          \"Failed to get environment variable \"\n              + SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED\n              + \". Ignored\",\n          false);\n    }\n    return true;\n  }\n\n  /**\n   * Convert cache key to base64 encoded cert id\n   *\n   * @param ocsp_cache_key Cache key to encode\n   */\n  private static String encodeCacheKey(OcspResponseCacheKey ocsp_cache_key) {\n    try {\n      DigestCalculator digest = new SHA1DigestCalculator();\n      AlgorithmIdentifier algo = digest.getAlgorithmIdentifier();\n      ASN1OctetString nameHash = ASN1OctetString.getInstance(ocsp_cache_key.nameHash);\n      ASN1OctetString keyHash = ASN1OctetString.getInstance(ocsp_cache_key.keyHash);\n      ASN1Integer snumber = new ASN1Integer(ocsp_cache_key.serialNumber);\n      CertID cid = new CertID(algo, nameHash, keyHash, snumber);\n      return Base64.encodeBase64String(cid.toASN1Primitive().getEncoded());\n    } catch (Exception ex) {\n      logger.debug(\"Failed to encode cache key to base64 encoded cert id\", false);\n    }\n    return null;\n  }\n\n  /**\n   * CertificateID to string\n   *\n   * @param certificateID CertificateID\n   * @return a string representation of CertificateID\n   */\n  private static String CertificateIDToString(CertificateID certificateID) {\n    return String.format(\n        \"CertID. NameHash: %s, KeyHash: %s, Serial Number: %s\",\n        SnowflakeUtil.byteToHexString(certificateID.getIssuerNameHash()),\n        SnowflakeUtil.byteToHexString(certificateID.getIssuerKeyHash()),\n        MessageFormat.format(\"{0,number,#}\", certificateID.getSerialNumber()));\n  }\n\n  /**\n   * Decodes OCSP Response Cache key from JSON\n   *\n   * @param elem A JSON element\n   * @return OcspResponseCacheKey object\n   */\n  private static SFPair<OcspResponseCacheKey, SFPair<Long, String>> decodeCacheFromJSON(\n      Map.Entry<String, JsonNode> elem) throws IOException {\n    long currentTimeSecond = new Date().getTime() / 1000;\n    byte[] certIdDer = Base64.decodeBase64(elem.getKey());\n    DLSequence rawCertId = (DLSequence) ASN1ObjectIdentifier.fromByteArray(certIdDer);\n    ASN1Encodable[] rawCertIdArray = rawCertId.toArray();\n    byte[] issuerNameHashDer = ((DEROctetString) rawCertIdArray[1]).getEncoded();\n    byte[] issuerKeyHashDer = ((DEROctetString) rawCertIdArray[2]).getEncoded();\n    BigInteger serialNumber = ((ASN1Integer) rawCertIdArray[3]).getValue();\n\n    OcspResponseCacheKey k =\n        new OcspResponseCacheKey(issuerNameHashDer, issuerKeyHashDer, serialNumber);\n\n    JsonNode ocspRespBase64 = elem.getValue();\n    if (!ocspRespBase64.isArray() || ocspRespBase64.size() != 2) {\n      logger.debug(\"Invalid cache file format. Ignored\", false);\n      return null;\n    }\n    long producedAt = ocspRespBase64.get(0).asLong();\n    String ocspResp = ocspRespBase64.get(1).asText();\n\n    if (currentTimeSecond - CACHE_EXPIRATION_IN_SECONDS <= producedAt) {\n      // add cache\n      return SFPair.of(k, SFPair.of(producedAt, ocspResp));\n    } else {\n      // delete cache\n      return SFPair.of(k, SFPair.of(producedAt, null));\n    }\n  }\n\n  /**\n   * Encode OCSP Response Cache to JSON\n   *\n   * @return JSON object\n   */\n  private static ObjectNode encodeCacheToJSON() {\n    try {\n      ObjectNode out = OBJECT_MAPPER.createObjectNode();\n      for (Map.Entry<OcspResponseCacheKey, SFPair<Long, String>> elem :\n          OCSP_RESPONSE_CACHE.entrySet()) {\n        OcspResponseCacheKey key = elem.getKey();\n        SFPair<Long, String> value0 = elem.getValue();\n        long currentTimeSecond = value0.left;\n\n        DigestCalculator digest = new SHA1DigestCalculator();\n        AlgorithmIdentifier algo = digest.getAlgorithmIdentifier();\n        ASN1OctetString nameHash = ASN1OctetString.getInstance(key.nameHash);\n        ASN1OctetString keyHash = ASN1OctetString.getInstance(key.keyHash);\n        ASN1Integer serialNumber = new ASN1Integer(key.serialNumber);\n        CertID cid = new CertID(algo, nameHash, keyHash, serialNumber);\n        ArrayNode vout = OBJECT_MAPPER.createArrayNode();\n        vout.add(currentTimeSecond);\n        vout.add(value0.right);\n        out.set(Base64.encodeBase64String(cid.toASN1Primitive().getEncoded()), vout);\n      }\n      return out;\n    } catch (IOException ex) {\n      logger.debug(\"Failed to encode ASN1 object.\", false);\n    }\n    return null;\n  }\n\n  private static synchronized void readJsonStoreCache(JsonNode m) {\n    if (m == null || !m.getNodeType().equals(JsonNodeType.OBJECT)) {\n      logger.debug(\"Invalid cache file format.\", false);\n      return;\n    }\n    try {\n      for (Iterator<Map.Entry<String, JsonNode>> itr = m.fields(); itr.hasNext(); ) {\n        SFPair<OcspResponseCacheKey, SFPair<Long, String>> ky = decodeCacheFromJSON(itr.next());\n        if (ky != null && ky.right != null && ky.right.right != null) {\n          // valid range. cache the result in memory\n          OCSP_RESPONSE_CACHE.put(ky.left, ky.right);\n          WAS_CACHE_UPDATED.set(true);\n        } else if (ky != null && OCSP_RESPONSE_CACHE.containsKey(ky.left)) {\n          // delete it from the cache if no OCSP response is back.\n          OCSP_RESPONSE_CACHE.remove(ky.left);\n          WAS_CACHE_UPDATED.set(true);\n        }\n      }\n    } catch (IOException ex) {\n      logger.debug(\"Failed to decode the cache file\", false);\n    }\n  }\n\n  /**\n   * Verifies the signature of the data\n   *\n   * @param cert a certificate for public key.\n   * @param sig signature in a byte array.\n   * @param data data in a byte array.\n   * @param idf algorithm identifier object.\n   * @throws CertificateException raises if the verification fails.\n   */\n  private static void verifySignature(\n      X509CertificateHolder cert, byte[] sig, byte[] data, AlgorithmIdentifier idf)\n      throws CertificateException {\n    try {\n      String algorithm = SIGNATURE_OID_TO_STRING.get(idf.getAlgorithm());\n      if (algorithm == null) {\n        throw new NoSuchAlgorithmException(\n            String.format(\"Unsupported signature OID. OID: %s\", idf));\n      }\n      Signature signer = Signature.getInstance(algorithm);\n\n      X509Certificate c = CONVERTER_X509.getCertificate(cert);\n      signer.initVerify(c.getPublicKey());\n      signer.update(data);\n      if (!signer.verify(sig)) {\n        throw new CertificateEncodingException(\n            String.format(\n                \"Failed to verify the signature. Potentially the \"\n                    + \"data was not generated by by the cert, %s\",\n                cert.getSubject()));\n      }\n    } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {\n      throw new CertificateEncodingException(\"Failed to verify the signature.\", ex);\n    }\n  }\n\n  private static long maxLong(long v1, long v2) {\n    return Math.max(v1, v2);\n  }\n\n  /**\n   * Calculates the tolerable validity time beyond the next update.\n   *\n   * <p>Sometimes CA's OCSP response update is delayed beyond the clock skew as the update is not\n   * populated to all OCSP servers for certain period.\n   *\n   * @param thisUpdate the last update\n   * @param nextUpdate the next update\n   * @return the tolerable validity beyond the next update.\n   */\n  private static long calculateTolerableValidity(Date thisUpdate, Date nextUpdate) {\n    return maxLong(\n        (long)\n            ((float) (nextUpdate.getTime() - thisUpdate.getTime())\n                * TOLERABLE_VALIDITY_RANGE_RATIO),\n        MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS);\n  }\n\n  /**\n   * Checks the validity\n   *\n   * @param currentTime the current time\n   * @param thisUpdate the last update timestamp\n   * @param nextUpdate the next update timestamp\n   * @return true if valid or false\n   */\n  private static boolean isValidityRange(Date currentTime, Date thisUpdate, Date nextUpdate) {\n    if (checkOCSPResponseValidityErrorParameter()) {\n      return false; // test\n    }\n    long tolerableValidity = calculateTolerableValidity(thisUpdate, nextUpdate);\n    return thisUpdate.getTime() - MAX_CLOCK_SKEW_IN_MILLISECONDS <= currentTime.getTime()\n        && currentTime.getTime() <= nextUpdate.getTime() + tolerableValidity;\n  }\n\n  private static boolean checkOCSPResponseValidityErrorParameter() {\n    String injectValidityError = systemGetProperty(SF_OCSP_TEST_INJECT_VALIDITY_ERROR);\n    return Boolean.TRUE.toString().equalsIgnoreCase(injectValidityError);\n  }\n\n  /**\n   * Is the test parameter enabled?\n   *\n   * @param key the test parameter\n   * @return true if enabled otherwise false\n   */\n  private boolean isEnabledSystemTestParameter(String key) {\n    return Boolean.TRUE.toString().equalsIgnoreCase(systemGetProperty(key));\n  }\n\n  /** fail open mode current state */\n  private boolean isOCSPFailOpen() {\n    return ocspMode == OCSPMode.FAIL_OPEN;\n  }\n\n  private void checkNewOCSPEndpointAvailability() {\n    String new_ocsp_ept;\n    try {\n      new_ocsp_ept = systemGetEnv(\"SF_OCSP_ACTIVATE_NEW_ENDPOINT\");\n    } catch (Throwable ex) {\n      logger.debug(\n          \"Could not get environment variable to check for New OCSP Endpoint Availability\", false);\n      new_ocsp_ept = systemGetProperty(\"net.snowflake.jdbc.ocsp_activate_new_endpoint\");\n    }\n    ocspCacheServer.new_endpoint_enabled = new_ocsp_ept != null;\n  }\n\n  /**\n   * Get TrustManager for the algorithm. This is mainly used to get the JVM default trust manager\n   * and cache all of the root CA.\n   *\n   * @param algorithm algorithm.\n   * @return TrustManager object.\n   */\n  private X509TrustManager getTrustManager(String algorithm) {\n    try {\n      TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm);\n      factory.init((KeyStore) null);\n      X509TrustManager ret = null;\n      for (TrustManager tm : factory.getTrustManagers()) {\n        // Multiple TrustManager may be attached. We just need X509 Trust\n        // Manager here.\n        if (tm instanceof X509TrustManager) {\n          ret = (X509TrustManager) tm;\n          break;\n        }\n      }\n      if (ret == null) {\n        return null;\n      }\n      synchronized (ROOT_CA_LOCK) {\n        // cache root CA certificates for later use.\n        if (ROOT_CA.isEmpty()) {\n          for (X509Certificate cert : ret.getAcceptedIssuers()) {\n            Certificate bcCert = Certificate.getInstance(cert.getEncoded());\n            ROOT_CA.put(bcCert.getSubject().hashCode(), bcCert);\n          }\n        }\n      }\n      return ret;\n    } catch (NoSuchAlgorithmException | KeyStoreException | CertificateEncodingException ex) {\n      throw new SSLInitializationException(ex.getMessage(), ex);\n    }\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    // default behavior\n    trustManager.checkClientTrusted(chain, authType);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    trustManager.checkServerTrusted(chain, authType);\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType, java.net.Socket socket)\n      throws CertificateException {\n    if (exTrustManager != null) {\n      exTrustManager.checkClientTrusted(chain, authType, socket);\n    } else {\n      trustManager.checkClientTrusted(chain, authType);\n    }\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)\n      throws CertificateException {\n    if (exTrustManager != null) {\n      exTrustManager.checkClientTrusted(chain, authType, sslEngine);\n    } else {\n      trustManager.checkClientTrusted(chain, authType);\n    }\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType, java.net.Socket socket)\n      throws CertificateException {\n    if (exTrustManager != null) {\n      exTrustManager.checkServerTrusted(chain, authType, socket);\n    } else {\n      trustManager.checkServerTrusted(chain, authType);\n    }\n    String host = socket.getInetAddress().getHostName();\n    this.validateRevocationStatus(chain, host);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)\n      throws CertificateException {\n    if (exTrustManager != null) {\n      exTrustManager.checkServerTrusted(chain, authType, sslEngine);\n    } else {\n      trustManager.checkServerTrusted(chain, authType);\n    }\n    this.validateRevocationStatus(chain, sslEngine.getPeerHost());\n  }\n\n  @Override\n  public X509Certificate[] getAcceptedIssuers() {\n    return trustManager.getAcceptedIssuers();\n  }\n\n  /**\n   * Certificate Revocation checks\n   *\n   * @param chain chain of certificates attached.\n   * @param peerHost Hostname of the server\n   * @throws CertificateException if any certificate validation fails\n   */\n  void validateRevocationStatus(X509Certificate[] chain, String peerHost)\n      throws CertificateException {\n    final List<Certificate> bcChain = convertToBouncyCastleCertificate(chain);\n    final List<SFPair<Certificate, Certificate>> pairIssuerSubjectList =\n        getPairIssuerSubject(bcChain);\n\n    if (peerHost.startsWith(\"ocspssd\")) {\n      return;\n    }\n\n    if (ocspCacheServer.new_endpoint_enabled) {\n      ocspCacheServer.resetOCSPResponseCacheServer(peerHost);\n    }\n\n    boolean isCached = isCached(pairIssuerSubjectList);\n    if (useOCSPResponseCacheServer() && !isCached) {\n      if (!ocspCacheServer.new_endpoint_enabled) {\n        logger.debug(\n            \"Downloading OCSP response cache from the server. URL: {}\",\n            SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);\n      } else {\n        logger.debug(\n            \"Downloading OCSP response cache from the server. URL: {}\",\n            ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER);\n      }\n      try {\n        readOcspResponseCacheServer();\n      } catch (SFOCSPException ex) {\n        logger.debug(\n            \"Error downloading OCSP Response from cache server : {}.\"\n                + \"OCSP Responses will be fetched directly from the CA OCSP\"\n                + \"Responder \",\n            ex.getMessage());\n      }\n      // if the cache is downloaded from the server, it should be written\n      // to the file cache at all times.\n    }\n    executeRevocationStatusChecks(pairIssuerSubjectList, peerHost);\n    if (WAS_CACHE_UPDATED.getAndSet(false)) {\n      JsonNode input = encodeCacheToJSON();\n      fileCacheManager.writeCacheFile(input);\n    }\n  }\n\n  /**\n   * Executes the revocation status checks for all chained certificates\n   *\n   * @param pairIssuerSubjectList a list of pair of issuer and subject certificates.\n   * @throws CertificateException raises if any error occurs.\n   */\n  private void executeRevocationStatusChecks(\n      List<SFPair<Certificate, Certificate>> pairIssuerSubjectList, String peerHost)\n      throws CertificateException {\n    long currentTimeSecond = new Date().getTime() / 1000L;\n    for (SFPair<Certificate, Certificate> pairIssuerSubject : pairIssuerSubjectList) {\n      executeOneRevocationStatusCheck(pairIssuerSubject, currentTimeSecond, peerHost);\n    }\n  }\n\n  private String generateFailOpenLog(String logData) {\n    return \"OCSP responder didn't respond correctly. Assuming certificate is \"\n        + \"not revoked. Details: \"\n        + logData;\n  }\n\n  /**\n   * Executes a single revocation status check\n   *\n   * @param pairIssuerSubject a pair of issuer and subject certificate\n   * @param currentTimeSecond the current timestamp\n   * @throws CertificateException if certificate exception is raised.\n   */\n  private void executeOneRevocationStatusCheck(\n      SFPair<Certificate, Certificate> pairIssuerSubject, long currentTimeSecond, String peerHost)\n      throws CertificateException {\n    OCSPReq req;\n    OcspResponseCacheKey keyOcspResponse;\n    try {\n      req = createRequest(pairIssuerSubject);\n      CertID cid = req.getRequestList()[0].getCertID().toASN1Primitive();\n      keyOcspResponse =\n          new OcspResponseCacheKey(\n              cid.getIssuerNameHash().getEncoded(),\n              cid.getIssuerKeyHash().getEncoded(),\n              cid.getSerialNumber().getValue());\n    } catch (IOException ex) {\n      throw new CertificateException(ex.getMessage(), ex);\n    }\n\n    long sleepTime = INITIAL_SLEEPING_TIME_IN_MILLISECONDS;\n    DecorrelatedJitterBackoff backoff =\n        new DecorrelatedJitterBackoff(sleepTime, MAX_SLEEPING_TIME_IN_MILLISECONDS);\n    CertificateException error;\n    boolean success = false;\n    String ocspLog;\n    OCSPTelemetryData telemetryData = new OCSPTelemetryData();\n    telemetryData.setSfcPeerHost(peerHost);\n    telemetryData.setCertId(encodeCacheKey(keyOcspResponse));\n    telemetryData.setCacheEnabled(useOCSPResponseCacheServer());\n    telemetryData.setOCSPMode(ocspMode);\n    Throwable cause = null;\n    try {\n      final int maxRetryCounter = isOCSPFailOpen() ? 1 : 2;\n      for (int retry = 0; retry < maxRetryCounter; ++retry) {\n        try {\n          SFPair<Long, String> value0 = OCSP_RESPONSE_CACHE.get(keyOcspResponse);\n          OCSPResp ocspResp;\n          try {\n            try {\n              if (value0 == null) {\n                telemetryData.setCacheHit(false);\n                ocspResp =\n                    fetchOcspResponse(\n                        pairIssuerSubject,\n                        req,\n                        encodeCacheKey(keyOcspResponse),\n                        peerHost,\n                        telemetryData);\n\n                OCSP_RESPONSE_CACHE.put(\n                    keyOcspResponse, SFPair.of(currentTimeSecond, ocspResponseToB64(ocspResp)));\n                WAS_CACHE_UPDATED.set(true);\n                value0 = SFPair.of(currentTimeSecond, ocspResponseToB64(ocspResp));\n              } else {\n                telemetryData.setCacheHit(true);\n              }\n            } catch (Throwable ex) {\n              logger.debug(\n                  \"Exception occurred while trying to fetch OCSP Response - {}\", ex.getMessage());\n              throw new SFOCSPException(\n                  OCSPErrorCode.OCSP_RESPONSE_FETCH_FAILURE,\n                  \"Exception occurred while trying to fetch OCSP Response\",\n                  ex);\n            }\n\n            logger.debug(\n                \"Validating. {}\", CertificateIDToString(req.getRequestList()[0].getCertID()));\n            try {\n              validateRevocationStatusMain(pairIssuerSubject, value0.right);\n              success = true;\n              break;\n            } catch (SFOCSPException ex) {\n              if (ex.getErrorCode() != OCSPErrorCode.REVOCATION_CHECK_FAILURE) {\n                throw ex;\n              }\n              throw new CertificateException(ex.getMessage(), ex);\n            }\n          } catch (SFOCSPException ex) {\n            if (ex.getErrorCode() == OCSPErrorCode.CERTIFICATE_STATUS_REVOKED) {\n              throw ex;\n            } else {\n              throw new CertificateException(ex.getMessage(), ex);\n            }\n          }\n        } catch (CertificateException ex) {\n          WAS_CACHE_UPDATED.set(OCSP_RESPONSE_CACHE.remove(keyOcspResponse) != null);\n          if (WAS_CACHE_UPDATED.get()) {\n            logger.debug(\"Deleting the invalid OCSP cache.\", false);\n          }\n\n          cause = ex;\n          logger.debug(\n              \"Retrying {}/{} after sleeping {} ms\", retry + 1, maxRetryCounter, sleepTime);\n          try {\n            if (retry + 1 < maxRetryCounter) {\n              Thread.sleep(sleepTime);\n              sleepTime = backoff.nextSleepTime(sleepTime);\n            }\n          } catch (InterruptedException ex0) { // nop\n          }\n        }\n      }\n    } catch (SFOCSPException ex) {\n      // Revoked Certificate\n      error = new CertificateException(ex);\n      ocspLog =\n          telemetryData.generateTelemetry(SF_OCSP_EVENT_TYPE_REVOKED_CERTIFICATE_ERROR, error);\n      logger.error(ocspLog, false);\n      throw error;\n    }\n\n    if (!success) {\n      if (cause != null) // cause is set in the above catch block\n      {\n        error =\n            new CertificateException(\n                \"Certificate Revocation check failed. Could not retrieve OCSP Response.\", cause);\n        logger.debug(cause.getMessage(), false);\n      } else {\n        error =\n            new CertificateException(\n                \"Certificate Revocation check failed. Could not retrieve OCSP Response.\");\n        logger.debug(error.getMessage(), false);\n      }\n\n      ocspLog = telemetryData.generateTelemetry(SF_OCSP_EVENT_TYPE_VALIDATION_ERROR, error);\n      if (isOCSPFailOpen()) {\n        // Log includes fail-open warning.\n        logger.debug(generateFailOpenLog(ocspLog), false);\n      } else {\n        // still not success, raise an error.\n        logger.debug(ocspLog, false);\n        throw error;\n      }\n    }\n  }\n\n  /**\n   * Is OCSP Response cached?\n   *\n   * @param pairIssuerSubjectList a list of pair of issuer and subject certificates\n   * @return true if all of OCSP response are cached else false\n   */\n  private boolean isCached(List<SFPair<Certificate, Certificate>> pairIssuerSubjectList) {\n    long currentTimeSecond = new Date().getTime() / 1000L;\n    boolean isCached = true;\n    try {\n      for (SFPair<Certificate, Certificate> pairIssuerSubject : pairIssuerSubjectList) {\n        OCSPReq req = createRequest(pairIssuerSubject);\n        CertificateID certificateId = req.getRequestList()[0].getCertID();\n        logger.debug(CertificateIDToString(certificateId), false);\n        CertID cid = certificateId.toASN1Primitive();\n        OcspResponseCacheKey k =\n            new OcspResponseCacheKey(\n                cid.getIssuerNameHash().getEncoded(),\n                cid.getIssuerKeyHash().getEncoded(),\n                cid.getSerialNumber().getValue());\n\n        SFPair<Long, String> res = OCSP_RESPONSE_CACHE.get(k);\n        if (res == null) {\n          logger.debug(\"Not all OCSP responses for the certificate is in the cache.\", false);\n          isCached = false;\n          break;\n        } else if (currentTimeSecond - CACHE_EXPIRATION_IN_SECONDS > res.left) {\n          logger.debug(\"Cache for CertID expired.\", false);\n          isCached = false;\n          break;\n        } else {\n          try {\n            validateRevocationStatusMain(pairIssuerSubject, res.right);\n          } catch (SFOCSPException ex) {\n            logger.debug(\n                \"Cache includes invalid OCSPResponse. \"\n                    + \"Will download the OCSP cache from Snowflake OCSP server\",\n                false);\n            isCached = false;\n          }\n        }\n      }\n    } catch (IOException ex) {\n      logger.debug(\"Failed to encode CertID.\", false);\n    }\n    return isCached;\n  }\n\n  /** Reads the OCSP response cache from the server. */\n  private void readOcspResponseCacheServer() throws SFOCSPException {\n    String ocspCacheServerInUse;\n\n    if (ocspCacheServer.new_endpoint_enabled) {\n      ocspCacheServerInUse = ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER;\n    } else {\n      ocspCacheServerInUse = SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE;\n    }\n\n    CloseableHttpResponse response = null;\n    CloseableHttpClient httpClient =\n        ocspCacheServerClient.computeIfAbsent(proxySettingsKey, HttpUtil::getHttpClientForOcsp);\n    try {\n      URI uri = new URI(ocspCacheServerInUse);\n      HttpGet get = new HttpGet(uri);\n      response = httpClient.execute(get);\n      if (response == null || response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {\n        throw new IOException(\n            String.format(\n                \"Failed to get the OCSP response from the OCSP \" + \"cache server: HTTP: %d\",\n                response != null ? response.getStatusLine().getStatusCode() : -1));\n      }\n\n      ByteArrayOutputStream out = new ByteArrayOutputStream();\n      IOUtils.copy(response.getEntity().getContent(), out);\n      JsonNode m = OBJECT_MAPPER.readTree(out.toByteArray());\n      out.close();\n      readJsonStoreCache(m);\n      logger.debug(\"Successfully downloaded OCSP cache from the server.\", false);\n    } catch (IOException ex) {\n      logger.debug(\n          \"Failed to read the OCSP response cache from the server. \" + \"Server: {}, Err: {}\",\n          ocspCacheServerInUse,\n          ex);\n    } catch (URISyntaxException ex) {\n      logger.debug(\"Indicate that a string could not be parsed as a URI reference.\", false);\n      throw new SFOCSPException(\n          OCSPErrorCode.INVALID_CACHE_SERVER_URL, \"Invalid OCSP Cache Server URL used\", ex);\n    } finally {\n      IOUtils.closeQuietly(response);\n    }\n  }\n\n  private int getOCSPCacheServerConnectionTimeout() {\n    int timeout = DEFAULT_OCSP_CACHE_SERVER_CONNECTION_TIMEOUT;\n    if (systemGetProperty(SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT) != null) {\n      try {\n        timeout =\n            Integer.parseInt(systemGetProperty(SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT));\n      } catch (Exception ex) {\n        // nop\n      }\n    }\n    return timeout;\n  }\n\n  /**\n   * Fetches OCSP response from OCSP server\n   *\n   * @param pairIssuerSubject a pair of issuer and subject certificates\n   * @param req OCSP Request object\n   * @return OCSP Response object\n   * @throws CertificateEncodingException if any other error occurs\n   */\n  private OCSPResp fetchOcspResponse(\n      SFPair<Certificate, Certificate> pairIssuerSubject,\n      OCSPReq req,\n      String cid_enc,\n      String hname,\n      OCSPTelemetryData telemetryData)\n      throws CertificateEncodingException {\n    CloseableHttpResponse response = null;\n    try {\n      byte[] ocspReqDer = req.getEncoded();\n      String ocspReqDerBase64 = Base64.encodeBase64String(ocspReqDer);\n      Set<String> ocspUrls = getOcspUrls(pairIssuerSubject.right);\n      checkExistOCSPURL(ocspUrls);\n      String ocspUrlStr = ocspUrls.iterator().next(); // first one\n      ocspUrlStr = overrideOCSPURL(ocspUrlStr);\n      telemetryData.setOcspUrl(ocspUrlStr);\n      telemetryData.setOcspReq(ocspReqDerBase64);\n\n      URL url;\n      String path = \"\";\n      if (!ocspCacheServer.new_endpoint_enabled) {\n        String urlEncodedOCSPReq = URLUtil.urlEncode(ocspReqDerBase64);\n        if (SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN != null) {\n          URL ocspUrl = new URL(ocspUrlStr);\n          if (!isNullOrEmpty(ocspUrl.getPath())) {\n            path = ocspUrl.getPath();\n          }\n          if (ocspUrl.getPort() > 0) {\n            url =\n                new URL(\n                    String.format(\n                        SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN,\n                        ocspUrl.getHost() + \":\" + ocspUrl.getPort() + path,\n                        urlEncodedOCSPReq));\n          } else {\n            url =\n                new URL(\n                    String.format(\n                        SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN,\n                        ocspUrl.getHost() + path,\n                        urlEncodedOCSPReq));\n          }\n\n        } else {\n          url = new URL(String.format(\"%s/%s\", ocspUrlStr, urlEncodedOCSPReq));\n        }\n        logger.debug(\"Not hit cache. Fetching OCSP response from CA OCSP server. {}\", url);\n      } else {\n        url = new URL(ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL);\n        logger.debug(\n            \"Not hit cache. Fetching OCSP response from Snowflake OCSP Response Fetcher. {}\", url);\n      }\n\n      long sleepTime = INITIAL_SLEEPING_TIME_IN_MILLISECONDS;\n      DecorrelatedJitterBackoff backoff =\n          new DecorrelatedJitterBackoff(sleepTime, MAX_SLEEPING_TIME_IN_MILLISECONDS);\n      boolean success = false;\n\n      final int maxRetryCounter = isOCSPFailOpen() ? 1 : 2;\n      Exception savedEx = null;\n      CloseableHttpClient httpClient =\n          ocspCacheServerClient.computeIfAbsent(proxySettingsKey, HttpUtil::getHttpClientForOcsp);\n\n      for (int retry = 0; retry < maxRetryCounter; ++retry) {\n        try {\n          if (!ocspCacheServer.new_endpoint_enabled) {\n            HttpGet get = new HttpGet(url.toString());\n            response = httpClient.execute(get);\n          } else {\n            HttpPost post = new HttpPost(url.toString());\n            post.setHeader(\"Content-Type\", \"application/json\");\n            OCSPPostReqData postReqData =\n                new OCSPPostReqData(ocspUrlStr, ocspReqDerBase64, cid_enc, hname);\n            String json_payload = OBJECT_MAPPER.writeValueAsString(postReqData);\n            post.setEntity(new StringEntity(json_payload, \"utf-8\"));\n            response = httpClient.execute(post);\n          }\n          success =\n              response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK;\n          if (success) {\n            break;\n          }\n        } catch (IOException ex) {\n          logger.debug(\"Failed to reach out OCSP responder: {}\", ex.getMessage());\n          savedEx = ex;\n        }\n        IOUtils.closeQuietly(response);\n\n        logger.debug(\"Retrying {}/{} after sleeping {} ms\", retry + 1, maxRetryCounter, sleepTime);\n        try {\n          if (retry + 1 < maxRetryCounter) {\n            Thread.sleep(sleepTime);\n            sleepTime = backoff.nextSleepTime(sleepTime);\n          }\n        } catch (InterruptedException ex0) { // nop\n        }\n      }\n      if (!success) {\n        throw new CertificateEncodingException(\n            String.format(\n                \"Failed to get OCSP response. StatusCode: %d, URL: %s\",\n                response == null ? null : response.getStatusLine().getStatusCode(), ocspUrlStr),\n            savedEx);\n      }\n      ByteArrayOutputStream out = new ByteArrayOutputStream();\n      IOUtils.copy(response.getEntity().getContent(), out);\n      OCSPResp ocspResp = new OCSPResp(out.toByteArray());\n      out.close();\n      if (ocspResp.getStatus() != OCSPResp.SUCCESSFUL) {\n        throw new CertificateEncodingException(\n            String.format(\n                \"Failed to get OCSP response. Status: %s\",\n                OCSP_RESPONSE_CODE_TO_STRING.get(ocspResp.getStatus())));\n      }\n\n      return ocspResp;\n    } catch (IOException ex) {\n      throw new CertificateEncodingException(\"Failed to encode object.\", ex);\n    } finally {\n      IOUtils.closeQuietly(response);\n    }\n  }\n\n  private void checkExistOCSPURL(Set<String> ocspUrls) throws CertificateEncodingException {\n    if (ocspUrls.size() == 0 || isEnabledSystemTestParameter(SF_OCSP_TEST_NO_OCSP_RESPONDER_URL)) {\n      throw new CertificateEncodingException(\n          \"No OCSP Responder URL is attached to the certificate.\",\n          new SFOCSPException(\n              OCSPErrorCode.NO_OCSP_URL_ATTACHED,\n              \"No OCSP Responder URL is attached to the certificate.\"));\n    }\n  }\n\n  private String overrideOCSPURL(String ocspURL) {\n    String ocspURLInput = systemGetProperty(SF_OCSP_TEST_RESPONDER_URL);\n    if (ocspURLInput != null) {\n      logger.debug(\"Overriding OCSP url to: {}\", ocspURLInput);\n      return ocspURLInput;\n    }\n    logger.debug(\"Overriding OCSP url to: {}\", ocspURL);\n    return ocspURL;\n  }\n\n  /**\n   * Validates the certificate revocation status\n   *\n   * @param pairIssuerSubject a pair of issuer and subject certificates\n   * @param ocspRespB64 Base64 encoded OCSP Response object\n   * @throws SFOCSPException raises if any other error occurs\n   */\n  // Package-private for testing (see SFTrustManagerOcspCachePoisoningTest).\n  void validateRevocationStatusMain(\n      SFPair<Certificate, Certificate> pairIssuerSubject, String ocspRespB64)\n      throws SFOCSPException {\n    try {\n      OCSPResp ocspResp = b64ToOCSPResp(ocspRespB64);\n      if (ocspResp == null) {\n        throw new SFOCSPException(\n            OCSPErrorCode.INVALID_OCSP_RESPONSE, \"OCSP response is null. The content is invalid.\");\n      }\n      Date currentTime = new Date();\n      // getResponseObject() returns null for non-SUCCESSFUL OCSP responses (e.g. unauthorized(6)).\n      // Surface as SFOCSPException so cache eviction and fail-open run instead of NPEing.\n      BasicOCSPResp basicOcspResp = (BasicOCSPResp) (ocspResp.getResponseObject());\n      if (basicOcspResp == null) {\n        throw new SFOCSPException(\n            OCSPErrorCode.INVALID_OCSP_RESPONSE,\n            \"OCSP response body is null (non-SUCCESSFUL or malformed response). The content is invalid.\");\n      }\n      X509CertificateHolder[] attachedCerts = basicOcspResp.getCerts();\n      X509CertificateHolder signVerifyCert;\n      checkInvalidSigningCertTestParameter();\n      if (attachedCerts.length > 0) {\n        logger.debug(\n            \"Certificate is attached for verification. \"\n                + \"Verifying it by the issuer certificate.\",\n            false);\n        signVerifyCert = attachedCerts[0];\n        if (currentTime.after(signVerifyCert.getNotAfter())\n            || currentTime.before(signVerifyCert.getNotBefore())) {\n          throw new SFOCSPException(\n              OCSPErrorCode.EXPIRED_OCSP_SIGNING_CERTIFICATE,\n              String.format(\n                  \"Cert attached to \"\n                      + \"OCSP Response is invalid.\"\n                      + \"Current time - %s\"\n                      + \"Certificate not before time - %s\"\n                      + \"Certificate not after time - %s\",\n                  currentTime, signVerifyCert.getNotBefore(), signVerifyCert.getNotAfter()));\n        }\n        try {\n          verifySignature(\n              new X509CertificateHolder(pairIssuerSubject.left.getEncoded()),\n              signVerifyCert.getSignature(),\n              CONVERTER_X509.getCertificate(signVerifyCert).getTBSCertificate(),\n              signVerifyCert.getSignatureAlgorithm());\n        } catch (CertificateException ex) {\n          logger.debug(\"OCSP Signing Certificate signature verification failed\", false);\n          throw new SFOCSPException(\n              OCSPErrorCode.INVALID_CERTIFICATE_SIGNATURE,\n              \"OCSP Signing Certificate signature verification failed\",\n              ex);\n        }\n        logger.debug(\"Verifying OCSP signature by the attached certificate public key.\", false);\n      } else {\n        logger.debug(\n            \"Certificate is NOT attached for verification. \"\n                + \"Verifying OCSP signature by the issuer public key.\",\n            false);\n        signVerifyCert = new X509CertificateHolder(pairIssuerSubject.left.getEncoded());\n      }\n      try {\n        verifySignature(\n            signVerifyCert,\n            basicOcspResp.getSignature(),\n            basicOcspResp.getTBSResponseData(),\n            basicOcspResp.getSignatureAlgorithmID());\n      } catch (CertificateException ex) {\n        logger.debug(\"OCSP signature verification failed\", false);\n        throw new SFOCSPException(\n            OCSPErrorCode.INVALID_OCSP_RESPONSE_SIGNATURE,\n            \"OCSP signature verification failed\",\n            ex);\n      }\n\n      validateBasicOcspResponse(currentTime, basicOcspResp);\n    } catch (IOException | OCSPException ex) {\n      throw new SFOCSPException(\n          OCSPErrorCode.REVOCATION_CHECK_FAILURE, \"Failed to check revocation status.\", ex);\n    } catch (RuntimeException ex) {\n      // Convert unexpected unchecked failures so the caller can evict the bad cache entry and\n      // fail open instead of letting an unchecked exception escape into the SSL handshake.\n      throw new SFOCSPException(\n          OCSPErrorCode.REVOCATION_CHECK_FAILURE,\n          \"Failed to check revocation status due to an unexpected error.\",\n          ex);\n    }\n  }\n\n  private void checkInvalidSigningCertTestParameter() throws SFOCSPException {\n    if (isEnabledSystemTestParameter(SF_OCSP_TEST_INVALID_SIGNING_CERT)) {\n      throw new SFOCSPException(\n          OCSPErrorCode.EXPIRED_OCSP_SIGNING_CERTIFICATE,\n          \"Cert attached to OCSP Response is invalid\");\n    }\n  }\n\n  /**\n   * Validates OCSP Basic OCSP response.\n   *\n   * @param currentTime the current timestamp.\n   * @param basicOcspResp BasicOcspResponse data.\n   * @throws SFOCSPException raises if any failure occurs.\n   */\n  private void validateBasicOcspResponse(Date currentTime, BasicOCSPResp basicOcspResp)\n      throws SFOCSPException {\n    for (SingleResp singleResps : basicOcspResp.getResponses()) {\n      checkCertUnknownTestParameter();\n      CertificateStatus certStatus = singleResps.getCertStatus();\n      if (certStatus != CertificateStatus.GOOD) {\n        if (certStatus instanceof RevokedStatus) {\n          RevokedStatus status = (RevokedStatus) certStatus;\n          int reason;\n          try {\n            reason = status.getRevocationReason();\n          } catch (IllegalStateException ex) {\n            reason = -1;\n          }\n          Date revocationTime = status.getRevocationTime();\n          throw new SFOCSPException(\n              OCSPErrorCode.CERTIFICATE_STATUS_REVOKED,\n              String.format(\n                  \"The certificate has been revoked. Reason: %d, Time: %s\",\n                  reason, DATE_FORMAT_UTC.format(revocationTime)));\n        } else {\n          // Unknown status\n          throw new SFOCSPException(\n              OCSPErrorCode.CERTIFICATE_STATUS_UNKNOWN,\n              \"Failed to validate the certificate for UNKNOWN reason.\");\n        }\n      }\n\n      Date thisUpdate = singleResps.getThisUpdate();\n      Date nextUpdate = singleResps.getNextUpdate();\n      logger.debug(\n          \"Current Time: {}, This Update: {}, Next Update: {}\",\n          currentTime,\n          thisUpdate,\n          nextUpdate);\n      if (!isValidityRange(currentTime, thisUpdate, nextUpdate)) {\n        throw new SFOCSPException(\n            OCSPErrorCode.INVALID_OCSP_RESPONSE_VALIDITY,\n            String.format(\n                \"The OCSP response validity is out of range: \"\n                    + \"Current Time: %s, This Update: %s, Next Update: %s\",\n                DATE_FORMAT_UTC.format(currentTime),\n                DATE_FORMAT_UTC.format(thisUpdate),\n                DATE_FORMAT_UTC.format(nextUpdate)));\n      }\n    }\n    logger.debug(\"OK. Verified the certificate revocation status.\", false);\n  }\n\n  private void checkCertUnknownTestParameter() throws SFOCSPException {\n    if (isEnabledSystemTestParameter(SF_OCSP_TEST_INJECT_UNKNOWN_STATUS)) {\n      throw new SFOCSPException(\n          OCSPErrorCode.CERTIFICATE_STATUS_UNKNOWN,\n          \"Failed to validate the certificate for UNKNOWN reason.\");\n    }\n  }\n\n  /**\n   * Creates a OCSP Request\n   *\n   * @param pairIssuerSubject a pair of issuer and subject certificates\n   * @return OCSPReq object\n   */\n  private OCSPReq createRequest(SFPair<Certificate, Certificate> pairIssuerSubject)\n      throws IOException {\n    Certificate issuer = pairIssuerSubject.left;\n    Certificate subject = pairIssuerSubject.right;\n    OCSPReqBuilder gen = new OCSPReqBuilder();\n    try {\n      DigestCalculator digest = new SHA1DigestCalculator();\n      X509CertificateHolder certHolder = new X509CertificateHolder(issuer.getEncoded());\n      CertificateID certId =\n          new CertificateID(digest, certHolder, subject.getSerialNumber().getValue());\n      gen.addRequest(certId);\n      return gen.build();\n    } catch (OCSPException ex) {\n      throw new IOException(\"Failed to build a OCSPReq.\", ex);\n    }\n  }\n\n  /**\n   * Converts X509Certificate to Bouncy Castle Certificate\n   *\n   * @param chain an array of X509Certificate\n   * @return a list of Bouncy Castle Certificate\n   */\n  private List<Certificate> convertToBouncyCastleCertificate(X509Certificate[] chain)\n      throws CertificateEncodingException {\n    final List<Certificate> bcChain = new ArrayList<>();\n    for (X509Certificate cert : chain) {\n      bcChain.add(Certificate.getInstance(cert.getEncoded()));\n    }\n    return bcChain;\n  }\n\n  /**\n   * Creates a pair of Issuer and Subject certificates\n   *\n   * @param bcChain a list of bouncy castle Certificate\n   * @return a list of pair of Issuer and Subject certificates\n   */\n  private List<SFPair<Certificate, Certificate>> getPairIssuerSubject(List<Certificate> bcChain)\n      throws CertificateException {\n    List<SFPair<Certificate, Certificate>> pairIssuerSubject = new ArrayList<>();\n    for (int i = 0, len = bcChain.size(); i < len; ++i) {\n      Certificate bcCert = bcChain.get(i);\n      if (bcCert.getIssuer().equals(bcCert.getSubject())) {\n        continue; // skipping ROOT CA\n      }\n      if (i < len - 1) {\n        // Check if the root certificate has been found and stop going down the chain.\n        Certificate issuer = ROOT_CA.get(bcCert.getIssuer().hashCode());\n        if (issuer != null) {\n          logger.debug(\n              \"A trusted root certificate found: %s, stopping chain traversal here\",\n              bcCert.getIssuer().toString());\n          pairIssuerSubject.add(SFPair.of(issuer, bcChain.get(i)));\n          break;\n        }\n        pairIssuerSubject.add(SFPair.of(bcChain.get(i + 1), bcChain.get(i)));\n      } else {\n        // no root CA certificate is attached in the certificate chain, so\n        // getting one from the root CA from JVM.\n        Certificate issuer = ROOT_CA.get(bcCert.getIssuer().hashCode());\n        if (issuer == null) {\n          throw new CertificateException(\n              \"Failed to find the root CA.\",\n              new SFOCSPException(OCSPErrorCode.NO_ROOTCA_FOUND, \"Failed to find the root CA.\"));\n        }\n        pairIssuerSubject.add(SFPair.of(issuer, bcChain.get(i)));\n      }\n    }\n    return pairIssuerSubject;\n  }\n\n  /**\n   * Gets OCSP URLs associated with the certificate.\n   *\n   * @param bcCert Bouncy Castle Certificate\n   * @return a set of OCSP URLs\n   */\n  private Set<String> getOcspUrls(Certificate bcCert) throws IOException {\n    TBSCertificate bcTbsCert = bcCert.getTBSCertificate();\n    Extensions bcExts = bcTbsCert.getExtensions();\n    if (bcExts == null) {\n      throw new IOException(\"Failed to get Tbs Certificate.\");\n    }\n\n    Set<String> ocsp = new HashSet<>();\n    for (Enumeration<?> en = bcExts.oids(); en.hasMoreElements(); ) {\n      ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) en.nextElement();\n      Extension bcExt = bcExts.getExtension(oid);\n      if (Extension.authorityInfoAccess.equals(bcExt.getExtnId())) {\n        // OCSP URLS are included in authorityInfoAccess\n        DLSequence seq = (DLSequence) bcExt.getParsedValue();\n        for (ASN1Encodable asn : seq) {\n          ASN1Encodable[] pairOfAsn = ((DLSequence) asn).toArray();\n          if (pairOfAsn.length == 2) {\n            ASN1ObjectIdentifier key = (ASN1ObjectIdentifier) pairOfAsn[0];\n            if (OIDocsp.equals(key)) {\n              // ensure OCSP and not CRL\n              GeneralName gn = GeneralName.getInstance(pairOfAsn[1]);\n              ocsp.add(gn.getName().toString());\n            }\n          }\n        }\n      }\n    }\n    return ocsp;\n  }\n\n  /** OCSP Response Utils */\n  private String ocspResponseToB64(OCSPResp ocspResp) {\n    if (ocspResp == null) {\n      return null;\n    }\n    try {\n      return Base64.encodeBase64String(ocspResp.getEncoded());\n    } catch (Throwable ex) {\n      logger.debug(\"Could not convert OCSP Response to Base64\", false);\n      return null;\n    }\n  }\n\n  private OCSPResp b64ToOCSPResp(String ocspRespB64) {\n    try {\n      return new OCSPResp(Base64.decodeBase64(ocspRespB64));\n    } catch (Throwable ex) {\n      logger.debug(\"Could not cover OCSP Response from Base64 to OCSPResp object\", false);\n      return null;\n    }\n  }\n\n  static class OCSPCacheServer {\n    String SF_OCSP_RESPONSE_CACHE_SERVER;\n    String SF_OCSP_RESPONSE_RETRY_URL;\n    boolean new_endpoint_enabled;\n\n    void resetOCSPResponseCacheServer(String host) {\n      String ocspCacheServerUrl;\n      if (host.toLowerCase().contains(\".global.snowflakecomputing.\")) {\n        ocspCacheServerUrl =\n            String.format(\"https://ocspssd%s/%s\", host.substring(host.indexOf('-')), \"ocsp\");\n      } else if (host.toLowerCase().contains(\".snowflakecomputing.\")) {\n        ocspCacheServerUrl =\n            String.format(\"https://ocspssd%s/%s\", host.substring(host.indexOf('.')), \"ocsp\");\n      } else {\n        String topLevelDomain = host.substring(host.lastIndexOf(\".\") + 1);\n        ocspCacheServerUrl =\n            String.format(\"https://ocspssd.snowflakecomputing.%s/ocsp\", topLevelDomain);\n      }\n      SF_OCSP_RESPONSE_CACHE_SERVER = String.format(\"%s/%s\", ocspCacheServerUrl, \"fetch\");\n      SF_OCSP_RESPONSE_RETRY_URL = String.format(\"%s/%s\", ocspCacheServerUrl, \"retry\");\n    }\n  }\n\n  private static class OCSPPostReqData {\n    private String ocsp_url;\n    private String ocsp_req;\n    private String cert_id_enc;\n    private String hostname;\n\n    OCSPPostReqData(String ocsp_url, String ocsp_req, String cert_id_enc, String hname) {\n      this.ocsp_url = ocsp_url;\n      this.ocsp_req = ocsp_req;\n      this.cert_id_enc = cert_id_enc;\n      this.hostname = hname;\n    }\n  }\n\n  /** OCSP response cache key object */\n  static class OcspResponseCacheKey {\n    final byte[] nameHash;\n    final byte[] keyHash;\n    final BigInteger serialNumber;\n\n    OcspResponseCacheKey(byte[] nameHash, byte[] keyHash, BigInteger serialNumber) {\n      this.nameHash = nameHash;\n      this.keyHash = keyHash;\n      this.serialNumber = serialNumber;\n    }\n\n    public int hashCode() {\n      int ret = Arrays.hashCode(this.nameHash) * 37;\n      ret = ret * 10 + Arrays.hashCode(this.keyHash) * 37;\n      ret = ret * 10 + this.serialNumber.hashCode();\n      return ret;\n    }\n\n    public boolean equals(Object obj) {\n      if (!(obj instanceof OcspResponseCacheKey)) {\n        return false;\n      }\n      OcspResponseCacheKey target = (OcspResponseCacheKey) obj;\n      return Arrays.equals(this.nameHash, target.nameHash)\n          && Arrays.equals(this.keyHash, target.keyHash)\n          && this.serialNumber.equals(target.serialNumber);\n    }\n\n    public String toString() {\n      return String.format(\n          \"OcspResponseCacheKey: NameHash: %s, KeyHash: %s, SerialNumber: %s\",\n          SnowflakeUtil.byteToHexString(nameHash),\n          SnowflakeUtil.byteToHexString(keyHash),\n          serialNumber.toString());\n    }\n  }\n\n  /** SHA1 Digest Calculator used in OCSP Req. */\n  static class SHA1DigestCalculator implements DigestCalculator {\n    private ByteArrayOutputStream bOut = new ByteArrayOutputStream();\n\n    public AlgorithmIdentifier getAlgorithmIdentifier() {\n      return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);\n    }\n\n    public OutputStream getOutputStream() {\n      return bOut;\n    }\n\n    public byte[] getDigest() {\n      byte[] bytes = bOut.toByteArray();\n      bOut.reset();\n      try {\n        MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM_SHA1_NAME);\n        return messageDigest.digest(bytes);\n      } catch (NoSuchAlgorithmException ex) {\n        String errMsg =\n            String.format(\n                \"Failed to instantiate the algorithm: %s. err=%s\",\n                ALGORITHM_SHA1_NAME, ex.getMessage());\n        logger.error(errMsg, false);\n        throw new RuntimeException(errMsg);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SdkProxyRoutePlanner.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.Arrays;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.http.HttpException;\nimport org.apache.http.HttpHost;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.impl.conn.DefaultRoutePlanner;\nimport org.apache.http.impl.conn.DefaultSchemePortResolver;\nimport org.apache.http.protocol.HttpContext;\n\n/**\n * SdkProxyRoutePlanner delegates a Proxy Route Planner from the settings instead of the system\n * properties. It will use the proxy created from proxyHost, proxyPort, and proxyProtocol and filter\n * the hosts who matches nonProxyHosts pattern.\n */\npublic class SdkProxyRoutePlanner extends DefaultRoutePlanner {\n  private final HttpHost proxy;\n  private final String[] hostPatterns;\n\n  public SdkProxyRoutePlanner(\n      String proxyHost, int proxyPort, HttpProtocol proxyProtocol, String nonProxyHosts) {\n    super(DefaultSchemePortResolver.INSTANCE);\n    proxy = new HttpHost(proxyHost, proxyPort, proxyProtocol.toString());\n    if (!SnowflakeUtil.isNullOrEmpty(nonProxyHosts)) {\n      hostPatterns = nonProxyHosts.split(\"\\\\|\");\n    } else {\n      hostPatterns = null;\n    }\n  }\n\n  private boolean doesTargetMatchNonProxyHosts(HttpHost target) {\n    if (hostPatterns == null) {\n      return false;\n    }\n    String targetHost = target.getHostName().toLowerCase();\n    return Arrays.stream(hostPatterns)\n        .anyMatch(pattern -> SnowflakeUtil.hostnameMatchesGlob(targetHost, pattern));\n  }\n\n  @Override\n  protected HttpHost determineProxy(\n      final HttpHost target, final HttpRequest request, final HttpContext context)\n      throws HttpException {\n    return doesTargetMatchNonProxyHosts(target) ? null : proxy;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SecureStorageAppleManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.sun.jna.Library;\nimport com.sun.jna.Native;\nimport com.sun.jna.Pointer;\nimport java.nio.charset.StandardCharsets;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SecureStorageAppleManager implements SecureStorageManager {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SecureStorageAppleManager.class);\n\n  private final SecurityLib securityLib;\n\n  private SecureStorageAppleManager() {\n    securityLib = SecurityLibManager.getInstance();\n  }\n\n  public static SecureStorageAppleManager builder() {\n    logger.debug(\"Using Apple Keychain as a token cache storage\");\n    return new SecureStorageAppleManager();\n  }\n\n  public SecureStorageStatus setCredential(String host, String user, String type, String cred) {\n    if (isNullOrEmpty(cred)) {\n      logger.debug(\"No credential provided\", false);\n      return SecureStorageStatus.SUCCESS;\n    }\n\n    String target = SecureStorageManager.buildCredentialsKey(host, user, type);\n    byte[] targetBytes = target.getBytes(StandardCharsets.UTF_8);\n    byte[] userBytes = user.toUpperCase().getBytes(StandardCharsets.UTF_8);\n    byte[] credBytes = cred.getBytes(StandardCharsets.UTF_8);\n\n    Pointer[] itemRef = new Pointer[1];\n    int errCode = 0;\n    synchronized (securityLib) {\n      errCode =\n          securityLib.SecKeychainFindGenericPassword(\n              null,\n              targetBytes.length,\n              targetBytes,\n              userBytes.length,\n              userBytes,\n              null,\n              null,\n              itemRef);\n    }\n\n    if (errCode != SecurityLib.ERR_SEC_SUCCESS && errCode != SecurityLib.ERR_SEC_ITEM_NOT_FOUND) {\n      logger.warn(\n          String.format(\n              \"Failed to check the existence of the item in keychain. Error code = %d\",\n              Native.getLastError()));\n      return SecureStorageStatus.FAILURE;\n    }\n\n    if (itemRef[0] != null) {\n      synchronized (securityLib) {\n        errCode =\n            securityLib.SecKeychainItemModifyContent(itemRef[0], null, credBytes.length, credBytes);\n      }\n    } else {\n      synchronized (securityLib) {\n        errCode =\n            securityLib.SecKeychainAddGenericPassword(\n                Pointer.NULL,\n                targetBytes.length,\n                targetBytes,\n                userBytes.length,\n                userBytes,\n                credBytes.length,\n                credBytes,\n                null);\n      }\n    }\n\n    if (errCode != SecurityLib.ERR_SEC_SUCCESS) {\n      logger.warn(\n          String.format(\n              \"Failed to set/modify the item in keychain. Error code = %d\", Native.getLastError()));\n      return SecureStorageStatus.FAILURE;\n    }\n\n    logger.debug(\"Set the item in keychain successfully\");\n    return SecureStorageStatus.SUCCESS;\n  }\n\n  public String getCredential(String host, String user, String type) {\n    String target = SecureStorageManager.buildCredentialsKey(host, user, type);\n    byte[] targetBytes = target.getBytes(StandardCharsets.UTF_8);\n    byte[] userBytes = user.toUpperCase().getBytes(StandardCharsets.UTF_8);\n\n    int[] dataLength = new int[1];\n    Pointer[] data = new Pointer[1];\n\n    try {\n      int errCode = 0;\n      synchronized (securityLib) {\n        errCode =\n            securityLib.SecKeychainFindGenericPassword(\n                null,\n                targetBytes.length,\n                targetBytes,\n                userBytes.length,\n                userBytes,\n                dataLength,\n                data,\n                null);\n      }\n\n      if (errCode != SecurityLib.ERR_SEC_SUCCESS) {\n        logger.warn(\n            String.format(\n                \"Failed to find the item in keychain or item not exists. Error code = %d\",\n                Native.getLastError()));\n        return null;\n      }\n      if (dataLength[0] == 0 || data[0] == null) {\n        logger.warn(\"Found empty item or no item is found\", false);\n        return null;\n      }\n\n      byte[] credBytes = data[0].getByteArray(0, dataLength[0]);\n      String res = new String(credBytes, StandardCharsets.UTF_8);\n\n      logger.debug(\"Successfully read the credential. Will return it as String now\");\n      return res;\n    } finally {\n      if (data[0] != null) {\n        synchronized (securityLib) {\n          securityLib.SecKeychainItemFreeContent(null, data[0]);\n        }\n      }\n    }\n  }\n\n  public SecureStorageStatus deleteCredential(String host, String user, String type) {\n    String target = SecureStorageManager.buildCredentialsKey(host, user, type);\n    byte[] targetBytes = target.getBytes(StandardCharsets.UTF_8);\n    byte[] userBytes = user.toUpperCase().getBytes(StandardCharsets.UTF_8);\n\n    Pointer[] itemRef = new Pointer[1];\n\n    int errCode = 0;\n    synchronized (securityLib) {\n      errCode =\n          securityLib.SecKeychainFindGenericPassword(\n              null,\n              targetBytes.length,\n              targetBytes,\n              userBytes.length,\n              userBytes,\n              null,\n              null,\n              itemRef);\n    }\n\n    if (errCode != SecurityLib.ERR_SEC_SUCCESS && errCode != SecurityLib.ERR_SEC_ITEM_NOT_FOUND) {\n      logger.warn(\n          String.format(\n              \"Failed to delete the item in keychain. Error code = %d\", Native.getLastError()));\n      return SecureStorageStatus.FAILURE;\n    }\n\n    if (itemRef[0] != null) {\n      synchronized (securityLib) {\n        errCode = securityLib.SecKeychainItemDelete(itemRef[0]);\n      }\n\n      if (errCode != SecurityLib.ERR_SEC_SUCCESS) {\n        logger.warn(\n            String.format(\n                \"Failed to delete the item in keychain. Error code = %d\", Native.getLastError()));\n        return SecureStorageStatus.FAILURE;\n      }\n    }\n\n    return SecureStorageStatus.SUCCESS;\n  }\n\n  static class SecurityLibManager {\n    private static SecurityLib INSTANCE = null;\n\n    private static class ResourceHolder {\n      private static final SecurityLib INSTANCE =\n          (SecurityLib) Native.loadLibrary(\"Security\", SecurityLib.class);\n    }\n\n    public static SecurityLib getInstance() {\n      if (INSTANCE == null) {\n        INSTANCE = ResourceHolder.INSTANCE;\n      }\n      return INSTANCE;\n    }\n\n    /** This function is used only for unit test */\n    public static void setInstance(SecurityLib instance) {\n      INSTANCE = instance;\n    }\n\n    /** This function is a helper function for testing */\n    public static void resetInstance() {\n      if (Constants.getOS() == Constants.OS.MAC) {\n        INSTANCE = ResourceHolder.INSTANCE;\n      }\n    }\n  }\n\n  /** the java mapping of OS X Security Library */\n  interface SecurityLib extends Library {\n    // SecurityLib INSTANCE = (SecurityLib) Native.loadLibrary(\"Security\", SecurityLib.class);\n\n    int ERR_SEC_SUCCESS = 0;\n    int ERR_SEC_ITEM_NOT_FOUND = -25300;\n\n    /**\n     * https://developer.apple.com/documentation/security/1397301-seckeychainfindgenericpassword\n     *\n     * <p>func SecKeychainFindGenericPassword(_ keychainOrArray: CFTypeRef?, _ serviceNameLength:\n     * UInt32, _ serviceName: UnsafePointer<Int8>?, const char* _ accountNameLength: UInt32, _\n     * accountName: UnsafePointer<Int8>?, const char* _ passwordLength:\n     * UnsafeMutablePointer<UInt32>?, UInt32* _ passwordData:\n     * UnsafeMutablePointer<UnsafeMutableRawPointer?>?, void** _ itemRef:\n     * UnsafeMutablePointer<SecKeychainItem?>?), SecKeychainItemRef* -> OSStatus\n     */\n    public int SecKeychainFindGenericPassword(\n        Pointer keychainOrArray,\n        int serviceNameLength,\n        byte[] serviceName,\n        int accountNameLength,\n        byte[] accountName,\n        int[] passwordLength,\n        Pointer[] passwordData,\n        Pointer[] itemRef);\n\n    /**\n     * func SecKeychainAddGenericPassword(_ keychain: SecKeychain?, SecKeychainRef _\n     * serviceNameLength: UInt32, _ serviceName: UnsafePointer<Int8>?, const char* _\n     * accountNameLength: UInt32, _ accountName: UnsafePointer<Int8>?, const char* _ passwordLength:\n     * UInt32, _ passwordData: UnsafeRawPointer, const void* _ itemRef:\n     * UnsafeMutablePointer<SecKeychainItem?>?) SecKeychainItemRef* -> OSStatus\n     */\n    public int SecKeychainAddGenericPassword(\n        Pointer keychain,\n        int serviceNameLength,\n        byte[] serviceName,\n        int accountNameLength,\n        byte[] accountName,\n        int passwordLength,\n        byte[] passwordData,\n        Pointer[] itemRef);\n\n    /**\n     * OSStatus SecKeychainItemModifyContent(SecKeychainItemRef itemRef, const\n     * SecKeychainAttributeList *attrList, UInt32 length, const void *data);\n     */\n    public int SecKeychainItemModifyContent(\n        Pointer itemRef, Pointer attrList, int length, byte[] data);\n\n    /** OSStatus SecKeychainItemDelete(SecKeychainItemRef itemRef); */\n    public int SecKeychainItemDelete(Pointer itemRef);\n\n    /** OSStatus SecKeychainItemFreeContent(SecKeychainAttributeList *attrList, void *data); */\n    public int SecKeychainItemFreeContent(Pointer[] attrList, Pointer data);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SecureStorageLinuxManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION;\nimport static net.snowflake.client.internal.core.StmtUtil.mapper;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.convertSystemGetEnvToBooleanValue;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.node.JsonNodeType;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Linux currently doesn't have a local secure storage like Keychain/Credential Manager in\n * Mac/Windows. This class just wraps the local file cache logic to keep Linux platform api\n * consistent Mac/Windows platform.\n */\npublic class SecureStorageLinuxManager implements SecureStorageManager {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SecureStorageLinuxManager.class);\n  private static final String CACHE_FILE_NAME = \"credential_cache_v1.json\";\n  private static final String CACHE_DIR_PROP = \"net.snowflake.jdbc.temporaryCredentialCacheDir\";\n  private static final String CACHE_DIR_ENV = \"SF_TEMPORARY_CREDENTIAL_CACHE_DIR\";\n  private static final String CACHE_FILE_TOKENS_OBJECT_NAME = \"tokens\";\n  private static final long CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS = 60L;\n  private final FileCacheManager fileCacheManager;\n\n  private SecureStorageLinuxManager() {\n    boolean shouldSkipTokenFilePermissionsVerification =\n        convertSystemGetEnvToBooleanValue(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION, false);\n    if (shouldSkipTokenFilePermissionsVerification) {\n      logger.debug(\n          \"Skip credential cache file permissions verification because {} is enabled\",\n          SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION);\n    }\n    fileCacheManager =\n        new FileCacheManagerBuilder()\n            .setCacheDirectorySystemProperty(CACHE_DIR_PROP)\n            .setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV)\n            .setBaseCacheFileName(CACHE_FILE_NAME)\n            .setOnlyOwnerPermissions(!shouldSkipTokenFilePermissionsVerification)\n            .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS)\n            .build();\n  }\n\n  private static class SecureStorageLinuxManagerHolder {\n    private static final SecureStorageLinuxManager INSTANCE = new SecureStorageLinuxManager();\n  }\n\n  public static SecureStorageLinuxManager getInstance() {\n    return SecureStorageLinuxManagerHolder.INSTANCE;\n  }\n\n  @Override\n  public synchronized SecureStorageStatus setCredential(\n      String host, String user, String type, String token) {\n    if (isNullOrEmpty(token)) {\n      logger.warn(\"No token provided\", false);\n      return SecureStorageStatus.SUCCESS;\n    }\n    fileCacheManager.withLock(\n        () -> {\n          Map<String, Map<String, String>> cachedCredentials =\n              readJsonStoreCache(fileCacheManager.readCacheFile());\n          cachedCredentials.computeIfAbsent(\n              CACHE_FILE_TOKENS_OBJECT_NAME, tokensMap -> new HashMap<>());\n          Map<String, String> credentialsMap = cachedCredentials.get(CACHE_FILE_TOKENS_OBJECT_NAME);\n          credentialsMap.put(SecureStorageManager.buildCredentialsKey(host, user, type), token);\n          fileCacheManager.writeCacheFile(\n              SecureStorageLinuxManager.this.localCacheToJson(cachedCredentials));\n          return null;\n        });\n    return SecureStorageStatus.SUCCESS;\n  }\n\n  @Override\n  public synchronized String getCredential(String host, String user, String type) {\n    return fileCacheManager.withLock(\n        () -> {\n          JsonNode res = fileCacheManager.readCacheFile();\n          Map<String, Map<String, String>> cache = readJsonStoreCache(res);\n          Map<String, String> credentialsMap = cache.get(CACHE_FILE_TOKENS_OBJECT_NAME);\n          if (credentialsMap == null) {\n            return null;\n          }\n          return credentialsMap.get(SecureStorageManager.buildCredentialsKey(host, user, type));\n        });\n  }\n\n  @Override\n  public synchronized SecureStorageStatus deleteCredential(String host, String user, String type) {\n    fileCacheManager.withLock(\n        () -> {\n          JsonNode res = fileCacheManager.readCacheFile();\n          Map<String, Map<String, String>> cache = readJsonStoreCache(res);\n          Map<String, String> credentialsMap = cache.get(CACHE_FILE_TOKENS_OBJECT_NAME);\n          if (credentialsMap != null) {\n            credentialsMap.remove(SecureStorageManager.buildCredentialsKey(host, user, type));\n            if (credentialsMap.isEmpty()) {\n              cache.remove(CACHE_FILE_TOKENS_OBJECT_NAME);\n            }\n          }\n          fileCacheManager.writeCacheFile(localCacheToJson(cache));\n          return null;\n        });\n    return SecureStorageStatus.SUCCESS;\n  }\n\n  private ObjectNode localCacheToJson(Map<String, Map<String, String>> cache) {\n    ObjectNode jsonNode = mapper.createObjectNode();\n    Map<String, String> tokensMap = cache.get(CACHE_FILE_TOKENS_OBJECT_NAME);\n    if (tokensMap != null) {\n      ObjectNode tokensNode = mapper.createObjectNode();\n      for (Map.Entry<String, String> credential : tokensMap.entrySet()) {\n        tokensNode.put(credential.getKey(), credential.getValue());\n      }\n      jsonNode.set(CACHE_FILE_TOKENS_OBJECT_NAME, tokensNode);\n    }\n    return jsonNode;\n  }\n\n  private Map<String, Map<String, String>> readJsonStoreCache(JsonNode node) {\n    Map<String, Map<String, String>> cache = new HashMap<>();\n    if (node == null || !node.getNodeType().equals(JsonNodeType.OBJECT)) {\n      logger.debug(\"Invalid cache file format.\");\n      return cache;\n    }\n    cache.put(CACHE_FILE_TOKENS_OBJECT_NAME, new HashMap<>());\n    JsonNode credentialsNode = node.get(CACHE_FILE_TOKENS_OBJECT_NAME);\n    Map<String, String> credentialsCache = cache.get(CACHE_FILE_TOKENS_OBJECT_NAME);\n    if (credentialsNode != null && node.getNodeType().equals(JsonNodeType.OBJECT)) {\n      for (Iterator<Map.Entry<String, JsonNode>> itr = credentialsNode.fields(); itr.hasNext(); ) {\n        Map.Entry<String, JsonNode> credential = itr.next();\n        credentialsCache.put(credential.getKey(), credential.getValue().asText());\n      }\n    }\n    return cache;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SecureStorageManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\n/**\n * Interface for accessing Platform specific Local Secure Storage E.g. keychain on Mac credential\n * manager on Windows\n */\ninterface SecureStorageManager {\n  int COLON_CHAR_LENGTH = 1;\n\n  SecureStorageStatus setCredential(String host, String user, String type, String token);\n\n  String getCredential(String host, String user, String type);\n\n  SecureStorageStatus deleteCredential(String host, String user, String type);\n\n  static String buildCredentialsKey(String host, String user, String type) {\n    StringBuilder target =\n        new StringBuilder(host.length() + user.length() + type.length() + 3 * COLON_CHAR_LENGTH);\n\n    target.append(host.toUpperCase());\n    target.append(\":\");\n    target.append(user.toUpperCase());\n    target.append(\":\");\n    target.append(type.toUpperCase());\n\n    try {\n      MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n      byte[] hash = md.digest(target.toString().getBytes());\n      return SnowflakeUtil.byteToHexString(hash);\n    } catch (NoSuchAlgorithmException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  enum SecureStorageStatus {\n    NOT_FOUND,\n    FAILURE,\n    SUCCESS,\n    UNSUPPORTED\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SecureStorageWindowsManager.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.sun.jna.Memory;\nimport com.sun.jna.Native;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.Structure;\nimport com.sun.jna.WString;\nimport com.sun.jna.platform.win32.WinBase.FILETIME;\nimport com.sun.jna.ptr.PointerByReference;\nimport com.sun.jna.win32.StdCallLibrary;\nimport com.sun.jna.win32.W32APIOptions;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SecureStorageWindowsManager implements SecureStorageManager {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SecureStorageWindowsManager.class);\n\n  private final Advapi32Lib advapi32Lib;\n\n  private SecureStorageWindowsManager() {\n    advapi32Lib = Advapi32LibManager.getInstance();\n  }\n\n  public static SecureStorageWindowsManager builder() {\n    logger.debug(\"Using Windows Credential Manager as a token cache storage\");\n    return new SecureStorageWindowsManager();\n  }\n\n  public SecureStorageStatus setCredential(String host, String user, String type, String token) {\n    if (isNullOrEmpty(token)) {\n      logger.warn(\"No token provided\", false);\n      return SecureStorageStatus.SUCCESS;\n    }\n\n    byte[] credBlob = token.getBytes(StandardCharsets.UTF_16LE);\n    Memory credBlobMem = new Memory(credBlob.length);\n    credBlobMem.write(0, credBlob, 0, credBlob.length);\n\n    String target = SecureStorageManager.buildCredentialsKey(host, user, type);\n\n    SecureStorageWindowsCredential cred = new SecureStorageWindowsCredential();\n    cred.Type = SecureStorageWindowsCredentialType.CRED_TYPE_GENERIC.getType();\n    cred.TargetName = new WString(target);\n    cred.CredentialBlobSize = (int) credBlobMem.size();\n    cred.CredentialBlob = credBlobMem;\n    cred.Persist = SecureStorageWindowsCredentialPersistType.CRED_PERSIST_LOCAL_MACHINE.getType();\n    cred.UserName = new WString(user.toUpperCase());\n\n    boolean ret = false;\n    synchronized (advapi32Lib) {\n      ret = advapi32Lib.CredWriteW(cred, 0);\n    }\n\n    if (!ret) {\n      logger.warn(\n          String.format(\n              \"Failed to write to Windows Credential Manager. Error code = %d\",\n              Native.getLastError()));\n      return SecureStorageStatus.FAILURE;\n    }\n    logger.debug(\"Wrote to Windows Credential Manager successfully\", false);\n\n    return SecureStorageStatus.SUCCESS;\n  }\n\n  public String getCredential(String host, String user, String type) {\n    PointerByReference pCredential = new PointerByReference();\n    String target = SecureStorageManager.buildCredentialsKey(host, user, type);\n\n    try {\n      boolean ret = false;\n      synchronized (advapi32Lib) {\n        ret =\n            advapi32Lib.CredReadW(\n                target,\n                SecureStorageWindowsCredentialType.CRED_TYPE_GENERIC.getType(),\n                0,\n                pCredential);\n      }\n\n      if (!ret) {\n        logger.warn(\n            String.format(\n                \"Failed to read target or could not find it in Windows Credential Manager. Error code = %d\",\n                Native.getLastError()));\n        return null;\n      }\n\n      logger.debug(\"Found the token from Windows Credential Manager and now copying it\", false);\n\n      SecureStorageWindowsCredential cred =\n          new SecureStorageWindowsCredential(pCredential.getValue());\n\n      if (SecureStorageWindowsCredentialType.typeOf(cred.Type)\n          != SecureStorageWindowsCredentialType.CRED_TYPE_GENERIC) {\n        logger.warn(\"Wrong type of credential. Expected: CRED_TYPE_GENERIC\", false);\n        return null;\n      }\n\n      if (cred.CredentialBlobSize == 0) {\n        logger.debug(\"Returned credential is empty\", false);\n        return null;\n      }\n\n      byte[] credBytes = cred.CredentialBlob.getByteArray(0, cred.CredentialBlobSize);\n      String res = new String(credBytes, StandardCharsets.UTF_16LE);\n      logger.debug(\"Successfully read the token. Will return it as String now\", false);\n      return res;\n    } finally {\n      if (pCredential.getValue() != null) {\n        synchronized (advapi32Lib) {\n          advapi32Lib.CredFree(pCredential.getValue());\n        }\n      }\n    }\n  }\n\n  public SecureStorageStatus deleteCredential(String host, String user, String type) {\n    String target = SecureStorageManager.buildCredentialsKey(host, user, type);\n\n    boolean ret = false;\n    synchronized (advapi32Lib) {\n      ret =\n          advapi32Lib.CredDeleteW(\n              target, SecureStorageWindowsCredentialType.CRED_TYPE_GENERIC.getType(), 0);\n    }\n\n    if (!ret) {\n      logger.warn(\n          String.format(\n              \"Failed to delete target in Windows Credential Manager. Error code = %d\",\n              Native.getLastError()));\n      return SecureStorageStatus.FAILURE;\n    }\n\n    logger.debug(\"Deleted target in Windows Credential Manager successfully\", false);\n    return SecureStorageStatus.SUCCESS;\n  }\n\n  public static class SecureStorageWindowsCredential extends Structure {\n    /**\n     * typedef struct _CREDENTIAL { DWORD Flags; DWORD Type; LPTSTR TargetName; LPTSTR Comment;\n     * FILETIME LastWritten; DWORD CredentialBlobSize; LPBYTE CredentialBlob; DWORD Persist; DWORD\n     * AttributeCount; PCREDENTIAL_ATTRIBUTE Attributes; LPTSTR TargetAlias; LPTSTR UserName; }\n     * CREDENTIAL, *PCREDENTIAL;\n     */\n    public int Flags;\n\n    public int Type;\n    public WString TargetName;\n    public WString Comment;\n    public FILETIME LastWritten = new FILETIME();\n    public int CredentialBlobSize;\n    public Pointer CredentialBlob;\n    public int Persist;\n    public int AttributeCount;\n    public Pointer Attributes;\n    public WString TargetAlias;\n    public WString UserName;\n\n    @Override\n    protected List<String> getFieldOrder() {\n      return Arrays.asList(\n          \"Flags\",\n          \"Type\",\n          \"TargetName\",\n          \"Comment\",\n          \"LastWritten\",\n          \"CredentialBlobSize\",\n          \"CredentialBlob\",\n          \"Persist\",\n          \"AttributeCount\",\n          \"Attributes\",\n          \"TargetAlias\",\n          \"UserName\");\n    }\n\n    public SecureStorageWindowsCredential() {\n      super();\n    }\n\n    public SecureStorageWindowsCredential(Pointer p) {\n      super(p);\n      read();\n    }\n  }\n\n  /** Windows credential types */\n  enum SecureStorageWindowsCredentialType {\n    CRED_TYPE_GENERIC(1),\n    CRED_TYPE_DOMAIN_PASSWORD(2),\n    CRED_TYPE_DOMAIN_CERTIFICATE(3),\n    CRED_TYPE_DOMAIN_VISIBLE_PASSWORD(4),\n    CRED_TYPE_GENERIC_CERTIFICATE(5),\n    CRED_TYPE_DOMAIN_EXTENDED(6),\n    CRED_TYPE_MAXIMUM(7);\n\n    private int type;\n    private static Map<Integer, SecureStorageWindowsCredentialType> map =\n        new HashMap<Integer, SecureStorageWindowsCredentialType>();\n\n    SecureStorageWindowsCredentialType(int type) {\n      this.type = type;\n    }\n\n    static {\n      for (SecureStorageWindowsCredentialType credType :\n          SecureStorageWindowsCredentialType.values()) {\n        map.put(credType.type, credType);\n      }\n    }\n\n    public static SecureStorageWindowsCredentialType typeOf(int type) {\n      return map.get(type);\n    }\n\n    public int getType() {\n      return type;\n    }\n  }\n\n  enum SecureStorageWindowsCredentialPersistType {\n    CRED_PERSIST_NONE(0),\n    CRED_PERSIST_SESSION(1),\n    CRED_PERSIST_LOCAL_MACHINE(2),\n    CRED_PERSIST_ENTERPRISE(3);\n\n    private int type;\n    private static Map<Integer, SecureStorageWindowsCredentialPersistType> map =\n        new HashMap<Integer, SecureStorageWindowsCredentialPersistType>();\n\n    SecureStorageWindowsCredentialPersistType(int type) {\n      this.type = type;\n    }\n\n    static {\n      for (SecureStorageWindowsCredentialPersistType credPersistType :\n          SecureStorageWindowsCredentialPersistType.values()) {\n        map.put(credPersistType.type, credPersistType);\n      }\n    }\n\n    public int getType() {\n      return type;\n    }\n  }\n\n  static class Advapi32LibManager {\n    private static Advapi32Lib INSTANCE = null;\n\n    private static class ResourceHolder {\n      // map Windows advapi32.dll to Interface Advapi32Lib\n      private static final Advapi32Lib INSTANCE =\n          (Advapi32Lib)\n              Native.loadLibrary(\"advapi32\", Advapi32Lib.class, W32APIOptions.UNICODE_OPTIONS);\n    }\n\n    public static Advapi32Lib getInstance() {\n      if (INSTANCE == null) {\n        INSTANCE = ResourceHolder.INSTANCE;\n      }\n      return INSTANCE;\n    }\n\n    /** This function is used only for unit test */\n    public static void setInstance(Advapi32Lib instance) {\n      INSTANCE = instance;\n    }\n\n    /** This function is a helper function for testing */\n    public static void resetInstance() {\n      if (Constants.getOS() == Constants.OS.WINDOWS) {\n        INSTANCE = ResourceHolder.INSTANCE;\n      }\n    }\n  }\n\n  interface Advapi32Lib extends StdCallLibrary {\n    /** BOOL CredReadW( LPCWSTR TargetName, DWORD Type, DWORD Flags, PCREDENTIALW *Credential ); */\n    boolean CredReadW(String targetName, int type, int flags, PointerByReference pcred);\n\n    /** BOOL CredWriteW( PCREDENTIALW Credential, DWORD Flags ); */\n    boolean CredWriteW(SecureStorageWindowsManager.SecureStorageWindowsCredential cred, int flags);\n\n    /** BOOL CredDeleteW( LPCWSTR TargetName, DWORD Type, DWORD Flags ); */\n    boolean CredDeleteW(String targetName, int type, int flags);\n\n    /** void CredFree( PVOID Buffer ); */\n    void CredFree(Pointer cred);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SecurityUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.security.Provider;\nimport java.security.Security;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SecurityUtil {\n\n  private static final SFLogger LOGGER = SFLoggerFactory.getLogger(SecurityUtil.class);\n\n  /** provider name for FIPS */\n  public static final String BOUNCY_CASTLE_FIPS_PROVIDER = \"BCFIPS\";\n\n  public static final String BOUNCY_CASTLE_PROVIDER = \"BC\";\n  private static final String DEFAULT_SECURITY_PROVIDER_NAME =\n      \"org.bouncycastle.jce.provider.BouncyCastleProvider\";\n\n  public static final String USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM =\n      \"net.snowflake.jdbc.useBundledBouncyCastleForPrivateKeyDecryption\";\n\n  public static void addBouncyCastleProvider() {\n    // Add Bouncy Castle to the list of security providers. This is required to\n    // verify the signature on OCSP response and attached certificates.\n    // It is also required to decrypt password protected private keys.\n    // Check to see if the BouncyCastleFipsProvider has already been added.\n    // If so, then we don't want to add the provider BouncyCastleProvider.\n    // The addProvider() method won't add the provider if it already exists.\n    try {\n      if (Security.getProvider(BOUNCY_CASTLE_FIPS_PROVIDER) == null) {\n        Security.addProvider(instantiateSecurityProvider());\n      }\n    } catch (SecurityException ex) {\n      LOGGER.warn(\n          \"SecurityManager denied access to security providers. \"\n              + \"BouncyCastle provider was not added: {}\",\n          ex.getMessage());\n    }\n  }\n\n  private static Provider instantiateSecurityProvider() {\n\n    try {\n      Class klass = Class.forName(DEFAULT_SECURITY_PROVIDER_NAME);\n      return (Provider) klass.getDeclaredConstructor().newInstance();\n    } catch (ExceptionInInitializerError\n        | ClassNotFoundException\n        | NoSuchMethodException\n        | InstantiationException\n        | IllegalAccessException\n        | IllegalArgumentException\n        | InvocationTargetException\n        | SecurityException ex) {\n      String errMsg =\n          String.format(\n              \"Failed to load %s, err=%s. If you use Snowflake JDBC for FIPS jar, \"\n                  + \"import BouncyCastleFipsProvider in the application.\",\n              DEFAULT_SECURITY_PROVIDER_NAME, ex.getMessage());\n      LOGGER.error(errMsg, true);\n      throw new RuntimeException(errMsg, ex);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SessionUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SFTrustManager.resetOCSPResponseCacherServerURL;\nimport static net.snowflake.client.internal.core.SFTrustManager.setOCSPResponseCacheServerURL;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.recordIfExternal;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.security.PrivateKey;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.auth.ClientAuthnDTO;\nimport net.snowflake.client.internal.core.auth.ClientAuthnParameter;\nimport net.snowflake.client.internal.core.auth.oauth.AccessTokenProvider;\nimport net.snowflake.client.internal.core.auth.oauth.DPoPUtil;\nimport net.snowflake.client.internal.core.auth.oauth.OAuthAccessTokenForRefreshTokenProvider;\nimport net.snowflake.client.internal.core.auth.oauth.OAuthAccessTokenProviderFactory;\nimport net.snowflake.client.internal.core.auth.oauth.TokenResponseDTO;\nimport net.snowflake.client.internal.core.auth.wif.AwsAttestationService;\nimport net.snowflake.client.internal.core.auth.wif.AwsIdentityAttestationCreator;\nimport net.snowflake.client.internal.core.auth.wif.AzureAttestationService;\nimport net.snowflake.client.internal.core.auth.wif.AzureIdentityAttestationCreator;\nimport net.snowflake.client.internal.core.auth.wif.GcpIdentityAttestationCreator;\nimport net.snowflake.client.internal.core.auth.wif.OidcIdentityAttestationCreator;\nimport net.snowflake.client.internal.core.auth.wif.WorkloadIdentityAttestation;\nimport net.snowflake.client.internal.core.auth.wif.WorkloadIdentityAttestationProvider;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport net.snowflake.client.internal.core.minicore.MinicoreTelemetry;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.RetryContext;\nimport net.snowflake.client.internal.jdbc.RetryContextManager;\nimport net.snowflake.client.internal.jdbc.SnowflakeReauthenticationRequest;\nimport net.snowflake.client.internal.jdbc.SnowflakeSQLExceptionWithRetryContext;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.jdbc.util.DriverUtil;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.LibcDetails;\nimport net.snowflake.client.internal.util.LibcInfo;\nimport net.snowflake.client.internal.util.OsReleaseDetails;\nimport net.snowflake.client.internal.util.PlatformDetector;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.client.internal.util.ThrowingFunction;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.HttpHeaders;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.message.BasicHeader;\nimport org.apache.http.message.HeaderGroup;\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.select.Elements;\n\n/** Low level session util */\npublic class SessionUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SessionUtil.class);\n  // Response Field Name\n  private static final String SF_QUERY_DATABASE = \"databaseName\";\n  private static final String SF_QUERY_SCHEMA = \"schemaName\";\n  private static final String SF_QUERY_WAREHOUSE = \"warehouse\";\n  private static final String SF_QUERY_ROLE = \"roleName\";\n\n  // Request path\n  private static final String SF_PATH_LOGIN_REQUEST = \"/session/v1/login-request\";\n  private static final String SF_PATH_TOKEN_REQUEST = \"/session/token-request\";\n  private static final String SF_PATH_OKTA_TOKEN_REQUEST_SUFFIX = \"/api/v1/authn\";\n  private static final String SF_PATH_OKTA_SSO_REQUEST_SUFFIX = \"/sso/saml\";\n  public static final String SF_PATH_AUTHENTICATOR_REQUEST = \"/session/authenticator-request\";\n  public static final String SF_PATH_CONSOLE_LOGIN_REQUEST = \"/console/login\";\n\n  public static final String SF_QUERY_SESSION_DELETE = \"delete\";\n\n  static final String CLIENT_STORE_TEMPORARY_CREDENTIAL = \"CLIENT_STORE_TEMPORARY_CREDENTIAL\";\n\n  // Headers\n  @Deprecated\n  public static final String SF_HEADER_AUTHORIZATION = SFSession.SF_HEADER_AUTHORIZATION;\n\n  // Authentication type\n  private static final String SF_HEADER_BASIC_AUTHTYPE = \"Basic\";\n  private static final String CLIENT_REQUEST_MFA_TOKEN = \"CLIENT_REQUEST_MFA_TOKEN\";\n  private static final String SERVICE_NAME = \"SERVICE_NAME\";\n  private static final String CLIENT_IN_BAND_TELEMETRY_ENABLED = \"CLIENT_TELEMETRY_ENABLED\";\n  private static final String CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED =\n      \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\";\n  private static final String CLIENT_RESULT_COLUMN_CASE_INSENSITIVE =\n      \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\";\n  private static final String JDBC_RS_COLUMN_CASE_INSENSITIVE = \"JDBC_RS_COLUMN_CASE_INSENSITIVE\";\n  private static final String JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC = \"JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\";\n  private static final String JDBC_FORMAT_DATE_WITH_TIMEZONE = \"JDBC_FORMAT_DATE_WITH_TIMEZONE\";\n  private static final String JDBC_USE_SESSION_TIMEZONE = \"JDBC_USE_SESSION_TIMEZONE\";\n  public static final String JDBC_CHUNK_DOWNLOADER_MAX_RETRY = \"JDBC_CHUNK_DOWNLOADER_MAX_RETRY\";\n  private static final String CLIENT_RESULT_CHUNK_SIZE_JVM =\n      \"net.snowflake.jdbc.clientResultChunkSize\";\n  public static final String CLIENT_RESULT_CHUNK_SIZE = \"CLIENT_RESULT_CHUNK_SIZE\";\n  public static final String CLIENT_MEMORY_LIMIT_JVM = \"net.snowflake.jdbc.clientMemoryLimit\";\n  public static final String CLIENT_MEMORY_LIMIT = \"CLIENT_MEMORY_LIMIT\";\n  public static final String QUERY_CONTEXT_CACHE_SIZE = \"QUERY_CONTEXT_CACHE_SIZE\";\n  public static final String JDBC_ENABLE_PUT_GET = \"JDBC_ENABLE_PUT_GET\";\n  public static final String CLIENT_PREFETCH_THREADS_JVM =\n      \"net.snowflake.jdbc.clientPrefetchThreads\";\n  public static final String CLIENT_PREFETCH_THREADS = \"CLIENT_PREFETCH_THREADS\";\n  public static final String CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE_JVM =\n      \"net.snowflake.jdbc.clientEnableConservativeMemoryUsage\";\n  public static final String CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE =\n      \"CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\";\n  public static final String CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP =\n      \"CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP\";\n  public static final String OCSP_FAIL_OPEN_JVM = \"net.snowflake.jdbc.ocspFailOpen\";\n  private static final String OCSP_FAIL_OPEN = \"ocspFailOpen\";\n  public static final String CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY =\n      \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\";\n  public static final String CLIENT_SFSQL = \"CLIENT_SFSQL\";\n  public static final String CLIENT_VALIDATE_DEFAULT_PARAMETERS =\n      \"CLIENT_VALIDATE_DEFAULT_PARAMETERS\";\n  public static final String CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS =\n      \"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\";\n  public static final String CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX =\n      \"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\";\n  public static final String CLIENT_METADATA_USE_SESSION_DATABASE =\n      \"CLIENT_METADATA_USE_SESSION_DATABASE\";\n  public static final String ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1 =\n      \"ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1\";\n\n  static final String SF_HEADER_SERVICE_NAME = \"X-Snowflake-Service\";\n\n  public static final String SF_HEADER_CLIENT_APP_ID = \"CLIENT_APP_ID\";\n\n  public static final String SF_HEADER_CLIENT_APP_VERSION = \"CLIENT_APP_VERSION\";\n\n  private static final String ID_TOKEN_AUTHENTICATOR = \"ID_TOKEN\";\n\n  private static final String NO_QUERY_ID = \"\";\n  private static final String SF_PATH_SESSION = \"/session\";\n  public static long DEFAULT_CLIENT_MEMORY_LIMIT = 1536; // MB\n  public static int DEFAULT_CLIENT_PREFETCH_THREADS = 4;\n  public static int MIN_CLIENT_CHUNK_SIZE = 48;\n  public static int MAX_CLIENT_CHUNK_SIZE = 160;\n\n  public static Map<String, String> JVM_PARAMS_TO_PARAMS =\n      Stream.of(\n              new String[][] {\n                {CLIENT_RESULT_CHUNK_SIZE_JVM, CLIENT_RESULT_CHUNK_SIZE},\n                {CLIENT_MEMORY_LIMIT_JVM, CLIENT_MEMORY_LIMIT},\n                {CLIENT_PREFETCH_THREADS_JVM, CLIENT_PREFETCH_THREADS},\n                {OCSP_FAIL_OPEN_JVM, OCSP_FAIL_OPEN},\n                {\n                  CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE_JVM,\n                  CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\n                }\n              })\n          .collect(Collectors.toMap(data -> data[0], data -> data[1]));\n  private static ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n  private static int DEFAULT_HEALTH_CHECK_INTERVAL = 45; // sec\n  private static Set<String> STRING_PARAMS =\n      new HashSet<>(\n          Arrays.asList(\n              \"TIMEZONE\",\n              \"TIMESTAMP_OUTPUT_FORMAT\",\n              \"TIMESTAMP_NTZ_OUTPUT_FORMAT\",\n              \"TIMESTAMP_LTZ_OUTPUT_FORMAT\",\n              \"TIMESTAMP_TZ_OUTPUT_FORMAT\",\n              \"DATE_OUTPUT_FORMAT\",\n              \"TIME_OUTPUT_FORMAT\",\n              \"BINARY_OUTPUT_FORMAT\",\n              \"CLIENT_TIMESTAMP_TYPE_MAPPING\",\n              SERVICE_NAME,\n              \"GEOGRAPHY_OUTPUT_FORMAT\"));\n  private static final Set<String> INT_PARAMS =\n      new HashSet<>(\n          Arrays.asList(\n              CLIENT_PREFETCH_THREADS,\n              CLIENT_MEMORY_LIMIT,\n              CLIENT_RESULT_CHUNK_SIZE,\n              \"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\",\n              \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\"));\n  private static final Set<String> BOOLEAN_PARAMS =\n      new HashSet<>(\n          Arrays.asList(\n              CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY,\n              \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\",\n              \"CLIENT_DISABLE_INCIDENTS\",\n              \"CLIENT_SESSION_KEEP_ALIVE\",\n              CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS,\n              CLIENT_IN_BAND_TELEMETRY_ENABLED,\n              CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED,\n              CLIENT_STORE_TEMPORARY_CREDENTIAL,\n              CLIENT_REQUEST_MFA_TOKEN,\n              \"JDBC_USE_JSON_PARSER\",\n              \"AUTOCOMMIT\",\n              \"JDBC_EFFICIENT_CHUNK_STORAGE\",\n              JDBC_RS_COLUMN_CASE_INSENSITIVE,\n              JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC,\n              JDBC_FORMAT_DATE_WITH_TIMEZONE,\n              JDBC_USE_SESSION_TIMEZONE,\n              CLIENT_RESULT_COLUMN_CASE_INSENSITIVE,\n              CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX,\n              CLIENT_METADATA_USE_SESSION_DATABASE,\n              \"JDBC_TREAT_DECIMAL_AS_INT\",\n              \"JDBC_ENABLE_COMBINED_DESCRIBE\",\n              CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE,\n              CLIENT_VALIDATE_DEFAULT_PARAMETERS,\n              ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1,\n              \"SNOWPARK_LAZY_ANALYSIS\"));\n\n  /**\n   * Returns Authenticator type\n   *\n   * @param loginInput login information\n   * @return Authenticator type\n   */\n  private static AuthenticatorType getAuthenticator(SFLoginInput loginInput) {\n    if (loginInput.getAuthenticator() != null) {\n      if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.EXTERNALBROWSER.name())) {\n        // SAML 2.0 compliant service/application\n        return AuthenticatorType.EXTERNALBROWSER;\n      } else if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.OAUTH_AUTHORIZATION_CODE.name())) {\n        return AuthenticatorType.OAUTH_AUTHORIZATION_CODE;\n      } else if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.OAUTH_CLIENT_CREDENTIALS.name())) {\n        return AuthenticatorType.OAUTH_CLIENT_CREDENTIALS;\n      } else if (loginInput.getAuthenticator().equalsIgnoreCase(AuthenticatorType.OAUTH.name())) {\n        // OAuth access code Authentication\n        return AuthenticatorType.OAUTH;\n      } else if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.PROGRAMMATIC_ACCESS_TOKEN.name())) {\n        return AuthenticatorType.PROGRAMMATIC_ACCESS_TOKEN;\n      } else if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.WORKLOAD_IDENTITY.name())) {\n        return AuthenticatorType.WORKLOAD_IDENTITY;\n      } else if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.SNOWFLAKE_JWT.name())) {\n        return AuthenticatorType.SNOWFLAKE_JWT;\n      } else if (loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.USERNAME_PASSWORD_MFA.name())) {\n        return AuthenticatorType.USERNAME_PASSWORD_MFA;\n      } else if (!loginInput\n          .getAuthenticator()\n          .equalsIgnoreCase(AuthenticatorType.SNOWFLAKE.name())) {\n        // OKTA authenticator v1.\n        return AuthenticatorType.OKTA;\n      }\n    }\n\n    // authenticator is null, then jdbc will decide authenticator depends on\n    // if privateKey is specified or not. If yes, authenticator type will be\n    // SNOWFLAKE_JWT, otherwise it will use SNOWFLAKE.\n    return loginInput.isPrivateKeyProvided()\n        ? AuthenticatorType.SNOWFLAKE_JWT\n        : AuthenticatorType.SNOWFLAKE;\n  }\n\n  /**\n   * Open a new session\n   *\n   * @param loginInput login information\n   * @return information get after login such as token information\n   * @throws SFException if unexpected uri syntax\n   * @throws SnowflakeSQLException if failed to establish connection with snowflake\n   */\n  static SFLoginOutput openSession(\n      SFLoginInput loginInput,\n      Map<SFSessionProperty, Object> connectionPropertiesMap,\n      String tracingLevel)\n      throws SFException, SnowflakeSQLException {\n    AssertUtil.assertTrue(\n        loginInput.getServerUrl() != null, \"missing server URL for opening session\");\n\n    AssertUtil.assertTrue(loginInput.getAppId() != null, \"missing app id for opening session\");\n\n    AssertUtil.assertTrue(\n        loginInput.getLoginTimeout() >= 0, \"negative login timeout for opening session\");\n\n    final AuthenticatorType authenticator = getAuthenticator(loginInput);\n\n    if (isTokenOrPasswordRequired(authenticator)) {\n      AssertUtil.assertTrue(\n          loginInput.getToken() != null || loginInput.getPassword() != null,\n          \"missing token or password for opening session\");\n    }\n    if (isUsernameRequired(authenticator)) {\n      AssertUtil.assertTrue(\n          loginInput.getUserName() != null, \"missing user name for opening session\");\n    }\n    if (isEligibleForTokenCaching(authenticator)) {\n      if ((Constants.getOS() == Constants.OS.MAC || Constants.getOS() == Constants.OS.WINDOWS)\n          && loginInput.isEnableClientStoreTemporaryCredential()) {\n        // force to set the flag for Mac/Windows users\n        loginInput.getSessionParameters().put(CLIENT_STORE_TEMPORARY_CREDENTIAL, true);\n      } else {\n        // Linux should read from JDBC configuration. For other unsupported OS, we set it to false\n        // as default value\n        if (!loginInput.getSessionParameters().containsKey(CLIENT_STORE_TEMPORARY_CREDENTIAL)) {\n          loginInput.getSessionParameters().put(CLIENT_STORE_TEMPORARY_CREDENTIAL, false);\n        }\n      }\n    } else {\n      // TODO: patch for now. We should update mergeProperties\n      // to normalize all parameters using STRING_PARAMS, INT_PARAMS and\n      // BOOLEAN_PARAMS.\n      Object value = loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL);\n      if (value != null) {\n        loginInput.getSessionParameters().put(CLIENT_STORE_TEMPORARY_CREDENTIAL, asBoolean(value));\n      }\n    }\n\n    if (authenticator.equals(AuthenticatorType.USERNAME_PASSWORD_MFA)\n        && loginInput.isEnableClientRequestMfaToken()) {\n      loginInput.getSessionParameters().put(CLIENT_REQUEST_MFA_TOKEN, true);\n    }\n\n    if (authenticator.equals(AuthenticatorType.WORKLOAD_IDENTITY)) {\n      WorkloadIdentityAttestation attestation = getWorkloadIdentityAttestation(loginInput);\n      if (attestation != null) {\n        loginInput.setWorkloadIdentityAttestation(attestation);\n      } else {\n        throw new SFException(\n            ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n            \"Unable to obtain workload identity attestation. Make sure that correct workload identity provider has been set and that Snowflake-JDBC driver runs on supported environment.\");\n      }\n    }\n\n    convertSessionParameterStringValueToBooleanIfGiven(loginInput, CLIENT_REQUEST_MFA_TOKEN);\n\n    readCachedCredentialsIfPossible(loginInput);\n\n    try {\n      resetOCSPUrlIfNecessary(loginInput.getServerUrl());\n    } catch (IOException ex) {\n      throw new SFException(ex, ErrorCode.IO_ERROR, \"unexpected URL syntax exception\");\n    }\n\n    if (OAuthAccessTokenProviderFactory.isEligible(getAuthenticator(loginInput))) {\n      obtainAuthAccessTokenAndUpdateInput(loginInput);\n    }\n\n    try {\n      return newSession(loginInput, connectionPropertiesMap, tracingLevel);\n    } catch (SnowflakeReauthenticationRequest ex) {\n      if (ex.getErrorCode() == Constants.OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE\n          && isNativeOAuthOriginalAuthenticator(loginInput)) {\n        if (loginInput.getOauthRefreshToken() != null\n            && AuthenticatorType.OAUTH_AUTHORIZATION_CODE\n                .name()\n                .equals(loginInput.getOriginalAuthenticator())) {\n          refreshOAuthAccessTokenAndUpdateInput(loginInput);\n        } else {\n          loginInput.restoreOriginalAuthenticator();\n          fetchOAuthAccessTokenAndUpdateInput(loginInput);\n        }\n      }\n      return newSession(loginInput, connectionPropertiesMap, tracingLevel);\n    }\n  }\n\n  private static boolean isNativeOAuthOriginalAuthenticator(SFLoginInput loginInput) {\n    return AuthenticatorType.OAUTH_AUTHORIZATION_CODE\n            .name()\n            .equals(loginInput.getOriginalAuthenticator())\n        || AuthenticatorType.OAUTH_CLIENT_CREDENTIALS\n            .name()\n            .equals(loginInput.getOriginalAuthenticator());\n  }\n\n  private static WorkloadIdentityAttestation getWorkloadIdentityAttestation(SFLoginInput loginInput)\n      throws SFException {\n    WorkloadIdentityAttestationProvider attestationProvider =\n        new WorkloadIdentityAttestationProvider(\n            new AwsIdentityAttestationCreator(new AwsAttestationService(), loginInput),\n            new GcpIdentityAttestationCreator(loginInput),\n            new AzureIdentityAttestationCreator(new AzureAttestationService(), loginInput),\n            new OidcIdentityAttestationCreator(loginInput.getToken()));\n    return attestationProvider.getAttestation(loginInput.getWorkloadIdentityProvider());\n  }\n\n  private static boolean isEligibleForTokenCaching(AuthenticatorType authenticator) {\n    return authenticator.equals(AuthenticatorType.EXTERNALBROWSER)\n        || authenticator.equals(AuthenticatorType.OAUTH_AUTHORIZATION_CODE)\n        || authenticator.equals(AuthenticatorType.OAUTH_CLIENT_CREDENTIALS);\n  }\n\n  private static boolean isTokenOrPasswordRequired(AuthenticatorType authenticator) {\n    return authenticator.equals(AuthenticatorType.OAUTH)\n        || authenticator.equals(AuthenticatorType.PROGRAMMATIC_ACCESS_TOKEN);\n  }\n\n  private static boolean isUsernameRequired(AuthenticatorType authenticator) {\n    return !authenticator.equals(AuthenticatorType.OAUTH)\n        && !authenticator.equals(AuthenticatorType.PROGRAMMATIC_ACCESS_TOKEN)\n        && !authenticator.equals(AuthenticatorType.OAUTH_AUTHORIZATION_CODE)\n        && !authenticator.equals(AuthenticatorType.OAUTH_CLIENT_CREDENTIALS)\n        && !authenticator.equals(AuthenticatorType.WORKLOAD_IDENTITY);\n  }\n\n  private static void obtainAuthAccessTokenAndUpdateInput(SFLoginInput loginInput)\n      throws SFException {\n    if (loginInput.getOauthAccessToken() != null) { // Access Token was cached\n      loginInput.setAuthenticator(AuthenticatorType.OAUTH.name());\n      loginInput.setToken(loginInput.getOauthAccessToken());\n    } else { // Access Token not cached\n      fetchOAuthAccessTokenAndUpdateInput(loginInput);\n    }\n  }\n\n  private static void fetchOAuthAccessTokenAndUpdateInput(SFLoginInput loginInput)\n      throws SFException {\n    OAuthAccessTokenProviderFactory accessTokenProviderFactory =\n        new OAuthAccessTokenProviderFactory();\n    AccessTokenProvider accessTokenProvider =\n        accessTokenProviderFactory.createAccessTokenProvider(\n            getAuthenticator(loginInput), loginInput);\n    TokenResponseDTO tokenResponse = accessTokenProvider.getAccessToken(loginInput);\n    loginInput.setAuthenticator(AuthenticatorType.OAUTH.name());\n    loginInput.setToken(tokenResponse.getAccessToken());\n    loginInput.setOauthAccessToken(tokenResponse.getAccessToken());\n    loginInput.setOauthRefreshToken(tokenResponse.getRefreshToken());\n    if (loginInput.isDPoPEnabled() && accessTokenProvider.getDPoPPublicKey() != null) {\n      loginInput.setDPoPPublicKey(accessTokenProvider.getDPoPPublicKey());\n    }\n  }\n\n  private static void refreshOAuthAccessTokenAndUpdateInput(SFLoginInput loginInput)\n      throws SFException {\n    try {\n      OAuthAccessTokenForRefreshTokenProvider tokenRefresher =\n          new OAuthAccessTokenForRefreshTokenProvider();\n      TokenResponseDTO tokenResponse = tokenRefresher.getAccessToken(loginInput);\n      loginInput.setToken(tokenResponse.getAccessToken());\n      loginInput.setOauthAccessToken(tokenResponse.getAccessToken());\n      loginInput.setAuthenticator(AuthenticatorType.OAUTH.name());\n      if (loginInput.isDPoPEnabled() && tokenRefresher.getDPoPPublicKey() != null) {\n        loginInput.setDPoPPublicKey(tokenRefresher.getDPoPPublicKey());\n      }\n      if (tokenResponse.getRefreshToken() != null) {\n        loginInput.setOauthRefreshToken(tokenResponse.getRefreshToken());\n      }\n    } catch (SFException | Exception e) {\n      logger.debug(\n          \"Refreshing OAuth access token failed. Removing OAuth refresh token from cache and restarting OAuth flow...\",\n          e);\n      if (asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) {\n        CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInput);\n      }\n      loginInput.restoreOriginalAuthenticator();\n      fetchOAuthAccessTokenAndUpdateInput(loginInput);\n    }\n  }\n\n  private static void convertSessionParameterStringValueToBooleanIfGiven(\n      SFLoginInput loginInput, String parameterName) {\n    Object currentClientRequestMfaToken = loginInput.getSessionParameters().get(parameterName);\n    if (currentClientRequestMfaToken instanceof String) {\n      loginInput\n          .getSessionParameters()\n          .put(parameterName, Boolean.parseBoolean((String) currentClientRequestMfaToken));\n    }\n  }\n\n  private static void readCachedCredentialsIfPossible(SFLoginInput loginInput) throws SFException {\n    if (!isNullOrEmpty(loginInput.getUserName())) {\n      if (asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) {\n        CredentialManager.fillCachedIdToken(loginInput);\n        if (AuthenticatorType.OAUTH_AUTHORIZATION_CODE.equals(getAuthenticator(loginInput))) {\n          CredentialManager.fillCachedOAuthRefreshToken(loginInput);\n          if (loginInput.isDPoPEnabled()) {\n            CredentialManager.fillCachedDPoPBundledAccessToken(loginInput);\n          }\n          if (loginInput.getOauthAccessToken() == null && loginInput.getDPoPPublicKey() == null) {\n            CredentialManager.fillCachedOAuthAccessToken(loginInput);\n          }\n        }\n      }\n\n      if (asBoolean(loginInput.getSessionParameters().get(CLIENT_REQUEST_MFA_TOKEN))) {\n        CredentialManager.fillCachedMfaToken(loginInput);\n      }\n    }\n  }\n\n  private static boolean asBoolean(Object value) {\n    if (value == null) {\n      return false;\n    }\n    switch (value.getClass().getName()) {\n      case \"java.lang.Boolean\":\n        return (Boolean) value;\n      case \"java.lang.String\":\n        return Boolean.valueOf((String) value);\n    }\n    return false;\n  }\n\n  static SFLoginOutput newSession(\n      SFLoginInput loginInput,\n      Map<SFSessionProperty, Object> connectionPropertiesMap,\n      String tracingLevel)\n      throws SFException, SnowflakeSQLException {\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    // build URL for login request\n    URIBuilder uriBuilder;\n    URI loginURI;\n    String tokenOrSamlResponse = null;\n    String samlProofKey = null;\n    boolean consentCacheIdToken = true;\n\n    String sessionToken;\n    String masterToken;\n    String sessionDatabase;\n    String sessionSchema;\n    String sessionRole;\n    String sessionWarehouse;\n    String sessionId;\n    long masterTokenValidityInSeconds;\n    String idToken;\n    String mfaToken;\n    String databaseVersion = null;\n    int databaseMajorVersion = 0;\n    int databaseMinorVersion = 0;\n    String newClientForUpgrade;\n    int healthCheckInterval = DEFAULT_HEALTH_CHECK_INTERVAL;\n    int httpClientSocketTimeout = loginInput.getSocketTimeoutInMillis();\n    int httpClientConnectionTimeout = loginInput.getConnectionTimeoutInMillis();\n    final AuthenticatorType authenticatorType = getAuthenticator(loginInput);\n    Map<String, Object> commonParams;\n\n    String oktaUsername = loginInput.getOKTAUserName();\n    logger.debug(\n        \"Authenticating user: {}, host: {} with authentication method: {}.\"\n            + \" Login timeout: {} s, auth timeout: {} s, OCSP mode: {}{}\",\n        loginInput.getUserName(),\n        loginInput.getHostFromServerUrl(),\n        authenticatorType,\n        loginInput.getLoginTimeout(),\n        loginInput.getAuthTimeout(),\n        loginInput.getOCSPMode(),\n        isNullOrEmpty(oktaUsername) ? \"\" : \", okta username: \" + oktaUsername);\n\n    try {\n\n      uriBuilder = new URIBuilder(loginInput.getServerUrl());\n      // add database name and schema name as query parameters\n      if (loginInput.getDatabaseName() != null) {\n        uriBuilder.addParameter(SF_QUERY_DATABASE, loginInput.getDatabaseName());\n      }\n\n      if (loginInput.getSchemaName() != null) {\n        uriBuilder.addParameter(SF_QUERY_SCHEMA, loginInput.getSchemaName());\n      }\n\n      if (loginInput.getWarehouse() != null) {\n        uriBuilder.addParameter(SF_QUERY_WAREHOUSE, loginInput.getWarehouse());\n      }\n\n      if (loginInput.getRole() != null) {\n        uriBuilder.addParameter(SF_QUERY_ROLE, loginInput.getRole());\n      }\n\n      if (authenticatorType == AuthenticatorType.EXTERNALBROWSER) {\n        // try to reuse id_token if exists\n        if (loginInput.getIdToken() == null) {\n          // SAML 2.0 compliant service/application\n          SessionUtilExternalBrowser s = SessionUtilExternalBrowser.createInstance(loginInput);\n          s.authenticate();\n          tokenOrSamlResponse = s.getToken();\n          samlProofKey = s.getProofKey();\n          consentCacheIdToken = s.isConsentCacheIdToken();\n        }\n      } else if (authenticatorType == AuthenticatorType.OKTA) {\n        // okta authenticator v1\n        tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput);\n      } else if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT) {\n        SessionUtilKeyPair s =\n            new SessionUtilKeyPair(\n                loginInput.getPrivateKey(),\n                loginInput.getPrivateKeyFile(),\n                loginInput.getPrivateKeyBase64(),\n                loginInput.getPrivateKeyPwd(),\n                loginInput.getAccountName(),\n                loginInput.getUserName());\n\n        loginInput.setToken(s.issueJwtToken());\n        loginInput.setAuthTimeout(SessionUtilKeyPair.getTimeout());\n      }\n\n      uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, UUIDUtils.getUUID().toString());\n\n      uriBuilder.setPath(SF_PATH_LOGIN_REQUEST);\n      loginURI = uriBuilder.build();\n    } catch (URISyntaxException ex) {\n      logger.error(\"Exception when building URL\", ex);\n\n      throw new SFException(ex, ErrorCode.INTERNAL_ERROR, \"unexpected URI syntax exception:1\");\n    }\n\n    HttpPost postRequest = null;\n    HttpResponseWithHeaders response = null;\n\n    try {\n      Map<String, Object> data = new HashMap<>();\n      data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());\n\n      /*\n       * username is always included regardless of authenticator to identify\n       * the user.\n       */\n      data.put(ClientAuthnParameter.LOGIN_NAME.name(), loginInput.getUserName());\n\n      /*\n       * only include password information in the request to GS if federated\n       * authentication method is not specified.\n       * When specified, this password information is really to be used to\n       * authenticate with the IDP provider only, and GS should not have any\n       * trace for this information.\n       */\n      if (authenticatorType == AuthenticatorType.SNOWFLAKE) {\n        data.put(ClientAuthnParameter.PASSWORD.name(), loginInput.getPassword());\n      } else if (authenticatorType == AuthenticatorType.EXTERNALBROWSER) {\n        if (loginInput.getIdToken() != null) {\n          data.put(ClientAuthnParameter.AUTHENTICATOR.name(), ID_TOKEN_AUTHENTICATOR);\n          data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getIdToken());\n        } else {\n          data.put(\n              ClientAuthnParameter.AUTHENTICATOR.name(), AuthenticatorType.EXTERNALBROWSER.name());\n          data.put(ClientAuthnParameter.PROOF_KEY.name(), samlProofKey);\n          data.put(ClientAuthnParameter.TOKEN.name(), tokenOrSamlResponse);\n        }\n      } else if (authenticatorType == AuthenticatorType.OKTA) {\n        data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse);\n      } else if (authenticatorType == AuthenticatorType.OAUTH) {\n        data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticatorType.name());\n\n        // Fix for HikariCP refresh token issue:SNOW-533673.\n        // If token value is not set but password field is set then\n        // the driver treats password as token.\n        if (loginInput.getToken() != null) {\n          data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getToken());\n        } else {\n          data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getPassword());\n        }\n\n      } else if (authenticatorType == AuthenticatorType.PROGRAMMATIC_ACCESS_TOKEN) {\n        data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticatorType.name());\n        data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getToken());\n      } else if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT) {\n        data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticatorType.name());\n        data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getToken());\n      } else if (authenticatorType == AuthenticatorType.USERNAME_PASSWORD_MFA) {\n        // No authenticator name should be added here, since this will be treated as snowflake\n        // default authenticator by backend\n        data.put(ClientAuthnParameter.PASSWORD.name(), loginInput.getPassword());\n        if (loginInput.getMfaToken() != null) {\n          data.put(ClientAuthnParameter.TOKEN.name(), loginInput.getMfaToken());\n        }\n      }\n\n      if (authenticatorType == AuthenticatorType.WORKLOAD_IDENTITY) {\n        data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticatorType.name());\n        data.put(\n            ClientAuthnParameter.TOKEN.name(),\n            loginInput.getWorkloadIdentityAttestation().getCredential());\n        data.put(\n            ClientAuthnParameter.PROVIDER.name(),\n            loginInput.getWorkloadIdentityAttestation().getProvider());\n      }\n\n      Map<String, Object> clientEnv =\n          createClientEnvironmentInfo(\n              loginInput, connectionPropertiesMap, tracingLevel, authenticatorType);\n\n      data.put(ClientAuthnParameter.CLIENT_ENVIRONMENT.name(), clientEnv);\n\n      // Initialize the session parameters\n      Map<String, Object> sessionParameter = loginInput.getSessionParameters();\n      if (loginInput.isValidateDefaultParameters()) {\n        sessionParameter.put(CLIENT_VALIDATE_DEFAULT_PARAMETERS, true);\n      }\n\n      if (sessionParameter != null) {\n        data.put(ClientAuthnParameter.SESSION_PARAMETERS.name(), loginInput.getSessionParameters());\n      }\n\n      if (loginInput.getAccountName() != null) {\n        data.put(ClientAuthnParameter.ACCOUNT_NAME.name(), loginInput.getAccountName());\n      }\n\n      // Second Factor Authentication\n      if (loginInput.isPasscodeInPassword()) {\n        data.put(ClientAuthnParameter.EXT_AUTHN_DUO_METHOD.name(), \"passcode\");\n      } else if (loginInput.getPasscode() != null) {\n        data.put(ClientAuthnParameter.EXT_AUTHN_DUO_METHOD.name(), \"passcode\");\n        data.put(ClientAuthnParameter.PASSCODE.name(), loginInput.getPasscode());\n      } else {\n        data.put(ClientAuthnParameter.EXT_AUTHN_DUO_METHOD.name(), \"push\");\n      }\n\n      data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion());\n\n      // SPCS service-identifier token - when the driver is\n      // running inside an SPCS container, the runtime-issued token is attached to every login\n      // request so the backend can identify the originating service. The token is rotated by SPCS,\n      // so it is re-read from disk on every login and never cached.\n      String spcsToken = new SpcsTokenReader().readSpcsToken();\n      if (spcsToken != null) {\n        data.put(ClientAuthnParameter.SPCS_TOKEN.name(), spcsToken);\n      }\n\n      ClientAuthnDTO authnData = new ClientAuthnDTO(data, loginInput.getInFlightCtx());\n      String json = mapper.writeValueAsString(authnData);\n\n      postRequest = new HttpPost(loginURI);\n\n      // Add custom headers before adding common headers\n      HttpUtil.applyAdditionalHeadersForSnowsight(\n          postRequest, loginInput.getAdditionalHttpHeadersForSnowsight());\n\n      // Add headers for driver name and version\n      postRequest.addHeader(SF_HEADER_CLIENT_APP_ID, loginInput.getAppId());\n      postRequest.addHeader(SF_HEADER_CLIENT_APP_VERSION, loginInput.getAppVersion());\n\n      // attach the login info json body to the post request\n      StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);\n      input.setContentType(\"application/json\");\n      postRequest.setEntity(input);\n\n      postRequest.addHeader(\"accept\", \"application/json\");\n      postRequest.addHeader(\"Accept-Encoding\", \"\");\n      if (loginInput.isDPoPEnabled()) {\n        new DPoPUtil(loginInput.getDPoPPublicKey()).addDPoPProofHeaderToRequest(postRequest, null);\n      }\n\n      /*\n       * HttpClient should take authorization header from char[] instead of\n       * String.\n       */\n      postRequest.setHeader(SFSession.SF_HEADER_AUTHORIZATION, SF_HEADER_BASIC_AUTHTYPE);\n\n      setServiceNameHeader(loginInput, postRequest);\n\n      String theString = null;\n\n      int leftRetryTimeout = loginInput.getLoginTimeout();\n      int leftsocketTimeout = loginInput.getSocketTimeoutInMillis();\n      int maxRetryCount = loginInput.getMaxRetryCount();\n      int retryedCount = 0;\n\n      Exception lastRestException = null;\n\n      while (true) {\n        try {\n          response =\n              HttpUtil.executeGeneralRequestWithContext(\n                  postRequest,\n                  leftRetryTimeout,\n                  loginInput.getAuthTimeout(),\n                  leftsocketTimeout,\n                  maxRetryCount,\n                  retryedCount,\n                  loginInput.getHttpClientSettingsKey(),\n                  null);\n          theString = response.getResponseBody();\n        } catch (SnowflakeSQLException ex) {\n          lastRestException = ex;\n          if (ex.getErrorCode() == ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()) {\n            if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT\n                || authenticatorType == AuthenticatorType.OKTA) {\n\n              if (authenticatorType == AuthenticatorType.SNOWFLAKE_JWT) {\n                SessionUtilKeyPair s =\n                    new SessionUtilKeyPair(\n                        loginInput.getPrivateKey(),\n                        loginInput.getPrivateKeyFile(),\n                        loginInput.getPrivateKeyBase64(),\n                        loginInput.getPrivateKeyPwd(),\n                        loginInput.getAccountName(),\n                        loginInput.getUserName());\n\n                data.put(ClientAuthnParameter.TOKEN.name(), s.issueJwtToken());\n              } else if (authenticatorType == AuthenticatorType.OKTA) {\n                // TODO: there is no retry manager passed here for now - we still raise the\n                // exception to retry in the old way\n                logger.debug(\"Retrieve new token for Okta authentication.\");\n                // If we need to retry, we need to get a new Okta token\n                tokenOrSamlResponse = getSamlResponseUsingOkta(loginInput);\n                data.put(ClientAuthnParameter.RAW_SAML_RESPONSE.name(), tokenOrSamlResponse);\n                ClientAuthnDTO updatedAuthnData =\n                    new ClientAuthnDTO(data, loginInput.getInFlightCtx());\n                String updatedJson = mapper.writeValueAsString(updatedAuthnData);\n\n                StringEntity updatedInput = new StringEntity(updatedJson, StandardCharsets.UTF_8);\n                updatedInput.setContentType(\"application/json\");\n                postRequest.setEntity(updatedInput);\n              }\n\n              // Extract retry context information if available\n              long elapsedSeconds = 0;\n              long elapsedMiliSeconds = 0;\n              boolean isSocketTimeoutNoBackoff = false;\n              if (ex instanceof SnowflakeSQLExceptionWithRetryContext) {\n                SnowflakeSQLExceptionWithRetryContext retryEx =\n                    (SnowflakeSQLExceptionWithRetryContext) ex;\n                elapsedSeconds = retryEx.getElapsedSeconds();\n                isSocketTimeoutNoBackoff = retryEx.isSocketTimeoutNoBackoff();\n                elapsedMiliSeconds = retryEx.getElapsedSeconds() * 1000;\n                retryedCount = retryEx.getRetryCount();\n              }\n\n              if (loginInput.getLoginTimeout() > 0) {\n                if (leftRetryTimeout > elapsedSeconds) {\n                  leftRetryTimeout -= elapsedSeconds;\n                } else {\n                  leftRetryTimeout = 1;\n                }\n                logger.debug(\"The remaining Retry timeout is {}\", leftRetryTimeout);\n              }\n\n              // In RestRequest.execute(), socket timeout is replaced with auth timeout\n              // so we can renew the request within auth timeout.\n              // auth timeout within socket timeout is thrown without backoff,\n              // and we need to update time remained in socket timeout here to control\n              // the actual socket timeout from customer setting.\n              if (loginInput.getSocketTimeoutInMillis() > 0) {\n                if (isSocketTimeoutNoBackoff) {\n                  if (leftsocketTimeout > elapsedMiliSeconds) {\n                    leftsocketTimeout -= elapsedMiliSeconds;\n                  } else {\n                    leftsocketTimeout = 1;\n                  }\n                } else {\n                  // reset curl timeout for retry with backoff.\n                  leftsocketTimeout = loginInput.getSocketTimeoutInMillis();\n                }\n                logger.debug(\"The remaining socket timeout is {}\", leftsocketTimeout);\n              }\n\n              // JWT or Okta renew should not count as a retry, so we pass back the current retry\n              // count from the exception context.\n\n              continue;\n            }\n          } else {\n            throw ex;\n          }\n        } catch (Exception ex) {\n          lastRestException = ex;\n        }\n        break;\n      }\n\n      handleEmptyAuthResponse(theString, loginInput, lastRestException);\n\n      // general method, same as with data binding\n      JsonNode jsonNode = mapper.readTree(theString);\n\n      // check the success field first\n      if (!jsonNode.path(\"success\").asBoolean()) {\n        logger.debug(\"Response: {}\", theString);\n\n        int errorCode = jsonNode.path(\"code\").asInt();\n        if (errorCode == Constants.ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE) {\n          // clean id_token first\n          loginInput.setIdToken(null);\n          if (asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) {\n            deleteIdTokenCache(loginInput.getHostFromServerUrl(), loginInput.getUserName());\n          }\n\n          logger.debug(\n              \"ID Token Expired / Not Applicable. Reauthenticating with ID Token cleared...: {}\",\n              errorCode);\n          SnowflakeUtil.checkErrorAndThrowExceptionIncludingReauth(jsonNode);\n        }\n\n        if (errorCode == Constants.OAUTH_ACCESS_TOKEN_INVALID_GS_CODE) {\n          logger.debug(\"OAuth Access Token Invalid: {}\", errorCode);\n          clearAccessTokenCache(loginInput);\n        }\n\n        if (errorCode == Constants.OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE) {\n          clearAccessTokenCache(loginInput);\n\n          logger.debug(\"OAuth Access Token Expired: {}\", errorCode);\n          if (AuthenticatorType.OAUTH.name().equals(loginInput.getOriginalAuthenticator())) {\n            SnowflakeUtil.checkErrorAndThrowException(jsonNode);\n          } else {\n            SnowflakeUtil.checkErrorAndThrowExceptionIncludingReauth(jsonNode);\n          }\n        }\n\n        if (authenticatorType == AuthenticatorType.USERNAME_PASSWORD_MFA\n            && asBoolean(loginInput.getSessionParameters().get(CLIENT_REQUEST_MFA_TOKEN))) {\n          deleteMfaTokenCache(loginInput.getHostFromServerUrl(), loginInput.getUserName());\n        }\n\n        String errorMessage = jsonNode.path(\"message\").asText();\n\n        logger.error(\n            \"Failed to open new session for user: {}, host: {}. Error: {}\",\n            loginInput.getUserName(),\n            loginInput.getHostFromServerUrl(),\n            errorMessage);\n        throw new SnowflakeSQLException(\n            NO_QUERY_ID,\n            errorMessage,\n            SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n            errorCode);\n      }\n\n      // session token is in the data field of the returned json response\n      sessionToken = jsonNode.path(\"data\").path(\"token\").asText();\n      masterToken = jsonNode.path(\"data\").path(\"masterToken\").asText();\n      idToken = nullStringAsEmptyString(jsonNode.path(\"data\").path(\"idToken\").asText());\n      mfaToken = nullStringAsEmptyString(jsonNode.path(\"data\").path(\"mfaToken\").asText());\n      masterTokenValidityInSeconds = jsonNode.path(\"data\").path(\"masterValidityInSeconds\").asLong();\n      String serverVersion = jsonNode.path(\"data\").path(\"serverVersion\").asText();\n      sessionId = jsonNode.path(\"data\").path(\"sessionId\").asText();\n\n      JsonNode dbNode = jsonNode.path(\"data\").path(\"sessionInfo\").path(\"databaseName\");\n      sessionDatabase = dbNode.isNull() ? null : dbNode.asText();\n      JsonNode schemaNode = jsonNode.path(\"data\").path(\"sessionInfo\").path(\"schemaName\");\n      sessionSchema = schemaNode.isNull() ? null : schemaNode.asText();\n      JsonNode roleNode = jsonNode.path(\"data\").path(\"sessionInfo\").path(\"roleName\");\n      sessionRole = roleNode.isNull() ? null : roleNode.asText();\n      JsonNode warehouseNode = jsonNode.path(\"data\").path(\"sessionInfo\").path(\"warehouseName\");\n      sessionWarehouse = warehouseNode.isNull() ? null : warehouseNode.asText();\n\n      commonParams = SessionUtil.getCommonParams(jsonNode.path(\"data\").path(\"parameters\"));\n\n      if (serverVersion != null) {\n        logger.debug(\"Server version: {}\", serverVersion);\n\n        if (serverVersion.indexOf(\" \") > 0) {\n          databaseVersion = serverVersion.substring(0, serverVersion.indexOf(\" \"));\n        } else {\n          databaseVersion = serverVersion;\n        }\n      } else {\n        logger.debug(\"Server version is null\", false);\n      }\n\n      if (databaseVersion != null) {\n        String[] components = databaseVersion.split(\"\\\\.\");\n        if (components.length >= 2) {\n          try {\n            databaseMajorVersion = Integer.parseInt(components[0]);\n            databaseMinorVersion = Integer.parseInt(components[1]);\n          } catch (Exception ex) {\n            logger.error(\n                \"Exception encountered when parsing server \" + \"version: {} Exception: {}\",\n                databaseVersion,\n                ex.getMessage());\n          }\n        }\n      } else {\n        logger.debug(\"database version is null\", false);\n      }\n\n      if (!jsonNode.path(\"data\").path(\"newClientForUpgrade\").isNull()) {\n        newClientForUpgrade = jsonNode.path(\"data\").path(\"newClientForUpgrade\").asText();\n\n        logger.debug(\"New client: {}\", newClientForUpgrade);\n      }\n\n      // get health check interval and adjust network timeouts if different\n      int healthCheckIntervalFromGS = jsonNode.path(\"data\").path(\"healthCheckInterval\").asInt();\n\n      logger.debug(\"Health check interval: {}\", healthCheckIntervalFromGS);\n\n      if (healthCheckIntervalFromGS > 0 && healthCheckIntervalFromGS != healthCheckInterval) {\n        // add health check interval to socket timeout\n        httpClientSocketTimeout =\n            loginInput.getSocketTimeoutInMillis() + (healthCheckIntervalFromGS * 1000);\n\n        final RequestConfig requestConfig =\n            RequestConfig.copy(HttpUtil.getRequestConfigWithoutCookies())\n                .setConnectTimeout(httpClientConnectionTimeout)\n                .setSocketTimeout(httpClientSocketTimeout)\n                .build();\n\n        HttpUtil.setRequestConfig(requestConfig);\n\n        logger.debug(\"Adjusted connection timeout to: {}\", httpClientConnectionTimeout);\n\n        logger.debug(\"Adjusted socket timeout to: {}\", httpClientSocketTimeout);\n      }\n    } catch (SnowflakeSQLException ex) {\n      throw ex; // must catch here to avoid Throwable to get the exception\n    } catch (IOException ex) {\n      logger.error(\"IOException when creating session: \" + postRequest, ex);\n\n      throw new SnowflakeSQLException(\n          ex,\n          SqlState.IO_ERROR,\n          ErrorCode.NETWORK_ERROR.getMessageCode(),\n          \"Exception encountered when opening connection: \" + ex.getMessage());\n    } catch (Throwable ex) {\n      logger.error(\"Exception when creating session: \" + postRequest, ex);\n\n      throw new SnowflakeSQLException(\n          ex,\n          SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n          ErrorCode.CONNECTION_ERROR.getMessageCode(),\n          ErrorCode.CONNECTION_ERROR.getMessageCode(),\n          ex.getMessage());\n    }\n\n    SFLoginOutput ret =\n        new SFLoginOutput(\n            sessionToken,\n            masterToken,\n            masterTokenValidityInSeconds,\n            idToken,\n            mfaToken,\n            loginInput.getOauthAccessToken(),\n            loginInput.getOauthRefreshToken(),\n            databaseVersion,\n            databaseMajorVersion,\n            databaseMinorVersion,\n            httpClientSocketTimeout,\n            httpClientConnectionTimeout,\n            sessionDatabase,\n            sessionSchema,\n            sessionRole,\n            sessionWarehouse,\n            sessionId,\n            commonParams,\n            response != null ? response.getHeaders() : new HashMap<>());\n\n    if (asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) {\n      if (consentCacheIdToken) {\n        CredentialManager.writeIdToken(loginInput, ret.getIdToken());\n      }\n      if (AuthenticatorType.OAUTH_AUTHORIZATION_CODE\n          .name()\n          .equalsIgnoreCase(loginInput.getOriginalAuthenticator())) {\n        if (loginInput.getOauthRefreshToken() != null) {\n          CredentialManager.writeOAuthRefreshToken(loginInput);\n        }\n        if (loginInput.getDPoPPublicKey() != null\n            && loginInput.getOauthAccessToken() != null\n            && loginInput.isDPoPEnabled()) {\n          CredentialManager.writeDPoPBundledAccessToken(loginInput);\n        } else if (loginInput.getOauthAccessToken() != null) {\n          CredentialManager.writeOAuthAccessToken(loginInput);\n        }\n      }\n    }\n\n    if (asBoolean(loginInput.getSessionParameters().get(CLIENT_REQUEST_MFA_TOKEN))) {\n      CredentialManager.writeMfaToken(loginInput, ret.getMfaToken());\n    }\n\n    stopwatch.stop();\n    logger.debug(\n        \"User: {}, host: {} with authentication method: {} authenticated successfully in {} ms\",\n        loginInput.getUserName(),\n        loginInput.getHostFromServerUrl(),\n        authenticatorType,\n        stopwatch.elapsedMillis());\n    return ret;\n  }\n\n  static Map<String, Object> createClientEnvironmentInfo(\n      SFLoginInput loginInput,\n      Map<SFSessionProperty, Object> connectionPropertiesMap,\n      String tracingLevel,\n      AuthenticatorType authenticatorType) {\n    // map of client environment parameters, including connection parameters\n    // and environment properties like OS version, etc.\n    Map<String, Object> clientEnv = new HashMap<>();\n\n    clientEnv.put(\"OS\", systemGetProperty(\"os.name\"));\n    clientEnv.put(\"OS_VERSION\", systemGetProperty(\"os.version\"));\n\n    // Add Linux distribution details from /etc/os-release (only on Linux)\n    Map<String, String> osDetails = OsReleaseDetails.load();\n    if (!osDetails.isEmpty()) {\n      clientEnv.put(\"OS_DETAILS\", osDetails);\n    }\n\n    // Add libc family and version when detectable on Linux. Fields are omitted on non-Linux\n    // systems and on Linux when detection is unavailable or inconclusive. See LibcDetails for\n    // detection strategies.\n    try {\n      LibcInfo libcInfo = LibcDetails.load();\n      if (libcInfo.getFamily() != null) {\n        clientEnv.put(\"LIBC_FAMILY\", libcInfo.getFamily());\n      }\n      if (libcInfo.getVersion() != null) {\n        clientEnv.put(\"LIBC_VERSION\", libcInfo.getVersion());\n      }\n    } catch (Exception | LinkageError e) {\n      logger.debug(\"Failed to detect libc details: {}\", e.getMessage());\n    }\n\n    clientEnv.put(\"JAVA_VERSION\", systemGetProperty(\"java.version\"));\n    clientEnv.put(\"JAVA_RUNTIME\", systemGetProperty(\"java.runtime.name\"));\n    clientEnv.put(\"JAVA_VM\", systemGetProperty(\"java.vm.name\"));\n    clientEnv.put(\"OCSP_MODE\", loginInput.getOCSPMode().name());\n    clientEnv.put(\"CERT_REVOCATION_CHECK_MODE\", getCertRevocationMode(loginInput));\n\n    if (loginInput.getApplication() != null) {\n      clientEnv.put(\"APPLICATION\", loginInput.getApplication());\n    } else {\n      // When you add new client environment info, please add new keys to\n      // messages_en_US.src.json so that they can be displayed properly in UI\n      // detect app name\n      String appName = systemGetProperty(\"sun.java.command\");\n      // remove the arguments\n      if (appName != null) {\n        if (appName.indexOf(\" \") > 0) {\n          appName = appName.substring(0, appName.indexOf(\" \"));\n        }\n\n        clientEnv.put(\"APPLICATION\", appName);\n      }\n    }\n\n    // SNOW-20103: track additional client info in session\n    String clientInfoJSONStr;\n    if (connectionPropertiesMap.containsKey(SFSessionProperty.CLIENT_INFO)) {\n      clientInfoJSONStr = (String) connectionPropertiesMap.get(SFSessionProperty.CLIENT_INFO);\n    }\n    // if connection property is not set, check session property\n    else {\n      clientInfoJSONStr = systemGetProperty(\"snowflake.client.info\");\n    }\n    if (clientInfoJSONStr != null) {\n      JsonNode clientInfoJSON = null;\n\n      try {\n        clientInfoJSON = mapper.readTree(clientInfoJSONStr);\n      } catch (Throwable ex) {\n        logger.debug(\n            \"failed to process snowflake.client.info property as JSON: {}\", clientInfoJSONStr, ex);\n      }\n\n      if (clientInfoJSON != null) {\n        Iterator<Map.Entry<String, JsonNode>> fields = clientInfoJSON.fields();\n        while (fields.hasNext()) {\n          Map.Entry<String, JsonNode> field = fields.next();\n          clientEnv.put(field.getKey(), field.getValue().asText());\n        }\n      }\n    }\n    /*\n     Add all connection parameters and their values that have been set for this\n     * current session into clientEnv. These are the params set via the Properties map or in the\n     * connection string. Includes username, password, serverUrl, timeout values, etc\n    */\n\n    for (Map.Entry<SFSessionProperty, Object> entry : connectionPropertiesMap.entrySet()) {\n      // exclude client parameters already covered by other runtime parameters that have been\n      // added to clientEnv\n      if (entry.getKey().equals(SFSessionProperty.APP_ID)\n          || entry.getKey().equals(SFSessionProperty.APP_VERSION)) {\n        continue;\n      }\n      String propKey = entry.getKey().getPropertyKey();\n      // mask sensitive values like passwords, tokens, etc\n      String propVal = SecretDetector.maskParameterValue(propKey, entry.getValue().toString());\n      clientEnv.put(propKey, propVal);\n    }\n    // if map does not contain the tracing property, the default is set. Add\n    // this default value to the map.\n    if (!connectionPropertiesMap.containsKey(SFSessionProperty.TRACING)) {\n      clientEnv.put(SFSessionProperty.TRACING.getPropertyKey(), tracingLevel);\n    }\n\n    clientEnv.put(\"JDBC_JAR_NAME\", DriverUtil.getJdbcJarname());\n    clientEnv.put(\"LOGGING_IMPLEMENTATION\", SFLoggerFactory.getLoggerImplementationName());\n\n    // Add platform detection (if not disabled)\n    if (!loginInput.isDisablePlatformDetection()) {\n      try {\n        // Use cached platform detection results (initialized once on first use)\n        List<String> detectedPlatforms = PlatformDetector.getCachedPlatformDetection();\n        clientEnv.put(\"PLATFORM\", detectedPlatforms);\n      } catch (Exception e) {\n        logger.debug(\"Platform detection failed: {}\", e.getMessage());\n        // Continue without platform information\n      }\n    } else {\n      logger.debug(\"Platform detection is disabled\");\n    }\n\n    // OAuth metrics data\n    if (authenticatorType == AuthenticatorType.OAUTH\n        && loginInput.getOriginalAuthenticator() != null) {\n      clientEnv.put(ClientAuthnParameter.OAUTH_TYPE.name(), loginInput.getOriginalAuthenticator());\n    }\n\n    // Application path\n    try {\n      String applicationPath =\n          new File(SessionUtil.class.getProtectionDomain().getCodeSource().getLocation().toURI())\n              .getPath();\n      clientEnv.put(ClientAuthnParameter.APPLICATION_PATH.name(), applicationPath);\n    } catch (Exception e) {\n      logger.debug(\"Exception in retrieving application path for client environment\", e);\n      clientEnv.put(ClientAuthnParameter.APPLICATION_PATH.name(), \"UNKNOWN\");\n    }\n\n    try {\n      addMinicoreTelemetry(clientEnv);\n    } catch (Throwable t) {\n      logger.debug(\"Failed to add minicore telemetry: {}\", t.getMessage());\n    }\n\n    return clientEnv;\n  }\n\n  private static void addMinicoreTelemetry(Map<String, Object> clientEnv) {\n    MinicoreTelemetry telemetry = MinicoreTelemetry.create();\n    clientEnv.putAll(telemetry.toClientEnvironmentTelemetryMap());\n  }\n\n  private static String getCertRevocationMode(SFLoginInput loginInput) {\n    HttpClientSettingsKey httpClientSettings = loginInput.getHttpClientSettingsKey();\n    if (httpClientSettings == null) {\n      return null;\n    }\n    CertRevocationCheckMode revocationCheckMode = httpClientSettings.getRevocationCheckMode();\n    if (revocationCheckMode == null) {\n      return null;\n    }\n    return revocationCheckMode.name();\n  }\n\n  private static void clearAccessTokenCache(SFLoginInput loginInput) throws SFException {\n    loginInput.setOauthAccessToken(null);\n    loginInput.setDPoPPublicKey(null);\n    if (asBoolean(loginInput.getSessionParameters().get(CLIENT_STORE_TEMPORARY_CREDENTIAL))) {\n      CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput);\n      CredentialManager.deleteDPoPBundledAccessTokenCacheEntry(loginInput);\n    }\n  }\n\n  private static void setServiceNameHeader(SFLoginInput loginInput, HttpPost postRequest) {\n    if (!isNullOrEmpty(loginInput.getServiceName())) {\n      // service name is used to route a request to appropriate cluster.\n      postRequest.setHeader(SF_HEADER_SERVICE_NAME, loginInput.getServiceName());\n    }\n  }\n\n  private static String nullStringAsEmptyString(String value) {\n    if (isNullOrEmpty(value) || \"null\".equals(value)) {\n      return \"\";\n    }\n    return value;\n  }\n\n  /**\n   * Delete the id token cache\n   *\n   * @param host The host string\n   * @param user The user\n   */\n  public static void deleteIdTokenCache(String host, String user) {\n    CredentialManager.deleteIdTokenCacheEntry(host, user);\n  }\n\n  /**\n   * Delete the Oauth access token cache\n   *\n   * @param host The host string\n   * @param user The user\n   */\n  public static void deleteOAuthAccessTokenCache(String host, String user) {\n    CredentialManager.deleteOAuthAccessTokenCacheEntry(host, user);\n  }\n\n  /**\n   * Delete the Oauth refresh token cache\n   *\n   * @param host The host string\n   * @param user The user\n   */\n  public static void deleteOAuthRefreshTokenCache(String host, String user) {\n    CredentialManager.deleteOAuthRefreshTokenCacheEntry(host, user);\n  }\n\n  /**\n   * Delete the mfa token cache\n   *\n   * @param host The host string\n   * @param user The user\n   */\n  public static void deleteMfaTokenCache(String host, String user) {\n    CredentialManager.deleteMfaTokenCacheEntry(host, user);\n  }\n\n  /**\n   * Renew a session.\n   *\n   * @param loginInput login information\n   * @param session the session associated with the request\n   * @return login output\n   * @throws SFException if unexpected uri information\n   * @throws SnowflakeSQLException if failed to renew the session\n   */\n  static SFLoginOutput renewSession(SFLoginInput loginInput, SFBaseSession session)\n      throws SFException, SnowflakeSQLException {\n    return renewTokenRequest(loginInput, session);\n  }\n\n  private static SFLoginOutput renewTokenRequest(SFLoginInput loginInput, SFBaseSession session)\n      throws SFException, SnowflakeSQLException {\n    AssertUtil.assertTrue(loginInput.getServerUrl() != null, \"missing server URL for tokenRequest\");\n\n    AssertUtil.assertTrue(\n        loginInput.getMasterToken() != null, \"missing master token for tokenRequest\");\n    AssertUtil.assertTrue(\n        loginInput.getSessionToken() != null, \"missing session token for tokenRequest\");\n    AssertUtil.assertTrue(\n        loginInput.getLoginTimeout() >= 0, \"negative login timeout for tokenRequest\");\n\n    // build URL for login request\n    URIBuilder uriBuilder;\n    HttpPost postRequest;\n    String sessionToken;\n    String masterToken;\n\n    try {\n      uriBuilder = new URIBuilder(loginInput.getServerUrl());\n      uriBuilder.setPath(SF_PATH_TOKEN_REQUEST);\n\n      uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, UUIDUtils.getUUID().toString());\n\n      postRequest = new HttpPost(uriBuilder.build());\n\n      // Add headers for driver name and version\n      postRequest.addHeader(SF_HEADER_CLIENT_APP_ID, loginInput.getAppId());\n      postRequest.addHeader(SF_HEADER_CLIENT_APP_VERSION, loginInput.getAppVersion());\n\n      // Add custom headers before adding common headers\n      HttpUtil.applyAdditionalHeadersForSnowsight(\n          postRequest, loginInput.getAdditionalHttpHeadersForSnowsight());\n    } catch (URISyntaxException ex) {\n      logger.error(\"Exception when creating http request\", ex);\n\n      throw new SFException(ex, ErrorCode.INTERNAL_ERROR, \"unexpected URI syntax exception:3\");\n    }\n\n    try {\n      // input json with old session token and request type, notice the\n      // session token needs to be quoted.\n      Map<String, String> payload = new HashMap<>();\n      String headerToken = loginInput.getMasterToken();\n      payload.put(\"oldSessionToken\", loginInput.getSessionToken());\n      payload.put(\"requestType\", TokenRequestType.RENEW.value);\n      String json = mapper.writeValueAsString(payload);\n\n      // attach the login info json body to the post request\n      StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);\n      input.setContentType(\"application/json\");\n      postRequest.setEntity(input);\n\n      postRequest.addHeader(\"accept\", \"application/json\");\n\n      postRequest.setHeader(\n          SFSession.SF_HEADER_AUTHORIZATION,\n          SFSession.SF_HEADER_SNOWFLAKE_AUTHTYPE\n              + \" \"\n              + SFSession.SF_HEADER_TOKEN_TAG\n              + \"=\\\"\"\n              + headerToken\n              + \"\\\"\");\n\n      setServiceNameHeader(loginInput, postRequest);\n\n      logger.debug(\n          \"Request type: {}, old session token: {}, \" + \"master token: {}\",\n          TokenRequestType.RENEW.value,\n          (ArgSupplier) () -> loginInput.getSessionToken() != null ? \"******\" : null,\n          (ArgSupplier) () -> loginInput.getMasterToken() != null ? \"******\" : null);\n\n      String theString =\n          HttpUtil.executeGeneralRequest(\n              postRequest,\n              loginInput.getLoginTimeout(),\n              loginInput.getAuthTimeout(),\n              loginInput.getSocketTimeoutInMillis(),\n              0,\n              loginInput.getHttpClientSettingsKey(),\n              session);\n\n      // general method, same as with data binding\n      JsonNode jsonNode = mapper.readTree(theString);\n\n      // check the success field first\n      if (!jsonNode.path(\"success\").asBoolean()) {\n        logger.debug(\"Response: {}\", theString);\n\n        String errorCode = jsonNode.path(\"code\").asText();\n        String message = jsonNode.path(\"message\").asText();\n\n        EventUtil.triggerBasicEvent(\n            Event.EventType.NETWORK_ERROR,\n            \"SessionUtil:renewSession failure, error code=\" + errorCode + \", message=\" + message,\n            true);\n\n        SnowflakeUtil.checkErrorAndThrowExceptionIncludingReauth(jsonNode);\n      }\n\n      // session token is in the data field of the returned json response\n      sessionToken = jsonNode.path(\"data\").path(\"sessionToken\").asText();\n      masterToken = jsonNode.path(\"data\").path(\"masterToken\").asText();\n    } catch (IOException ex) {\n      logger.error(\"IOException when renewing session: \" + postRequest, ex);\n\n      // Any EventType.NETWORK_ERRORs should have been triggered before\n      // exception was thrown.\n      throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());\n    }\n\n    SFLoginOutput loginOutput = new SFLoginOutput();\n    loginOutput.setSessionToken(sessionToken).setMasterToken(masterToken);\n\n    return loginOutput;\n  }\n\n  /**\n   * Close a session\n   *\n   * @param loginInput login information\n   * @param session the session associated with the request\n   * @throws SnowflakeSQLException if failed to close session\n   * @throws SFException if failed to close session\n   */\n  static void closeSession(SFLoginInput loginInput, SFBaseSession session)\n      throws SFException, SnowflakeSQLException {\n    logger.trace(\"void close() throws SFException\");\n\n    // assert the following inputs are valid\n    AssertUtil.assertTrue(\n        loginInput.getServerUrl() != null, \"missing server URL for closing session\");\n\n    AssertUtil.assertTrue(\n        loginInput.getSessionToken() != null, \"missing session token for closing session\");\n\n    AssertUtil.assertTrue(\n        loginInput.getLoginTimeout() >= 0, \"missing login timeout for closing session\");\n\n    HttpPost postRequest = null;\n\n    try {\n      URIBuilder uriBuilder;\n\n      uriBuilder = new URIBuilder(loginInput.getServerUrl());\n\n      uriBuilder.addParameter(SF_QUERY_SESSION_DELETE, Boolean.TRUE.toString());\n      uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, UUIDUtils.getUUID().toString());\n\n      uriBuilder.setPath(SF_PATH_SESSION);\n\n      postRequest = new HttpPost(uriBuilder.build());\n\n      // Add custom headers before adding common headers\n      HttpUtil.applyAdditionalHeadersForSnowsight(\n          postRequest, loginInput.getAdditionalHttpHeadersForSnowsight());\n\n      postRequest.setHeader(\n          SFSession.SF_HEADER_AUTHORIZATION,\n          SFSession.SF_HEADER_SNOWFLAKE_AUTHTYPE\n              + \" \"\n              + SFSession.SF_HEADER_TOKEN_TAG\n              + \"=\\\"\"\n              + loginInput.getSessionToken()\n              + \"\\\"\");\n\n      setServiceNameHeader(loginInput, postRequest);\n\n      String theString =\n          HttpUtil.executeGeneralRequestWithContext(\n                  postRequest,\n                  loginInput.getLoginTimeout(),\n                  0,\n                  loginInput.getSocketTimeoutInMillis(),\n                  0,\n                  0,\n                  loginInput.getHttpClientSettingsKey(),\n                  session)\n              .getResponseBody();\n\n      JsonNode rootNode;\n\n      logger.debug(\"Connection close response: {}\", theString);\n\n      rootNode = mapper.readTree(theString);\n\n      SnowflakeUtil.checkErrorAndThrowException(rootNode);\n    } catch (URISyntaxException ex) {\n      throw new RuntimeException(\"Unexpected URI syntax exception\", ex);\n    } catch (IOException ex) {\n      logger.error(\"Unexpected IO exception for: \" + postRequest, ex);\n    } catch (SnowflakeSQLException ex) {\n      // ignore exceptions for session expiration exceptions and for\n      // sessions that no longer exist\n      if (ex.getErrorCode() != Constants.SESSION_EXPIRED_GS_CODE\n          && ex.getErrorCode() != Constants.SESSION_GONE) {\n        throw ex;\n      }\n    }\n  }\n\n  /**\n   * Given access token, query IDP URL snowflake app to get SAML response We also need to perform\n   * important client side validation: validate the post back url come back with the SAML response\n   * contains the same prefix as the Snowflake's server url, which is the intended destination url\n   * to Snowflake. Explanation: This emulates the behavior of IDP initiated login flow in the user\n   * browser where the IDP instructs the browser to POST the SAML assertion to the specific SP\n   * endpoint. This is critical in preventing a SAML assertion issued to one SP from being sent to\n   * another SP.\n   *\n   * @param loginInput Login Info for the request\n   * @param ssoUrl URL to use for SSO\n   * @param oneTimeTokenSupplier The function returning token used for SSO\n   * @return The response in HTML form\n   * @throws SnowflakeSQLException Will be thrown if the destination URL in the SAML assertion does\n   *     not match\n   */\n  private static String federatedFlowStep4(\n      SFLoginInput loginInput,\n      String ssoUrl,\n      ThrowingFunction<RetryContext, String, SnowflakeSQLException> oneTimeTokenSupplier)\n      throws SnowflakeSQLException {\n    // This call of the oneTimeTokenSupplier is a part of the basic federated flow (before any\n    // retries). It is distinguished by a retrieval of a token without any RetryContext (passing\n    // 'null'). We pass a RetryContext instance only when we are currently during retries process -\n    // and we want to exchange information between the injected logic and the outer scope.\n    String oneTimeToken = oneTimeTokenSupplier.apply(null);\n    String responseHtml = \"\";\n\n    try {\n      RetryContextManager retryWithNewOTTManager =\n          createFederatedFlowStep4RetryContext(ssoUrl, oneTimeTokenSupplier, loginInput);\n\n      HttpGet httpGet = new HttpGet();\n      prepareFederatedFlowStep4Request(httpGet, ssoUrl, oneTimeToken);\n\n      responseHtml =\n          HttpUtil.executeGeneralRequest(\n              httpGet,\n              loginInput.getLoginTimeout(),\n              loginInput.getAuthTimeout(),\n              loginInput.getSocketTimeoutInMillis(),\n              0,\n              loginInput.getHttpClientSettingsKey(),\n              retryWithNewOTTManager,\n              null);\n\n      // step 5\n      validateSAML(responseHtml, loginInput);\n    } catch (IOException | URISyntaxException ex) {\n      handleFederatedFlowError(loginInput, ex);\n    }\n    return responseHtml;\n  }\n\n  private static RetryContextManager createFederatedFlowStep4RetryContext(\n      String ssoUrl,\n      ThrowingFunction<RetryContext, String, SnowflakeSQLException> oneTimeTokenSupplier,\n      SFLoginInput loginInput) {\n    RetryContextManager retryWithNewOTTManager =\n        new RetryContextManager(RetryContextManager.RetryHook.ALWAYS_BEFORE_RETRY);\n    retryWithNewOTTManager.registerRetryCallback(\n        (HttpRequestBase retrieveSamlRequest, RetryContext retryContext) -> {\n          try {\n            String newOneTimeToken = oneTimeTokenSupplier.apply(retryContext);\n            prepareFederatedFlowStep4Request(retrieveSamlRequest, ssoUrl, newOneTimeToken);\n          } catch (MalformedURLException | URISyntaxException ex) {\n            handleFederatedFlowError(loginInput, ex);\n          }\n          return retryContext;\n        });\n    return retryWithNewOTTManager;\n  }\n\n  private static void validateSAML(String responseHtml, SFLoginInput loginInput)\n      throws SnowflakeSQLException, MalformedURLException {\n    if (!loginInput.getDisableSamlURLCheck()) {\n      String postBackUrl = getPostBackUrlFromHTML(responseHtml);\n      if (!isPrefixEqual(postBackUrl, loginInput.getServerUrl())) {\n        URL idpDestinationUrl = new URL(postBackUrl);\n        URL clientDestinationUrl = new URL(loginInput.getServerUrl());\n        String idpDestinationHostName = idpDestinationUrl.getHost();\n        String clientDestinationHostName = clientDestinationUrl.getHost();\n\n        logger.error(\n            \"The Snowflake hostname specified in the client connection {} does not match \"\n                + \"the destination hostname in the SAML response returned by the IdP: {}\",\n            clientDestinationHostName,\n            idpDestinationHostName);\n\n        // Session is in process of getting created, so exception constructor takes in null\n        throw new SnowflakeSQLLoggedException(\n            null,\n            ErrorCode.IDP_INCORRECT_DESTINATION.getMessageCode(),\n            SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION);\n      }\n    }\n  }\n\n  /**\n   * Query IDP token url to authenticate and retrieve access token\n   *\n   * @param loginInput The login info for the request\n   * @param tokenUrl The URL used to retrieve the access token\n   * @return Returns the one time token\n   * @throws SnowflakeSQLException Will be thrown if the execute request fails\n   */\n  private static String federatedFlowStep3(\n      SFLoginInput loginInput, String tokenUrl, RetryContext retryContext)\n      throws SnowflakeSQLException {\n\n    String oneTimeToken = \"\";\n    try {\n      URL url = new URL(tokenUrl);\n      URI tokenUri = url.toURI();\n      final HttpPost postRequest = new HttpPost(tokenUri);\n      setFederatedFlowStep3PostRequestAuthData(postRequest, loginInput);\n\n      int retryTimeout;\n\n      if (retryContext != null) {\n        // This casting could be avoided if all execution methods from SessionUtil to RestRequest\n        // shared the same data type (either long or int) for the retryTimeout parameter. Now they\n        // are all cast to long at the end (in RestRequest's methods).\n        retryTimeout = (int) retryContext.getRemainingRetryTimeoutInSeconds();\n      } else {\n        retryTimeout = loginInput.getLoginTimeout();\n      }\n\n      final String idpResponse =\n          HttpUtil.executeRequestWithoutCookies(\n              postRequest,\n              retryTimeout,\n              loginInput.getAuthTimeout(),\n              loginInput.getSocketTimeoutInMillis(),\n              0,\n              0,\n              null,\n              loginInput.getHttpClientSettingsKey(),\n              null);\n\n      // session token is in the data field of the returned json response\n      final JsonNode jsonNode = mapper.readTree(idpResponse);\n      boolean isMfaEnabledInOkta = \"MFA_REQUIRED\".equals(jsonNode.get(\"status\").asText());\n      if (isMfaEnabledInOkta) {\n        throw new SnowflakeSQLLoggedException(\n            null,\n            ErrorCode.OKTA_MFA_NOT_SUPPORTED.getMessageCode(),\n            SqlState.FEATURE_NOT_SUPPORTED);\n      }\n      oneTimeToken =\n          jsonNode.get(\"sessionToken\") != null\n              ? jsonNode.get(\"sessionToken\").asText()\n              : jsonNode.get(\"cookieToken\").asText();\n    } catch (IOException | URISyntaxException ex) {\n      handleFederatedFlowError(loginInput, ex);\n    }\n    logger.debug(\"User is authenticated against {}.\", loginInput.getAuthenticator());\n    return oneTimeToken;\n  }\n\n  /**\n   * Perform important client side validation: validate both token url and sso url contains same\n   * prefix (protocol + host + port) as the given authenticator url. Explanation: This provides a\n   * way for the user to 'authenticate' the IDP it is sending his/her credentials to. Without such a\n   * check, the user could be coerced to provide credentials to an IDP impersonator.\n   *\n   * @param loginInput The login info for the request\n   * @param tokenUrl The token URL\n   * @param ssoUrl The SSO URL\n   * @throws SnowflakeSQLException Will be thrown if the prefix for the tokenUrl and ssoUrl do not\n   *     match\n   */\n  private static void federatedFlowStep2(SFLoginInput loginInput, String tokenUrl, String ssoUrl)\n      throws SnowflakeSQLException {\n    try {\n      if (!isPrefixEqual(loginInput.getAuthenticator(), tokenUrl)\n          || !isPrefixEqual(loginInput.getAuthenticator(), ssoUrl)) {\n        logger.debug(\n            \"The specified authenticator {} is not supported.\", loginInput.getAuthenticator());\n        // Session is in process of getting created, so exception constructor takes in null session\n        // value\n        throw new SnowflakeSQLLoggedException(\n            null,\n            ErrorCode.IDP_CONNECTION_ERROR.getMessageCode(),\n            SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION\n            /* session= */ );\n      }\n    } catch (MalformedURLException ex) {\n      handleFederatedFlowError(loginInput, ex);\n    }\n  }\n\n  /**\n   * Query Snowflake to obtain IDP token url and IDP SSO url\n   *\n   * @param loginInput The login info for the request\n   * @throws SnowflakeSQLException Will be thrown if the execute request step fails\n   */\n  private static JsonNode federatedFlowStep1(SFLoginInput loginInput) throws SnowflakeSQLException {\n    JsonNode dataNode = null;\n    try {\n      StringEntity requestInput = prepareFederatedFlowStep1RequestInput(loginInput);\n      HttpPost postRequest = new HttpPost();\n      prepareFederatedFlowStep1PostRequest(postRequest, loginInput, requestInput);\n\n      final String gsResponse =\n          HttpUtil.executeGeneralRequest(\n              postRequest,\n              loginInput.getLoginTimeout(),\n              loginInput.getAuthTimeout(),\n              loginInput.getSocketTimeoutInMillis(),\n              0,\n              loginInput.getHttpClientSettingsKey(),\n              null);\n\n      logger.debug(\"Authenticator-request response: {}\", gsResponse);\n      JsonNode jsonNode = mapper.readTree(gsResponse);\n\n      // check the success field first\n      if (!jsonNode.path(\"success\").asBoolean()) {\n        logger.debug(\"Response: {}\", gsResponse);\n        int errorCode = jsonNode.path(\"code\").asInt();\n\n        throw new SnowflakeSQLException(\n            NO_QUERY_ID,\n            jsonNode.path(\"message\").asText(),\n            SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n            errorCode);\n      }\n\n      // session token is in the data field of the returned json response\n      dataNode = jsonNode.path(\"data\");\n    } catch (IOException | URISyntaxException ex) {\n      handleFederatedFlowError(loginInput, ex);\n    }\n    return dataNode;\n  }\n\n  /**\n   * Logs an error generated during the federated authentication flow and re-throws it as a\n   * SnowflakeSQLException. Note that we separate IOExceptions since those tend to be network\n   * related.\n   *\n   * @param loginInput The login info from the request\n   * @param ex The exception to process\n   * @throws SnowflakeSQLException Will be thrown for all calls to this method\n   */\n  private static void handleFederatedFlowError(SFLoginInput loginInput, Exception ex)\n      throws SnowflakeSQLException {\n\n    if (ex instanceof IOException) {\n      logger.error(\"IOException when authenticating with \" + loginInput.getAuthenticator(), ex);\n      throw new SnowflakeSQLException(\n          ex,\n          SqlState.IO_ERROR,\n          ErrorCode.NETWORK_ERROR.getMessageCode(),\n          \"Exception encountered when opening connection: \" + ex.getMessage());\n    }\n    logger.error(\"Exception when authenticating with \" + loginInput.getAuthenticator(), ex);\n    throw new SnowflakeSQLException(\n        ex,\n        SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n        ErrorCode.CONNECTION_ERROR.getMessageCode(),\n        ErrorCode.CONNECTION_ERROR.getMessageCode(),\n        ex.getMessage());\n  }\n\n  /**\n   * FEDERATED FLOW See SNOW-27798 for additional details.\n   *\n   * @param loginInput The login info from the request\n   * @return saml response\n   * @throws SnowflakeSQLException Will be thrown if any of the federated steps fail\n   */\n  private static String getSamlResponseUsingOkta(SFLoginInput loginInput)\n      throws SnowflakeSQLException {\n    while (true) {\n      try {\n        JsonNode dataNode = federatedFlowStep1(loginInput);\n        String tokenUrl = dataNode.path(\"tokenUrl\").asText();\n        String ssoUrl = dataNode.path(\"ssoUrl\").asText();\n        federatedFlowStep2(loginInput, tokenUrl, ssoUrl);\n        ThrowingFunction<RetryContext, String, SnowflakeSQLException> oneTimeTokenSupplier =\n            (RetryContext retryContext) -> federatedFlowStep3(loginInput, tokenUrl, retryContext);\n\n        return federatedFlowStep4(loginInput, ssoUrl, oneTimeTokenSupplier);\n      } catch (SnowflakeSQLException ex) {\n        // This error gets thrown if the okta request encountered a retry-able error that\n        // requires getting a new one-time token.\n        if (ex.getErrorCode() == ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()) {\n          logger.debug(\"Failed to get Okta SAML response. Retrying without changing retry count.\");\n        } else {\n          throw ex;\n        }\n      }\n    }\n  }\n\n  /**\n   * Verify if two input urls have the same protocol, host, and port.\n   *\n   * @param aUrlStr a source URL string\n   * @param bUrlStr a target URL string\n   * @return true if matched otherwise false\n   * @throws MalformedURLException raises if a URL string is not valid.\n   */\n  static boolean isPrefixEqual(String aUrlStr, String bUrlStr) throws MalformedURLException {\n    URL aUrl = new URL(aUrlStr);\n    URL bUrl = new URL(bUrlStr);\n    int aPort = aUrl.getPort();\n    int bPort = bUrl.getPort();\n    if (aPort == -1 && \"https\".equals(aUrl.getProtocol())) {\n      // default port number for HTTPS\n      aPort = 443;\n    }\n    if (bPort == -1 && \"https\".equals(bUrl.getProtocol())) {\n      // default port number for HTTPS\n      bPort = 443;\n    }\n    // no default port number for HTTP is supported.\n    return aUrl.getHost().equalsIgnoreCase(bUrl.getHost())\n        && aUrl.getProtocol().equalsIgnoreCase(bUrl.getProtocol())\n        && aPort == bPort;\n  }\n\n  /**\n   * Extracts post back url from the HTML returned by the IDP\n   *\n   * @param html The HTML that we are parsing to find the post back url\n   * @return The post back url\n   */\n  private static String getPostBackUrlFromHTML(String html) {\n    Document doc = Jsoup.parse(html);\n    Elements e1 = doc.getElementsByTag(\"body\");\n    Elements e2 = e1.get(0).getElementsByTag(\"form\");\n    return e2.first().attr(\"action\");\n  }\n\n  /**\n   * Helper function to parse a JsonNode from a GS response containing CommonParameters, emitting an\n   * EnumMap of parameters\n   *\n   * @param paramsNode parameters in JSON form\n   * @return map object including key and value pairs\n   */\n  public static Map<String, Object> getCommonParams(JsonNode paramsNode) {\n    Map<String, Object> parameters = new HashMap<>();\n\n    for (JsonNode child : paramsNode) {\n      // If there isn't a name then the response from GS must be erroneous.\n      if (!child.hasNonNull(\"name\")) {\n        logger.error(\"Common Parameter JsonNode encountered with \" + \"no parameter name!\", false);\n        continue;\n      }\n\n      // Look up the parameter based on the \"name\" attribute of the node.\n      String paramName = child.path(\"name\").asText();\n\n      // What type of value is it and what's the value?\n      if (!child.hasNonNull(\"value\")) {\n        logger.debug(\"No value found for Common Parameter: {}\", child.path(\"name\").asText());\n        continue;\n      }\n\n      if (STRING_PARAMS.contains(paramName.toUpperCase())) {\n        parameters.put(paramName, child.path(\"value\").asText());\n      } else if (INT_PARAMS.contains(paramName.toUpperCase())) {\n        parameters.put(paramName, child.path(\"value\").asInt());\n      } else if (BOOLEAN_PARAMS.contains(paramName.toUpperCase())) {\n        parameters.put(paramName, child.path(\"value\").asBoolean());\n      } else {\n        try {\n          // Value should only be boolean, int or string so we don't expect exceptions here.\n          parameters.put(paramName, mapper.treeToValue(child.path(\"value\"), Object.class));\n        } catch (Exception e) {\n          logger.debug(\n              \"Unknown Common Parameter Failed to Parse: {} -> {}. Exception: {}\",\n              paramName,\n              child.path(\"value\"),\n              e.getMessage());\n        }\n        logger.debug(\"Unknown Common Parameter: {}\", paramName);\n      }\n\n      logger.debug(\"Parameter {}: {}\", paramName, child.path(\"value\").asText());\n    }\n\n    return parameters;\n  }\n\n  static void updateSfDriverParamValues(Map<String, Object> parameters, SFBaseSession session) {\n    if (parameters != null && !parameters.isEmpty()) {\n      session.setCommonParameters(parameters);\n    }\n    for (Map.Entry<String, Object> entry : parameters.entrySet()) {\n      logger.debug(\"Processing parameter {}\", entry.getKey());\n\n      if (\"CLIENT_SESSION_KEEP_ALIVE\".equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setEnableHeartbeat((Boolean) entry.getValue());\n        }\n      } else if (CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setHeartbeatFrequency((int) entry.getValue());\n        }\n      } else if (\"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\".equalsIgnoreCase(entry.getKey())) {\n        boolean enableLogging = (Boolean) entry.getValue();\n        if (session != null && session.getPreparedStatementLogging() != enableLogging) {\n          session.setPreparedStatementLogging(enableLogging);\n        }\n      } else if (\"AUTOCOMMIT\".equalsIgnoreCase(entry.getKey())) {\n        boolean autoCommit = (Boolean) entry.getValue();\n        if (session != null && session.getAutoCommit() != autoCommit) {\n          session.setAutoCommit(autoCommit);\n        }\n      } else if (JDBC_RS_COLUMN_CASE_INSENSITIVE.equalsIgnoreCase(entry.getKey())\n          || CLIENT_RESULT_COLUMN_CASE_INSENSITIVE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null && !session.isResultColumnCaseInsensitive()) {\n          session.setResultColumnCaseInsensitive((boolean) entry.getValue());\n        }\n      } else if (CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setMetadataRequestUseConnectionCtx((boolean) entry.getValue());\n        }\n      } else if (CLIENT_METADATA_USE_SESSION_DATABASE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setMetadataRequestUseSessionDatabase((boolean) entry.getValue());\n        }\n      } else if (JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setTreatNTZAsUTC((boolean) entry.getValue());\n        }\n      } else if (JDBC_FORMAT_DATE_WITH_TIMEZONE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setFormatDateWithTimezone((boolean) entry.getValue());\n        }\n      } else if (JDBC_USE_SESSION_TIMEZONE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setUseSessionTimezone((boolean) entry.getValue());\n        }\n      } else if (\"CLIENT_TIMESTAMP_TYPE_MAPPING\".equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setTimestampMappedType(\n              SnowflakeType.valueOf(((String) entry.getValue()).toUpperCase()));\n        }\n      } else if (\"JDBC_TREAT_DECIMAL_AS_INT\".equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setJdbcTreatDecimalAsInt((boolean) entry.getValue());\n        }\n      } else if (\"JDBC_ENABLE_COMBINED_DESCRIBE\".equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setEnableCombineDescribe((boolean) entry.getValue());\n        }\n      } else if (CLIENT_IN_BAND_TELEMETRY_ENABLED.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setClientTelemetryEnabled((boolean) entry.getValue());\n        }\n      } else if (\"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\".equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setArrayBindStageThreshold((int) entry.getValue());\n        }\n      } else if (CLIENT_STORE_TEMPORARY_CREDENTIAL.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setStoreTemporaryCredential((boolean) entry.getValue());\n        }\n      } else if (SERVICE_NAME.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setServiceName((String) entry.getValue());\n        }\n      } else if (CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setEnableConservativeMemoryUsage((boolean) entry.getValue());\n        }\n      } else if (CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setConservativeMemoryAdjustStep((int) entry.getValue());\n        }\n      } else if (CLIENT_MEMORY_LIMIT.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setClientMemoryLimit((int) entry.getValue());\n        }\n      } else if (CLIENT_RESULT_CHUNK_SIZE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setClientResultChunkSize((int) entry.getValue());\n        }\n      } else if (CLIENT_PREFETCH_THREADS.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setClientPrefetchThreads((int) entry.getValue());\n        }\n      } else if (CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED.equalsIgnoreCase(entry.getKey())) {\n        // we ignore the parameter CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\n        // OOB telemetry is always disabled\n        TelemetryService.disableOOBTelemetry();\n      } else if (CLIENT_VALIDATE_DEFAULT_PARAMETERS.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setValidateDefaultParameters(SFLoginInput.getBooleanValue(entry.getValue()));\n        }\n      } else if (ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1.equalsIgnoreCase((entry.getKey()))) {\n        if (session != null) {\n          session.setUseRegionalS3EndpointsForPresignedURL(\n              SFLoginInput.getBooleanValue(entry.getValue()));\n        }\n      } else if (QUERY_CONTEXT_CACHE_SIZE.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setQueryContextCacheSize((int) entry.getValue());\n        }\n      } else if (JDBC_ENABLE_PUT_GET.equalsIgnoreCase(entry.getKey())) {\n        if (session != null) {\n          session.setJdbcEnablePutGet(SFLoginInput.getBooleanValue(entry.getValue()));\n        }\n      } else {\n        if (session != null) {\n          session.setOtherParameter(entry.getKey(), entry.getValue());\n        }\n      }\n    }\n  }\n\n  enum TokenRequestType {\n    RENEW(\"RENEW\"),\n    CLONE(\"CLONE\"),\n    ISSUE(\"ISSUE\");\n\n    private String value;\n\n    TokenRequestType(String value) {\n      this.value = value;\n    }\n  }\n\n  /**\n   * Set OCSP cache server. If the URL is for private link sets it to special cache server.\n   *\n   * @param serverUrl The Snowflake URL includes protocol such as \"https://\"\n   * @throws IOException If exception encountered\n   */\n  public static void resetOCSPUrlIfNecessary(String serverUrl) throws IOException {\n    setOCSPResponseCacheServerURL(serverUrl);\n    if (PrivateLinkDetector.isPrivateLink(serverUrl)) {\n      // Privatelink uses special OCSP Cache server\n      URL url = new URL(serverUrl);\n      String host = url.getHost();\n      logger.debug(\"HOST: {}\", host);\n      String ocspCacheServerUrl =\n          String.format(\"http://ocsp.%s/%s\", host, SFTrustManager.CACHE_FILE_NAME);\n      logger.debug(\"OCSP Cache Server for Privatelink: {}\", ocspCacheServerUrl);\n      resetOCSPResponseCacherServerURL(ocspCacheServerUrl);\n    }\n  }\n\n  /**\n   * Helper function to generate a JWT token\n   *\n   * @param privateKey private key\n   * @param privateKeyFile path to private key file\n   * @param privateKeyBase64 base64 encoded content of the private key file\n   * @param privateKeyPwd password for private key file or base64 encoded private key\n   * @param accountName account name\n   * @param userName user name\n   * @return JWT token\n   * @throws SFException if Snowflake error occurs\n   */\n  public static String generateJWTToken(\n      PrivateKey privateKey,\n      String privateKeyFile,\n      String privateKeyBase64,\n      String privateKeyPwd,\n      String accountName,\n      String userName)\n      throws SFException {\n    return generateJWTToken(\n        privateKey, privateKeyFile, privateKeyBase64, privateKeyPwd, accountName, userName, null);\n  }\n\n  public static String generateJWTToken(\n      PrivateKey privateKey,\n      String privateKeyFile,\n      String privateKeyBase64,\n      String privateKeyPwd,\n      String accountName,\n      String userName,\n      InternalCallMarker internalCallMarker)\n      throws SFException {\n    recordIfExternal(\"SessionUtil\", \"generateJWTToken\", internalCallMarker);\n    SessionUtilKeyPair s =\n        new SessionUtilKeyPair(\n            privateKey, privateKeyFile, privateKeyBase64, privateKeyPwd, accountName, userName);\n    return s.issueJwtToken();\n  }\n\n  /**\n   * Helper function to generate a JWT token. Use {@link #generateJWTToken(PrivateKey, String,\n   * String, String, String, String)}\n   *\n   * @param privateKey private key\n   * @param privateKeyFile path to private key file\n   * @param privateKeyFilePwd password for private key file\n   * @param accountName account name\n   * @param userName user name\n   * @return JWT token\n   * @throws SFException if Snowflake error occurs\n   */\n  @Deprecated\n  public static String generateJWTToken(\n      PrivateKey privateKey,\n      String privateKeyFile,\n      String privateKeyFilePwd,\n      String accountName,\n      String userName)\n      throws SFException {\n    return generateJWTToken(\n        privateKey, privateKeyFile, null, privateKeyFilePwd, accountName, userName, null);\n  }\n\n  /**\n   * Helper method to check if the request path is a login/auth request to use for retry strategy.\n   *\n   * @param request the post request\n   * @return true if this is a login/auth request, false otherwise\n   */\n  public static boolean isNewRetryStrategyRequest(HttpRequestBase request) {\n    URI requestURI = request.getURI();\n    String requestPath = requestURI.getPath();\n    if (requestPath != null) {\n      return requestPath.equals(SF_PATH_LOGIN_REQUEST)\n          || requestPath.equals(SF_PATH_AUTHENTICATOR_REQUEST)\n          || requestPath.equals(SF_PATH_TOKEN_REQUEST)\n          || requestPath.contains(SF_PATH_OKTA_TOKEN_REQUEST_SUFFIX)\n          || requestPath.contains(SF_PATH_OKTA_SSO_REQUEST_SUFFIX);\n    }\n    return false;\n  }\n\n  /**\n   * Prepares an HTTP POST request for the first step of the federated authentication flow.\n   *\n   * @param loginInput The login information for the request.\n   * @param inputData The JSON input data to include in the request.\n   * @throws URISyntaxException If the constructed URI is invalid.\n   */\n  private static void prepareFederatedFlowStep1PostRequest(\n      HttpPost postRequest, SFLoginInput loginInput, StringEntity inputData)\n      throws URISyntaxException {\n    URIBuilder fedUriBuilder = new URIBuilder(loginInput.getServerUrl());\n    // TODO: if loginInput.serverUrl contains port or additional segments - it will be ignored and\n    // overwritten here - to be fixed in SNOW-1922872\n    fedUriBuilder.setPath(SF_PATH_AUTHENTICATOR_REQUEST);\n    URI fedUrlUri = fedUriBuilder.build();\n    postRequest.setURI(fedUrlUri);\n\n    postRequest.setEntity(inputData);\n    postRequest.addHeader(\"accept\", \"application/json\");\n\n    postRequest.addHeader(SF_HEADER_CLIENT_APP_ID, loginInput.getAppId());\n    postRequest.addHeader(SF_HEADER_CLIENT_APP_VERSION, loginInput.getAppVersion());\n  }\n\n  /**\n   * Prepares the JSON input for the first step of the federated authentication flow.\n   *\n   * @param loginInput The login information for the request.\n   * @return A {@link StringEntity} containing the JSON input for the request.\n   * @throws JsonProcessingException If there is an error generating the JSON input.\n   */\n  private static StringEntity prepareFederatedFlowStep1RequestInput(SFLoginInput loginInput)\n      throws JsonProcessingException {\n    Map<String, Object> data = new HashMap<>();\n    data.put(ClientAuthnParameter.ACCOUNT_NAME.name(), loginInput.getAccountName());\n    data.put(ClientAuthnParameter.AUTHENTICATOR.name(), loginInput.getAuthenticator());\n    data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());\n    data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion());\n\n    ClientAuthnDTO authnData = new ClientAuthnDTO(data, null);\n    String json = mapper.writeValueAsString(authnData);\n\n    StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);\n    input.setContentType(\"application/json\");\n    return input;\n  }\n\n  /**\n   * Sets the authentication data for the third step of the federated authentication flow.\n   *\n   * @param postRequest The {@link HttpPost} request to update with authentication data.\n   * @param loginInput The login information for the request.\n   * @throws SnowflakeSQLException If an error occurs while preparing the request.\n   */\n  private static void setFederatedFlowStep3PostRequestAuthData(\n      HttpPost postRequest, SFLoginInput loginInput) throws SnowflakeSQLException {\n    String userName =\n        isNullOrEmpty(loginInput.getOKTAUserName())\n            ? loginInput.getUserName()\n            : loginInput.getOKTAUserName();\n    try {\n      StringEntity params =\n          new StringEntity(\n              \"{\\\"username\\\":\\\"\"\n                  + userName\n                  + \"\\\",\\\"password\\\":\\\"\"\n                  + loginInput.getPassword()\n                  + \"\\\"}\");\n      postRequest.setEntity(params);\n\n      HeaderGroup headers = new HeaderGroup();\n      headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, \"application/json\"));\n      headers.addHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, \"application/json\"));\n      postRequest.setHeaders(headers.getAllHeaders());\n    } catch (IOException ex) {\n      handleFederatedFlowError(loginInput, ex);\n    }\n  }\n\n  /**\n   * Prepares an HTTP GET request for the fourth step of the federated authentication flow.\n   *\n   * @param retrieveSamlRequest The {@link HttpRequestBase} to update with the SAML request details.\n   * @param ssoUrl The SSO URL to use for the request.\n   * @param oneTimeToken The one-time token to include in the request.\n   * @throws MalformedURLException If the SSO URL is malformed.\n   * @throws URISyntaxException If the URI for the request cannot be built.\n   */\n  private static void prepareFederatedFlowStep4Request(\n      HttpRequestBase retrieveSamlRequest, String ssoUrl, String oneTimeToken)\n      throws MalformedURLException, URISyntaxException {\n    final URL url = new URL(ssoUrl);\n    URI oktaGetUri =\n        new URIBuilder()\n            .setScheme(url.getProtocol())\n            .setHost(url.getHost())\n            .setPort(url.getPort())\n            .setPath(url.getPath())\n            .setParameter(\"RelayState\", \"%2Fsome%2Fdeep%2Flink\")\n            .setParameter(\"onetimetoken\", oneTimeToken)\n            .build();\n    retrieveSamlRequest.setURI(oktaGetUri);\n\n    HeaderGroup headers = new HeaderGroup();\n    headers.addHeader(new BasicHeader(HttpHeaders.ACCEPT, \"*/*\"));\n    retrieveSamlRequest.setHeaders(headers.getAllHeaders());\n  }\n\n  private static void handleEmptyAuthResponse(\n      String theString, SFLoginInput loginInput, Exception lastRestException)\n      throws Exception, SFException {\n    if (theString == null) {\n      if (lastRestException != null) {\n        logger.error(\n            \"Failed to open new session for user: {}, host: {}. Error: {}\",\n            loginInput.getUserName(),\n            loginInput.getHostFromServerUrl(),\n            lastRestException);\n        throw lastRestException;\n      } else {\n        SnowflakeSQLException exception =\n            new SnowflakeSQLException(\n                NO_QUERY_ID,\n                \"empty authentication response\",\n                SqlState.CONNECTION_EXCEPTION,\n                ErrorCode.CONNECTION_ERROR.getMessageCode());\n        logger.error(\n            \"Failed to open new session for user: {}, host: {}. Error: {}\",\n            loginInput.getUserName(),\n            loginInput.getHostFromServerUrl(),\n            exception);\n        throw exception;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SessionUtilExternalBrowser.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.awt.Desktop;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.PrintWriter;\nimport java.net.InetAddress;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.security.SecureRandom;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.auth.ClientAuthnDTO;\nimport net.snowflake.client.internal.core.auth.ClientAuthnParameter;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.NameValuePair;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.client.utils.URLEncodedUtils;\nimport org.apache.http.entity.StringEntity;\n\n/**\n * SAML 2.0 Compliant service/application federated authentication 1. Query GS to obtain IDP SSO url\n * 2. Listen a localhost port to accept Saml response 3. Open a browser in the backend so that the\n * user can type IdP username and password. 4. Return token and proof key to the GS to gain access.\n */\npublic class SessionUtilExternalBrowser {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SessionUtilExternalBrowser.class);\n\n  public interface AuthExternalBrowserHandlers {\n    // build a HTTP post object\n    HttpPost build(URI uri);\n\n    // open a browser\n    void openBrowser(String ssoUrl) throws SFException;\n\n    // output\n    void output(String msg);\n  }\n\n  public static class DefaultAuthExternalBrowserHandlers implements AuthExternalBrowserHandlers {\n    @Override\n    public HttpPost build(URI uri) {\n      return new HttpPost(uri);\n    }\n\n    @Override\n    public void openBrowser(String ssoUrl) throws SFException {\n      if (!URLUtil.isValidURL(ssoUrl)) {\n        throw new SFException(ErrorCode.INVALID_CONNECTION_URL, \"Invalid SSOUrl found - \" + ssoUrl);\n      }\n      try {\n        // start web browser\n        Runtime runtime = Runtime.getRuntime();\n        Constants.OS os = Constants.getOS();\n        if (Desktop.isDesktopSupported()\n            && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {\n          Desktop.getDesktop().browse(new URI(ssoUrl));\n        } else if (os == Constants.OS.MAC) {\n          runtime.exec(new String[] {\"open\", ssoUrl});\n        } else if (os == Constants.OS.WINDOWS) {\n          runtime.exec(new String[] {\"rundll32\", \"url.dll,FileProtocolHandler\", ssoUrl});\n        } else {\n          runtime.exec(new String[] {\"xdg-open\", ssoUrl});\n        }\n      } catch (URISyntaxException | IOException ex) {\n        throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());\n      }\n    }\n\n    @Override\n    public void output(String msg) {\n      System.out.println(msg);\n    }\n  }\n\n  private final ObjectMapper mapper;\n\n  private final SFLoginInput loginInput;\n\n  String token;\n  private boolean consentCacheIdToken;\n  private String proofKey;\n  private String origin;\n  private AuthExternalBrowserHandlers handlers;\n  private static final String PREFIX_GET = \"GET \";\n  private static final String PREFIX_POST = \"POST \";\n  private static final String PREFIX_OPTIONS = \"OPTIONS \";\n  private static final String PREFIX_USER_AGENT = \"USER-AGENT: \";\n  private static Charset UTF8_CHARSET;\n\n  static {\n    UTF8_CHARSET = Charset.forName(\"UTF-8\");\n  }\n\n  public static SessionUtilExternalBrowser createInstance(SFLoginInput loginInput) {\n    return new SessionUtilExternalBrowser(loginInput, new DefaultAuthExternalBrowserHandlers());\n  }\n\n  public SessionUtilExternalBrowser(SFLoginInput loginInput, AuthExternalBrowserHandlers handlers) {\n    this.mapper = ObjectMapperFactory.getObjectMapper();\n    this.loginInput = loginInput;\n    this.handlers = handlers;\n    this.consentCacheIdToken = true; // true by default\n    this.origin = null;\n  }\n\n  /**\n   * Gets a free port on localhost\n   *\n   * @return port number\n   * @throws SFException raised if an error occurs.\n   */\n  protected ServerSocket getServerSocket() throws SFException {\n    try {\n      return new ServerSocket(\n          0, // free port\n          0, // default number of connections\n          InetAddress.getByName(\"localhost\"));\n    } catch (IOException ex) {\n      throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());\n    }\n  }\n\n  /**\n   * Get a port listening\n   *\n   * @param ssocket server socket\n   * @return port number\n   */\n  protected int getLocalPort(ServerSocket ssocket) {\n    return ssocket.getLocalPort();\n  }\n\n  /**\n   * Gets SSO URL and proof key\n   *\n   * @return SSO URL.\n   * @throws SFException if Snowflake error occurs\n   * @throws SnowflakeSQLException if Snowflake SQL error occurs\n   */\n  private String getSSOUrl(int port) throws SFException, SnowflakeSQLException {\n    try {\n      String serverUrl = loginInput.getServerUrl();\n      String authenticator = loginInput.getAuthenticator();\n\n      URIBuilder fedUriBuilder = new URIBuilder(serverUrl);\n      fedUriBuilder.setPath(SessionUtil.SF_PATH_AUTHENTICATOR_REQUEST);\n      URI fedUrlUri = fedUriBuilder.build();\n\n      HttpPost postRequest = this.handlers.build(fedUrlUri);\n\n      Map<String, Object> data = new HashMap<>();\n\n      data.put(ClientAuthnParameter.AUTHENTICATOR.name(), authenticator);\n      data.put(ClientAuthnParameter.ACCOUNT_NAME.name(), loginInput.getAccountName());\n      data.put(ClientAuthnParameter.LOGIN_NAME.name(), loginInput.getUserName());\n      data.put(ClientAuthnParameter.BROWSER_MODE_REDIRECT_PORT.name(), Integer.toString(port));\n      data.put(ClientAuthnParameter.CLIENT_APP_ID.name(), loginInput.getAppId());\n      data.put(ClientAuthnParameter.CLIENT_APP_VERSION.name(), loginInput.getAppVersion());\n\n      ClientAuthnDTO authnData = new ClientAuthnDTO(data, null);\n      String json = mapper.writeValueAsString(authnData);\n\n      // attach the login info json body to the post request\n      StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);\n      input.setContentType(\"application/json\");\n      postRequest.setEntity(input);\n\n      postRequest.addHeader(\"accept\", \"application/json\");\n\n      String theString =\n          HttpUtil.executeGeneralRequestWithContext(\n                  postRequest,\n                  loginInput.getLoginTimeout(),\n                  loginInput.getAuthTimeout(),\n                  loginInput.getSocketTimeoutInMillis(),\n                  0,\n                  0,\n                  loginInput.getHttpClientSettingsKey(),\n                  null)\n              .getResponseBody();\n\n      logger.debug(\"Authenticator-request response: {}\", theString);\n\n      // general method, same as with data binding\n      JsonNode jsonNode = mapper.readTree(theString);\n\n      // check the success field first\n      if (!jsonNode.path(\"success\").asBoolean()) {\n        logger.debug(\"Response: {}\", theString);\n        String errorCode = jsonNode.path(\"code\").asText();\n        throw new SnowflakeSQLException(\n            SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n            Integer.valueOf(errorCode),\n            jsonNode.path(\"message\").asText());\n      }\n\n      JsonNode dataNode = jsonNode.path(\"data\");\n\n      // session token is in the data field of the returned json response\n      this.proofKey = dataNode.path(\"proofKey\").asText();\n      return dataNode.path(\"ssoUrl\").asText();\n    } catch (IOException | URISyntaxException ex) {\n      throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());\n    }\n  }\n\n  private String getConsoleLoginUrl(int port) throws SFException {\n    try {\n      proofKey = generateProofKey();\n      String serverUrl = loginInput.getServerUrl();\n\n      URIBuilder consoleLoginUriBuilder = new URIBuilder(serverUrl);\n      consoleLoginUriBuilder.setPath(SessionUtil.SF_PATH_CONSOLE_LOGIN_REQUEST);\n      consoleLoginUriBuilder.addParameter(\"login_name\", loginInput.getUserName());\n      consoleLoginUriBuilder.addParameter(\"browser_mode_redirect_port\", Integer.toString(port));\n      consoleLoginUriBuilder.addParameter(\"proof_key\", proofKey);\n\n      String consoleLoginUrl = consoleLoginUriBuilder.build().toURL().toString();\n\n      logger.debug(\"Console login url: {}\", consoleLoginUrl);\n\n      return consoleLoginUrl;\n    } catch (Exception ex) {\n      throw new SFException(ex, ErrorCode.INTERNAL_ERROR, ex.getMessage());\n    }\n  }\n\n  private String generateProofKey() {\n    SecureRandom secureRandom = new SecureRandom();\n    byte[] randomness = new byte[32];\n    secureRandom.nextBytes(randomness);\n    return Base64.getEncoder().encodeToString(randomness);\n  }\n\n  private int getBrowserResponseTimeout() {\n    return (int) loginInput.getBrowserResponseTimeout().toMillis();\n  }\n\n  /**\n   * Authenticate\n   *\n   * @throws SFException if any error occurs\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  void authenticate() throws SFException, SnowflakeSQLException {\n    ServerSocket ssocket = this.getServerSocket();\n    try {\n      ssocket.setSoTimeout(getBrowserResponseTimeout());\n      // main procedure\n      int port = this.getLocalPort(ssocket);\n      logger.debug(\"Listening localhost: {}\", port);\n\n      if (loginInput.getDisableConsoleLogin()) {\n        // Access GS to get SSO URL\n        String ssoUrl = getSSOUrl(port);\n        this.handlers.output(\n            \"Initiating login request with your identity provider. A \"\n                + \"browser window should have opened for you to complete the \"\n                + \"login. If you can't see it, check existing browser windows, \"\n                + \"or your OS settings. Press CTRL+C to abort and try again...\");\n        this.handlers.openBrowser(ssoUrl);\n      } else {\n        // Multiple SAML way to do authentication via console login\n        String consoleLoginUrl = getConsoleLoginUrl(port);\n        this.handlers.output(\n            \"Initiating login request with your identity provider(s). A \"\n                + \"browser window should have opened for you to complete the \"\n                + \"login. If you can't see it, check existing browser windows, \"\n                + \"or your OS settings. Press CTRL+C to abort and try again...\");\n        this.handlers.openBrowser(consoleLoginUrl);\n      }\n\n      while (true) {\n        Socket socket = ssocket.accept(); // start accepting the request\n        try {\n          BufferedReader in =\n              new BufferedReader(new InputStreamReader(socket.getInputStream(), UTF8_CHARSET));\n          char[] buf = new char[16384];\n          int strLen = in.read(buf);\n          String[] rets = new String(buf, 0, strLen).split(\"\\r\\n\");\n          if (!processOptions(rets, socket)) {\n            processSamlToken(rets, socket);\n            break;\n          }\n        } finally {\n          socket.close();\n        }\n      }\n    } catch (SocketTimeoutException e) {\n      throw new SFException(\n          e,\n          ErrorCode.NETWORK_ERROR,\n          \"External browser authentication failed within timeout of \"\n              + getBrowserResponseTimeout()\n              + \" milliseconds\");\n    } catch (IOException ex) {\n      throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());\n    } finally {\n      try {\n        ssocket.close();\n      } catch (IOException ex) {\n        throw new SFException(ex, ErrorCode.NETWORK_ERROR, ex.getMessage());\n      }\n    }\n  }\n\n  private boolean processOptions(String[] rets, Socket socket) throws IOException {\n    String targetLine = null;\n    String userAgent = null;\n    String requestedHeaderLine = null;\n    for (String line : rets) {\n      if (line.length() > PREFIX_OPTIONS.length()\n          && line.substring(0, PREFIX_OPTIONS.length()).equalsIgnoreCase(PREFIX_OPTIONS)) {\n        targetLine = line;\n      } else if (line.length() > PREFIX_USER_AGENT.length()\n          && line.substring(0, PREFIX_USER_AGENT.length()).equalsIgnoreCase(PREFIX_USER_AGENT)) {\n        userAgent = line;\n      } else if (line.startsWith(\"Access-Control-Request-Method\")) {\n        String[] kv = line.split(\":\");\n        if (kv.length != 2) {\n          logger.error(\"no value for HTTP header: Access-Control-Request-Method. line={}\", line);\n          return false;\n        }\n        if (!kv[1].trim().contains(\"POST\")) {\n          return false;\n        }\n      } else if (line.startsWith(\"Access-Control-Request-Headers\")) {\n        String[] kv = line.split(\":\");\n        if (kv.length != 2) {\n          logger.error(\"no value for HTTP header: Access-Control-Request-Method. line={}\", line);\n          return false;\n        }\n        requestedHeaderLine = kv[1].trim();\n      } else if (line.startsWith(\"Origin\")) {\n        String[] kv = line.split(\":\");\n        if (kv.length < 2) {\n          logger.error(\"no value for HTTP header: Origin. line={}\", line);\n          return false;\n        }\n        this.origin = line.substring(line.indexOf(':') + 1).trim();\n      }\n    }\n    if (userAgent != null) {\n      logger.debug(\"{}\", userAgent);\n    }\n    if (isNullOrEmpty(targetLine)\n        || isNullOrEmpty(requestedHeaderLine)\n        || isNullOrEmpty(this.origin)) {\n      return false;\n    }\n    returnToBrowserForOptions(requestedHeaderLine, socket);\n    return true;\n  }\n\n  private void returnToBrowserForOptions(String requestedHeader, Socket socket) throws IOException {\n    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);\n    SimpleDateFormat fmt = new SimpleDateFormat(\"EEE, dd MMM yyyy HH:mm:ss\");\n    fmt.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n    String[] content = {\n      \"HTTP/1.1 200 OK\",\n      String.format(\"Date: %s\", fmt.format(new Date()) + \" GMT\"),\n      \"Access-Control-Allow-Methods: POST, GET\",\n      String.format(\"Access-Control-Allow-Headers: %s\", requestedHeader),\n      \"Access-Control-Max-Age: 86400\",\n      String.format(\"Access-Control-Allow-Origin: %s\", this.origin),\n      \"\",\n      \"\"\n    };\n    for (int i = 0; i < content.length; ++i) {\n      if (i > 0) {\n        out.print(\"\\r\\n\");\n      }\n      out.print(content[i]);\n    }\n    out.flush();\n  }\n\n  /**\n   * Receives SAML token from Snowflake via web browser\n   *\n   * @param socket socket\n   * @throws IOException if any IO error occurs\n   * @throws SFException if a HTTP request from browser is invalid\n   */\n  private void processSamlToken(String[] rets, Socket socket) throws IOException, SFException {\n    String targetLine = null;\n    String userAgent = null;\n    boolean isPost = false;\n    for (String line : rets) {\n      if (line.length() > PREFIX_GET.length()\n          && line.substring(0, PREFIX_GET.length()).equalsIgnoreCase(PREFIX_GET)) {\n        targetLine = line;\n      } else if (line.length() > PREFIX_POST.length()\n          && line.substring(0, PREFIX_POST.length()).equalsIgnoreCase(PREFIX_POST)) {\n        targetLine = rets[rets.length - 1];\n        isPost = true;\n      } else if (line.length() > PREFIX_USER_AGENT.length()\n          && line.substring(0, PREFIX_USER_AGENT.length()).equalsIgnoreCase(PREFIX_USER_AGENT)) {\n        userAgent = line;\n      }\n    }\n    if (targetLine == null) {\n      throw new SFException(\n          ErrorCode.NETWORK_ERROR, \"Invalid HTTP request. No token is given from the browser.\");\n    }\n    if (userAgent != null) {\n      logger.debug(\"{}\", userAgent);\n    }\n\n    try {\n      // attempt to get JSON response\n      extractJsonTokenFromPostRequest(targetLine);\n    } catch (IOException ex) {\n      String parameters =\n          isPost ? extractTokenFromPostRequest(targetLine) : extractTokenFromGetRequest(targetLine);\n      try {\n        URI inputParameter = new URI(parameters);\n        for (NameValuePair urlParam : URLEncodedUtils.parse(inputParameter, UTF8_CHARSET)) {\n          if (\"token\".equals(urlParam.getName())) {\n            this.token = urlParam.getValue();\n            break;\n          }\n        }\n      } catch (URISyntaxException ex0) {\n        throw new SFException(\n            ErrorCode.NETWORK_ERROR,\n            String.format(\n                \"Invalid HTTP request. No token is given from the browser. %s, err: %s\",\n                targetLine, ex0));\n      }\n    }\n    if (this.token == null) {\n      throw new SFException(\n          ErrorCode.NETWORK_ERROR,\n          String.format(\n              \"Invalid HTTP request. No token is given from the browser: %s\", targetLine));\n    }\n\n    returnToBrowser(socket);\n  }\n\n  private void extractJsonTokenFromPostRequest(String targetLine) throws IOException {\n    JsonNode jsonNode = mapper.readTree(targetLine);\n    this.token = jsonNode.get(\"token\").asText();\n    this.consentCacheIdToken = jsonNode.get(\"consent\").asBoolean();\n  }\n\n  private String extractTokenFromPostRequest(String targetLine) {\n    return \"/?\" + targetLine;\n  }\n\n  private String extractTokenFromGetRequest(String targetLine) throws SFException {\n    String[] elems = targetLine.split(\"\\\\s\");\n    if (elems.length != 3\n        || !elems[0].toLowerCase(Locale.US).equalsIgnoreCase(\"GET\")\n        || !elems[2].startsWith(\"HTTP/1.\")) {\n      throw new SFException(\n          ErrorCode.NETWORK_ERROR,\n          String.format(\n              \"Invalid HTTP request. No token is given from the browser: %s\", targetLine));\n    }\n    return elems[1];\n  }\n\n  /**\n   * Output the message to the browser\n   *\n   * @param socket client socket\n   * @throws IOException if any IO error occurs\n   */\n  private void returnToBrowser(Socket socket) throws IOException {\n    PrintWriter out = new PrintWriter(socket.getOutputStream(), true);\n\n    List<String> content = new ArrayList<>();\n    content.add(\"HTTP/1.0 200 OK\");\n    content.add(\"Content-Type: text/html\");\n    String responseText;\n    if (this.origin != null) {\n      content.add(String.format(\"Access-Control-Allow-Origin: %s\", this.origin));\n      content.add(\"Vary: Accept-Encoding, Origin\");\n      Map<String, Object> data = new HashMap<>();\n      data.put(\"consent\", this.consentCacheIdToken);\n      responseText = mapper.writeValueAsString(data);\n    } else {\n      responseText =\n          \"<!DOCTYPE html><html><head><meta charset=\\\"UTF-8\\\"/>\"\n              + \"<title>SAML Response for Snowflake</title></head>\"\n              + \"<body>Your identity was confirmed and propagated to \"\n              + \"Snowflake JDBC driver. You can close this window now and go back \"\n              + \"where you started from.</body></html>\";\n    }\n    content.add(String.format(\"Content-Length: %s\", responseText.length()));\n    content.add(\"\");\n    content.add(responseText);\n\n    for (int i = 0; i < content.size(); ++i) {\n      if (i > 0) {\n        out.print(\"\\r\\n\");\n      }\n      out.print(content.get(i));\n    }\n    out.flush();\n  }\n\n  /**\n   * Returns encoded SAML token\n   *\n   * @return SAML token\n   */\n  String getToken() {\n    return this.token;\n  }\n\n  /**\n   * Returns proofkey provided in the first roundtrip with GS and back to GS in the last\n   * login-request authentication.\n   *\n   * @return proofkey\n   */\n  String getProofKey() {\n    return this.proofKey;\n  }\n\n  /**\n   * True if the user consented to cache id token\n   *\n   * @return true or false\n   */\n  boolean isConsentCacheIdToken() {\n    return this.consentCacheIdToken;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SessionUtilKeyPair.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.nimbusds.jose.JOSEException;\nimport com.nimbusds.jose.JWSAlgorithm;\nimport com.nimbusds.jose.JWSHeader;\nimport com.nimbusds.jose.JWSSigner;\nimport com.nimbusds.jose.crypto.RSASSASigner;\nimport com.nimbusds.jwt.JWTClaimsSet;\nimport com.nimbusds.jwt.SignedJWT;\nimport java.io.IOException;\nimport java.io.StringReader;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.InvalidKeyException;\nimport java.security.KeyFactory;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PrivateKey;\nimport java.security.Provider;\nimport java.security.PublicKey;\nimport java.security.Security;\nimport java.security.interfaces.RSAPrivateCrtKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.PKCS8EncodedKeySpec;\nimport java.security.spec.RSAPublicKeySpec;\nimport java.util.Date;\nimport javax.crypto.EncryptedPrivateKeyInfo;\nimport javax.crypto.SecretKeyFactory;\nimport javax.crypto.spec.PBEKeySpec;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.commons.codec.binary.Base64;\nimport org.bouncycastle.asn1.pkcs.PrivateKeyInfo;\nimport org.bouncycastle.openssl.PEMKeyPair;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\nimport org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;\nimport org.bouncycastle.operator.InputDecryptorProvider;\nimport org.bouncycastle.operator.OperatorCreationException;\nimport org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;\nimport org.bouncycastle.pkcs.PKCSException;\nimport org.bouncycastle.util.io.pem.PemReader;\n\n/** Class used to compute jwt token for key pair authentication. */\nclass SessionUtilKeyPair {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SessionUtilKeyPair.class);\n\n  // user name in upper case\n  private final String userName;\n\n  // account name in upper case\n  private final String accountName;\n\n  private final PrivateKey privateKey;\n\n  private PublicKey publicKey = null;\n\n  private boolean isFipsMode = false;\n\n  private Provider SecurityProvider = null;\n\n  private static final String ISSUER_FMT = \"%s.%s.%s\";\n\n  private static final String SUBJECT_FMT = \"%s.%s\";\n\n  private static final int JWT_DEFAULT_AUTH_TIMEOUT = 0;\n\n  private boolean useBundledBouncyCastleForPrivateKeyDecryption = true;\n\n  SessionUtilKeyPair(\n      PrivateKey privateKey,\n      String privateKeyFile,\n      String privateKeyBase64,\n      String privateKeyPwd,\n      String accountName,\n      String userName)\n      throws SFException {\n    this.userName = userName.toUpperCase();\n    this.accountName = excludeRegionInformation(accountName).toUpperCase();\n    String useBundledBouncyCastleJvm =\n        systemGetProperty(SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM);\n    if (useBundledBouncyCastleJvm != null) {\n      useBundledBouncyCastleForPrivateKeyDecryption =\n          useBundledBouncyCastleJvm.equalsIgnoreCase(\"true\");\n    }\n    // check if in FIPS mode\n    for (Provider p : Security.getProviders()) {\n      if (SecurityUtil.BOUNCY_CASTLE_FIPS_PROVIDER.equals(p.getName())) {\n        this.isFipsMode = true;\n        this.SecurityProvider = p;\n        break;\n      }\n    }\n\n    ensurePrivateKeyProvidedInOnlyOneProperty(privateKey, privateKeyFile, privateKeyBase64);\n    this.privateKey = buildPrivateKey(privateKey, privateKeyFile, privateKeyBase64, privateKeyPwd);\n\n    // construct public key from raw bytes\n    if (this.privateKey instanceof RSAPrivateCrtKey) {\n      RSAPrivateCrtKey rsaPrivateCrtKey = (RSAPrivateCrtKey) this.privateKey;\n      RSAPublicKeySpec rsaPublicKeySpec =\n          new RSAPublicKeySpec(rsaPrivateCrtKey.getModulus(), rsaPrivateCrtKey.getPublicExponent());\n\n      try {\n        this.publicKey = getKeyFactoryInstance().generatePublic(rsaPublicKeySpec);\n      } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {\n        throw new SFException(e, ErrorCode.INTERNAL_ERROR, \"Error retrieving public key\");\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY,\n          \"Use java.security.interfaces.RSAPrivateCrtKey.class for the private key\");\n    }\n  }\n\n  private static void ensurePrivateKeyProvidedInOnlyOneProperty(\n      PrivateKey privateKey, String privateKeyFile, String privateKeyBase64) throws SFException {\n    if (!isNullOrEmpty(privateKeyFile) && privateKey != null) {\n      throw new SFException(\n          ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY,\n          \"Cannot have both private key object and private key file.\");\n    }\n    if (!isNullOrEmpty(privateKeyBase64) && !isNullOrEmpty(privateKeyFile)) {\n      throw new SFException(\n          ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY,\n          \"Cannot have both private key file and private key base64 string value.\");\n    }\n    if (!isNullOrEmpty(privateKeyBase64) && privateKey != null) {\n      throw new SFException(\n          ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY,\n          \"Cannot have both private key object and private key base64 string value.\");\n    }\n  }\n\n  private PrivateKey buildPrivateKey(\n      PrivateKey privateKey, String privateKeyFile, String privateKeyBase64, String privateKeyPwd)\n      throws SFException {\n    if (!isNullOrEmpty(privateKeyBase64)) {\n      logger.trace(\"Reading private key from base64 string\");\n      return extractPrivateKeyFromBase64(privateKeyBase64, privateKeyPwd);\n    }\n    if (!isNullOrEmpty(privateKeyFile)) {\n      logger.trace(\"Reading private key from file\");\n      return extractPrivateKeyFromFile(privateKeyFile, privateKeyPwd);\n    }\n    return privateKey;\n  }\n\n  private KeyFactory getKeyFactoryInstance() throws NoSuchAlgorithmException {\n    if (isFipsMode) {\n      return KeyFactory.getInstance(\"RSA\", this.SecurityProvider);\n    } else {\n      return KeyFactory.getInstance(\"RSA\");\n    }\n  }\n\n  private SecretKeyFactory getSecretKeyFactory(String algorithm) throws NoSuchAlgorithmException {\n    if (isFipsMode) {\n      return SecretKeyFactory.getInstance(algorithm, this.SecurityProvider);\n    } else {\n      return SecretKeyFactory.getInstance(algorithm);\n    }\n  }\n\n  private PrivateKey extractPrivateKeyFromFile(String privateKeyFile, String privateKeyPwd)\n      throws SFException {\n\n    try {\n      Path privKeyPath = Paths.get(privateKeyFile);\n      FileUtil.logFileUsage(privKeyPath, \"Extract private key from file\", true);\n      byte[] bytes = Files.readAllBytes(privKeyPath);\n      return extractPrivateKeyFromBytes(bytes, privateKeyPwd);\n    } catch (IOException ie) {\n      logger.error(\"Could not read private key from file\", ie);\n      throw new SFException(ie, ErrorCode.INVALID_PARAMETER_VALUE, ie.getCause());\n    }\n  }\n\n  private PrivateKey extractPrivateKeyFromBytes(byte[] privateKeyBytes, String privateKeyPwd)\n      throws SFException {\n    if (useBundledBouncyCastleForPrivateKeyDecryption) {\n      try {\n        return extractPrivateKeyWithBouncyCastle(privateKeyBytes, privateKeyPwd);\n      } catch (IOException | PKCSException | OperatorCreationException e) {\n        logger.error(\"Could not extract private key using Bouncy Castle provider\", e);\n        throw new SFException(e, ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY, e.getCause());\n      }\n    } else {\n      try {\n        return extractPrivateKeyWithJdk(privateKeyBytes, privateKeyPwd);\n      } catch (NoSuchAlgorithmException\n          | InvalidKeySpecException\n          | IOException\n          | IllegalArgumentException\n          | NullPointerException\n          | InvalidKeyException e) {\n        logger.error(\n            \"Could not extract private key using standard JDK. Try setting the JVM argument: \"\n                + \"-D{}\"\n                + \"=TRUE\",\n            SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM);\n        throw new SFException(e, ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY, e.getMessage());\n      }\n    }\n  }\n\n  private PrivateKey extractPrivateKeyFromBase64(String privateKeyBase64, String privateKeyPwd)\n      throws SFException {\n    byte[] decodedKey = Base64.decodeBase64(privateKeyBase64);\n    return extractPrivateKeyFromBytes(decodedKey, privateKeyPwd);\n  }\n\n  public String issueJwtToken() throws SFException {\n    JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();\n    String sub = String.format(SUBJECT_FMT, this.accountName, this.userName);\n    String iss =\n        String.format(\n            ISSUER_FMT,\n            this.accountName,\n            this.userName,\n            this.calculatePublicKeyFingerprint(this.publicKey));\n\n    // iat is now\n    Date iat = new Date(System.currentTimeMillis());\n\n    // expiration is 60 seconds later\n    Date exp = new Date(iat.getTime() + 60L * 1000);\n\n    JWTClaimsSet claimsSet =\n        builder.issuer(iss).subject(sub).issueTime(iat).expirationTime(exp).build();\n\n    SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsSet);\n    JWSSigner signer = new RSASSASigner(this.privateKey);\n\n    try {\n      signedJWT.sign(signer);\n    } catch (JOSEException e) {\n      throw new SFException(e, ErrorCode.FAILED_TO_GENERATE_JWT);\n    }\n    // Log the contents of the token, displaying expiration and issue time in epoch time\n    logger.debug(\n        \"JWT:\\n'{'\\niss: {}\\nsub: {}\\niat: {}\\nexp: {}\\n'}'\",\n        iss,\n        sub,\n        String.valueOf(iat.getTime() / 1000),\n        String.valueOf(exp.getTime() / 1000));\n    return signedJWT.serialize();\n  }\n\n  private String calculatePublicKeyFingerprint(PublicKey publicKey) throws SFException {\n    try {\n      MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n      byte[] sha256Hash = md.digest(publicKey.getEncoded());\n      return \"SHA256:\" + Base64.encodeBase64String(sha256Hash);\n    } catch (NoSuchAlgorithmException e) {\n      throw new SFException(e, ErrorCode.INTERNAL_ERROR, \"Error when calculating fingerprint\");\n    }\n  }\n\n  public static int getTimeout() {\n    String jwtAuthTimeoutStr = systemGetEnv(\"JWT_AUTH_TIMEOUT\");\n    int jwtAuthTimeout = JWT_DEFAULT_AUTH_TIMEOUT;\n    if (jwtAuthTimeoutStr != null) {\n      jwtAuthTimeout = Integer.parseInt(jwtAuthTimeoutStr);\n    }\n    return jwtAuthTimeout;\n  }\n\n  private PrivateKey extractPrivateKeyWithBouncyCastle(byte[] privateKeyBytes, String privateKeyPwd)\n      throws IOException, PKCSException, OperatorCreationException {\n    logger.trace(\"Extracting private key using Bouncy Castle provider\");\n    PrivateKeyInfo privateKeyInfo = null;\n    PEMParser pemParser =\n        new PEMParser(new StringReader(new String(privateKeyBytes, StandardCharsets.UTF_8)));\n    Object pemObject = pemParser.readObject();\n    if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {\n      // Handle the case where the private key is encrypted.\n      PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo =\n          (PKCS8EncryptedPrivateKeyInfo) pemObject;\n      InputDecryptorProvider pkcs8Prov =\n          new JceOpenSSLPKCS8DecryptorProviderBuilder().build(privateKeyPwd.toCharArray());\n      privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(pkcs8Prov);\n    } else if (pemObject instanceof PEMKeyPair) {\n      // PKCS#1 private key\n      privateKeyInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();\n    } else if (pemObject instanceof PrivateKeyInfo) {\n      // Handle the case where the private key is unencrypted.\n      privateKeyInfo = (PrivateKeyInfo) pemObject;\n    }\n    pemParser.close();\n    JcaPEMKeyConverter converter =\n        new JcaPEMKeyConverter()\n            .setProvider(\n                isFipsMode\n                    ? SecurityUtil.BOUNCY_CASTLE_FIPS_PROVIDER\n                    : SecurityUtil.BOUNCY_CASTLE_PROVIDER);\n    return converter.getPrivateKey(privateKeyInfo);\n  }\n\n  private PrivateKey extractPrivateKeyWithJdk(byte[] privateKeyFileBytes, String privateKeyPwd)\n      throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {\n    logger.trace(\"Extracting private key using JDK\");\n    String privateKeyContent = new String(privateKeyFileBytes, StandardCharsets.UTF_8);\n    if (isNullOrEmpty(privateKeyPwd)) {\n      // unencrypted private key file\n      return generatePrivateKey(false, privateKeyContent, privateKeyPwd);\n    } else {\n      // encrypted private key file\n      return generatePrivateKey(true, privateKeyContent, privateKeyPwd);\n    }\n  }\n\n  private PrivateKey generatePrivateKey(\n      boolean isEncrypted, String privateKeyContent, String privateKeyPwd)\n      throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {\n    if (isEncrypted) {\n      try (PemReader pr = new PemReader(new StringReader(privateKeyContent))) {\n        byte[] decoded = pr.readPemObject().getContent();\n        pr.close();\n        EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(decoded);\n        PBEKeySpec keySpec = new PBEKeySpec(privateKeyPwd.toCharArray());\n        SecretKeyFactory pbeKeyFactory = this.getSecretKeyFactory(pkInfo.getAlgName());\n        PKCS8EncodedKeySpec encodedKeySpec =\n            pkInfo.getKeySpec(pbeKeyFactory.generateSecret(keySpec));\n        KeyFactory keyFactory = getKeyFactoryInstance();\n        return keyFactory.generatePrivate(encodedKeySpec);\n      }\n    } else {\n      try (PemReader pr = new PemReader(new StringReader(privateKeyContent))) {\n        byte[] decoded = pr.readPemObject().getContent();\n        pr.close();\n        PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(decoded);\n        KeyFactory keyFactory = getKeyFactoryInstance();\n        return keyFactory.generatePrivate(encodedKeySpec);\n      }\n    }\n  }\n\n  private static String excludeRegionInformation(String accountName) {\n    int dotIndex = accountName.indexOf('.');\n    return dotIndex > 0 ? accountName.substring(0, dotIndex) : accountName;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SfSqlArray.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.FieldSchemaCreator.buildBindingSchemaForType;\nimport static net.snowflake.client.internal.core.FieldSchemaCreator.logger;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.sql.Array;\nimport java.sql.JDBCType;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.util.Arrays;\nimport java.util.Map;\nimport net.snowflake.client.internal.jdbc.BindingParameterMetadata;\n\npublic class SfSqlArray implements Array {\n\n  private final String text;\n  private final int baseType;\n  private final Object elements;\n  private String jsonStringFromElements;\n  private final ObjectMapper objectMapper;\n\n  public SfSqlArray(\n      String text,\n      int baseType,\n      Object elements,\n      SFBaseSession session,\n      ObjectMapper objectMapper) {\n    this.text = text;\n    this.baseType = baseType;\n    this.elements = elements;\n    this.objectMapper = objectMapper;\n  }\n\n  public SfSqlArray(\n      int baseType, Object elements, SFBaseSession session, ObjectMapper objectMapper) {\n    this(null, baseType, elements, session, objectMapper);\n  }\n\n  @Override\n  public String getBaseTypeName() throws SQLException {\n    return JDBCType.valueOf(baseType).getName();\n  }\n\n  @Override\n  public int getBaseType() throws SQLException {\n    return baseType;\n  }\n\n  @Override\n  public Object getArray() throws SQLException {\n    return elements;\n  }\n\n  @Override\n  public Object getArray(Map<String, Class<?>> map) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"getArray(Map<String, Class<?>> map)\");\n  }\n\n  @Override\n  public Object getArray(long index, int count) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"getArray(long index, int count)\");\n  }\n\n  @Override\n  public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\n        \"getArray(long index, int count, Map<String, Class<?>> map)\");\n  }\n\n  @Override\n  public ResultSet getResultSet() throws SQLException {\n    throw new SQLFeatureNotSupportedException(\n        \"getArray(long index, int count, Map<String, Class<?>> map)\");\n  }\n\n  @Override\n  public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"getResultSet(Map<String, Class<?>> map)\");\n  }\n\n  @Override\n  public ResultSet getResultSet(long index, int count) throws SQLException {\n    throw new SQLFeatureNotSupportedException(\"getResultSet(long index, int count)\");\n  }\n\n  @Override\n  public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map)\n      throws SQLException {\n    throw new SQLFeatureNotSupportedException(\n        \"getResultSet(long index, int count, Map<String, Class<?>> map)\");\n  }\n\n  @Override\n  public void free() throws SQLException {}\n\n  public String getText() {\n    if (text == null) {\n      logger.warn(\"Text field wasn't initialized. Should never happen.\");\n    }\n    return text;\n  }\n\n  public String getJsonString() throws SQLException {\n    if (jsonStringFromElements == null) {\n      jsonStringFromElements = buildJsonStringFromElements(elements);\n    }\n\n    return jsonStringFromElements;\n  }\n\n  private String buildJsonStringFromElements(Object elements) throws SQLException {\n    try {\n      return objectMapper.writeValueAsString(elements);\n    } catch (JsonProcessingException e) {\n      throw new SQLException(\"There is exception during array to json string.\", e);\n    }\n  }\n\n  public BindingParameterMetadata getSchema() throws SQLException {\n    return BindingParameterMetadata.BindingParameterMetadataBuilder.bindingParameterMetadata()\n        .withType(\"array\")\n        .withFields(Arrays.asList(buildBindingSchemaForType(getBaseType(), false)))\n        .build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SfTimestampUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\npublic class SfTimestampUtil {\n\n  static final long MS_IN_DAY = 86400 * 1000;\n\n  public static Timestamp getTimestampFromType(\n      int columnSubType,\n      String value,\n      SFBaseSession session,\n      TimeZone sessionTimeZone,\n      TimeZone tz) {\n    if (columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ) {\n      return getTimestampFromFormat(\n          \"TIMESTAMP_LTZ_OUTPUT_FORMAT\", value, session, sessionTimeZone, tz);\n    } else if (columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ\n        || columnSubType == Types.TIMESTAMP) {\n      return getTimestampFromFormat(\n          \"TIMESTAMP_NTZ_OUTPUT_FORMAT\", value, session, sessionTimeZone, TimeZone.getDefault());\n    } else if (columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ) {\n      return getTimestampFromFormat(\n          \"TIMESTAMP_TZ_OUTPUT_FORMAT\", value, session, sessionTimeZone, tz);\n    } else {\n      return null;\n    }\n  }\n\n  private static Timestamp getTimestampFromFormat(\n      String format, String value, SFBaseSession session, TimeZone sessionTimeZone, TimeZone tz) {\n    String rawFormat = (String) session.getCommonParameters().get(format);\n    if (rawFormat == null || rawFormat.isEmpty()) {\n      rawFormat = (String) session.getCommonParameters().get(\"TIMESTAMP_OUTPUT_FORMAT\");\n    }\n    if (tz == null) {\n      tz = sessionTimeZone;\n    }\n    SnowflakeDateTimeFormat formatter = SnowflakeDateTimeFormat.fromSqlFormat(rawFormat);\n    return formatter.parse(value, tz, 0, false).getTimestamp();\n  }\n\n  public static long getTimeInNanoseconds(Time x) {\n    long msSinceEpoch = x.getTime();\n    // Use % + % instead of just % to get the nonnegative remainder.\n    // TODO(mkember): Change to use Math.floorMod when Client is on Java 8.\n    long msSinceMidnight = (msSinceEpoch % MS_IN_DAY + MS_IN_DAY) % MS_IN_DAY;\n    long nanosSinceMidnight = msSinceMidnight * 1000 * 1000;\n    return nanosSinceMidnight;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SnowflakeMutableProxyRoutePlanner.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.Serializable;\nimport org.apache.http.HttpException;\nimport org.apache.http.HttpHost;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.conn.routing.HttpRoute;\nimport org.apache.http.conn.routing.HttpRoutePlanner;\nimport org.apache.http.protocol.HttpContext;\n\n/**\n * This class defines a ProxyRoutePlanner (used for creating HttpClients) that has the ability to\n * change the nonProxyHosts setting.\n */\npublic class SnowflakeMutableProxyRoutePlanner implements HttpRoutePlanner, Serializable {\n\n  private SdkProxyRoutePlanner proxyRoutePlanner = null;\n  private String host;\n  private int proxyPort;\n  private String nonProxyHosts;\n  private HttpProtocol protocol;\n\n  /**\n   * @param host host\n   * @param proxyPort proxy port\n   * @param proxyProtocol proxy protocol\n   * @param nonProxyHosts non-proxy hosts\n   */\n  public SnowflakeMutableProxyRoutePlanner(\n      String host, int proxyPort, HttpProtocol proxyProtocol, String nonProxyHosts) {\n    proxyRoutePlanner = new SdkProxyRoutePlanner(host, proxyPort, proxyProtocol, nonProxyHosts);\n    this.host = host;\n    this.proxyPort = proxyPort;\n    this.nonProxyHosts = nonProxyHosts;\n    this.protocol = proxyProtocol;\n  }\n\n  /**\n   * Set non-proxy hosts\n   *\n   * @param nonProxyHosts non-proxy hosts\n   */\n  public void setNonProxyHosts(String nonProxyHosts) {\n    this.nonProxyHosts = nonProxyHosts;\n    proxyRoutePlanner = new SdkProxyRoutePlanner(host, proxyPort, protocol, nonProxyHosts);\n  }\n\n  /**\n   * @return non-proxy hosts string\n   */\n  public String getNonProxyHosts() {\n    return nonProxyHosts;\n  }\n\n  @Override\n  public HttpRoute determineRoute(HttpHost target, HttpRequest request, HttpContext context)\n      throws HttpException {\n    return proxyRoutePlanner.determineRoute(target, request, context);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SpcsTokenReader.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Reads the SPCS service-identifier token that the SPCS runtime writes into the container. */\npublic class SpcsTokenReader {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SpcsTokenReader.class);\n\n  static final String SPCS_RUNNING_INSIDE_ENV_VAR = \"SNOWFLAKE_RUNNING_INSIDE_SPCS\";\n  static final String SPCS_TOKEN_FILE_PATH = \"/snowflake/session/spcs_token\";\n\n  public String readSpcsToken() {\n    if (!isRunningInsideSpcs()) {\n      return null;\n    }\n    try {\n      String token = new String(readTokenFileBytes(), StandardCharsets.UTF_8).trim();\n      if (token.isEmpty()) {\n        logger.warn(\"SPCS token file at {} is empty\", SPCS_TOKEN_FILE_PATH);\n        return null;\n      }\n      return token;\n    } catch (Exception ex) {\n      logger.warn(\"Failed to read SPCS token from {}: {}\", SPCS_TOKEN_FILE_PATH, ex.getMessage());\n      return null;\n    }\n  }\n\n  // Overridable in tests via Mockito.spy(...). Production reads the real env / filesystem.\n  boolean isRunningInsideSpcs() {\n    String env = SnowflakeUtil.systemGetEnv(SPCS_RUNNING_INSIDE_ENV_VAR);\n    return env != null && !env.isEmpty();\n  }\n\n  byte[] readTokenFileBytes() throws IOException {\n    return Files.readAllBytes(Paths.get(SPCS_TOKEN_FILE_PATH));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/StmtUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.zip.GZIPOutputStream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.BasicEvent.QueryState;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.common.api.QueryInProgressResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.entity.ByteArrayEntity;\nimport org.apache.http.entity.StringEntity;\n\n/** Statement Util */\npublic class StmtUtil {\n  static final EventHandler eventHandler = EventUtil.getEventHandlerInstance();\n\n  static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  static final String SF_PATH_QUERY_V1 = \"/queries/v1/query-request\";\n\n  private static final String SF_PATH_ABORT_REQUEST_V1 = \"/queries/v1/abort-request\";\n\n  private static final String SF_PATH_QUERY_RESULT = \"/queries/%s/result\";\n\n  private static final String SF_QUERY_COMBINE_DESCRIBE_EXECUTE = \"combinedDescribe\";\n\n  static final String SF_MEDIA_TYPE = \"application/snowflake\";\n\n  // we don't want to retry canceling forever so put a limit which is\n  // twice as much as our default socket timeout\n  static final int SF_CANCELING_RETRY_TIMEOUT_IN_MILLIS = 600000; // 10 min\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(StmtUtil.class);\n\n  /** Input for executing a statement on server */\n  static class StmtInput {\n    String sql;\n\n    // default to snowflake (a special json format for snowflake query result\n    String mediaType = SF_MEDIA_TYPE;\n    Map<String, ParameterBindingDTO> bindValues;\n    String bindStage;\n    boolean describeOnly;\n    String serverUrl;\n    String requestId;\n    int sequenceId = -1;\n    boolean internal = false;\n    boolean asyncExec = false;\n\n    Map<String, Object> parametersMap;\n    String sessionToken;\n    int networkTimeoutInMillis;\n    int socketTimeout;\n    int injectSocketTimeout; // seconds\n    int injectClientPause; // seconds\n\n    int maxRetries;\n\n    AtomicBoolean canceling = null; // canceling flag\n    boolean retry;\n    String prevGetResultURL = null; // previous get result URL from ping pong\n\n    boolean combineDescribe = false;\n\n    String describedJobId;\n\n    long querySubmissionTime; // millis since epoch\n\n    String serviceName;\n\n    OCSPMode ocspMode;\n\n    HttpClientSettingsKey httpClientSettingsKey;\n\n    QueryContextDTO queryContextDTO;\n\n    Map<String, String> additionalHttpHeadersForSnowsight;\n\n    StmtInput() {}\n\n    public StmtInput setSql(String sql) {\n      this.sql = sql;\n      return this;\n    }\n\n    public StmtInput setMediaType(String mediaType) {\n      this.mediaType = mediaType;\n      return this;\n    }\n\n    public StmtInput setParametersMap(Map<String, Object> parametersMap) {\n      this.parametersMap = parametersMap;\n      return this;\n    }\n\n    public StmtInput setBindValues(Map<String, ParameterBindingDTO> bindValues) {\n      this.bindValues = bindValues;\n      return this;\n    }\n\n    public StmtInput setBindStage(String bindStage) {\n      this.bindStage = bindStage;\n      return this;\n    }\n\n    public StmtInput setDescribeOnly(boolean describeOnly) {\n      this.describeOnly = describeOnly;\n      return this;\n    }\n\n    public StmtInput setInternal(boolean internal) {\n      this.internal = internal;\n      return this;\n    }\n\n    public StmtInput setServerUrl(String serverUrl) {\n      this.serverUrl = serverUrl;\n      return this;\n    }\n\n    public StmtInput setRequestId(String requestId) {\n      this.requestId = requestId;\n      return this;\n    }\n\n    public StmtInput setSequenceId(int sequenceId) {\n      this.sequenceId = sequenceId;\n      return this;\n    }\n\n    public StmtInput setSessionToken(String sessionToken) {\n      this.sessionToken = sessionToken;\n      return this;\n    }\n\n    public StmtInput setNetworkTimeoutInMillis(int networkTimeoutInMillis) {\n      this.networkTimeoutInMillis = networkTimeoutInMillis;\n      return this;\n    }\n\n    public StmtInput setSocketTimeout(int socketTimeout) {\n      this.socketTimeout = socketTimeout;\n      return this;\n    }\n\n    public StmtInput setInjectSocketTimeout(int injectSocketTimeout) {\n      this.injectSocketTimeout = injectSocketTimeout;\n      return this;\n    }\n\n    public StmtInput setInjectClientPause(int injectClientPause) {\n      this.injectClientPause = injectClientPause;\n      return this;\n    }\n\n    public StmtInput setCanceling(AtomicBoolean canceling) {\n      this.canceling = canceling;\n      return this;\n    }\n\n    public StmtInput setRetry(boolean retry) {\n      this.retry = retry;\n      return this;\n    }\n\n    public StmtInput setCombineDescribe(boolean combineDescribe) {\n      this.combineDescribe = combineDescribe;\n      return this;\n    }\n\n    public StmtInput setDescribedJobId(String describedJobId) {\n      this.describedJobId = describedJobId;\n      return this;\n    }\n\n    public StmtInput setQuerySubmissionTime(long querySubmissionTime) {\n      this.querySubmissionTime = querySubmissionTime;\n      return this;\n    }\n\n    public StmtInput setServiceName(String serviceName) {\n      this.serviceName = serviceName;\n      return this;\n    }\n\n    public StmtInput setOCSPMode(OCSPMode ocspMode) {\n      this.ocspMode = ocspMode;\n      return this;\n    }\n\n    public StmtInput setHttpClientSettingsKey(HttpClientSettingsKey key) {\n      this.httpClientSettingsKey = key;\n      return this;\n    }\n\n    public StmtInput setAsync(boolean async) {\n      this.asyncExec = async;\n      return this;\n    }\n\n    public StmtInput setQueryContextDTO(QueryContextDTO queryContext) {\n      this.queryContextDTO = queryContext;\n      return this;\n    }\n\n    public StmtInput setMaxRetries(int maxRetries) {\n      this.maxRetries = maxRetries;\n      return this;\n    }\n\n    /**\n     * Set additional http headers to apply to the outgoing request. The additional headers cannot\n     * be used to replace or overwrite a header in use by the driver. These will be applied to the\n     * outgoing request. Primarily used by Snowsight, as described in {@link\n     * HttpUtil#applyAdditionalHeadersForSnowsight(org.apache.http.client.methods.HttpRequestBase,\n     * Map)}\n     *\n     * @param additionalHttpHeaders The new headers to add\n     * @return The input object, for chaining\n     * @see\n     *     HttpUtil#applyAdditionalHeadersForSnowsight(org.apache.http.client.methods.HttpRequestBase,\n     *     Map)\n     */\n    @SuppressWarnings(\"unchecked\")\n    public StmtInput setAdditionalHttpHeadersForSnowsight(\n        Map<String, String> additionalHttpHeaders) {\n      this.additionalHttpHeadersForSnowsight = additionalHttpHeaders;\n      return this;\n    }\n  }\n\n  /** Output for running a statement on server */\n  public static class StmtOutput {\n    JsonNode result;\n\n    public StmtOutput(JsonNode result) {\n      this.result = result;\n    }\n\n    public JsonNode getResult() {\n      return result;\n    }\n  }\n\n  /**\n   * Execute a statement\n   *\n   * <p>side effect: stmtInput.prevGetResultURL is set if we have started ping pong and receives an\n   * exception from session token expiration so that during retry we don't retry the query\n   * submission, but continue the ping pong process.\n   *\n   * @param stmtInput input statement\n   * @param execTimeData ExecTimeTelemetryData\n   * @return StmtOutput output statement\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  public static StmtOutput execute(StmtInput stmtInput, ExecTimeTelemetryData execTimeData)\n      throws SFException, SnowflakeSQLException {\n    return execute(stmtInput, execTimeData, null);\n  }\n\n  /**\n   * Execute a statement\n   *\n   * <p>side effect: stmtInput.prevGetResultURL is set if we have started ping pong and receives an\n   * exception from session token expiration so that during retry we don't retry the query\n   * submission, but continue the ping pong process.\n   *\n   * @param stmtInput input statement\n   * @param execTimeData ExecTimeTelemetryData\n   * @param sfSession the session associated with the request\n   * @return StmtOutput output statement\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  public static StmtOutput execute(\n      StmtInput stmtInput, ExecTimeTelemetryData execTimeData, SFBaseSession sfSession)\n      throws SFException, SnowflakeSQLException {\n    HttpPost httpRequest = null;\n\n    AssertUtil.assertTrue(\n        stmtInput.serverUrl != null, \"Missing server url for statement execution\");\n\n    AssertUtil.assertTrue(stmtInput.sql != null, \"Missing sql for statement execution\");\n\n    AssertUtil.assertTrue(\n        stmtInput.requestId != null, \"Missing request id for statement execution\");\n\n    AssertUtil.assertTrue(\n        stmtInput.sequenceId >= 0, \"Negative sequence id for statement execution\");\n\n    AssertUtil.assertTrue(\n        stmtInput.mediaType != null, \"Missing media type for statement execution\");\n\n    try {\n      String resultAsString = null;\n\n      // SNOW-20443: if we are retrying and there is get result URL, we\n      // don't need to execute the query again\n      if (stmtInput.retry && stmtInput.prevGetResultURL != null) {\n        logger.debug(\n            \"Retrying statement execution with get result URL: {}\", stmtInput.prevGetResultURL);\n      } else {\n        URIBuilder uriBuilder = new URIBuilder(stmtInput.serverUrl);\n\n        uriBuilder.setPath(SF_PATH_QUERY_V1);\n        uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, stmtInput.requestId);\n\n        if (stmtInput.combineDescribe) {\n          uriBuilder.addParameter(SF_QUERY_COMBINE_DESCRIBE_EXECUTE, Boolean.TRUE.toString());\n        }\n\n        httpRequest = new HttpPost(uriBuilder.build());\n\n        // Add custom headers before adding common headers\n        HttpUtil.applyAdditionalHeadersForSnowsight(\n            httpRequest, stmtInput.additionalHttpHeadersForSnowsight);\n\n        /*\n         * sequence id is only needed for old query API, when old query API\n         * is deprecated, we can remove sequence id.\n         */\n        QueryExecDTO sqlJsonBody =\n            new QueryExecDTO(\n                stmtInput.sql,\n                stmtInput.describeOnly,\n                stmtInput.sequenceId,\n                stmtInput.bindValues,\n                stmtInput.bindStage,\n                stmtInput.parametersMap,\n                stmtInput.queryContextDTO,\n                stmtInput.querySubmissionTime,\n                stmtInput.describeOnly || stmtInput.internal,\n                stmtInput.asyncExec);\n\n        if (!stmtInput.describeOnly) {\n          sqlJsonBody.setDescribedJobId(stmtInput.describedJobId);\n        }\n\n        String queryContextDTO = mapper.writeValueAsString(stmtInput.queryContextDTO);\n\n        logger.debug(\"queryContextDTO: {}\", queryContextDTO);\n\n        String json = mapper.writeValueAsString(sqlJsonBody);\n\n        logger.debug(\"JSON: {}\", json);\n\n        ByteArrayEntity input;\n        if (!stmtInput.httpClientSettingsKey.getGzipDisabled()) {\n          execTimeData.setGzipStart();\n          // SNOW-18057: compress the post body in gzip\n          ByteArrayOutputStream baos = new ByteArrayOutputStream();\n          try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) {\n            byte[] bytes = json.getBytes(\"UTF-8\");\n            gzos.write(bytes);\n            gzos.finish();\n            input = new ByteArrayEntity(baos.toByteArray());\n            httpRequest.addHeader(\"content-encoding\", \"gzip\");\n            execTimeData.setGzipEnd();\n          }\n        } else {\n          input = new ByteArrayEntity(json.getBytes(\"UTF-8\"));\n        }\n        input.setContentType(\"application/json\");\n        httpRequest.setEntity(input);\n        httpRequest.addHeader(\"accept\", stmtInput.mediaType);\n\n        httpRequest.setHeader(\n            SFSession.SF_HEADER_AUTHORIZATION,\n            SFSession.SF_HEADER_SNOWFLAKE_AUTHTYPE\n                + \" \"\n                + SFSession.SF_HEADER_TOKEN_TAG\n                + \"=\\\"\"\n                + stmtInput.sessionToken\n                + \"\\\"\");\n\n        setServiceNameHeader(stmtInput, httpRequest);\n        eventHandler.triggerStateTransition(\n            BasicEvent.QueryState.SENDING_QUERY,\n            String.format(QueryState.SENDING_QUERY.getArgString(), stmtInput.requestId));\n\n        resultAsString =\n            HttpUtil.executeRequest(\n                httpRequest,\n                stmtInput.networkTimeoutInMillis / 1000,\n                0,\n                stmtInput.socketTimeout,\n                stmtInput.maxRetries,\n                stmtInput.injectSocketTimeout,\n                stmtInput.canceling,\n                true, // include retry parameters\n                false, // no retry on HTTP 403\n                stmtInput.httpClientSettingsKey,\n                execTimeData,\n                sfSession);\n      }\n\n      return pollForOutput(resultAsString, stmtInput, httpRequest, execTimeData, sfSession);\n    } catch (Exception ex) {\n      if (!(ex instanceof SnowflakeSQLException)) {\n        if (ex instanceof IOException) {\n          logger.error(\"IOException encountered\", ex);\n\n          // network error\n          throw new SFException(\n              ex,\n              ErrorCode.NETWORK_ERROR,\n              \"Exception encountered when executing statement: \" + ex.getLocalizedMessage());\n        } else {\n          logger.error(\"Exception encountered\", ex);\n\n          // raise internal exception if this is not a snowflake exception\n          throw new SFException(ex, ErrorCode.INTERNAL_ERROR, ex.getLocalizedMessage());\n        }\n      } else {\n        throw (SnowflakeSQLException) ex;\n      }\n    } finally {\n      // we can release the http connection now\n      if (httpRequest != null) {\n        httpRequest.releaseConnection();\n      }\n    }\n  }\n\n  static void updateQueryContextFromResponse(JsonNode responseJson, SFBaseSession session) {\n    if (session == null) {\n      return;\n    }\n    JsonNode queryContextNode = responseJson.path(\"data\").path(\"queryContext\");\n    if (SnowflakeUtil.isJsonNodePresent(queryContextNode)) {\n      session.setQueryContext(queryContextNode.toString());\n    }\n  }\n\n  private static void setServiceNameHeader(StmtInput stmtInput, HttpRequestBase httpRequest) {\n    if (!isNullOrEmpty(stmtInput.serviceName)) {\n      httpRequest.setHeader(SessionUtil.SF_HEADER_SERVICE_NAME, stmtInput.serviceName);\n    }\n  }\n\n  private static StmtOutput pollForOutput(\n      String resultAsString,\n      StmtInput stmtInput,\n      HttpPost httpRequest,\n      ExecTimeTelemetryData execTimeData,\n      SFBaseSession session)\n      throws SFException, SnowflakeSQLException {\n    /*\n     * Check response for error or for ping pong response\n     *\n     * For ping-pong: want to make sure our connection is not silently dropped\n     * by middle players (e.g load balancer/VPN timeout) between client and GS\n     */\n    JsonNode pingPongResponseJson;\n    boolean queryInProgress;\n    boolean firstResponse = !stmtInput.retry;\n    String previousGetResultPath = (stmtInput.retry ? stmtInput.prevGetResultURL : null);\n    int retries = 0;\n    final int MAX_RETRIES = 3;\n\n    do {\n      pingPongResponseJson = null;\n\n      if (resultAsString != null) {\n        try {\n          pingPongResponseJson = mapper.readTree(resultAsString);\n        } catch (Exception ex) {\n          logger.error(\n              \"Bad result json: {}, \" + \"JSON parsing exception: {}, http request: {}\",\n              resultAsString,\n              ex.getLocalizedMessage(),\n              httpRequest);\n\n          logger.error(\"Exception stack trace\", ex);\n        }\n      }\n\n      eventHandler.triggerStateTransition(\n          BasicEvent.QueryState.WAITING_FOR_RESULT,\n          \"{requestId: \" + stmtInput.requestId + \",\" + \"pingNumber: \" + retries + \"}\");\n\n      if (pingPongResponseJson == null) {\n        /*\n         * Retry for bad response for server.\n         * But we don't want to retry too many times\n         */\n        if (retries >= MAX_RETRIES) {\n          throw new SFException(ErrorCode.BAD_RESPONSE, resultAsString);\n        } else {\n          logger.debug(\"Will retry get result. Retry count: {}\", retries);\n          execTimeData.incrementRetryCount();\n          execTimeData.addRetryLocation(\"StmtUtil null response\");\n          retries++;\n        }\n      } else {\n        retries = 0; // reset retry counter after a successful response\n\n        // Merge QueryContext before error checking so that QCC is updated\n        // even for failed queries (e.g. DPO changes from aborted transactions).\n        updateQueryContextFromResponse(pingPongResponseJson, session);\n\n        // raise server side error as an exception if any\n        SnowflakeUtil.checkErrorAndThrowException(pingPongResponseJson);\n      }\n\n      // check the response code to see if it is a progress report response\n      if (pingPongResponseJson != null\n          && !QueryInProgressResponse.QUERY_IN_PROGRESS_CODE.equals(\n              pingPongResponseJson.path(\"code\").asText())\n          && !QueryInProgressResponse.QUERY_IN_PROGRESS_ASYNC_CODE.equals(\n              pingPongResponseJson.path(\"code\").asText())) {\n        queryInProgress = false;\n      }\n      // for the purposes of this function, return false instead of true\n      else if (stmtInput.asyncExec\n          && QueryInProgressResponse.QUERY_IN_PROGRESS_ASYNC_CODE.equals(\n              pingPongResponseJson.path(\"code\").asText())) {\n        queryInProgress = false;\n      } else {\n        queryInProgress = true;\n\n        if (firstResponse) {\n          // sleep some time to simulate client pause. The purpose is to\n          // simulate client pause before trying to fetch result so that\n          // we can test query behavior related to disconnected client\n          if (stmtInput.injectClientPause != 0) {\n            logger.debug(\"Inject client pause for {} seconds\", stmtInput.injectClientPause);\n            try {\n              Thread.sleep(stmtInput.injectClientPause * 1000);\n            } catch (InterruptedException ex) {\n              logger.debug(\"Exception encountered while injecting pause\", false);\n            }\n          }\n        }\n        execTimeData.incrementRetryCount();\n        execTimeData.addRetryLocation(\"StmtUtil queryInProgress\");\n        resultAsString =\n            getQueryResult(pingPongResponseJson, previousGetResultPath, stmtInput, session);\n\n        // save the previous get result path in case we run into session\n        // expiration\n        if (pingPongResponseJson != null) {\n          previousGetResultPath = pingPongResponseJson.path(\"data\").path(\"getResultUrl\").asText();\n          stmtInput.prevGetResultURL = previousGetResultPath;\n        }\n      }\n\n      // not first response any more\n      if (firstResponse) {\n        firstResponse = false;\n      }\n    } while (queryInProgress);\n\n    logger.debug(\"Returning result\", false);\n\n    eventHandler.triggerStateTransition(\n        BasicEvent.QueryState.PROCESSING_RESULT,\n        String.format(QueryState.PROCESSING_RESULT.getArgString(), stmtInput.requestId));\n\n    return new StmtOutput(pingPongResponseJson);\n  }\n\n  /**\n   * Issue get-result call to get query result given an in-progress response.\n   *\n   * @param inProgressResponse In progress response in JSON form\n   * @param previousGetResultPath previous get results path\n   * @param stmtInput input statement\n   * @return results in string form\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  @Deprecated\n  protected static String getQueryResult(\n      JsonNode inProgressResponse, String previousGetResultPath, StmtInput stmtInput)\n      throws SFException, SnowflakeSQLException {\n    return getQueryResult(inProgressResponse, previousGetResultPath, stmtInput, null);\n  }\n\n  /**\n   * Issue get-result call to get query result given an in-progress response.\n   *\n   * @param inProgressResponse In progress response in JSON form\n   * @param previousGetResultPath previous get results path\n   * @param stmtInput input statement\n   * @param sfSession the session associated with the request\n   * @return results in string form\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  protected static String getQueryResult(\n      JsonNode inProgressResponse,\n      String previousGetResultPath,\n      StmtInput stmtInput,\n      SFBaseSession sfSession)\n      throws SFException, SnowflakeSQLException {\n    String getResultPath = null;\n\n    // get result url better not be empty\n    if (inProgressResponse == null\n        || inProgressResponse.path(\"data\").path(\"getResultUrl\").isMissingNode()) {\n      if (previousGetResultPath == null) {\n        throw new SFException(\n            ErrorCode.INTERNAL_ERROR, \"No query response or missing get result URL\");\n      } else {\n        logger.debug(\n            \"No query response or missing get result URL, \" + \"use previous get result URL: {}\",\n            previousGetResultPath);\n        getResultPath = previousGetResultPath;\n      }\n    } else {\n      getResultPath = inProgressResponse.path(\"data\").path(\"getResultUrl\").asText();\n    }\n    return getQueryResult(getResultPath, stmtInput, sfSession);\n  }\n\n  /**\n   * Issue get-result call to get query result given an in-progress response.\n   *\n   * @param getResultPath path to results\n   * @param stmtInput object with context information\n   * @return results in string form\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  @Deprecated\n  protected static String getQueryResult(String getResultPath, StmtInput stmtInput)\n      throws SFException, SnowflakeSQLException {\n    return getQueryResult(getResultPath, stmtInput, null);\n  }\n\n  /**\n   * Issue get-result call to get query result given an in-progress response.\n   *\n   * @param getResultPath path to results\n   * @param stmtInput object with context information\n   * @param sfSession the session associated with the request\n   * @return results in string form\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  protected static String getQueryResult(\n      String getResultPath, StmtInput stmtInput, SFBaseSession sfSession)\n      throws SFException, SnowflakeSQLException {\n    HttpGet httpRequest = null;\n    logger.debug(\"Get query result: {}\", getResultPath);\n\n    try {\n      URIBuilder uriBuilder = new URIBuilder(stmtInput.serverUrl);\n\n      uriBuilder.setPath(getResultPath);\n\n      uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, UUIDUtils.getUUID().toString());\n\n      httpRequest = new HttpGet(uriBuilder.build());\n      // Add custom headers before adding common headers\n      HttpUtil.applyAdditionalHeadersForSnowsight(\n          httpRequest, stmtInput.additionalHttpHeadersForSnowsight);\n\n      httpRequest.addHeader(\"accept\", stmtInput.mediaType);\n\n      httpRequest.setHeader(\n          SFSession.SF_HEADER_AUTHORIZATION,\n          SFSession.SF_HEADER_SNOWFLAKE_AUTHTYPE\n              + \" \"\n              + SFSession.SF_HEADER_TOKEN_TAG\n              + \"=\\\"\"\n              + stmtInput.sessionToken\n              + \"\\\"\");\n\n      setServiceNameHeader(stmtInput, httpRequest);\n\n      return HttpUtil.executeRequest(\n          httpRequest,\n          stmtInput.networkTimeoutInMillis / 1000,\n          0,\n          stmtInput.socketTimeout,\n          stmtInput.maxRetries,\n          0,\n          stmtInput.canceling,\n          false, // no retry parameter\n          false, // no retry on HTTP 403\n          stmtInput.httpClientSettingsKey,\n          new ExecTimeTelemetryData(),\n          sfSession);\n    } catch (URISyntaxException | IOException ex) {\n      logger.error(\"Exception encountered when getting result for \" + httpRequest, ex);\n\n      // raise internal exception if this is not a snowflake exception\n      throw new SFException(ex, ErrorCode.INTERNAL_ERROR, ex.getLocalizedMessage());\n    }\n  }\n\n  /**\n   * Issue get-result call to get query result given an in progress response.\n   *\n   * @param queryId id of query to get results for\n   * @param session the current session\n   * @return results in JSON\n   * @throws SFException exception raised from Snowflake components\n   * @throws SnowflakeSQLException exception raised from Snowflake components\n   */\n  protected static JsonNode getQueryResultJSON(String queryId, SFSession session)\n      throws SFException, SnowflakeSQLException {\n    String getResultPath = String.format(SF_PATH_QUERY_RESULT, queryId);\n    StmtInput stmtInput =\n        new StmtInput()\n            .setServerUrl(session.getServerUrl())\n            .setSessionToken(session.getSessionToken(internalCallMarker()))\n            .setNetworkTimeoutInMillis(session.getNetworkTimeoutInMilli())\n            .setSocketTimeout(session.getHttpClientSocketTimeout())\n            .setMediaType(SF_MEDIA_TYPE)\n            .setServiceName(session.getServiceName())\n            .setOCSPMode(session.getOCSPMode())\n            .setHttpClientSettingsKey(session.getHttpClientKey())\n            .setMaxRetries(session.getMaxHttpRetries());\n\n    String resultAsString = getQueryResult(getResultPath, stmtInput, session);\n\n    StmtOutput stmtOutput =\n        pollForOutput(resultAsString, stmtInput, null, new ExecTimeTelemetryData(), session);\n    return stmtOutput.getResult();\n  }\n\n  /**\n   * Cancel a statement identifiable by a request id\n   *\n   * @param stmtInput input statement\n   * @throws SFException if there is an internal exception\n   * @throws SnowflakeSQLException if failed to cancel the statement\n   * @deprecated use {@link #cancel(StmtInput, CancellationReason, SFBaseSession)} instead\n   */\n  @Deprecated\n  public static void cancel(StmtInput stmtInput) throws SFException, SnowflakeSQLException {\n    cancel(stmtInput, CancellationReason.UNKNOWN, null);\n  }\n\n  /**\n   * Cancel a statement identifiable by a request id\n   *\n   * @param stmtInput input statement\n   * @param cancellationReason reason for the cancellation\n   * @throws SFException if there is an internal exception\n   * @throws SnowflakeSQLException if failed to cancel the statement\n   */\n  public static void cancel(StmtInput stmtInput, CancellationReason cancellationReason)\n      throws SFException, SnowflakeSQLException {\n    cancel(stmtInput, cancellationReason, null);\n  }\n\n  /**\n   * Cancel a statement identifiable by a request id\n   *\n   * @param stmtInput input statement\n   * @param cancellationReason reason for the cancellation\n   * @param sfSession the session associated with the request\n   * @throws SFException if there is an internal exception\n   * @throws SnowflakeSQLException if failed to cancel the statement\n   */\n  public static void cancel(\n      StmtInput stmtInput, CancellationReason cancellationReason, SFBaseSession sfSession)\n      throws SFException, SnowflakeSQLException {\n    HttpPost httpRequest = null;\n\n    AssertUtil.assertTrue(\n        stmtInput.serverUrl != null, \"Missing server url for statement execution\");\n\n    AssertUtil.assertTrue(stmtInput.sql != null, \"Missing sql for statement execution\");\n\n    AssertUtil.assertTrue(\n        stmtInput.mediaType != null, \"Missing media type for statement execution\");\n\n    AssertUtil.assertTrue(\n        stmtInput.requestId != null, \"Missing request id for statement execution\");\n\n    AssertUtil.assertTrue(\n        stmtInput.sessionToken != null, \"Missing session token for statement execution\");\n\n    try {\n      URIBuilder uriBuilder = new URIBuilder(stmtInput.serverUrl);\n      logger.warn(\"Cancelling query {} with reason {}\", stmtInput.requestId, cancellationReason);\n      logger.debug(\"Aborting query: {}\", stmtInput.sql);\n\n      uriBuilder.setPath(SF_PATH_ABORT_REQUEST_V1);\n\n      uriBuilder.addParameter(SFSession.SF_QUERY_REQUEST_ID, UUIDUtils.getUUID().toString());\n\n      httpRequest = new HttpPost(uriBuilder.build());\n      // Add custom headers before adding common headers\n      HttpUtil.applyAdditionalHeadersForSnowsight(\n          httpRequest, stmtInput.additionalHttpHeadersForSnowsight);\n\n      /*\n       * The JSON input has two fields: sqlText and requestId\n       */\n      Map<String, Object> sqlJsonBody = new HashMap<String, Object>();\n      sqlJsonBody.put(\"sqlText\", stmtInput.sql);\n      sqlJsonBody.put(\"requestId\", stmtInput.requestId);\n\n      String json = mapper.writeValueAsString(sqlJsonBody);\n\n      logger.debug(\"JSON for cancel request: {}\", json);\n\n      StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);\n      input.setContentType(\"application/json\");\n      httpRequest.setEntity(input);\n\n      httpRequest.addHeader(\"accept\", stmtInput.mediaType);\n\n      httpRequest.setHeader(\n          SFSession.SF_HEADER_AUTHORIZATION,\n          SFSession.SF_HEADER_SNOWFLAKE_AUTHTYPE\n              + \" \"\n              + SFSession.SF_HEADER_TOKEN_TAG\n              + \"=\\\"\"\n              + stmtInput.sessionToken\n              + \"\\\"\");\n\n      setServiceNameHeader(stmtInput, httpRequest);\n\n      String jsonString =\n          HttpUtil.executeRequest(\n              httpRequest,\n              SF_CANCELING_RETRY_TIMEOUT_IN_MILLIS,\n              0,\n              stmtInput.socketTimeout,\n              0,\n              0,\n              null,\n              false, // no retry parameter\n              false, // no retry on HTTP 403\n              stmtInput.httpClientSettingsKey,\n              new ExecTimeTelemetryData(),\n              sfSession);\n\n      // trace the response if requested\n      logger.debug(\"Json response: {}\", jsonString);\n\n      JsonNode rootNode = null;\n      rootNode = mapper.readTree(jsonString);\n\n      // raise server side error as an exception if any\n      SnowflakeUtil.checkErrorAndThrowException(rootNode);\n    } catch (URISyntaxException | IOException ex) {\n      logger.error(\"Exception encountered when canceling \" + httpRequest, ex);\n\n      // raise internal exception if this is not a snowflake exception\n      throw new SFException(ex, ErrorCode.INTERNAL_ERROR, ex.getLocalizedMessage());\n    }\n  }\n\n  /**\n   * A simple function to check if the statement is related to manipulate stage.\n   *\n   * @param sql a SQL statement/command\n   * @return PUT/GET/LIST/RM if statement belongs to one of them, otherwise return NULL\n   */\n  public static SFStatementType checkStageManageCommand(String sql) {\n    if (sql == null) {\n      return null;\n    }\n\n    String trimmedSql = sql.trim();\n\n    // skip commenting prefixed with //\n    while (trimmedSql.startsWith(\"//\")) {\n      if (logger.isDebugEnabled()) {\n        logger.debug(\"Skipping // comments in: \\n{}\", trimmedSql);\n      }\n\n      if (trimmedSql.indexOf('\\n') > 0) {\n        trimmedSql = trimmedSql.substring(trimmedSql.indexOf('\\n'));\n        trimmedSql = trimmedSql.trim();\n      } else {\n        break;\n      }\n\n      if (logger.isDebugEnabled()) {\n        logger.debug(\"New sql after skipping // comments: \\n{}\", trimmedSql);\n      }\n    }\n\n    // skip commenting enclosed with /* */\n    while (trimmedSql.startsWith(\"/*\")) {\n      if (logger.isDebugEnabled()) {\n        logger.debug(\"skipping /* */ comments in: \\n{}\", trimmedSql);\n      }\n\n      if (trimmedSql.indexOf(\"*/\") > 0) {\n        trimmedSql = trimmedSql.substring(trimmedSql.indexOf(\"*/\") + 2);\n        trimmedSql = trimmedSql.trim();\n      } else {\n        break;\n      }\n\n      if (logger.isDebugEnabled()) {\n        logger.debug(\n            \"New sql after skipping /* */ comments: \\n{}\", SecretDetector.maskSecrets(trimmedSql));\n      }\n    }\n\n    trimmedSql = trimmedSql.toLowerCase();\n\n    if (trimmedSql.startsWith(\"put \")) {\n      return SFStatementType.PUT;\n    } else if (trimmedSql.startsWith(\"get \")) {\n      return SFStatementType.GET;\n    } else if (trimmedSql.startsWith(\"ls \") || trimmedSql.startsWith(\"list \")) {\n      return SFStatementType.LIST;\n    } else if (trimmedSql.startsWith(\"rm \") || trimmedSql.startsWith(\"remove \")) {\n      return SFStatementType.REMOVE;\n    } else {\n      return null;\n    }\n  }\n\n  /**\n   * Truncate a SQL text for logging\n   *\n   * @param sql original SQL\n   * @return truncated SQL command\n   */\n  public static String truncateSQL(String sql) {\n    return sql.length() > 20 ? sql.substring(0, 20) + \"...\" : sql;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/SystemUtil.java",
    "content": "package net.snowflake.client.internal.core;\r\n\r\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\r\n\r\nimport net.snowflake.client.internal.log.SFLogger;\r\nimport net.snowflake.client.internal.log.SFLoggerFactory;\r\n\r\nclass SystemUtil {\r\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SystemUtil.class);\r\n\r\n  /**\r\n   * Helper function to convert system properties to integers\r\n   *\r\n   * @param systemProperty name of the system property\r\n   * @param defaultValue default value used\r\n   * @return the value of the system property, else the default value\r\n   */\r\n  static int convertSystemPropertyToIntValue(String systemProperty, int defaultValue) {\r\n    String systemPropertyValue = systemGetProperty(systemProperty);\r\n    int returnVal = defaultValue;\r\n    if (systemPropertyValue != null) {\r\n      try {\r\n        returnVal = Integer.parseInt(systemPropertyValue);\r\n      } catch (NumberFormatException ex) {\r\n        logger.warn(\r\n            \"Failed to parse the system parameter {} with value {}\",\r\n            systemProperty,\r\n            systemPropertyValue);\r\n      }\r\n    }\r\n    return returnVal;\r\n  }\r\n}\r\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/URLUtil.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SFSession.SF_QUERY_REQUEST_ID;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.MalformedURLException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.regex.PatternSyntaxException;\nimport javax.annotation.Nullable;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.NameValuePair;\nimport org.apache.http.client.utils.URLEncodedUtils;\n\npublic class URLUtil {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(URLUtil.class);\n  static final String validURLPattern =\n      \"^http(s?)\\\\:\\\\/\\\\/[0-9a-zA-Z]([-.\\\\w]*[0-9a-zA-Z@:])*(:(0-9)*)*(\\\\/?)([a-zA-Z0-9\\\\-\\\\.\\\\?\\\\,\\\\&\\\\(\\\\)\\\\/\\\\\\\\\\\\+&%\\\\$#_=@]*)?$\";\n  static final Pattern pattern = Pattern.compile(validURLPattern);\n\n  public static boolean isValidURL(String url) {\n    try {\n      Matcher matcher = pattern.matcher(url);\n      return matcher.find();\n    } catch (PatternSyntaxException pex) {\n      logger.debug(\"The URL REGEX is invalid. Falling back to basic sanity test\");\n      try {\n        new URL(url).toURI();\n        return true;\n      } catch (MalformedURLException mex) {\n        logger.debug(\"The URL \" + url + \", is invalid\");\n        return false;\n      } catch (URISyntaxException uex) {\n        logger.debug(\"The URL \" + url + \", is invalid\");\n        return false;\n      }\n    }\n  }\n\n  @Nullable\n  public static String urlEncode(String target) throws UnsupportedEncodingException {\n    String encodedTarget;\n    try {\n      encodedTarget = URLEncoder.encode(target, StandardCharsets.UTF_8.toString());\n    } catch (UnsupportedEncodingException uex) {\n      logger.debug(\"The string to be encoded- \" + target + \", is invalid\");\n      return null;\n    }\n    return encodedTarget;\n  }\n\n  public static String getRequestId(URI uri) {\n    return URLEncodedUtils.parse(uri, StandardCharsets.UTF_8).stream()\n        .filter(p -> p.getName().equals(SF_QUERY_REQUEST_ID))\n        .findFirst()\n        .map(NameValuePair::getValue)\n        .orElse(null);\n  }\n\n  public static String getRequestIdLogStr(URI uri) {\n    String requestId = getRequestId(uri);\n\n    return requestId == null ? \"\" : \"[requestId=\" + requestId + \"] \";\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/UUIDUtils.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.UUID;\nimport java.util.concurrent.ThreadLocalRandom;\n\npublic class UUIDUtils {\n  static Boolean newUUIDEnabled = true;\n\n  public static UUID getUUID() {\n    if (UUIDUtils.newUUIDEnabled) {\n      return UUIDUtils.UUIDImpl();\n    } else {\n      return UUID.randomUUID();\n    }\n  }\n\n  private static UUID UUIDImpl() {\n    final byte[] randomBytes = new byte[16];\n    ThreadLocalRandom.current().nextBytes(randomBytes);\n    randomBytes[6] &= 0x0f;\n    randomBytes[6] |= 0x40;\n    randomBytes[8] &= 0x3f;\n    randomBytes[8] |= 0x80;\n\n    long msb = 0;\n    long lsb = 0;\n    for (int i = 0; i < 8; i++) {\n      msb = (msb << 8) | (randomBytes[i] & 0xff);\n    }\n    for (int i = 8; i < 16; i++) {\n      lsb = (lsb << 8) | (randomBytes[i] & 0xff);\n    }\n\n    return new UUID(msb, lsb);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/AbstractArrowVectorConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.ValueVector;\n\n/**\n * Abstract class of arrow vector converter. For most types, throw invalid convert error. It depends\n * child class to override conversion logic\n *\n * <p>Note: two method toObject and toString is abstract method because every converter\n * implementation needs to implement them\n */\nabstract class AbstractArrowVectorConverter implements ArrowVectorConverter {\n  /** snowflake logical type of the target arrow vector */\n  protected String logicalTypeStr;\n\n  /** value vector */\n  private ValueVector valueVector;\n\n  protected DataConversionContext context;\n\n  protected int columnIndex;\n\n  protected boolean treatNTZasUTC;\n\n  protected boolean useSessionTimezone;\n\n  protected TimeZone sessionTimeZone;\n\n  private boolean shouldTreatDecimalAsInt;\n\n  /** Field names of the struct vectors used by timestamp */\n  public static final String FIELD_NAME_EPOCH = \"epoch\"; // seconds since epoch\n\n  /** Timezone index */\n  public static final String FIELD_NAME_TIME_ZONE_INDEX = \"timezone\"; // time zone index\n\n  /** Fraction in nanoseconds */\n  public static final String FIELD_NAME_FRACTION = \"fraction\"; // fraction in nanoseconds\n\n  /**\n   * @param logicalTypeStr snowflake logical type of the target arrow vector.\n   * @param valueVector value vector\n   * @param vectorIndex value index\n   * @param context DataConversionContext\n   */\n  AbstractArrowVectorConverter(\n      String logicalTypeStr,\n      ValueVector valueVector,\n      int vectorIndex,\n      DataConversionContext context) {\n    this.logicalTypeStr = logicalTypeStr;\n    this.valueVector = valueVector;\n    this.columnIndex = vectorIndex + 1;\n    this.context = context;\n    this.shouldTreatDecimalAsInt =\n        context == null\n            || context.getSession() == null\n            || context.getSession().isJdbcArrowTreatDecimalAsInt()\n            || context.getSession().isJdbcTreatDecimalAsInt();\n  }\n\n  @Override\n  public boolean toBoolean(int rowIndex) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, \"\");\n  }\n\n  @Override\n  public byte toByte(int rowIndex) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BYTE_STR, \"\");\n  }\n\n  @Override\n  public short toShort(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, \"\");\n  }\n\n  @Override\n  public int toInt(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.INT_STR);\n  }\n\n  @Override\n  public long toLong(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.LONG_STR, \"\");\n  }\n\n  @Override\n  public double toDouble(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.DOUBLE_STR, \"\");\n  }\n\n  @Override\n  public float toFloat(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.FLOAT_STR, \"\");\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byteArray\", \"\");\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone jvmTz, boolean useDateFormat) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.DATE_STR, \"\");\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.TIME_STR, \"\");\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.TIMESTAMP_STR, \"\");\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BIG_DECIMAL_STR, \"\");\n  }\n\n  @Override\n  public Period toPeriod(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.PERIOD_STR, \"\");\n  }\n\n  @Override\n  public Duration toDuration(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.DURATION_STR, \"\");\n  }\n\n  /**\n   * True if should treat decimal as int type.\n   *\n   * @return true or false if decimal should be treated as int type.\n   */\n  boolean shouldTreatDecimalAsInt() {\n    return shouldTreatDecimalAsInt;\n  }\n\n  @Override\n  public void setTreatNTZAsUTC(boolean isUTC) {\n    this.treatNTZasUTC = isUTC;\n  }\n\n  @Override\n  public void setUseSessionTimezone(boolean useSessionTimezone) {\n    this.useSessionTimezone = useSessionTimezone;\n  }\n\n  @Override\n  public void setSessionTimeZone(TimeZone tz) {\n    this.sessionTimeZone = tz;\n  }\n\n  @Override\n  public boolean isNull(int index) {\n    return valueVector.isNull(index);\n  }\n\n  @Override\n  public abstract Object toObject(int index) throws SFException;\n\n  @Override\n  public abstract String toString(int index) throws SFException;\n\n  /**\n   * Thrown when a Snowflake timestamp cannot be manipulated in Java due to size limitations.\n   * Snowflake can use up to a full SB16 to represent a timestamp. Java, on the other hand, requires\n   * that the number of millis since epoch fit into a long. For timestamps whose millis since epoch\n   * don't fit into a long, certain operations, such as conversion to java .sql.Timestamp, are not\n   * available.\n   */\n  public static class TimestampOperationNotAvailableException extends RuntimeException {\n    private BigDecimal secsSinceEpoch;\n\n    TimestampOperationNotAvailableException(long secsSinceEpoch, int fraction) {\n      super(\"seconds=\" + secsSinceEpoch + \" nanos=\" + fraction);\n      this.secsSinceEpoch =\n          new BigDecimal(secsSinceEpoch).add(new BigDecimal(fraction).scaleByPowerOfTen(-9));\n    }\n\n    public BigDecimal getSecsSinceEpoch() {\n      return secsSinceEpoch;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/ArrayConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.tostringhelpers.ArrowArrayStringRepresentationBuilder;\nimport org.apache.arrow.vector.FieldVector;\nimport org.apache.arrow.vector.complex.ListVector;\n\n/** Array type converter. */\npublic class ArrayConverter extends AbstractArrowVectorConverter {\n\n  private final ListVector vector;\n\n  /**\n   * @param valueVector ListVector\n   * @param vectorIndex vector index\n   * @param context DataConversionContext\n   */\n  public ArrayConverter(ListVector valueVector, int vectorIndex, DataConversionContext context) {\n    super(SnowflakeType.ARRAY.name(), valueVector, vectorIndex, context);\n    this.vector = valueVector;\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return isNull(index) ? null : vector.getObject(index);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    return isNull(index) ? null : toString(index).getBytes();\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    FieldVector vectorUnpacked = vector.getChildrenFromFields().get(0);\n    SnowflakeType logicalType =\n        ArrowVectorConverterUtil.getSnowflakeTypeFromFieldMetadata(vectorUnpacked.getField());\n\n    ArrowArrayStringRepresentationBuilder builder =\n        new ArrowArrayStringRepresentationBuilder(logicalType);\n\n    final ArrowVectorConverter converter;\n\n    try {\n      converter = ArrowVectorConverterUtil.initConverter(vectorUnpacked, context, columnIndex);\n    } catch (SnowflakeSQLException e) {\n      return vector.getObject(index).toString();\n    }\n\n    for (int i = vector.getElementStartIndex(index); i < vector.getElementEndIndex(index); i++) {\n      builder.appendValue(converter.toString(i));\n    }\n\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/ArrowResultChunkIndexSorter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.util.List;\nimport java.util.stream.IntStream;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\n\n/**\n * Use quick sort to sort Arrow result chunk The sorted order is represented in the indices vector\n */\npublic class ArrowResultChunkIndexSorter {\n  private List<ValueVector> resultChunk;\n  private List<ArrowVectorConverter> converters;\n  /** Vector indices to sort. */\n  private IntVector indices;\n\n  public ArrowResultChunkIndexSorter(\n      List<ValueVector> resultChunk, List<ArrowVectorConverter> converters) {\n    this.resultChunk = resultChunk;\n    this.converters = converters;\n    initIndices();\n  }\n\n  /** initialize original indices */\n  private void initIndices() {\n    BufferAllocator rootAllocator = resultChunk.get(0).getAllocator();\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, null);\n\n    indices = new IntVector(\"indices\", fieldType, rootAllocator);\n    IntStream.range(0, resultChunk.get(0).getValueCount()).forEach(i -> indices.setSafe(i, i));\n  }\n\n  /**\n   * This method is only used when sf-property sort is on\n   *\n   * @return sorted indices\n   * @throws SFException when exception encountered\n   */\n  public IntVector sort() throws SFException {\n    quickSort(0, resultChunk.get(0).getValueCount() - 1);\n    return indices;\n  }\n\n  private void quickSort(int low, int high) throws SFException {\n    if (low < high) {\n      int mid = partition(low, high);\n      quickSort(low, mid - 1);\n      quickSort(mid + 1, high);\n    }\n  }\n\n  private int partition(int low, int high) throws SFException {\n    int pivotIndex = indices.get(low);\n\n    while (low < high) {\n      while (low < high && compare(indices.get(high), pivotIndex) >= 0) {\n        high -= 1;\n      }\n      indices.set(low, indices.get(high));\n\n      while (low < high && compare(indices.get(low), pivotIndex) <= 0) {\n        low += 1;\n      }\n      indices.set(high, indices.get(low));\n    }\n\n    indices.setSafe(low, pivotIndex);\n    return low;\n  }\n\n  /**\n   * Implement the same compare method as JSON result\n   *\n   * @throws SFException\n   */\n  private int compare(int index1, int index2) throws SFException {\n    int numCols = converters.size();\n    for (int colIdx = 0; colIdx < numCols; colIdx++) {\n      if (converters.get(colIdx).isNull(index1) && converters.get(colIdx).isNull(index2)) {\n        continue;\n      }\n\n      // null is considered bigger than all values\n      if (converters.get(colIdx).isNull(index1)) {\n        return 1;\n      }\n\n      if (converters.get(colIdx).isNull(index2)) {\n        return -1;\n      }\n\n      int res =\n          converters\n              .get(colIdx)\n              .toString(index1)\n              .compareTo(converters.get(colIdx).toString(index2));\n\n      // continue to next column if no difference\n      if (res == 0) {\n        continue;\n      }\n\n      return res;\n    }\n\n    // all columns are the same\n    return 0;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/ArrowResultUtil.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.sql.Date;\nimport java.sql.Timestamp;\nimport java.time.LocalDate;\nimport java.util.Calendar;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimestampWithTimezone;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.CalendarCache;\n\n/** Result utility methods specifically for Arrow format */\npublic class ArrowResultUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ArrowResultUtil.class);\n\n  private static final int[] POWERS_OF_10 = {\n    1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000\n  };\n\n  public static final int MAX_SCALE_POWERS_OF_10 = 9;\n\n  public static long powerOfTen(int pow) {\n    long val = 1;\n    while (pow > MAX_SCALE_POWERS_OF_10) {\n      val *= POWERS_OF_10[MAX_SCALE_POWERS_OF_10];\n      pow -= MAX_SCALE_POWERS_OF_10;\n    }\n    return val * POWERS_OF_10[pow];\n  }\n\n  public static String getStringFormat(int scale) {\n    StringBuilder sb = new StringBuilder();\n    return sb.append(\"%.\").append(scale).append('f').toString();\n  }\n\n  /**\n   * new method to get Date from integer\n   *\n   * @param day The day to convert.\n   * @return Date\n   */\n  public static Date getDate(int day) {\n    LocalDate localDate = LocalDate.ofEpochDay(day);\n    return Date.valueOf(localDate);\n  }\n\n  /**\n   * Method to get Date from integer using timezone offsets\n   *\n   * @param day The day to convert.\n   * @param oldTz The old timezone.\n   * @param newTz The new timezone.\n   * @return Date\n   * @throws SFException if date value is invalid\n   */\n  public static Date getDate(int day, TimeZone oldTz, TimeZone newTz) throws SFException {\n    try {\n      // return the date adjusted to the JVM default time zone\n      long milliSecsSinceEpoch = (long) day * ResultUtil.MILLIS_IN_ONE_DAY;\n\n      long milliSecsSinceEpochNew =\n          milliSecsSinceEpoch + moveToTimeZoneOffset(milliSecsSinceEpoch, oldTz, newTz);\n\n      Date preDate = new Date(milliSecsSinceEpochNew);\n\n      // if date is on or before 1582-10-04, apply the difference\n      // by (H-H/4-2) where H is the hundreds digit of the year according to:\n      // http://en.wikipedia.org/wiki/Gregorian_calendar\n      Date newDate = ResultUtil.adjustDate(preDate);\n      logger.debug(\n          \"Adjust date from {} to {}\",\n          (ArgSupplier) preDate::toString,\n          (ArgSupplier) newDate::toString);\n      return newDate;\n    } catch (NumberFormatException ex) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"Invalid date value: \" + day);\n    }\n  }\n\n  /**\n   * simplified moveToTimeZone method\n   *\n   * @param milliSecsSinceEpoch milliseconds since Epoch\n   * @param oldTZ old timezone\n   * @param newTZ new timezone\n   * @return offset offset value\n   */\n  private static long moveToTimeZoneOffset(\n      long milliSecsSinceEpoch, TimeZone oldTZ, TimeZone newTZ) {\n    if (oldTZ.hasSameRules(newTZ)) {\n      // same time zone\n      return 0;\n    }\n    int offsetMillisInOldTZ = oldTZ.getOffset(milliSecsSinceEpoch);\n\n    Calendar calendar = CalendarCache.get(oldTZ);\n    calendar.setTimeInMillis(milliSecsSinceEpoch);\n\n    int millisecondWithinDay =\n        ((calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * 60\n                    + calendar.get(Calendar.SECOND))\n                * 1000\n            + calendar.get(Calendar.MILLISECOND);\n\n    int era = calendar.get(Calendar.ERA);\n    int year = calendar.get(Calendar.YEAR);\n    int month = calendar.get(Calendar.MONTH);\n    int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);\n    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);\n\n    int offsetMillisInNewTZ =\n        newTZ.getOffset(era, year, month, dayOfMonth, dayOfWeek, millisecondWithinDay);\n\n    int offsetMillis = offsetMillisInOldTZ - offsetMillisInNewTZ;\n    return offsetMillis;\n  }\n\n  /**\n   * move the input timestamp form oldTZ to newTZ\n   *\n   * @param ts Timestamp\n   * @param oldTZ Old timezone\n   * @param newTZ New timezone\n   * @return timestamp in newTZ\n   */\n  public static Timestamp moveToTimeZone(Timestamp ts, TimeZone oldTZ, TimeZone newTZ) {\n    long offset = moveToTimeZoneOffset(ts.getTime(), oldTZ, newTZ);\n    if (offset == 0) {\n      return ts;\n    }\n    int nanos = ts.getNanos();\n    ts = new Timestamp(ts.getTime() + offset);\n    ts.setNanos(nanos);\n    return ts;\n  }\n\n  /**\n   * generate Java Timestamp object\n   *\n   * @param epoch the value since epoch time\n   * @param scale the scale of the value\n   * @return Timestamp\n   */\n  public static Timestamp toJavaTimestamp(long epoch, int scale) {\n    return toJavaTimestamp(epoch, scale, TimeZone.getDefault(), false);\n  }\n\n  /**\n   * generate Java Timestamp object\n   *\n   * @param epoch the value since epoch time\n   * @param scale the scale of the value\n   * @param sessionTimezone the session timezone\n   * @param useSessionTimezone should the session timezone be used\n   * @return Timestamp\n   */\n  public static Timestamp toJavaTimestamp(\n      long epoch, int scale, TimeZone sessionTimezone, boolean useSessionTimezone) {\n    long seconds = epoch / powerOfTen(scale);\n    int fraction = (int) ((epoch % powerOfTen(scale)) * powerOfTen(9 - scale));\n    if (fraction < 0) {\n      // handle negative case here\n      seconds--;\n      fraction += 1000000000;\n    }\n    return createTimestamp(seconds, fraction, sessionTimezone, useSessionTimezone);\n  }\n\n  /**\n   * check whether the input seconds out of the scope of Java timestamp\n   *\n   * @param seconds long value to check\n   * @return true if value is out of the scope of Java timestamp.\n   */\n  public static boolean isTimestampOverflow(long seconds) {\n    return seconds < Long.MIN_VALUE / powerOfTen(3) || seconds > Long.MAX_VALUE / powerOfTen(3);\n  }\n\n  /**\n   * create Java timestamp using seconds since epoch and fraction in nanoseconds For example,\n   * 1232.234 represents as epoch = 1232 and fraction = 234,000,000 For example, -1232.234\n   * represents as epoch = -1233 and fraction = 766,000,000 For example, -0.13 represents as epoch =\n   * -1 and fraction = 870,000,000\n   *\n   * @param seconds seconds value\n   * @param fraction fraction\n   * @param timezone The timezone being used for the toString() formatting\n   * @param useSessionTz boolean useSessionTz\n   * @return java timestamp object\n   */\n  public static Timestamp createTimestamp(\n      long seconds, int fraction, TimeZone timezone, boolean useSessionTz) {\n    // If JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true, set timezone to UTC to get\n    // timestamp object. This will avoid moving the timezone and creating\n    // daylight savings offset errors.\n    if (useSessionTz) {\n      return new SnowflakeTimestampWithTimezone(\n          seconds * ArrowResultUtil.powerOfTen(3), fraction, timezone);\n    }\n    Timestamp ts = new Timestamp(seconds * ArrowResultUtil.powerOfTen(3));\n    ts.setNanos(fraction);\n    return ts;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/ArrowVectorConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.TimeZone;\nimport net.snowflake.client.internal.core.SFException;\n\n/** Interface to convert from arrow vector values into java data types. */\npublic interface ArrowVectorConverter {\n\n  /**\n   * Set to true when time value should be displayed in wallclock time (no timezone offset)\n   *\n   * @param useSessionTimezone boolean value indicating if there is a timezone offset.\n   */\n  void setUseSessionTimezone(boolean useSessionTimezone);\n\n  void setSessionTimeZone(TimeZone tz);\n\n  /**\n   * Determine whether source value in arrow vector is null value or not\n   *\n   * @param index index of value to be checked\n   * @return true if null value otherwise false\n   */\n  boolean isNull(int index);\n\n  /**\n   * Convert value in arrow vector to boolean data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return boolean data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  boolean toBoolean(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to byte data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return byte data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  byte toByte(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to short data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return short data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  short toShort(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to int data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return int data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  int toInt(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to long data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return long data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  long toLong(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to double data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return double data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  double toDouble(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to float data\n   *\n   * @param index index of the value to be converted in the vector\n   * @return float data converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  float toFloat(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to byte array\n   *\n   * @param index index of the value to be converted in the vector\n   * @return byte array converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  byte[] toBytes(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to string\n   *\n   * @param index index of the value to be converted in the vector\n   * @return string converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  String toString(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to Date\n   *\n   * @param index index of the value to be converted in the vector\n   * @param jvmTz JVM timezone\n   * @param useDateFormat boolean value to check whether to change timezone or not\n   * @return Date converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  Date toDate(int index, TimeZone jvmTz, boolean useDateFormat) throws SFException;\n\n  /**\n   * Convert value in arrow vector to Time\n   *\n   * @param index index of the value to be converted in the vector\n   * @return Time converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  Time toTime(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to Timestamp\n   *\n   * @param index index of the value to be converted in the vector\n   * @param tz time zone\n   * @return Timestamp converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  Timestamp toTimestamp(int index, TimeZone tz) throws SFException;\n\n  /**\n   * Convert value in arrow vector to BigDecimal\n   *\n   * @param index index of the value to be converted in the vector\n   * @return BigDecimal converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  BigDecimal toBigDecimal(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to Period\n   *\n   * @param index index of the value to be converted in the vector\n   * @return Period converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  Period toPeriod(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to Duration\n   *\n   * @param index index of the value to be converted in the vector\n   * @return Duration converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  Duration toDuration(int index) throws SFException;\n\n  /**\n   * Convert value in arrow vector to Object\n   *\n   * @param index index of the value to be converted in the vector\n   * @return Object converted from arrow vector\n   * @throws SFException invalid data conversion\n   */\n  Object toObject(int index) throws SFException;\n\n  /**\n   * Set to true if NTZ timestamp should be set to UTC\n   *\n   * @param isUTC true or false value of whether NTZ timestamp should be set to UTC\n   */\n  void setTreatNTZAsUTC(boolean isUTC);\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/ArrowVectorConverterUtil.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.time.Duration;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.arrow.vector.FieldVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.complex.FixedSizeListVector;\nimport org.apache.arrow.vector.complex.ListVector;\nimport org.apache.arrow.vector.complex.MapVector;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.Field;\n\npublic final class ArrowVectorConverterUtil {\n  private ArrowVectorConverterUtil() {}\n\n  public static SnowflakeType getSnowflakeTypeFromFieldMetadata(Field field) {\n    Map<String, String> customMeta = field.getMetadata();\n    if (customMeta != null && customMeta.containsKey(\"logicalType\")) {\n      return SnowflakeType.valueOf(customMeta.get(\"logicalType\"));\n    }\n\n    return null;\n  }\n\n  /**\n   * Given an arrow vector (a single column in a single record batch), return an arrow vector\n   * converter. Note, converter is built on top of arrow vector, so that arrow data can be converted\n   * back to java data\n   *\n   * <p>Arrow converter mappings for Snowflake fixed-point numbers\n   * ----------------------------------------------------------------------------------------- Max\n   * position and scale Converter\n   * -----------------------------------------------------------------------------------------\n   * number(3,0) {@link TinyIntToFixedConverter} number(3,2) {@link TinyIntToScaledFixedConverter}\n   * number(5,0) {@link SmallIntToFixedConverter} number(5,4) {@link SmallIntToScaledFixedConverter}\n   * number(10,0) {@link IntToFixedConverter} number(10,9) {@link IntToScaledFixedConverter}\n   * number(19,0) {@link BigIntToFixedConverter} number(19,18) {@link BigIntToFixedConverter}\n   * number(38,37) {@link DecimalToScaledFixedConverter}\n   * ------------------------------------------------------------------------------------------\n   *\n   * @param vector an arrow vector\n   * @param context data conversion context\n   * @param session SFBaseSession for purposes of logging\n   * @param idx the index of the vector in its batch\n   * @return A converter on top og the vector\n   * @throws SnowflakeSQLException if error encountered\n   */\n  public static ArrowVectorConverter initConverter(\n      ValueVector vector, DataConversionContext context, SFBaseSession session, int idx)\n      throws SnowflakeSQLException {\n    // arrow minor type\n    Types.MinorType type = Types.getMinorTypeForArrowType(vector.getField().getType());\n\n    // each column's metadata\n    SnowflakeType st = getSnowflakeTypeFromFieldMetadata(vector.getField());\n    if (type == Types.MinorType.DECIMAL) {\n      // Note: Decimal vector is different from others\n      return new DecimalToScaledFixedConverter(vector, idx, context);\n    } else if (st != null) {\n      switch (st) {\n        case ANY:\n        case CHAR:\n        case TEXT:\n        case VARIANT:\n          return new VarCharConverter(vector, idx, context);\n\n        case MAP:\n          if (vector instanceof MapVector) {\n            return new MapConverter((MapVector) vector, idx, context);\n          } else {\n            return new VarCharConverter(vector, idx, context);\n          }\n\n        case VECTOR:\n          return new VectorTypeConverter((FixedSizeListVector) vector, idx, context);\n\n        case ARRAY:\n          if (vector instanceof ListVector) {\n            return new ArrayConverter((ListVector) vector, idx, context);\n          } else {\n            return new VarCharConverter(vector, idx, context);\n          }\n\n        case OBJECT:\n          if (vector instanceof StructVector) {\n            return new StructConverter((StructVector) vector, idx, context);\n          } else {\n            return new VarCharConverter(vector, idx, context);\n          }\n\n        case BINARY:\n          return new VarBinaryToBinaryConverter(vector, idx, context);\n\n        case BOOLEAN:\n          return new BitToBooleanConverter(vector, idx, context);\n\n        case DATE:\n          boolean getFormatDateWithTimeZone = false;\n          if (context.getSession() != null) {\n            getFormatDateWithTimeZone = context.getSession().getFormatDateWithTimezone();\n          }\n          return new DateConverter(vector, idx, context, getFormatDateWithTimeZone);\n\n        case FIXED:\n          String scaleStr = vector.getField().getMetadata().get(\"scale\");\n          int sfScale = Integer.parseInt(scaleStr);\n          switch (type) {\n            case TINYINT:\n              if (sfScale == 0) {\n                return new TinyIntToFixedConverter(vector, idx, context);\n              } else {\n                return new TinyIntToScaledFixedConverter(vector, idx, context, sfScale);\n              }\n            case SMALLINT:\n              if (sfScale == 0) {\n                return new SmallIntToFixedConverter(vector, idx, context);\n              } else {\n                return new SmallIntToScaledFixedConverter(vector, idx, context, sfScale);\n              }\n            case INT:\n              if (sfScale == 0) {\n                return new IntToFixedConverter(vector, idx, context);\n              } else {\n                return new IntToScaledFixedConverter(vector, idx, context, sfScale);\n              }\n            case BIGINT:\n              if (sfScale == 0) {\n                return new BigIntToFixedConverter(vector, idx, context);\n              } else {\n                return new BigIntToScaledFixedConverter(vector, idx, context, sfScale);\n              }\n          }\n          break;\n\n        case DECFLOAT:\n          return new DecfloatToDecimalConverter(vector, idx, context);\n\n        case INTERVAL_YEAR_MONTH:\n          return new IntervalYearMonthToPeriodConverter(vector, idx, context);\n\n        case INTERVAL_DAY_TIME:\n          return new IntervalDayTimeToDurationConverter(vector, idx, context);\n\n        case REAL:\n          return new DoubleToRealConverter(vector, idx, context);\n\n        case TIME:\n          switch (type) {\n            case INT:\n              return new IntToTimeConverter(vector, idx, context);\n            case BIGINT:\n              return new BigIntToTimeConverter(vector, idx, context);\n            default:\n              throw new SnowflakeSQLLoggedException(\n                  session,\n                  ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                  SqlState.INTERNAL_ERROR,\n                  \"Unexpected Arrow Field for \",\n                  st.name());\n          }\n\n        case TIMESTAMP_LTZ:\n          if (vector.getField().getChildren().isEmpty()) {\n            // case when the scale of the timestamp is equal or smaller than millisecs since epoch\n            return new BigIntToTimestampLTZConverter(vector, idx, context);\n          } else if (vector.getField().getChildren().size() == 2) {\n            // case when the scale of the timestamp is larger than millisecs since epoch, e.g.,\n            // nanosecs\n            return new TwoFieldStructToTimestampLTZConverter(vector, idx, context);\n          } else {\n            throw new SnowflakeSQLLoggedException(\n                session,\n                ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"Unexpected Arrow Field for \",\n                st.name());\n          }\n\n        case TIMESTAMP_NTZ:\n          if (vector.getField().getChildren().isEmpty()) {\n            // case when the scale of the timestamp is equal or smaller than 7\n            return new BigIntToTimestampNTZConverter(vector, idx, context);\n          } else if (vector.getField().getChildren().size() == 2) {\n            // when the timestamp is represent in two-field struct\n            return new TwoFieldStructToTimestampNTZConverter(vector, idx, context);\n          } else {\n            throw new SnowflakeSQLLoggedException(\n                session,\n                ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"Unexpected Arrow Field for \",\n                st.name());\n          }\n\n        case TIMESTAMP_TZ:\n          if (vector.getField().getChildren().size() == 2) {\n            // case when the scale of the timestamp is equal or smaller than millisecs since epoch\n            return new TwoFieldStructToTimestampTZConverter(vector, idx, context);\n          } else if (vector.getField().getChildren().size() == 3) {\n            // case when the scale of the timestamp is larger than millisecs since epoch, e.g.,\n            // nanosecs\n            return new ThreeFieldStructToTimestampTZConverter(vector, idx, context);\n          } else {\n            throw new SnowflakeSQLLoggedException(\n                session,\n                ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"Unexpected SnowflakeType \",\n                st.name());\n          }\n\n        default:\n          throw new SnowflakeSQLLoggedException(\n              session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"Unexpected Arrow Field for \",\n              st.name());\n      }\n    }\n    throw new SnowflakeSQLLoggedException(\n        session,\n        ErrorCode.INTERNAL_ERROR.getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected Arrow Field for \",\n        type.toString());\n  }\n\n  public static ArrowVectorConverter initConverter(\n      FieldVector vector, DataConversionContext context, int columnIndex)\n      throws SnowflakeSQLException {\n    return initConverter(vector, context, context.getSession(), columnIndex);\n  }\n\n  public static Duration getDurationFromNanos(BigDecimal numNanos) {\n    final BigDecimal nanoInSecond = BigDecimal.valueOf(1_000_000_000);\n    int sign = numNanos.signum();\n    numNanos = numNanos.abs();\n    // Duration.ofSeconds() with passed in negative second value results in overflow\n    // so instead we identify the sign of numNanos and use Duration.negated() accordingly\n    Duration duration =\n        Duration.ofSeconds(\n            numNanos.divide(nanoInSecond, RoundingMode.FLOOR).longValueExact(),\n            numNanos.remainder(nanoInSecond).longValueExact());\n    if (sign >= 0) {\n      return duration;\n    } else {\n      return duration.negated();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/BigIntToFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Data vector whose snowflake logical type is fixed while represented as a long value vector */\npublic class BigIntToFixedConverter extends AbstractArrowVectorConverter {\n  /** Underlying vector that this converter will convert from */\n  protected BigIntVector bigIntVector;\n\n  /** scale of the fixed value */\n  protected int sfScale;\n\n  protected ByteBuffer byteBuf = ByteBuffer.allocate(BigIntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public BigIntToFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\")),\n        fieldVector,\n        columnIndex,\n        context);\n    this.bigIntVector = (BigIntVector) fieldVector;\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putLong(0, getLong(index));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    long longVal = toLong(index);\n    if (longVal == 0) {\n      return false;\n    } else if (longVal == 1) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n          SnowflakeUtil.BOOLEAN_STR, longVal);\n    }\n  }\n\n  @Override\n  public byte toByte(int index) throws SFException {\n    long longVal = toLong(index);\n    byte byteVal = (byte) longVal;\n\n    if (byteVal == longVal) {\n      return byteVal;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n          SnowflakeUtil.BYTE_STR, longVal);\n    }\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    long longVal = toLong(index);\n    short shortVal = (short) longVal;\n\n    if (shortVal == longVal) {\n      return shortVal;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, longVal);\n    }\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    long longVal = toLong(index);\n    int intVal = (int) longVal;\n\n    if (intVal == longVal) {\n      return intVal;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.INT_STR, longVal);\n    }\n  }\n\n  protected long getLong(int index) {\n    return bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    if (bigIntVector.isNull(index)) {\n      return 0;\n    } else {\n      return getLong(index);\n    }\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    return toLong(index);\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    return toLong(index);\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) {\n    if (bigIntVector.isNull(index)) {\n      return null;\n    } else {\n      return BigDecimal.valueOf(getLong(index), sfScale);\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    if (bigIntVector.isNull(index)) {\n      return null;\n    } else if (!shouldTreatDecimalAsInt()) {\n      return BigDecimal.valueOf(getLong(index), sfScale);\n    }\n    return getLong(index);\n  }\n\n  @Override\n  public String toString(int index) {\n    return isNull(index) ? null : Long.toString(getLong(index));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/BigIntToScaledFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.ValueVector;\n\n/**\n * Data vector whose snowflake logical type is fixed while represented as a long value vector with\n * scale\n */\npublic class BigIntToScaledFixedConverter extends BigIntToFixedConverter {\n  public BigIntToScaledFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context, int scale) {\n    super(fieldVector, columnIndex, context);\n    logicalTypeStr =\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\"));\n    sfScale = scale;\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    return (float) toDouble(index);\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    if (sfScale > 9) {\n      return toBigDecimal(index).doubleValue();\n    }\n    int scale = sfScale;\n    double res = getLong(index);\n    res = res / ArrowResultUtil.powerOfTen(scale);\n    return res;\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal val = toBigDecimal(index);\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", val.toPlainString());\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal val = toBigDecimal(index);\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Int\", val.toPlainString());\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal val = toBigDecimal(index);\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", val.toPlainString());\n  }\n\n  @Override\n  public Object toObject(int index) {\n    return toBigDecimal(index);\n  }\n\n  @Override\n  public String toString(int index) {\n    return isNull(index) ? null : BigDecimal.valueOf(getLong(index), sfScale).toPlainString();\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    BigDecimal val = toBigDecimal(index);\n    if (val.compareTo(BigDecimal.ZERO) == 0) {\n      return false;\n    } else if (val.compareTo(BigDecimal.ONE) == 0) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val.toPlainString());\n    }\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putLong(0, getLong(index));\n      return byteBuf.array();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/BigIntToTimeConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.nio.ByteBuffer;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimestampWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.common.core.SFTime;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** BigInt to Time type converter. */\npublic class BigIntToTimeConverter extends AbstractArrowVectorConverter {\n  private BigIntVector bigIntVector;\n  protected ByteBuffer byteBuf = ByteBuffer.allocate(BigIntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public BigIntToTimeConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIME.name(), fieldVector, columnIndex, context);\n    this.bigIntVector = (BigIntVector) fieldVector;\n  }\n\n  /**\n   * parse long into SFTime\n   *\n   * @param index\n   * @return\n   */\n  private SFTime toSFTime(int index) {\n    long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    return SFTime.fromFractionalSeconds(val, context.getScale(columnIndex));\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else {\n      long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n      return getTime(val, context.getScale(columnIndex), useSessionTimezone);\n    }\n  }\n\n  /**\n   * Return the long value as a Time object.\n   *\n   * @param value long value to represent as Time\n   * @param scale the scale\n   * @param useSessionTimezone boolean indicating use of session timezone\n   * @return Time object representing the value\n   * @throws SFException invalid data conversion\n   */\n  public static Time getTime(long value, int scale, boolean useSessionTimezone) throws SFException {\n    SFTime sfTime = SFTime.fromFractionalSeconds(value, scale);\n    Time ts =\n        new Time(sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS));\n    if (useSessionTimezone) {\n      ts =\n          SnowflakeUtil.getTimeInSessionTimezone(\n              SnowflakeUtil.getSecondsFromMillis(ts.getTime()),\n              sfTime.getNanosecondsWithinSecond());\n    }\n    return ts;\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimeFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing time formatter\");\n    }\n    return isNull(index)\n        ? null\n        : ResultUtil.getSFTimeAsString(\n            toSFTime(index), context.getScale(columnIndex), context.getTimeFormatter());\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return isNull(index) ? null : toTime(index);\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    if (useSessionTimezone) {\n      SFTime sfTime = toSFTime(index);\n      return new SnowflakeTimestampWithTimezone(\n          sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS),\n          sfTime.getNanosecondsWithinSecond(),\n          TimeZone.getTimeZone(\"UTC\"));\n    }\n    return new Timestamp(toTime(index).getTime());\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Time val = toTime(index);\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/BigIntToTimestampLTZConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.nio.ByteBuffer;\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** converter from BigInt (Long) to Timestamp_LTZ */\npublic class BigIntToTimestampLTZConverter extends AbstractArrowVectorConverter {\n  private BigIntVector bigIntVector;\n  private ByteBuffer byteBuf = ByteBuffer.allocate(BigIntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public BigIntToTimestampLTZConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIMESTAMP_LTZ.name(), fieldVector, columnIndex, context);\n    this.bigIntVector = (BigIntVector) fieldVector;\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimestampLTZFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp LTZ formatter\");\n    }\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n\n    return ts == null\n        ? null\n        : context\n            .getTimestampLTZFormatter()\n            .format(ts, context.getTimeZone(), context.getScale(columnIndex));\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putLong(0, bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toTimestamp(index, TimeZone.getDefault());\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    return isNull(index) ? null : getTimestamp(index, tz);\n  }\n\n  private Timestamp getTimestamp(int index, TimeZone tz) throws SFException {\n    long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    int scale = context.getScale(columnIndex);\n    return getTimestamp(val, scale, sessionTimeZone, useSessionTimezone);\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone tz, boolean useDateFormat) throws SFException {\n    return isNull(index) ? null : new Date(getTimestamp(index, TimeZone.getDefault()).getTime());\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n    return ts == null ? null : new Time(ts.getTime());\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Timestamp val = toTimestamp(index, TimeZone.getDefault());\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  /**\n   * Use {@link #getTimestamp(long, int, TimeZone, boolean)}\n   *\n   * @param val epoch\n   * @param scale scale\n   * @return Timestamp value without timezone take into account\n   * @throws SFException if exception encountered\n   */\n  @Deprecated\n  public static Timestamp getTimestamp(long val, int scale) throws SFException {\n    Timestamp ts = ArrowResultUtil.toJavaTimestamp(val, scale);\n    return ResultUtil.adjustTimestamp(ts);\n  }\n\n  public static Timestamp getTimestamp(\n      long epoch, int scale, TimeZone sessionTimeZone, boolean useSessionTimezone)\n      throws SFException {\n    return ResultUtil.adjustTimestamp(\n        ArrowResultUtil.toJavaTimestamp(epoch, scale, sessionTimeZone, useSessionTimezone));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/BigIntToTimestampNTZConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.nio.ByteBuffer;\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimeWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** converter from BigInt (Long) to Timestamp_NTZ */\npublic class BigIntToTimestampNTZConverter extends AbstractArrowVectorConverter {\n  private BigIntVector bigIntVector;\n  private static final TimeZone NTZ = TimeZone.getTimeZone(\"UTC\");\n  private ByteBuffer byteBuf = ByteBuffer.allocate(BigIntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public BigIntToTimestampNTZConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIMESTAMP_NTZ.name(), fieldVector, columnIndex, context);\n    this.bigIntVector = (BigIntVector) fieldVector;\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimestampNTZFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp NTZ formatter\");\n    }\n    Timestamp ts = isNull(index) ? null : getTimestamp(index, TimeZone.getDefault(), true);\n\n    return ts == null\n        ? null\n        : context\n            .getTimestampNTZFormatter()\n            .format(ts, TimeZone.getTimeZone(\"UTC\"), context.getScale(columnIndex));\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putLong(0, bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toTimestamp(index, TimeZone.getDefault());\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    return isNull(index) ? null : getTimestamp(index, tz, false);\n  }\n\n  private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) throws SFException {\n    if (tz == null) {\n      tz = TimeZone.getDefault();\n    }\n    long val = bigIntVector.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    int scale = context.getScale(columnIndex);\n    return getTimestamp(val, tz, scale, context.getHonorClientTZForTimestampNTZ(), fromToString);\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone tz, boolean dateFormat) throws SFException {\n    return isNull(index)\n        ? null\n        : new Date(getTimestamp(index, TimeZone.getDefault(), false).getTime());\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n    return ts == null\n        ? null\n        : new SnowflakeTimeWithTimezone(ts.getTime(), ts.getNanos(), useSessionTimezone);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Timestamp val = toTimestamp(index, TimeZone.getDefault());\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  public static Timestamp getTimestamp(\n      long val, TimeZone tz, int scale, boolean honorClientTZForTimestampNTZ, boolean fromToString)\n      throws SFException {\n    if (tz == null) {\n      tz = TimeZone.getDefault();\n    }\n    Timestamp ts = ArrowResultUtil.toJavaTimestamp(val, scale);\n\n    // Note: honorClientTZForTimestampNTZ is not enabled for toString method\n    if (!fromToString && honorClientTZForTimestampNTZ) {\n      ts = ArrowResultUtil.moveToTimeZone(ts, NTZ, tz);\n    }\n\n    return ResultUtil.adjustTimestamp(ts);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/BitToBooleanConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.BitVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Convert Arrow BitVector to Boolean */\npublic class BitToBooleanConverter extends AbstractArrowVectorConverter {\n  private BitVector bitVector;\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public BitToBooleanConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.BOOLEAN.name(), fieldVector, columnIndex, context);\n    this.bitVector = (BitVector) fieldVector;\n  }\n\n  private int getBit(int index) {\n    // read a bit from the bitVector\n    // first find the byte value\n    final int byteIndex = index >> 3;\n    final byte b = bitVector.getDataBuffer().getByte(byteIndex);\n    // then get the bit value\n    final int bitIndex = index & 7;\n    return (b >> bitIndex) & 0x01;\n  }\n\n  @Override\n  public boolean toBoolean(int index) {\n    if (isNull(index)) {\n      return false;\n    } else {\n      return getBit(index) != 0;\n    }\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else if (toBoolean(index)) {\n\n      return new byte[] {1};\n    } else {\n      return new byte[] {0};\n    }\n  }\n\n  @Override\n  public Object toObject(int index) {\n    return isNull(index) ? null : toBoolean(index);\n  }\n\n  @Override\n  public String toString(int index) {\n    return isNull(index) ? null : toBoolean(index) ? \"TRUE\" : \"FALSE\";\n  }\n\n  @Override\n  public short toShort(int rowIndex) throws SFException {\n    return (short) (toBoolean(rowIndex) ? 1 : 0);\n  }\n\n  @Override\n  public int toInt(int rowIndex) throws SFException {\n    return toBoolean(rowIndex) ? 1 : 0;\n  }\n\n  @Override\n  public long toLong(int rowIndex) throws SFException {\n    return toBoolean(rowIndex) ? 1 : 0;\n  }\n\n  @Override\n  public float toFloat(int rowIndex) throws SFException {\n    return toBoolean(rowIndex) ? 1 : 0;\n  }\n\n  @Override\n  public double toDouble(int rowIndex) throws SFException {\n    return toBoolean(rowIndex) ? 1 : 0;\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int rowIndex) throws SFException {\n    return isNull(rowIndex) ? null : toBoolean(rowIndex) ? BigDecimal.ONE : BigDecimal.ZERO;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/DateConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.DateDayVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Convert Arrow DateDayVector to Date */\npublic class DateConverter extends AbstractArrowVectorConverter {\n  private DateDayVector dateVector;\n  private static TimeZone timeZoneUTC = TimeZone.getTimeZone(\"UTC\");\n\n  private boolean useDateFormat;\n\n  @Deprecated\n  public DateConverter(ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.DATE.name(), fieldVector, columnIndex, context);\n    this.dateVector = (DateDayVector) fieldVector;\n    this.useDateFormat = false;\n  }\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   * @param useDateFormat boolean indicates whether to use session timezone\n   */\n  public DateConverter(\n      ValueVector fieldVector,\n      int columnIndex,\n      DataConversionContext context,\n      boolean useDateFormat) {\n    super(SnowflakeType.DATE.name(), fieldVector, columnIndex, context);\n    this.dateVector = (DateDayVector) fieldVector;\n    this.useDateFormat = useDateFormat;\n  }\n\n  private Date getDate(int index, TimeZone jvmTz, boolean useDateFormat) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else {\n      int val = dateVector.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n      return getDate(val, jvmTz, sessionTimeZone, useDateFormat);\n    }\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone jvmTz, boolean useDateFormat) throws SFException {\n    return getDate(index, jvmTz, useDateFormat);\n  }\n\n  @Override\n  public int toInt(int index) {\n    if (isNull(index)) {\n      return 0;\n    } else {\n      int val = dateVector.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n      return val;\n    }\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    try {\n      return (short) toInt(index);\n    } catch (ClassCastException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, toInt(index));\n    }\n  }\n\n  @Override\n  public long toLong(int index) {\n    return toInt(index);\n  }\n\n  @Override\n  public float toFloat(int index) {\n    return toInt(index);\n  }\n\n  @Override\n  public double toDouble(int index) {\n    return toInt(index);\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) {\n    if (isNull(index)) {\n      return null;\n    }\n    return BigDecimal.valueOf(toInt(index));\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    boolean useDateFormat = true;\n    if (this.context.getSession() != null) {\n      useDateFormat = getUseDateFormat(true);\n    }\n    Date date = toDate(index, tz, useDateFormat);\n    if (date == null) {\n      return null;\n    } else {\n      return new Timestamp(date.getTime());\n    }\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getDateFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing date formatter\");\n    }\n    Date date = getDate(index, timeZoneUTC, getUseDateFormat(false));\n    return date == null ? null : ResultUtil.getDateAsString(date, context.getDateFormatter());\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toDate(index, TimeZone.getDefault(), getUseDateFormat(false));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Date val = toDate(index, TimeZone.getDefault(), getUseDateFormat(false));\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  public static Date getDate(\n      int value, TimeZone jvmTz, TimeZone sessionTimeZone, boolean useDateFormat)\n      throws SFException {\n    if (jvmTz == null || sessionTimeZone == null || !useDateFormat) {\n      return ArrowResultUtil.getDate(value);\n    }\n    // Note: use default time zone to match with current getDate() behavior\n    return ArrowResultUtil.getDate(value, jvmTz, sessionTimeZone);\n  }\n\n  private Boolean getUseDateFormat(Boolean defaultValue) {\n    return this.context.getSession() == null\n        ? defaultValue\n        : (this.context.getSession().getDefaultFormatDateWithTimezone()\n            ? defaultValue\n            : this.useDateFormat);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/DecfloatToDecimalConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.complex.StructVector;\n\nclass DecfloatToDecimalConverter extends AbstractArrowVectorConverter {\n\n  private StructVector vector;\n\n  public DecfloatToDecimalConverter(ValueVector vector, int idx, DataConversionContext context) {\n    super(SnowflakeType.DECFLOAT.name(), vector, idx, context);\n    this.vector = (StructVector) vector;\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) {\n    if (isNull(index)) {\n      return null;\n    }\n    Map<String, Object> value = (Map<String, Object>) vector.getObject(index);\n    byte[] significandBytes = (byte[]) value.get(\"significand\");\n    short exponent = (short) value.get(\"exponent\");\n    BigInteger significand = new BigInteger(significandBytes);\n    return new BigDecimal(significand, -exponent);\n  }\n\n  @Override\n  public double toDouble(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    return toBigDecimal(rowIndex).doubleValue();\n  }\n\n  @Override\n  public float toFloat(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    return toBigDecimal(rowIndex).floatValue();\n  }\n\n  @Override\n  public short toShort(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(rowIndex);\n    if (bigDecimal.scale() == 0) {\n      short shortVal = bigDecimal.shortValue();\n      if (shortVal == bigDecimal.longValue()) {\n        return shortVal;\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public int toInt(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(rowIndex);\n    if (bigDecimal.scale() == 0) {\n      int intVal = bigDecimal.intValue();\n      if (intVal == bigDecimal.longValue()) {\n        return intVal;\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Integer\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Integer\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public long toLong(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(rowIndex);\n    if (bigDecimal.scale() == 0) {\n      BigInteger intVal = bigDecimal.toBigIntegerExact();\n      if (intVal.bitLength() <= 63) {\n        return intVal.longValue();\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toBigDecimal(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    return toBigDecimal(index).toEngineeringString();\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BYTES_STR, null);\n  }\n\n  @Override\n  public boolean toBoolean(int rowIndex) throws SFException {\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BOOLEAN_STR, null);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/DecimalToScaledFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.time.Duration;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.DecimalVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/**\n * Data vector whose snowflake logical type is fixed while represented as a BigDecimal value vector\n */\npublic class DecimalToScaledFixedConverter extends AbstractArrowVectorConverter {\n  protected DecimalVector decimalVector;\n\n  /**\n   * @param fieldVector ValueVector\n   * @param vectorIndex vector index\n   * @param context DataConversionContext\n   */\n  public DecimalToScaledFixedConverter(\n      ValueVector fieldVector, int vectorIndex, DataConversionContext context) {\n    super(\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\")),\n        fieldVector,\n        vectorIndex,\n        context);\n    decimalVector = (DecimalVector) fieldVector;\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else {\n      return toBigDecimal(index).toBigInteger().toByteArray();\n    }\n  }\n\n  @Override\n  public byte toByte(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n\n    BigDecimal bigDecimal = toBigDecimal(index);\n    if (bigDecimal.scale() == 0) {\n      byte byteVal = bigDecimal.byteValue();\n\n      if (byteVal == bigDecimal.longValue()) {\n        return byteVal;\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Byte\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Byte\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(index);\n    if (bigDecimal.scale() == 0) {\n      short shortValue = bigDecimal.shortValue();\n\n      if (bigDecimal.compareTo(BigDecimal.valueOf((long) shortValue)) == 0) {\n        return shortValue;\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(index);\n    if (bigDecimal.scale() == 0) {\n      int intValue = bigDecimal.intValue();\n\n      if (bigDecimal.compareTo(BigDecimal.valueOf((long) intValue)) == 0) {\n        return intValue;\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Int\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Int\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(index);\n    if (bigDecimal.scale() == 0) {\n      long longValue = bigDecimal.longValue();\n\n      if (bigDecimal.compareTo(BigDecimal.valueOf(longValue)) == 0) {\n        return longValue;\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", bigDecimal.toPlainString());\n      }\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", bigDecimal.toPlainString());\n    }\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(index);\n    return bigDecimal.floatValue();\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    BigDecimal bigDecimal = toBigDecimal(index);\n    return bigDecimal.doubleValue();\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) {\n    return decimalVector.getObject(index);\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toBigDecimal(index);\n  }\n\n  @Override\n  public String toString(int index) {\n    BigDecimal bigDecimal = toBigDecimal(index);\n    return bigDecimal == null ? null : bigDecimal.toPlainString();\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    BigDecimal val = toBigDecimal(index);\n    if (val.compareTo(BigDecimal.ZERO) == 0) {\n      return false;\n    } else if (val.compareTo(BigDecimal.ONE) == 0) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val.toPlainString());\n    }\n  }\n\n  @Override\n  public Duration toDuration(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    BigDecimal numNanos = toBigDecimal(index);\n    try {\n      return ArrowVectorConverterUtil.getDurationFromNanos(numNanos);\n    } catch (ArithmeticException e) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Duration\", numNanos.toPlainString());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/DoubleToRealConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.Float8Vector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Convert from Arrow Float8Vector to Real. */\npublic class DoubleToRealConverter extends AbstractArrowVectorConverter {\n  private Float8Vector float8Vector;\n  private ByteBuffer byteBuf = ByteBuffer.allocate(Float8Vector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public DoubleToRealConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.REAL.name(), fieldVector, columnIndex, context);\n    this.float8Vector = (Float8Vector) fieldVector;\n  }\n\n  @Override\n  public double toDouble(int index) {\n    if (float8Vector.isNull(index)) {\n      return 0;\n    } else {\n      return float8Vector.getDataBuffer().getDouble(index * Float8Vector.TYPE_WIDTH);\n    }\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putDouble(0, toDouble(index));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public float toFloat(int index) {\n    return (float) toDouble(index);\n  }\n\n  @Override\n  public Object toObject(int index) {\n    return isNull(index) ? null : toDouble(index);\n  }\n\n  @Override\n  public String toString(int index) {\n    return isNull(index) ? null : String.valueOf(toDouble(index));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    double val = toDouble(index);\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  @Override\n  public short toShort(int rowIndex) throws SFException {\n    try {\n      if (isNull(rowIndex)) {\n        return 0;\n      } else {\n        return (short) toDouble(rowIndex);\n      }\n    } catch (ClassCastException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT,\n          logicalTypeStr,\n          SnowflakeUtil.SHORT_STR,\n          toObject(rowIndex));\n    }\n  }\n\n  @Override\n  public int toInt(int rowIndex) throws SFException {\n    try {\n      if (isNull(rowIndex)) {\n        return 0;\n      } else {\n        return (int) toDouble(rowIndex);\n      }\n    } catch (ClassCastException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT,\n          logicalTypeStr,\n          SnowflakeUtil.INT_STR,\n          toObject(rowIndex));\n    }\n  }\n\n  @Override\n  public long toLong(int rowIndex) throws SFException {\n    try {\n      if (isNull(rowIndex)) {\n        return 0;\n      } else {\n        return (long) toDouble(rowIndex);\n      }\n    } catch (ClassCastException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT,\n          logicalTypeStr,\n          SnowflakeUtil.LONG_STR,\n          toObject(rowIndex));\n    }\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return null;\n    }\n    return BigDecimal.valueOf(toDouble(rowIndex));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/IntToFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Data vector whose snowflake logical type is fixed while represented as a int value vector */\npublic class IntToFixedConverter extends AbstractArrowVectorConverter {\n  protected IntVector intVector;\n  protected int sfScale;\n  protected ByteBuffer byteBuf = ByteBuffer.allocate(IntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public IntToFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\")),\n        fieldVector,\n        columnIndex,\n        context);\n    this.intVector = (IntVector) fieldVector;\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putInt(0, getInt(index));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public byte toByte(int index) throws SFException {\n    int intVal = toInt(index);\n    byte byteVal = (byte) intVal;\n\n    if (byteVal == intVal) {\n      return byteVal;\n    } else {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byte\", intVal);\n    }\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    int intVal = toInt(index);\n    short shortVal = (short) intVal;\n\n    if (shortVal == intVal) {\n      return shortVal;\n    } else {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"short\", intVal);\n    }\n  }\n\n  protected int getInt(int index) throws SFException {\n    return intVector.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    if (intVector.isNull(index)) {\n      return 0;\n    } else {\n      return getInt(index);\n    }\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    return (long) toInt(index);\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    return toInt(index);\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    return toInt(index);\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) throws SFException {\n    if (intVector.isNull(index)) {\n      return null;\n    } else {\n      return BigDecimal.valueOf((long) getInt(index), sfScale);\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else if (!shouldTreatDecimalAsInt()) {\n      return BigDecimal.valueOf((long) getInt(index), sfScale);\n    }\n    return (long) getInt(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    return isNull(index) ? null : Integer.toString(getInt(index));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    int val = toInt(index);\n    if (val == 0) {\n      return false;\n    } else if (val == 1) {\n      return true;\n    } else {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/IntToScaledFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.ValueVector;\n\n/**\n * Data vector whose snowflake logical type is fixed while represented as a int value vector with\n * scale\n */\npublic class IntToScaledFixedConverter extends IntToFixedConverter {\n  private String format;\n\n  public IntToScaledFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context, int sfScale) {\n    super(fieldVector, columnIndex, context);\n    logicalTypeStr =\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\"));\n    format = ArrowResultUtil.getStringFormat(sfScale);\n    this.sfScale = sfScale;\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    return ((float) getInt(index)) / ArrowResultUtil.powerOfTen(sfScale);\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    return ((double) getInt(index)) / ArrowResultUtil.powerOfTen(sfScale);\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"short\", val);\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"int\", val);\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"long\", val);\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toBigDecimal(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    return isNull(index)\n        ? null\n        : String.format(format, (double) getInt(index) / ArrowResultUtil.powerOfTen(sfScale));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    BigDecimal val = toBigDecimal(index);\n    if (val.compareTo(BigDecimal.ZERO) == 0) {\n      return false;\n    } else if (val.compareTo(BigDecimal.ONE) == 0) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val.toPlainString());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/IntToTimeConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.nio.ByteBuffer;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimestampWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.common.core.SFTime;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Convert from Arrow IntVector to Time. */\npublic class IntToTimeConverter extends AbstractArrowVectorConverter {\n  private IntVector intVector;\n  private ByteBuffer byteBuf = ByteBuffer.allocate(IntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public IntToTimeConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIME.name(), fieldVector, columnIndex, context);\n    this.intVector = (IntVector) fieldVector;\n  }\n\n  /**\n   * parse long into SFTime\n   *\n   * @param index\n   * @return\n   */\n  private SFTime toSFTime(int index) {\n    long val = intVector.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n    return SFTime.fromFractionalSeconds(val, context.getScale(columnIndex));\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putInt(0, intVector.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else {\n      SFTime sfTime = toSFTime(index);\n      if (sfTime == null) {\n        return null;\n      }\n      Time ts =\n          new Time(\n              sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS));\n      if (useSessionTimezone) {\n        ts =\n            SnowflakeUtil.getTimeInSessionTimezone(\n                SnowflakeUtil.getSecondsFromMillis(ts.getTime()),\n                sfTime.getNanosecondsWithinSecond());\n      }\n      return ts;\n    }\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimeFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing time formatter\");\n    }\n    return isNull(index)\n        ? null\n        : ResultUtil.getSFTimeAsString(\n            toSFTime(index), context.getScale(columnIndex), context.getTimeFormatter());\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return isNull(index) ? null : toTime(index);\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    if (useSessionTimezone) {\n      SFTime sfTime = toSFTime(index);\n      return new SnowflakeTimestampWithTimezone(\n          sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS),\n          sfTime.getNanosecondsWithinSecond(),\n          TimeZone.getTimeZone(\"UTC\"));\n    }\n    return new Timestamp(toTime(index).getTime());\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Time val = toTime(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/IntervalDayTimeToDurationConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.time.Duration;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\nclass IntervalDayTimeToDurationConverter extends AbstractArrowVectorConverter {\n\n  private BigIntVector vector;\n  private static final long nanoInSecond = 1_000_000_000;\n\n  public IntervalDayTimeToDurationConverter(\n      ValueVector vector, int idx, DataConversionContext context) {\n    super(SnowflakeType.INTERVAL_DAY_TIME.name(), vector, idx, context);\n    this.vector = (BigIntVector) vector;\n  }\n\n  @Override\n  public Duration toDuration(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    long numNanos = vector.getObject(index);\n    try {\n      int sign = Long.signum(numNanos);\n      numNanos = Math.abs(numNanos);\n      // Duration.ofSeconds() with passed in negative second value results in overflow\n      // so instead we identify the sign of numNanos and use Duration.negated() accordingly\n      Duration duration = Duration.ofSeconds(numNanos / nanoInSecond, numNanos % nanoInSecond);\n      if (sign >= 0) {\n        return duration;\n      } else {\n        return duration.negated();\n      }\n    } catch (ArithmeticException e) {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Duration\", numNanos);\n    }\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    return toDuration(index).toString();\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toDuration(index);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/IntervalYearMonthToPeriodConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.time.Period;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.SmallIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\nclass IntervalYearMonthToPeriodConverter extends AbstractArrowVectorConverter {\n\n  private SmallIntVector smallIntVector;\n  private IntVector intVector;\n  private BigIntVector bigIntVector;\n  private static final int monthsInYear = 12;\n\n  public IntervalYearMonthToPeriodConverter(\n      ValueVector vector, int idx, DataConversionContext context) {\n    super(SnowflakeType.INTERVAL_YEAR_MONTH.name(), vector, idx, context);\n    if (vector instanceof SmallIntVector) {\n      // Underlying Interval Year-Month type is SB2\n      this.smallIntVector = (SmallIntVector) vector;\n    } else if (vector instanceof IntVector) {\n      // Underlying Interval Year-Month type is SB4\n      this.intVector = (IntVector) vector;\n    } else if (vector instanceof BigIntVector) {\n      // Underlying Interval Year-Month type is SB8\n      this.bigIntVector = (BigIntVector) vector;\n    }\n  }\n\n  @Override\n  public Period toPeriod(int index) {\n    if (isNull(index)) {\n      return null;\n    }\n    if (smallIntVector != null) {\n      short value = smallIntVector.get(index);\n      return Period.of(value / monthsInYear, value % monthsInYear, 0);\n    } else if (intVector != null) {\n      int value = intVector.get(index);\n      return Period.of(value / monthsInYear, value % monthsInYear, 0);\n    } else {\n      long value = bigIntVector.get(index);\n      return Period.of((int) (value / monthsInYear), (int) (value % monthsInYear), 0);\n    }\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    return toPeriod(index).toString();\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toPeriod(index);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/MapConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.tostringhelpers.ArrowObjectStringRepresentationBuilder;\nimport org.apache.arrow.vector.FieldVector;\nimport org.apache.arrow.vector.complex.MapVector;\nimport org.apache.arrow.vector.util.JsonStringHashMap;\n\n/** Arrow MapVector converter. */\npublic class MapConverter extends AbstractArrowVectorConverter {\n\n  private final MapVector vector;\n\n  /**\n   * @param valueVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public MapConverter(MapVector valueVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.MAP.name(), valueVector, columnIndex, context);\n    this.vector = valueVector;\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    List<JsonStringHashMap<String, Object>> entriesList =\n        (List<JsonStringHashMap<String, Object>>) vector.getObject(index);\n    return entriesList.stream()\n        .collect(\n            Collectors.toMap(entry -> entry.get(\"key\").toString(), entry -> entry.get(\"value\")));\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    return isNull(index) ? null : toString(index).getBytes(StandardCharsets.UTF_8);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    ArrowObjectStringRepresentationBuilder builder = new ArrowObjectStringRepresentationBuilder();\n\n    FieldVector vectorUnpacked = vector.getChildrenFromFields().get(0);\n\n    FieldVector keys = vectorUnpacked.getChildrenFromFields().get(0);\n    FieldVector values = vectorUnpacked.getChildrenFromFields().get(1);\n    final ArrowVectorConverter keyConverter;\n    final ArrowVectorConverter valueConverter;\n\n    SnowflakeType valueLogicalType =\n        ArrowVectorConverterUtil.getSnowflakeTypeFromFieldMetadata(values.getField());\n\n    try {\n      keyConverter = ArrowVectorConverterUtil.initConverter(keys, context, columnIndex);\n      valueConverter = ArrowVectorConverterUtil.initConverter(values, context, columnIndex);\n    } catch (SnowflakeSQLException e) {\n      return vector.getObject(index).toString();\n    }\n\n    for (int i = vector.getElementStartIndex(index); i < vector.getElementEndIndex(index); i++) {\n      builder.appendKeyValue(\n          keyConverter.toString(i), valueConverter.toString(i), valueLogicalType);\n    }\n\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/SmallIntToFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.SmallIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** Data vector whose snowflake logical type is fixed while represented as a short value vector */\npublic class SmallIntToFixedConverter extends AbstractArrowVectorConverter {\n  protected int sfScale;\n  protected SmallIntVector smallIntVector;\n  ByteBuffer byteBuf = ByteBuffer.allocate(SmallIntVector.TYPE_WIDTH);\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public SmallIntToFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\")),\n        fieldVector,\n        columnIndex,\n        context);\n    this.smallIntVector = (SmallIntVector) fieldVector;\n  }\n\n  protected short getShort(int index) throws SFException {\n    return smallIntVector.getDataBuffer().getShort(index * SmallIntVector.TYPE_WIDTH);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else {\n      byteBuf.putShort(0, getShort(index));\n      return byteBuf.array();\n    }\n  }\n\n  @Override\n  public byte toByte(int index) throws SFException {\n    short shortVal = toShort(index);\n    byte byteVal = (byte) shortVal;\n\n    if (byteVal == shortVal) {\n      return byteVal;\n    }\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byte\", shortVal);\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    if (smallIntVector.isNull(index)) {\n      return 0;\n    } else {\n      return getShort(index);\n    }\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    return (int) toShort(index);\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    return (long) toShort(index);\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) throws SFException {\n    if (smallIntVector.isNull(index)) {\n      return null;\n    } else {\n      return BigDecimal.valueOf((long) getShort(index), sfScale);\n    }\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    return toShort(index);\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    return toFloat(index);\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else if (!shouldTreatDecimalAsInt()) {\n      return BigDecimal.valueOf((long) getShort(index), sfScale);\n    }\n    return (long) getShort(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    return isNull(index) ? null : Short.toString(getShort(index));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    short val = toShort(index);\n    if (val == 0) {\n      return false;\n    } else if (val == 1) {\n      return true;\n    } else {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/SmallIntToScaledFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.ValueVector;\n\n/**\n * Data vector whose snowflake logical type is fixed while represented as a scaled short value\n * vector\n */\npublic class SmallIntToScaledFixedConverter extends SmallIntToFixedConverter {\n  private String format;\n\n  public SmallIntToScaledFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context, int sfScale) {\n    super(fieldVector, columnIndex, context);\n    logicalTypeStr =\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\"));\n    format = ArrowResultUtil.getStringFormat(sfScale);\n    this.sfScale = sfScale;\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    return ((float) getShort(index)) / ArrowResultUtil.powerOfTen(sfScale);\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", val);\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Int\", val);\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", val);\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toBigDecimal(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    return isNull(index)\n        ? null\n        : String.format(format, (float) getShort(index) / ArrowResultUtil.powerOfTen(sfScale));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    BigDecimal val = toBigDecimal(index);\n    if (val.compareTo(BigDecimal.ZERO) == 0) {\n      return false;\n    } else if (val.compareTo(BigDecimal.ONE) == 0) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val.toPlainString());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/StructConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.tostringhelpers.ArrowObjectStringRepresentationBuilder;\nimport org.apache.arrow.vector.FieldVector;\nimport org.apache.arrow.vector.complex.StructVector;\n\npublic class StructConverter extends AbstractArrowVectorConverter {\n\n  private final StructVector structVector;\n\n  public StructConverter(StructVector vector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.OBJECT.name(), vector, columnIndex, context);\n    structVector = vector;\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return isNull(index) ? null : structVector.getObject(index);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    return isNull(index) ? null : toString(index).getBytes();\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    ArrowObjectStringRepresentationBuilder builder = new ArrowObjectStringRepresentationBuilder();\n    for (String childName : structVector.getChildFieldNames()) {\n      FieldVector fieldVector = structVector.getChild(childName);\n      SnowflakeType logicalType =\n          ArrowVectorConverterUtil.getSnowflakeTypeFromFieldMetadata(fieldVector.getField());\n      try {\n        if (fieldVector.isNull(index)) {\n          builder.appendKeyValue(childName, null, logicalType);\n        } else {\n          ArrowVectorConverter converter =\n              ArrowVectorConverterUtil.initConverter(fieldVector, context, columnIndex);\n          builder.appendKeyValue(childName, converter.toString(index), logicalType);\n        }\n      } catch (SnowflakeSQLException e) {\n        return structVector.getObject(index).toString();\n      }\n    }\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/StructObjectWrapper.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\npublic class StructObjectWrapper {\n  private final String jsonString;\n  private final Object object;\n\n  public StructObjectWrapper(String jsonString, Object object) {\n    this.jsonString = jsonString;\n    this.object = object;\n  }\n\n  public String getJsonString() {\n    return jsonString;\n  }\n\n  public Object getObject() {\n    return object;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/StructuredTypeDateTimeConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static net.snowflake.client.api.resultset.SnowflakeType.TIMESTAMP_LTZ;\nimport static net.snowflake.client.api.resultset.SnowflakeType.TIMESTAMP_NTZ;\nimport static net.snowflake.client.api.resultset.SnowflakeType.TIMESTAMP_TZ;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.util.JsonStringHashMap;\n\npublic class StructuredTypeDateTimeConverter {\n\n  private final TimeZone sessionTimeZone;\n  private final long resultVersion;\n  private final boolean honorClientTZForTimestampNTZ;\n  private final boolean treatNTZAsUTC;\n  private final boolean useSessionTimezone;\n  private final boolean formatDateWithTimeZone;\n\n  public StructuredTypeDateTimeConverter(\n      TimeZone sessionTimeZone,\n      long resultVersion,\n      boolean honorClientTZForTimestampNTZ,\n      boolean treatNTZAsUTC,\n      boolean useSessionTimezone,\n      boolean formatDateWithTimeZone) {\n\n    this.sessionTimeZone = sessionTimeZone;\n    this.resultVersion = resultVersion;\n    this.honorClientTZForTimestampNTZ = honorClientTZForTimestampNTZ;\n    this.treatNTZAsUTC = treatNTZAsUTC;\n    this.useSessionTimezone = useSessionTimezone;\n    this.formatDateWithTimeZone = formatDateWithTimeZone;\n  }\n\n  public Timestamp getTimestamp(\n      Map<String, Object> obj, int columnType, int columnSubType, TimeZone tz, int scale)\n      throws SFException {\n    if (tz == null) {\n      tz = TimeZone.getDefault();\n    }\n    if (Types.TIMESTAMP == columnType) {\n      if (SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ == columnSubType) {\n        return convertTimestampLtz(obj, scale);\n      } else {\n        return convertTimestampNtz(obj, tz, scale);\n      }\n    } else if (Types.TIMESTAMP_WITH_TIMEZONE == columnType\n        && SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ == columnSubType) {\n      return convertTimestampTz(obj, scale);\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT,\n        \"Unexpected Arrow Field for columnType \"\n            + columnType\n            + \" , column subtype \"\n            + columnSubType\n            + \" , and object type \"\n            + obj.getClass());\n  }\n\n  public Date getDate(int value, TimeZone tz) throws SFException {\n    return DateConverter.getDate(value, tz, sessionTimeZone, formatDateWithTimeZone);\n  }\n\n  public Time getTime(long value, int scale) throws SFException {\n    return BigIntToTimeConverter.getTime(value, scale, useSessionTimezone);\n  }\n\n  private Timestamp convertTimestampLtz(Object obj, int scale) throws SFException {\n    if (obj instanceof JsonStringHashMap) {\n      JsonStringHashMap<String, Object> map = (JsonStringHashMap<String, Object>) obj;\n      if (map.values().size() == 2) {\n        return TwoFieldStructToTimestampLTZConverter.getTimestamp(\n            (long) map.get(\"epoch\"),\n            (int) map.get(\"fraction\"),\n            sessionTimeZone,\n            useSessionTimezone,\n            false);\n      }\n    } else if (obj instanceof Long) {\n      return BigIntToTimestampLTZConverter.getTimestamp(\n          (long) obj, scale, sessionTimeZone, useSessionTimezone);\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT,\n        \"Unexpected Arrow Field for \" + TIMESTAMP_LTZ + \" and object type \" + obj.getClass());\n  }\n\n  private Timestamp convertTimestampNtz(Object obj, TimeZone tz, int scale) throws SFException {\n    if (obj instanceof JsonStringHashMap) {\n      JsonStringHashMap<String, Object> map = (JsonStringHashMap<String, Object>) obj;\n      if (map.values().size() == 2) {\n        return TwoFieldStructToTimestampNTZConverter.getTimestamp(\n            (long) map.get(\"epoch\"),\n            (int) map.get(\"fraction\"),\n            tz,\n            sessionTimeZone,\n            treatNTZAsUTC,\n            useSessionTimezone,\n            honorClientTZForTimestampNTZ,\n            false);\n      }\n    } else if (obj instanceof Long) {\n      return BigIntToTimestampNTZConverter.getTimestamp(\n          (long) obj, tz, scale, honorClientTZForTimestampNTZ, false);\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT,\n        \"Unexpected Arrow Field for \" + TIMESTAMP_NTZ + \" and object type \" + obj.getClass());\n  }\n\n  private Timestamp convertTimestampTz(Object obj, int scale) throws SFException {\n    if (obj instanceof JsonStringHashMap) {\n      JsonStringHashMap<String, Object> map = (JsonStringHashMap<String, Object>) obj;\n      if (map.values().size() == 2) {\n        return TwoFieldStructToTimestampTZConverter.getTimestamp(\n            (long) map.get(\"epoch\"), (int) map.get(\"timezone\"), scale);\n      } else if (map.values().size() == 3) {\n        return ThreeFieldStructToTimestampTZConverter.getTimestamp(\n            (long) map.get(\"epoch\"),\n            (int) map.get(\"fraction\"),\n            (int) map.get(\"timezone\"),\n            resultVersion,\n            useSessionTimezone,\n            false);\n      }\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT,\n        \"Unexpected Arrow Field for \" + TIMESTAMP_TZ + \" and object type \" + obj.getClass());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/ThreeFieldStructToTimestampTZConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeDateWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimeWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.complex.StructVector;\n\n/** converter from three-field struct (including epoch, fraction, and timezone) to Timestamp_TZ */\npublic class ThreeFieldStructToTimestampTZConverter extends AbstractArrowVectorConverter {\n  private StructVector structVector;\n  private BigIntVector epochs;\n  private IntVector fractions;\n  private IntVector timeZoneIndices;\n  private TimeZone timeZone = TimeZone.getTimeZone(\"UTC\");\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public ThreeFieldStructToTimestampTZConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIMESTAMP_LTZ.name(), fieldVector, columnIndex, context);\n    structVector = (StructVector) fieldVector;\n    epochs = structVector.getChild(FIELD_NAME_EPOCH, BigIntVector.class);\n    fractions = structVector.getChild(FIELD_NAME_FRACTION, IntVector.class);\n    timeZoneIndices = structVector.getChild(FIELD_NAME_TIME_ZONE_INDEX, IntVector.class);\n  }\n\n  @Override\n  public boolean isNull(int index) {\n    return structVector.isNull(index)\n        || epochs.isNull(index)\n        || fractions.isNull(index)\n        || timeZoneIndices.isNull(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimestampTZFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp TZ formatter\");\n    }\n    try {\n      Timestamp ts = isNull(index) ? null : getTimestamp(index, TimeZone.getDefault(), true);\n      return ts == null\n          ? null\n          : context.getTimestampTZFormatter().format(ts, timeZone, context.getScale(columnIndex));\n    } catch (TimestampOperationNotAvailableException e) {\n      return e.getSecsSinceEpoch().toPlainString();\n    }\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byteArray\", toString(index));\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toTimestamp(index, TimeZone.getDefault());\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    return isNull(index) ? null : getTimestamp(index, tz, false);\n  }\n\n  private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) throws SFException {\n    long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    int fraction = fractions.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n    int timeZoneIndex = timeZoneIndices.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n    timeZone = convertFromTimeZoneIndex(timeZoneIndex, context.getResultVersion());\n    return getTimestamp(\n        epoch,\n        fraction,\n        timeZoneIndex,\n        context.getResultVersion(),\n        useSessionTimezone,\n        fromToString);\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone tz, boolean dateFormat) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    Timestamp ts = getTimestamp(index, TimeZone.getDefault(), false);\n    // ts can be null when Java's timestamp is overflow.\n    return ts == null\n        ? null\n        : new SnowflakeDateWithTimezone(ts.getTime(), timeZone, useSessionTimezone);\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n    return ts == null ? null : new SnowflakeTimeWithTimezone(ts, timeZone, useSessionTimezone);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Timestamp val = toTimestamp(index, TimeZone.getDefault());\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  @Override\n  public short toShort(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, \"\");\n  }\n\n  public static Timestamp getTimestamp(\n      long epoch,\n      int fraction,\n      int timeZoneIndex,\n      long resultVersion,\n      boolean useSessionTimezone,\n      boolean fromToString)\n      throws SFException {\n    if (ArrowResultUtil.isTimestampOverflow(epoch)) {\n      if (fromToString) {\n        throw new TimestampOperationNotAvailableException(epoch, fraction);\n      } else {\n        return null;\n      }\n    }\n    TimeZone timeZone = convertFromTimeZoneIndex(timeZoneIndex, resultVersion);\n    Timestamp ts = ArrowResultUtil.createTimestamp(epoch, fraction, timeZone, useSessionTimezone);\n    return ResultUtil.adjustTimestamp(ts);\n  }\n\n  private static TimeZone convertFromTimeZoneIndex(int timeZoneIndex, long resultVersion) {\n    if (resultVersion > 0) {\n      return SFTimestamp.convertTimezoneIndexToTimeZone(timeZoneIndex);\n    } else {\n      return TimeZone.getTimeZone(\"UTC\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/TinyIntToFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.TinyIntVector;\nimport org.apache.arrow.vector.ValueVector;\n\n/** A converter from arrow tinyint to Snowflake Fixed type converter */\npublic class TinyIntToFixedConverter extends AbstractArrowVectorConverter {\n  protected TinyIntVector tinyIntVector;\n  protected int sfScale = 0;\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public TinyIntToFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\")),\n        fieldVector,\n        columnIndex,\n        context);\n    this.tinyIntVector = (TinyIntVector) fieldVector;\n  }\n\n  @Override\n  public byte toByte(int index) throws SFException {\n    if (tinyIntVector.isNull(index)) {\n      return 0;\n    } else {\n      return getByte(index);\n    }\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (tinyIntVector.isNull(index)) {\n      return null;\n    }\n    ByteBuffer bytes = ByteBuffer.allocate(TinyIntVector.TYPE_WIDTH);\n    tinyIntVector.getDataBuffer().getBytes(index, bytes);\n    return bytes.array();\n  }\n\n  protected byte getByte(int index) throws SFException {\n    return tinyIntVector.getDataBuffer().getByte(index * TinyIntVector.TYPE_WIDTH);\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    return (short) toByte(index);\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    return (int) toByte(index);\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    return toByte(index);\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    return toFloat(index);\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    return (long) toByte(index);\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) throws SFException {\n    if (tinyIntVector.isNull(index)) {\n      return null;\n    } else {\n      return BigDecimal.valueOf((long) getByte(index), sfScale);\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    } else if (!shouldTreatDecimalAsInt()) {\n      return BigDecimal.valueOf((long) getByte(index), sfScale);\n    }\n    return (long) toByte(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    return isNull(index) ? null : Short.toString(getByte(index));\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    short val = toShort(index);\n    if (val == 0) {\n      return false;\n    } else if (val == 1) {\n      return true;\n    } else {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/TinyIntToScaledFixedConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.ValueVector;\n\n/** A converter from scaled arrow tinyint to Snowflake Fixed type converter */\npublic class TinyIntToScaledFixedConverter extends TinyIntToFixedConverter {\n  private String format;\n\n  public TinyIntToScaledFixedConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context, int sfScale) {\n    super(fieldVector, columnIndex, context);\n    logicalTypeStr =\n        String.format(\n            \"%s(%s,%s)\",\n            SnowflakeType.FIXED,\n            fieldVector.getField().getMetadata().get(\"precision\"),\n            fieldVector.getField().getMetadata().get(\"scale\"));\n    format = ArrowResultUtil.getStringFormat(sfScale);\n    this.sfScale = sfScale;\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Short\", val);\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Int\", val);\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    return ((float) getByte(index)) / ArrowResultUtil.powerOfTen(sfScale);\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    if (isNull(index)) {\n      return 0;\n    }\n    float val = toFloat(index);\n    throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Long\", val);\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toBigDecimal(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    float f = ((float) getByte(index)) / ArrowResultUtil.powerOfTen(sfScale);\n    return String.format(format, f);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    BigDecimal val = toBigDecimal(index);\n    if (val.compareTo(BigDecimal.ZERO) == 0) {\n      return false;\n    } else if (val.compareTo(BigDecimal.ONE) == 0) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", val.toPlainString());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/TwoFieldStructToTimestampLTZConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeDateWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimeWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.complex.StructVector;\n\n/** converter from two-field struct (epochs and fraction) to Timestamp_LTZ */\npublic class TwoFieldStructToTimestampLTZConverter extends AbstractArrowVectorConverter {\n  private StructVector structVector;\n  private BigIntVector epochs;\n  private IntVector fractions;\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public TwoFieldStructToTimestampLTZConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIMESTAMP_LTZ.name(), fieldVector, columnIndex, context);\n    structVector = (StructVector) fieldVector;\n    epochs = structVector.getChild(FIELD_NAME_EPOCH, BigIntVector.class);\n    fractions = structVector.getChild(FIELD_NAME_FRACTION, IntVector.class);\n  }\n\n  @Override\n  public boolean isNull(int index) {\n    return structVector.isNull(index) || epochs.isNull(index) || fractions.isNull(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimestampLTZFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp LTZ formatter\");\n    }\n\n    try {\n      Timestamp ts = isNull(index) ? null : getTimestamp(index, TimeZone.getDefault(), true);\n\n      return ts == null\n          ? null\n          : context\n              .getTimestampLTZFormatter()\n              .format(ts, context.getTimeZone(), context.getScale(columnIndex));\n    } catch (TimestampOperationNotAvailableException e) {\n      return e.getSecsSinceEpoch().toPlainString();\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toTimestamp(index, TimeZone.getDefault());\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    return isNull(index) ? null : getTimestamp(index, tz, false);\n  }\n\n  private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) throws SFException {\n    long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    int fraction = fractions.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n\n    return getTimestamp(epoch, fraction, sessionTimeZone, useSessionTimezone, fromToString);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byteArray\", toString(index));\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone tz, boolean dateFormat) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    Timestamp ts = getTimestamp(index, TimeZone.getDefault(), false);\n    // ts can be null when Java's timestamp is overflow.\n    return ts == null\n        ? null\n        : new SnowflakeDateWithTimezone(ts.getTime(), sessionTimeZone, useSessionTimezone);\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n    return ts == null\n        ? null\n        : new SnowflakeTimeWithTimezone(ts, sessionTimeZone, useSessionTimezone);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Timestamp val = toTimestamp(index, TimeZone.getDefault());\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  public static Timestamp getTimestamp(\n      long epoch,\n      int fraction,\n      TimeZone sessionTimeZone,\n      boolean useSessionTimezone,\n      boolean fromToString)\n      throws SFException {\n    if (ArrowResultUtil.isTimestampOverflow(epoch)) {\n      if (fromToString) {\n        throw new TimestampOperationNotAvailableException(epoch, fraction);\n      } else {\n        return null;\n      }\n    }\n    Timestamp ts =\n        ArrowResultUtil.createTimestamp(epoch, fraction, sessionTimeZone, useSessionTimezone);\n    return ResultUtil.adjustTimestamp(ts);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/TwoFieldStructToTimestampNTZConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimeWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.complex.StructVector;\n\n/** converter from two-field struct (epochs and fraction) to Timestamp_NTZ */\npublic class TwoFieldStructToTimestampNTZConverter extends AbstractArrowVectorConverter {\n  private StructVector structVector;\n  private BigIntVector epochs;\n  private IntVector fractions;\n\n  private static final TimeZone NTZ = TimeZone.getTimeZone(\"UTC\");\n\n  /**\n   * @param fieldVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public TwoFieldStructToTimestampNTZConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIMESTAMP_NTZ.name(), fieldVector, columnIndex, context);\n    structVector = (StructVector) fieldVector;\n    epochs = structVector.getChild(FIELD_NAME_EPOCH, BigIntVector.class);\n    fractions = structVector.getChild(FIELD_NAME_FRACTION, IntVector.class);\n  }\n\n  @Override\n  public boolean isNull(int index) {\n    return structVector.isNull(index) || epochs.isNull(index) || fractions.isNull(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimestampNTZFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp NTZ formatter\");\n    }\n    try {\n      Timestamp ts = isNull(index) ? null : getTimestamp(index, TimeZone.getDefault(), true);\n\n      return ts == null\n          ? null\n          : context\n              .getTimestampNTZFormatter()\n              .format(ts, TimeZone.getTimeZone(\"UTC\"), context.getScale(columnIndex));\n    } catch (TimestampOperationNotAvailableException e) {\n      return e.getSecsSinceEpoch().toPlainString();\n    }\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toTimestamp(index, TimeZone.getDefault());\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    if (tz == null) {\n      tz = TimeZone.getDefault();\n    }\n    return isNull(index) ? null : getTimestamp(index, tz, false);\n  }\n\n  private Timestamp getTimestamp(int index, TimeZone tz, boolean fromToString) throws SFException {\n    long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    int fraction = fractions.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n    return getTimestamp(\n        epoch,\n        fraction,\n        tz,\n        sessionTimeZone,\n        treatNTZasUTC,\n        useSessionTimezone,\n        context.getHonorClientTZForTimestampNTZ(),\n        fromToString);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byteArray\", toString(index));\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone tz, boolean dateFormat) throws SFException {\n    return isNull(index)\n        ? null\n        : new Date(getTimestamp(index, TimeZone.getDefault(), false).getTime());\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    Timestamp ts = toTimestamp(index, null);\n    if (useSessionTimezone) {\n      ts = toTimestamp(index, sessionTimeZone);\n    }\n    return ts == null\n        ? null\n        : new SnowflakeTimeWithTimezone(ts, sessionTimeZone, useSessionTimezone);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Timestamp val = toTimestamp(index, TimeZone.getDefault());\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  public static Timestamp getTimestamp(\n      long epoch,\n      int fraction,\n      TimeZone tz,\n      TimeZone sessionTimeZone,\n      boolean treatNTZasUTC,\n      boolean useSessionTimezone,\n      boolean honorClientTZForTimestampNTZ,\n      boolean fromToString)\n      throws SFException {\n\n    if (ArrowResultUtil.isTimestampOverflow(epoch)) {\n      if (fromToString) {\n        throw new TimestampOperationNotAvailableException(epoch, fraction);\n      } else {\n        return null;\n      }\n    }\n    Timestamp ts;\n    if (treatNTZasUTC || !useSessionTimezone) {\n      ts = ArrowResultUtil.createTimestamp(epoch, fraction, TimeZone.getTimeZone(\"UTC\"), true);\n    } else {\n      ts = ArrowResultUtil.createTimestamp(epoch, fraction, sessionTimeZone, false);\n    }\n\n    // Note: honorClientTZForTimestampNTZ is not enabled for toString method.\n    // If JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=false, default behavior is to honor\n    // client timezone for NTZ time. Move NTZ timestamp offset to correspond to\n    // client's timezone. UseSessionTimezone overrides treatNTZasUTC.\n    if (!fromToString && (honorClientTZForTimestampNTZ && !treatNTZasUTC) || useSessionTimezone) {\n      ts = ArrowResultUtil.moveToTimeZone(ts, NTZ, tz);\n    }\n    return ResultUtil.adjustTimestamp(ts);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/TwoFieldStructToTimestampTZConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimeWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.complex.StructVector;\n\n/** converter from two-field struct (epoch and time zone) to Timestamp_TZ */\npublic class TwoFieldStructToTimestampTZConverter extends AbstractArrowVectorConverter {\n  private StructVector structVector;\n  private BigIntVector epochs;\n  private IntVector timeZoneIndices;\n  private TimeZone timeZone = TimeZone.getTimeZone(\"UTC\");\n\n  public TwoFieldStructToTimestampTZConverter(\n      ValueVector fieldVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TIMESTAMP_LTZ.name(), fieldVector, columnIndex, context);\n    structVector = (StructVector) fieldVector;\n    epochs = structVector.getChild(FIELD_NAME_EPOCH, BigIntVector.class);\n    timeZoneIndices = structVector.getChild(FIELD_NAME_TIME_ZONE_INDEX, IntVector.class);\n  }\n\n  @Override\n  public boolean isNull(int index) {\n    return structVector.isNull(index) || epochs.isNull(index) || timeZoneIndices.isNull(index);\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    if (context.getTimestampTZFormatter() == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing timestamp LTZ formatter\");\n    }\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n\n    return ts == null\n        ? null\n        : context.getTimestampTZFormatter().format(ts, timeZone, context.getScale(columnIndex));\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    return toTimestamp(index, TimeZone.getDefault());\n  }\n\n  @Override\n  public Timestamp toTimestamp(int index, TimeZone tz) throws SFException {\n    return isNull(index) ? null : getTimestamp(index, tz);\n  }\n\n  private Timestamp getTimestamp(int index, TimeZone tz) throws SFException {\n    long epoch = epochs.getDataBuffer().getLong(index * BigIntVector.TYPE_WIDTH);\n    int timeZoneIndex = timeZoneIndices.getDataBuffer().getInt(index * IntVector.TYPE_WIDTH);\n\n    if (context.getResultVersion() > 0) {\n      timeZone = SFTimestamp.convertTimezoneIndexToTimeZone(timeZoneIndex);\n    } else {\n      timeZone = TimeZone.getTimeZone(\"UTC\");\n    }\n    return getTimestamp(epoch, timeZoneIndex, context.getScale(columnIndex));\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone tz, boolean dateFormat) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    Timestamp ts = getTimestamp(index, TimeZone.getDefault());\n    // ts can be null when Java's timestamp is overflow.\n    return ts == null ? null : new Date(ts.getTime());\n  }\n\n  @Override\n  public Time toTime(int index) throws SFException {\n    Timestamp ts = toTimestamp(index, TimeZone.getDefault());\n    return ts == null\n        ? null\n        : new SnowflakeTimeWithTimezone(ts.getTime(), ts.getNanos(), useSessionTimezone);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    if (isNull(index)) {\n      return false;\n    }\n    Timestamp val = toTimestamp(index, TimeZone.getDefault());\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n        SnowflakeUtil.BOOLEAN_STR, val);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"byteArray\", toString(index));\n  }\n\n  @Override\n  public short toShort(int rowIndex) throws SFException {\n    if (isNull(rowIndex)) {\n      return 0;\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, \"\");\n  }\n\n  public static Timestamp getTimestamp(long epoch, int timeZoneIndex, int scale)\n      throws SFException {\n    Timestamp ts = ArrowResultUtil.toJavaTimestamp(epoch, scale);\n    return ResultUtil.adjustTimestamp(ts);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/VarBinaryToBinaryConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.VarBinaryVector;\n\n/** Converter from Arrow VarBinaryVector to Binary. */\npublic class VarBinaryToBinaryConverter extends AbstractArrowVectorConverter {\n  private VarBinaryVector varBinaryVector;\n\n  /**\n   * @param valueVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public VarBinaryToBinaryConverter(\n      ValueVector valueVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.BINARY.name(), valueVector, columnIndex, context);\n    this.varBinaryVector = (VarBinaryVector) valueVector;\n  }\n\n  @Override\n  public String toString(int index) {\n    byte[] bytes = toBytes(index);\n    SFBinary binary = new SFBinary(bytes);\n    return bytes == null ? null : context.getBinaryFormatter().format(binary);\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    return varBinaryVector.getObject(index);\n  }\n\n  @Override\n  public Object toObject(int index) {\n    return toBytes(index);\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    String str = toString(index);\n    if (str == null) {\n      return false;\n    } else {\n      throw new SFException(ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, \"Boolean\", str);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/VarCharConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.math.BigDecimal;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Date;\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.VarCharVector;\n\n/** Convert Arrow VarCharVector to Java types */\npublic class VarCharConverter extends AbstractArrowVectorConverter {\n  private VarCharVector varCharVector;\n\n  /**\n   * @param valueVector ValueVector\n   * @param columnIndex column index\n   * @param context DataConversionContext\n   */\n  public VarCharConverter(ValueVector valueVector, int columnIndex, DataConversionContext context) {\n    super(SnowflakeType.TEXT.name(), valueVector, columnIndex, context);\n    this.varCharVector = (VarCharVector) valueVector;\n  }\n\n  @Override\n  public String toString(int index) {\n    byte[] bytes = toBytes(index);\n    return bytes == null ? null : new String(bytes, StandardCharsets.UTF_8);\n  }\n\n  @Override\n  public byte[] toBytes(int index) {\n    return isNull(index) ? null : varCharVector.get(index);\n  }\n\n  @Override\n  public Object toObject(int index) {\n    return toString(index);\n  }\n\n  @Override\n  public short toShort(int index) throws SFException {\n    String str = toString(index);\n    try {\n      if (str == null) {\n        return 0;\n      } else {\n        return Short.parseShort(str);\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.SHORT_STR, str);\n    }\n  }\n\n  @Override\n  public int toInt(int index) throws SFException {\n    String str = toString(index);\n    try {\n      if (str == null) {\n        return 0;\n      } else {\n        return Integer.parseInt(str);\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.INT_STR, str);\n    }\n  }\n\n  @Override\n  public long toLong(int index) throws SFException {\n    String str = toString(index);\n    try {\n      if (str == null) {\n        return 0;\n      } else {\n        return Long.parseLong(str);\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.LONG_STR, str);\n    }\n  }\n\n  @Override\n  public float toFloat(int index) throws SFException {\n    String str = toString(index);\n    try {\n      if (str == null) {\n        return 0;\n      } else {\n        return Float.parseFloat(str);\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.FLOAT_STR, str);\n    }\n  }\n\n  @Override\n  public double toDouble(int index) throws SFException {\n    String str = toString(index);\n    try {\n      if (str == null) {\n        return 0;\n      } else {\n        return Double.parseDouble(str);\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.DOUBLE_STR, str);\n    }\n  }\n\n  @Override\n  public BigDecimal toBigDecimal(int index) throws SFException {\n    String str = toString(index);\n    try {\n      if (str == null) {\n        return null;\n      } else {\n        return new BigDecimal(str);\n      }\n    } catch (Exception ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.BIG_DECIMAL_STR, str);\n    }\n  }\n\n  @Override\n  public boolean toBoolean(int index) throws SFException {\n    String str = toString(index);\n    if (str == null) {\n      return false;\n    } else if (\"0\".equals(str) || Boolean.FALSE.toString().equalsIgnoreCase(str)) {\n      return false;\n    } else if (\"1\".equals(str) || Boolean.TRUE.toString().equalsIgnoreCase(str)) {\n      return true;\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr,\n          SnowflakeUtil.BOOLEAN_STR, str);\n    }\n  }\n\n  @Override\n  public Date toDate(int index, TimeZone jvmTz, boolean useDateFormat) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    try {\n      DateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd\");\n      Date date = new Date(dateFormat.parse(toString(index)).getTime());\n      return date;\n    } catch (ParseException e) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, logicalTypeStr, SnowflakeUtil.DATE_STR, \"\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/VectorTypeConverter.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.util.List;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.vector.complex.FixedSizeListVector;\n\n/** Arrow FixedSizeListVector converter. */\npublic class VectorTypeConverter extends AbstractArrowVectorConverter {\n\n  private final FixedSizeListVector vector;\n\n  /**\n   * @param valueVector ValueVector\n   * @param vectorIndex vector index\n   * @param context DataConversionContext\n   */\n  public VectorTypeConverter(\n      FixedSizeListVector valueVector, int vectorIndex, DataConversionContext context) {\n    super(SnowflakeType.ARRAY.name(), valueVector, vectorIndex, context);\n    this.vector = valueVector;\n  }\n\n  @Override\n  public Object toObject(int index) throws SFException {\n    if (isNull(index)) {\n      return null;\n    }\n    return vector.getObject(index);\n  }\n\n  @Override\n  public byte[] toBytes(int index) throws SFException {\n    return isNull(index) ? null : toString(index).getBytes();\n  }\n\n  @Override\n  public String toString(int index) throws SFException {\n    List<?> object = vector.getObject(index);\n    if (object == null) {\n      return null;\n    }\n    return object.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/tostringhelpers/ArrowArrayStringRepresentationBuilder.java",
    "content": "package net.snowflake.client.internal.core.arrow.tostringhelpers;\n\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\npublic class ArrowArrayStringRepresentationBuilder extends ArrowStringRepresentationBuilderBase {\n\n  private final SnowflakeType valueType;\n\n  public ArrowArrayStringRepresentationBuilder(SnowflakeType valueType) {\n    super(\",\", \"[\", \"]\");\n    this.valueType = valueType;\n  }\n\n  public ArrowStringRepresentationBuilderBase appendValue(String value) {\n    return add(quoteIfNeeded(value, valueType));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/tostringhelpers/ArrowObjectStringRepresentationBuilder.java",
    "content": "package net.snowflake.client.internal.core.arrow.tostringhelpers;\n\nimport java.util.StringJoiner;\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\npublic class ArrowObjectStringRepresentationBuilder extends ArrowStringRepresentationBuilderBase {\n\n  public ArrowObjectStringRepresentationBuilder() {\n    super(\",\", \"{\", \"}\");\n  }\n\n  public ArrowStringRepresentationBuilderBase appendKeyValue(\n      String key, String value, SnowflakeType valueType) {\n    StringJoiner joiner = new StringJoiner(\": \");\n    joiner.add('\"' + key + '\"');\n    joiner.add(quoteIfNeeded(value, valueType));\n    return add(joiner.toString());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/arrow/tostringhelpers/ArrowStringRepresentationBuilderBase.java",
    "content": "package net.snowflake.client.internal.core.arrow.tostringhelpers;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.StringJoiner;\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\n/**\n * StringBuilder like class to aggregate the string representation of snowflake Native ARROW\n * structured types as JSON one-liners. Provides some additional snowflake-specific logic in order\n * to determine whether the value should be quoted or case should be changed.\n */\npublic abstract class ArrowStringRepresentationBuilderBase {\n  private final StringJoiner joiner;\n  private static final Set<SnowflakeType> quotableTypes;\n\n  static {\n    quotableTypes = new HashSet<>();\n    quotableTypes.add(SnowflakeType.ANY);\n    quotableTypes.add(SnowflakeType.CHAR);\n    quotableTypes.add(SnowflakeType.TEXT);\n    quotableTypes.add(SnowflakeType.BINARY);\n    quotableTypes.add(SnowflakeType.DATE);\n    quotableTypes.add(SnowflakeType.TIME);\n    quotableTypes.add(SnowflakeType.TIMESTAMP_LTZ);\n    quotableTypes.add(SnowflakeType.TIMESTAMP_NTZ);\n    quotableTypes.add(SnowflakeType.TIMESTAMP_TZ);\n  }\n\n  public ArrowStringRepresentationBuilderBase(String delimiter, String prefix, String suffix) {\n    joiner = new StringJoiner(delimiter, prefix, suffix);\n  }\n\n  protected ArrowStringRepresentationBuilderBase add(String string) {\n    joiner.add(string);\n    return this;\n  }\n\n  private boolean shouldQuoteValue(SnowflakeType type) {\n    return quotableTypes.contains(type);\n  }\n\n  protected String quoteIfNeeded(String string, SnowflakeType type) {\n    if (string == null) {\n      return null;\n    }\n    // Turn Boolean string representations lowercase to make the output JSON-compatible\n    // this should be changed on the converter level, but it would be a breaking change thus\n    // for now only structured types will be valid JSONs while in NATIVE ARROW mode\n    if (type == SnowflakeType.BOOLEAN) {\n      string = string.toLowerCase();\n    }\n\n    if (shouldQuoteValue(type)) {\n      return '\"' + string + '\"';\n    }\n\n    return string;\n  }\n\n  @Override\n  public String toString() {\n    return joiner.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/ClientAuthnDTO.java",
    "content": "package net.snowflake.client.internal.core.auth;\n\nimport java.util.Map;\nimport javax.annotation.Nullable;\n\npublic class ClientAuthnDTO {\n\n  // contains all the required data for current authn step\n  private final Map<String, Object> data;\n\n  /*\n   * current state\n   * tokenized string with all current parameters and the authn step\n   */\n  private final String inFlightCtx;\n\n  public ClientAuthnDTO(Map<String, Object> data, @Nullable String inFlightCtx) {\n    this.data = data;\n    this.inFlightCtx = inFlightCtx;\n  }\n\n  /** Required by Jackson */\n  public Map<String, Object> getData() {\n    return data;\n  }\n\n  /** Required by Jackson */\n  public String getInFlightCtx() {\n    return inFlightCtx;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/ClientAuthnParameter.java",
    "content": "package net.snowflake.client.internal.core.auth;\n\npublic enum ClientAuthnParameter {\n  LOGIN_NAME,\n  PASSWORD,\n  RAW_SAML_RESPONSE,\n  ACCOUNT_NAME,\n  CLIENT_APP_ID,\n  CLIENT_APP_VERSION,\n  EXT_AUTHN_DUO_METHOD,\n  PASSCODE,\n  CLIENT_ENVIRONMENT,\n  AUTHENTICATOR,\n  BROWSER_MODE_REDIRECT_PORT,\n  SESSION_PARAMETERS,\n  PROOF_KEY,\n  TOKEN,\n  OAUTH_TYPE,\n  PROVIDER,\n  APPLICATION_PATH,\n  SPCS_TOKEN\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/AccessTokenProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\n\npublic interface AccessTokenProvider {\n\n  TokenResponseDTO getAccessToken(SFLoginInput loginInput) throws SFException;\n\n  String getDPoPPublicKey();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/AuthorizationCodeRedirectRequestHandler.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.google.common.html.HtmlEscapers;\nimport com.nimbusds.oauth2.sdk.id.State;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass AuthorizationCodeRedirectRequestHandler {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(AuthorizationCodeRedirectRequestHandler.class);\n\n  static String handleRedirectRequest(\n      Map<String, String> urlParams,\n      CompletableFuture<String> authorizationCodeFuture,\n      State expectedState) {\n    String response;\n    if (urlParams.containsKey(\"error\")) {\n      response = \"Authorization error: \" + urlParams.get(\"error\");\n      authorizationCodeFuture.completeExceptionally(\n          new SFException(\n              ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR,\n              String.format(\n                  \"Error during authorization: %s, %s\",\n                  urlParams.get(\"error\"), urlParams.get(\"error_description\"))));\n    } else if (!expectedState.getValue().equals(urlParams.get(\"state\"))) {\n      authorizationCodeFuture.completeExceptionally(\n          new SFException(\n              ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR,\n              String.format(\n                  \"Invalid authorization request redirection state: %s, expected: %s\",\n                  urlParams.get(\"state\"), expectedState.getValue())));\n      response = \"Authorization error: invalid authorization request redirection state\";\n    } else {\n      String authorizationCode = urlParams.get(\"code\");\n      if (!isNullOrEmpty(authorizationCode)) {\n        logger.debug(\"Received authorization code on redirect URI\");\n        response = \"Authorization completed successfully.\";\n        authorizationCodeFuture.complete(authorizationCode);\n      } else {\n        authorizationCodeFuture.completeExceptionally(\n            new SFException(\n                ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR,\n                String.format(\n                    \"Authorization code redirect URI server received request without authorization code; queryParams: %s\",\n                    urlParams)));\n        response = \"Authorization error: authorization code has not been returned to the driver.\";\n      }\n    }\n    return HtmlEscapers.htmlEscaper().escape(response);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/DPoPUtil.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.nimbusds.jose.JOSEException;\nimport com.nimbusds.jose.JWSAlgorithm;\nimport com.nimbusds.jose.jwk.Curve;\nimport com.nimbusds.jose.jwk.ECKey;\nimport com.nimbusds.jose.jwk.gen.ECKeyGenerator;\nimport com.nimbusds.jwt.SignedJWT;\nimport com.nimbusds.oauth2.sdk.dpop.DPoPProofFactory;\nimport com.nimbusds.oauth2.sdk.dpop.DefaultDPoPProofFactory;\nimport com.nimbusds.oauth2.sdk.dpop.JWKThumbprintConfirmation;\nimport com.nimbusds.openid.connect.sdk.Nonce;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.http.client.methods.HttpRequestBase;\n\npublic class DPoPUtil {\n\n  private final ECKey jwk;\n\n  DPoPUtil() throws SFException {\n    try {\n      jwk = new ECKeyGenerator(Curve.P_256).generate();\n    } catch (JOSEException e) {\n      throw new SFException(\n          ErrorCode.INTERNAL_ERROR, \"Error during DPoP JWK initialization: \" + e.getMessage());\n    }\n  }\n\n  public DPoPUtil(String jsonKey) throws SFException {\n    try {\n      jwk = ECKey.parse(jsonKey);\n    } catch (Exception e) {\n      throw new SFException(\n          ErrorCode.INTERNAL_ERROR, \"Error during DPoP JWK initialization: \" + e.getMessage());\n    }\n  }\n\n  String getPublicKey() {\n    return jwk.toJSONString();\n  }\n\n  JWKThumbprintConfirmation getThumbprint() throws SFException {\n    try {\n      return JWKThumbprintConfirmation.of(this.jwk);\n    } catch (JOSEException e) {\n      throw new SFException(\n          ErrorCode.INTERNAL_ERROR, \"Error during JWK thumbprint generation: \" + e.getMessage());\n    }\n  }\n\n  public void addDPoPProofHeaderToRequest(HttpRequestBase httpRequest, String nonce)\n      throws SFException {\n    SignedJWT signedJWT = generateDPoPProof(httpRequest, nonce);\n    httpRequest.setHeader(\"DPoP\", signedJWT.serialize());\n  }\n\n  private SignedJWT generateDPoPProof(HttpRequestBase httpRequest, String nonce)\n      throws SFException {\n    try {\n      DPoPProofFactory proofFactory = new DefaultDPoPProofFactory(jwk, JWSAlgorithm.ES256);\n      if (nonce != null) {\n        return proofFactory.createDPoPJWT(\n            httpRequest.getMethod(), httpRequest.getURI(), new Nonce(nonce));\n      } else {\n        return proofFactory.createDPoPJWT(\n            httpRequest.getMethod(), getUriWithoutQuery(httpRequest.getURI()));\n      }\n    } catch (Exception e) {\n      throw new SFException(\n          ErrorCode.INTERNAL_ERROR, \" Error during DPoP proof generation: \" + e.getMessage());\n    }\n  }\n\n  /**\n   * Method needed for sake of DPoP proof JWT creation. URI claim (htu) does not support query\n   * parameters.\n   */\n  private URI getUriWithoutQuery(URI uri) throws URISyntaxException {\n    return new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), null, null);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/OAuthAccessTokenForRefreshTokenProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.nimbusds.oauth2.sdk.RefreshTokenGrant;\nimport com.nimbusds.oauth2.sdk.Scope;\nimport com.nimbusds.oauth2.sdk.TokenRequest;\nimport com.nimbusds.oauth2.sdk.auth.ClientAuthentication;\nimport com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;\nimport com.nimbusds.oauth2.sdk.auth.Secret;\nimport com.nimbusds.oauth2.sdk.http.HTTPRequest;\nimport com.nimbusds.oauth2.sdk.id.ClientID;\nimport com.nimbusds.oauth2.sdk.token.RefreshToken;\nimport java.net.URI;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.jdbc.SnowflakeUseDPoPNonceException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpRequestBase;\n\npublic class OAuthAccessTokenForRefreshTokenProvider implements AccessTokenProvider {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(OAuthClientCredentialsAccessTokenProvider.class);\n\n  private final DPoPUtil dPoPUtil;\n\n  public OAuthAccessTokenForRefreshTokenProvider() throws SFException {\n    this.dPoPUtil = new DPoPUtil();\n  }\n\n  @Override\n  public TokenResponseDTO getAccessToken(SFLoginInput loginInput) throws SFException {\n    return exchangeRefreshTokenForAccessToken(loginInput, null, false);\n  }\n\n  @Override\n  public String getDPoPPublicKey() {\n    return dPoPUtil.getPublicKey();\n  }\n\n  private TokenResponseDTO exchangeRefreshTokenForAccessToken(\n      SFLoginInput loginInput, String dpopNonce, boolean retried) throws SFException {\n    try {\n      logger.info(\"Obtaining new OAuth access token using refresh token...\");\n      HttpRequestBase tokenRequest = buildTokenRequest(loginInput, dpopNonce);\n      return OAuthUtil.sendTokenRequest(tokenRequest, loginInput);\n    } catch (SnowflakeUseDPoPNonceException e) {\n      logger.debug(\"Received \\\"use_dpop_nonce\\\" error from IdP while performing token request\");\n      if (!retried) {\n        logger.debug(\"Retrying token request with DPoP nonce included...\");\n        return exchangeRefreshTokenForAccessToken(loginInput, e.getNonce(), true);\n      } else {\n        logger.debug(\"Skipping DPoP nonce retry as it has been already retried\");\n        throw e;\n      }\n    } catch (Exception e) {\n      logger.error(\"Error during OAuth refresh token flow.\", e);\n      throw new SFException(e, ErrorCode.OAUTH_REFRESH_TOKEN_FLOW_ERROR, e.getMessage());\n    }\n  }\n\n  private HttpRequestBase buildTokenRequest(SFLoginInput loginInput, String dpopNonce)\n      throws SFException {\n    URI tokenRequestUrl =\n        OAuthUtil.getTokenRequestUrl(loginInput.getOauthLoginInput(), loginInput.getServerUrl());\n    ClientAuthentication clientAuthentication =\n        new ClientSecretBasic(\n            new ClientID(loginInput.getOauthLoginInput().getClientId()),\n            new Secret(loginInput.getOauthLoginInput().getClientSecret()));\n    Scope scope =\n        new Scope(OAuthUtil.getScope(loginInput.getOauthLoginInput(), loginInput.getRole()));\n    RefreshToken refreshToken = new RefreshToken(loginInput.getOauthRefreshToken());\n    TokenRequest tokenRequest =\n        new TokenRequest(\n            tokenRequestUrl, clientAuthentication, new RefreshTokenGrant(refreshToken), scope);\n    HTTPRequest tokenHttpRequest = tokenRequest.toHTTPRequest();\n    HttpRequestBase convertedTokenRequest = OAuthUtil.convertToBaseRequest(tokenHttpRequest);\n\n    if (loginInput.isDPoPEnabled()) {\n      dPoPUtil.addDPoPProofHeaderToRequest(convertedTokenRequest, dpopNonce);\n    }\n\n    return convertedTokenRequest;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/OAuthAccessTokenProviderFactory.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport java.net.URI;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.AssertUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.core.SFOauthLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class OAuthAccessTokenProviderFactory {\n\n  private static final String SNOWFLAKE_DOMAIN = \"snowflakecomputing.com\";\n\n  private final SFLogger logger = SFLoggerFactory.getLogger(OAuthAccessTokenProviderFactory.class);\n  private static final Set<AuthenticatorType> ELIGIBLE_AUTH_TYPES =\n      new HashSet<>(\n          Arrays.asList(\n              AuthenticatorType.OAUTH_AUTHORIZATION_CODE,\n              AuthenticatorType.OAUTH_CLIENT_CREDENTIALS));\n\n  public AccessTokenProvider createAccessTokenProvider(\n      AuthenticatorType authenticatorType, SFLoginInput loginInput) throws SFException {\n    switch (authenticatorType) {\n      case OAUTH_AUTHORIZATION_CODE:\n        if (isEligibleForDefaultClientCredentials(loginInput.getOauthLoginInput())) {\n          loginInput.getOauthLoginInput().setLocalApplicationClientCredential();\n        }\n        assertContainsClientCredentials(loginInput, authenticatorType);\n        validateHttpRedirectUriIfSpecified(loginInput);\n        validateAuthorizationAndTokenEndpointsIfSpecified(loginInput);\n        return new OAuthAuthorizationCodeAccessTokenProvider(\n            loginInput.getBrowserHandler(),\n            new RandomStateProvider(),\n            loginInput.getBrowserResponseTimeout().getSeconds());\n      case OAUTH_CLIENT_CREDENTIALS:\n        assertContainsClientCredentials(loginInput, authenticatorType);\n        AssertUtil.assertTrue(\n            loginInput.getOauthLoginInput().getTokenRequestUrl() != null,\n            \"passing oauthTokenRequestUrl is required for OAUTH_CLIENT_CREDENTIALS authentication\");\n        return new OAuthClientCredentialsAccessTokenProvider();\n      default:\n        String message = \"Unsupported authenticator type: \" + authenticatorType;\n        logger.error(message);\n        throw new SFException(ErrorCode.INTERNAL_ERROR, message);\n    }\n  }\n\n  static boolean isEligibleForDefaultClientCredentials(SFOauthLoginInput oauthLoginInput) {\n    return areClientCredentialsNotSupplied(oauthLoginInput) && isSnowflakeAsIdP(oauthLoginInput);\n  }\n\n  private static boolean areClientCredentialsNotSupplied(SFOauthLoginInput oauthLoginInput) {\n    return (oauthLoginInput.getClientId() == null && oauthLoginInput.getClientSecret() == null);\n  }\n\n  private static boolean isSnowflakeAsIdP(SFOauthLoginInput oauthLoginInput) {\n    return ((oauthLoginInput.getAuthorizationUrl() == null\n            || oauthLoginInput.getAuthorizationUrl().contains(SNOWFLAKE_DOMAIN))\n        && (oauthLoginInput.getTokenRequestUrl() == null\n            || oauthLoginInput.getTokenRequestUrl().contains(SNOWFLAKE_DOMAIN)));\n  }\n\n  private void validateAuthorizationAndTokenEndpointsIfSpecified(SFLoginInput loginInput)\n      throws SFException {\n    String authorizationEndpoint = loginInput.getOauthLoginInput().getAuthorizationUrl();\n    String tokenEndpoint = loginInput.getOauthLoginInput().getTokenRequestUrl();\n    if ((!isNullOrEmpty(authorizationEndpoint) && isNullOrEmpty(tokenEndpoint))\n        || (isNullOrEmpty(authorizationEndpoint) && !isNullOrEmpty(tokenEndpoint))) {\n      throw new SFException(\n          ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR,\n          \"For OAUTH_AUTHORIZATION_CODE authentication with external IdP, both oauthAuthorizationUrl and oauthTokenRequestUrl must be specified\");\n    } else if (!isNullOrEmpty(authorizationEndpoint) && !isNullOrEmpty(tokenEndpoint)) {\n      URI authorizationUrl = URI.create(authorizationEndpoint);\n      URI tokenUrl = URI.create(tokenEndpoint);\n      if (isNullOrEmpty(authorizationUrl.getHost()) || isNullOrEmpty(tokenUrl.getHost())) {\n        throw new SFException(\n            ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR,\n            String.format(\n                \"OAuth authorization URL and token URL must be specified in proper format; oauthAuthorizationUrl=%s oauthTokenRequestUrl=%s\",\n                authorizationUrl, tokenUrl));\n      }\n      if (!authorizationUrl.getHost().equals(tokenUrl.getHost())) {\n        logger.warn(\n            String.format(\n                \"Both oauthAuthorizationUrl and oauthTokenRequestUrl should belong to the same host; oauthAuthorizationUrl=%s oauthTokenRequestUrl=%s\",\n                authorizationUrl, tokenUrl));\n      }\n    }\n  }\n\n  private static void validateHttpRedirectUriIfSpecified(SFLoginInput loginInput)\n      throws SFException {\n    String redirectUri = loginInput.getOauthLoginInput().getRedirectUri();\n    if (redirectUri != null) {\n      AssertUtil.assertTrue(\n          !redirectUri.startsWith(\"https\"),\n          \"provided redirect URI should start with \\\"http\\\", not \\\"https\\\"\");\n    }\n  }\n\n  public static boolean isEligible(AuthenticatorType authenticatorType) {\n    return getEligible().contains(authenticatorType);\n  }\n\n  private static Set<AuthenticatorType> getEligible() {\n    return ELIGIBLE_AUTH_TYPES;\n  }\n\n  private static void assertContainsClientCredentials(\n      SFLoginInput loginInput, AuthenticatorType authenticatorType) throws SFException {\n    AssertUtil.assertTrue(\n        loginInput.getOauthLoginInput().getClientId() != null,\n        String.format(\n            \"passing oauthClientId is required for %s authentication\", authenticatorType.name()));\n    AssertUtil.assertTrue(\n        loginInput.getOauthLoginInput().getClientSecret() != null,\n        String.format(\n            \"passing oauthClientSecret is required for %s authentication\",\n            authenticatorType.name()));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/OAuthAuthorizationCodeAccessTokenProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport static net.snowflake.client.internal.core.SessionUtilExternalBrowser.AuthExternalBrowserHandlers;\n\nimport com.nimbusds.oauth2.sdk.AuthorizationCode;\nimport com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;\nimport com.nimbusds.oauth2.sdk.AuthorizationGrant;\nimport com.nimbusds.oauth2.sdk.AuthorizationRequest;\nimport com.nimbusds.oauth2.sdk.ResponseType;\nimport com.nimbusds.oauth2.sdk.Scope;\nimport com.nimbusds.oauth2.sdk.TokenRequest;\nimport com.nimbusds.oauth2.sdk.auth.ClientAuthentication;\nimport com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;\nimport com.nimbusds.oauth2.sdk.auth.Secret;\nimport com.nimbusds.oauth2.sdk.http.HTTPRequest;\nimport com.nimbusds.oauth2.sdk.id.ClientID;\nimport com.nimbusds.oauth2.sdk.id.State;\nimport com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;\nimport com.nimbusds.oauth2.sdk.pkce.CodeVerifier;\nimport com.sun.net.httpserver.HttpServer;\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.core.SFOauthLoginInput;\nimport net.snowflake.client.internal.jdbc.SnowflakeUseDPoPNonceException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.NameValuePair;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.client.utils.URLEncodedUtils;\n\npublic class OAuthAuthorizationCodeAccessTokenProvider implements AccessTokenProvider {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(OAuthAuthorizationCodeAccessTokenProvider.class);\n\n  static final String DEFAULT_REDIRECT_HOST = \"http://127.0.0.1\";\n  static final String DEFAULT_REDIRECT_URI_ENDPOINT = \"/\";\n\n  private final AuthExternalBrowserHandlers browserHandler;\n  private final StateProvider<String> stateProvider;\n  private final DPoPUtil dpopUtil;\n  private final long browserAuthorizationTimeoutSeconds;\n\n  public OAuthAuthorizationCodeAccessTokenProvider(\n      AuthExternalBrowserHandlers browserHandler,\n      StateProvider<String> stateProvider,\n      long browserAuthorizationTimeoutSeconds)\n      throws SFException {\n    this.browserHandler = browserHandler;\n    this.stateProvider = stateProvider;\n    this.dpopUtil = new DPoPUtil();\n    this.browserAuthorizationTimeoutSeconds = browserAuthorizationTimeoutSeconds;\n  }\n\n  @Override\n  public TokenResponseDTO getAccessToken(SFLoginInput loginInput) throws SFException {\n    try {\n      logger.info(\"Starting OAuth authorization code authentication flow...\");\n      CodeVerifier pkceVerifier = new CodeVerifier();\n      URI redirectUri = OAuthUtil.buildRedirectUri(loginInput.getOauthLoginInput());\n      AuthorizationCode authorizationCode =\n          requestAuthorizationCode(loginInput, pkceVerifier, redirectUri);\n      return exchangeAuthorizationCodeForAccessToken(\n          loginInput, authorizationCode, pkceVerifier, redirectUri, null, false);\n    } catch (Exception e) {\n      logger.error(\n          \"Error during OAuth authorization code flow. Verify configuration passed to driver and IdP (URLs, grant types, scope, etc.)\",\n          e);\n      throw new SFException(e, ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR, e.getMessage());\n    }\n  }\n\n  @Override\n  public String getDPoPPublicKey() {\n    return dpopUtil.getPublicKey();\n  }\n\n  private AuthorizationCode requestAuthorizationCode(\n      SFLoginInput loginInput, CodeVerifier pkceVerifier, URI redirectUri)\n      throws SFException, IOException {\n    State state = new State(stateProvider.getState());\n    AuthorizationRequest request =\n        buildAuthorizationRequest(loginInput, pkceVerifier, state, redirectUri);\n    URI authorizeRequestURI = request.toURI();\n    HttpServer httpServer = createHttpServer(redirectUri);\n    CompletableFuture<String> codeFuture =\n        setupRedirectURIServerForAuthorizationCode(httpServer, state);\n    logger.debug(\"Waiting for authorization code redirection to {}...\", redirectUri);\n    return letUserAuthorize(authorizeRequestURI, codeFuture, httpServer);\n  }\n\n  private TokenResponseDTO exchangeAuthorizationCodeForAccessToken(\n      SFLoginInput loginInput,\n      AuthorizationCode authorizationCode,\n      CodeVerifier pkceVerifier,\n      URI redirectUri,\n      String dpopNonce,\n      boolean retried)\n      throws SFException {\n    try {\n      HttpRequestBase request =\n          buildTokenRequest(loginInput, authorizationCode, pkceVerifier, redirectUri, dpopNonce);\n      return OAuthUtil.sendTokenRequest(request, loginInput);\n    } catch (SnowflakeUseDPoPNonceException e) {\n      logger.debug(\"Received \\\"use_dpop_nonce\\\" error from IdP while performing token request\");\n      if (!retried) {\n        logger.debug(\"Retrying token request with DPoP nonce included...\");\n        return exchangeAuthorizationCodeForAccessToken(\n            loginInput, authorizationCode, pkceVerifier, redirectUri, e.getNonce(), true);\n      } else {\n        logger.debug(\"Skipping DPoP nonce retry as it has been already retried\");\n        throw e;\n      }\n    } catch (Exception e) {\n      logger.error(\"Error during making OAuth access token request\", e);\n      throw new SFException(e, ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR, e.getMessage());\n    }\n  }\n\n  private AuthorizationCode letUserAuthorize(\n      URI authorizeRequestURI, CompletableFuture<String> codeFuture, HttpServer httpServer)\n      throws SFException {\n    try {\n      logger.debug(\n          \"Opening browser for authorization code request to: {}\",\n          authorizeRequestURI.getAuthority() + authorizeRequestURI.getPath());\n      browserHandler.openBrowser(authorizeRequestURI.toString());\n      String code = codeFuture.get(this.browserAuthorizationTimeoutSeconds, TimeUnit.SECONDS);\n      return new AuthorizationCode(code);\n    } catch (TimeoutException e) {\n      throw new SFException(\n          e,\n          ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR,\n          \"Authorization request timed out. Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.\");\n    } catch (Exception e) {\n      throw new SFException(e, ErrorCode.OAUTH_AUTHORIZATION_CODE_FLOW_ERROR, e.getMessage());\n    } finally {\n      logger.debug(\"Stopping OAuth redirect URI server @ {}\", httpServer.getAddress());\n      httpServer.stop(1);\n    }\n  }\n\n  private static CompletableFuture<String> setupRedirectURIServerForAuthorizationCode(\n      HttpServer httpServer, State expectedState) {\n    CompletableFuture<String> authorizationCodeFuture = new CompletableFuture<>();\n    httpServer.createContext(\n        DEFAULT_REDIRECT_URI_ENDPOINT,\n        exchange -> {\n          Map<String, String> urlParams =\n              URLEncodedUtils.parse(exchange.getRequestURI(), StandardCharsets.UTF_8).stream()\n                  .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue));\n          String response =\n              AuthorizationCodeRedirectRequestHandler.handleRedirectRequest(\n                  urlParams, authorizationCodeFuture, expectedState);\n          exchange.sendResponseHeaders(200, response.length());\n          exchange.getResponseBody().write(response.getBytes(StandardCharsets.UTF_8));\n          exchange.getResponseBody().close();\n        });\n    logger.debug(\"Starting OAuth redirect URI server @ {}\", httpServer.getAddress());\n    httpServer.start();\n    return authorizationCodeFuture;\n  }\n\n  private static HttpServer createHttpServer(URI redirectUri) throws IOException {\n    return HttpServer.create(\n        new InetSocketAddress(redirectUri.getHost(), redirectUri.getPort()), 0);\n  }\n\n  private AuthorizationRequest buildAuthorizationRequest(\n      SFLoginInput loginInput, CodeVerifier pkceVerifier, State state, URI redirectUri)\n      throws SFException {\n    SFOauthLoginInput oauthLoginInput = loginInput.getOauthLoginInput();\n    ClientID clientID = new ClientID(oauthLoginInput.getClientId());\n    String scope = OAuthUtil.getScope(loginInput.getOauthLoginInput(), loginInput.getRole());\n    AuthorizationRequest.Builder builder =\n        new AuthorizationRequest.Builder(new ResponseType(ResponseType.Value.CODE), clientID)\n            .scope(new Scope(scope))\n            .state(state)\n            .redirectionURI(redirectUri)\n            .codeChallenge(pkceVerifier, CodeChallengeMethod.S256)\n            .endpointURI(\n                OAuthUtil.getAuthorizationUrl(\n                    loginInput.getOauthLoginInput(), loginInput.getServerUrl()));\n\n    if (loginInput.isDPoPEnabled()) {\n      builder.dPoPJWKThumbprintConfirmation(new DPoPUtil().getThumbprint());\n    }\n    return builder.build();\n  }\n\n  private HttpRequestBase buildTokenRequest(\n      SFLoginInput loginInput,\n      AuthorizationCode authorizationCode,\n      CodeVerifier pkceVerifier,\n      URI redirectUri,\n      String dpopNonce)\n      throws SFException {\n    AuthorizationGrant codeGrant =\n        new AuthorizationCodeGrant(authorizationCode, redirectUri, pkceVerifier);\n    ClientAuthentication clientAuthentication =\n        new ClientSecretBasic(\n            new ClientID(loginInput.getOauthLoginInput().getClientId()),\n            new Secret(loginInput.getOauthLoginInput().getClientSecret()));\n    Scope scope =\n        new Scope(OAuthUtil.getScope(loginInput.getOauthLoginInput(), loginInput.getRole()));\n    TokenRequest.Builder tokenRequestBuilder =\n        new TokenRequest.Builder(\n                OAuthUtil.getTokenRequestUrl(\n                    loginInput.getOauthLoginInput(), loginInput.getServerUrl()),\n                clientAuthentication,\n                codeGrant)\n            .scope(scope);\n    if (loginInput.getOauthLoginInput().getEnableSingleUseRefreshTokens()) {\n      tokenRequestBuilder.customParameter(\"enable_single_use_refresh_tokens\", \"true\");\n    }\n    TokenRequest tokenRequest = tokenRequestBuilder.build();\n    HTTPRequest tokenHttpRequest = tokenRequest.toHTTPRequest();\n    HttpRequestBase convertedTokenRequest = OAuthUtil.convertToBaseRequest(tokenHttpRequest);\n\n    if (loginInput.isDPoPEnabled()) {\n      dpopUtil.addDPoPProofHeaderToRequest(convertedTokenRequest, dpopNonce);\n    }\n\n    return convertedTokenRequest;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/OAuthClientCredentialsAccessTokenProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.nimbusds.oauth2.sdk.ClientCredentialsGrant;\nimport com.nimbusds.oauth2.sdk.Scope;\nimport com.nimbusds.oauth2.sdk.TokenRequest;\nimport com.nimbusds.oauth2.sdk.auth.ClientAuthentication;\nimport com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;\nimport com.nimbusds.oauth2.sdk.auth.Secret;\nimport com.nimbusds.oauth2.sdk.http.HTTPRequest;\nimport com.nimbusds.oauth2.sdk.id.ClientID;\nimport java.net.URI;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.jdbc.SnowflakeUseDPoPNonceException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpRequestBase;\n\npublic class OAuthClientCredentialsAccessTokenProvider implements AccessTokenProvider {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(OAuthClientCredentialsAccessTokenProvider.class);\n\n  private final DPoPUtil dpopUtil;\n\n  public OAuthClientCredentialsAccessTokenProvider() throws SFException {\n    this.dpopUtil = new DPoPUtil();\n  }\n\n  @Override\n  public TokenResponseDTO getAccessToken(SFLoginInput loginInput) throws SFException {\n    return exchangeClientCredentialsForAccessToken(loginInput, null, false);\n  }\n\n  @Override\n  public String getDPoPPublicKey() {\n    return dpopUtil.getPublicKey();\n  }\n\n  private TokenResponseDTO exchangeClientCredentialsForAccessToken(\n      SFLoginInput loginInput, String dpopNonce, boolean retried) throws SFException {\n    try {\n      logger.info(\"Starting OAuth client credentials authentication flow...\");\n      HttpRequestBase tokenRequest = buildTokenRequest(loginInput, dpopNonce);\n      return OAuthUtil.sendTokenRequest(tokenRequest, loginInput);\n    } catch (SnowflakeUseDPoPNonceException e) {\n      logger.debug(\"Received \\\"use_dpop_nonce\\\" error from IdP while performing token request\");\n      if (!retried) {\n        logger.debug(\"Retrying token request with DPoP nonce included...\");\n        return exchangeClientCredentialsForAccessToken(loginInput, e.getNonce(), true);\n      } else {\n        logger.debug(\"Skipping DPoP nonce retry as it has been already retried\");\n        throw e;\n      }\n    } catch (Exception | SFException e) {\n      logger.error(\n          \"Error during OAuth client credentials flow. Verify configuration passed to driver and IdP (URLs, grant types, scope, etc.)\",\n          e);\n      throw new SFException(e, ErrorCode.OAUTH_CLIENT_CREDENTIALS_FLOW_ERROR, e.getMessage());\n    }\n  }\n\n  private HttpRequestBase buildTokenRequest(SFLoginInput loginInput, String dpopNonce)\n      throws SFException {\n    URI tokenRequestUrl =\n        OAuthUtil.getTokenRequestUrl(loginInput.getOauthLoginInput(), loginInput.getServerUrl());\n    ClientAuthentication clientAuthentication =\n        new ClientSecretBasic(\n            new ClientID(loginInput.getOauthLoginInput().getClientId()),\n            new Secret(loginInput.getOauthLoginInput().getClientSecret()));\n    Scope scope =\n        new Scope(OAuthUtil.getScope(loginInput.getOauthLoginInput(), loginInput.getRole()));\n    TokenRequest tokenRequest =\n        new TokenRequest(\n            tokenRequestUrl, clientAuthentication, new ClientCredentialsGrant(), scope);\n    HTTPRequest tokenHttpRequest = tokenRequest.toHTTPRequest();\n    HttpRequestBase convertedTokenRequest = OAuthUtil.convertToBaseRequest(tokenHttpRequest);\n\n    if (loginInput.isDPoPEnabled()) {\n      dpopUtil.addDPoPProofHeaderToRequest(convertedTokenRequest, dpopNonce);\n    }\n\n    return convertedTokenRequest;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/OAuthUtil.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport static net.snowflake.client.internal.core.auth.oauth.OAuthAuthorizationCodeAccessTokenProvider.DEFAULT_REDIRECT_HOST;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.nimbusds.oauth2.sdk.http.HTTPRequest;\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.core.SFOauthLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.entity.StringEntity;\n\nclass OAuthUtil {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(OAuthUtil.class);\n\n  private static final String SNOWFLAKE_AUTHORIZE_ENDPOINT = \"/oauth/authorize\";\n  private static final String SNOWFLAKE_TOKEN_REQUEST_ENDPOINT = \"/oauth/token-request\";\n\n  private static final String DEFAULT_SESSION_ROLE_SCOPE_PREFIX = \"session:role:\";\n\n  static URI getTokenRequestUrl(SFOauthLoginInput oauthLoginInput, String serverUrl) {\n    URI uri =\n        !isNullOrEmpty(oauthLoginInput.getTokenRequestUrl())\n            ? URI.create(oauthLoginInput.getTokenRequestUrl())\n            : URI.create(serverUrl + SNOWFLAKE_TOKEN_REQUEST_ENDPOINT);\n    logIfHttpInUse(uri);\n    return uri.normalize();\n  }\n\n  private static void logIfHttpInUse(URI uri) {\n    if (uri.getScheme().equalsIgnoreCase(\"http\")) {\n      logger.warn(\"OAuth URL uses insecure HTTP protocol: {}\", uri);\n    }\n  }\n\n  static HttpRequestBase convertToBaseRequest(HTTPRequest request) {\n    HttpPost baseRequest = new HttpPost(request.getURI());\n    baseRequest.setEntity(new StringEntity(request.getBody(), StandardCharsets.UTF_8));\n    request\n        .getHeaderMap()\n        .forEach((key, values) -> values.forEach(value -> baseRequest.addHeader(key, value)));\n    return baseRequest;\n  }\n\n  static URI getAuthorizationUrl(SFOauthLoginInput oauthLoginInput, String serverUrl) {\n    URI uri =\n        !isNullOrEmpty(oauthLoginInput.getAuthorizationUrl())\n            ? URI.create(oauthLoginInput.getAuthorizationUrl())\n            : URI.create(serverUrl + SNOWFLAKE_AUTHORIZE_ENDPOINT);\n    logIfHttpInUse(uri);\n    return uri.normalize();\n  }\n\n  static String getScope(SFOauthLoginInput oauthLoginInput, String role) {\n    return (!isNullOrEmpty(oauthLoginInput.getScope()))\n        ? oauthLoginInput.getScope()\n        : DEFAULT_SESSION_ROLE_SCOPE_PREFIX + role;\n  }\n\n  static URI buildRedirectUri(SFOauthLoginInput oauthLoginInput) throws IOException {\n    String redirectUri =\n        !isNullOrEmpty(oauthLoginInput.getRedirectUri())\n            ? oauthLoginInput.getRedirectUri()\n            : createDefaultRedirectUri();\n    return URI.create(redirectUri);\n  }\n\n  static TokenResponseDTO sendTokenRequest(HttpRequestBase request, SFLoginInput loginInput)\n      throws SnowflakeSQLException, IOException {\n    URI requestUri = request.getURI();\n    logger.debug(\n        \"Requesting OAuth access token from: {}\", requestUri.getAuthority() + requestUri.getPath());\n    String tokenResponse =\n        HttpUtil.executeGeneralRequest(\n            request,\n            loginInput.getLoginTimeout(),\n            loginInput.getAuthTimeout(),\n            loginInput.getSocketTimeoutInMillis(),\n            0,\n            loginInput.getHttpClientSettingsKey(),\n            null);\n    ObjectMapper objectMapper = new ObjectMapper();\n    TokenResponseDTO tokenResponseDTO =\n        objectMapper.readValue(tokenResponse, TokenResponseDTO.class);\n    logger.debug(\n        \"Received OAuth access token of type \\\"{}\\\" from: {}{}\",\n        tokenResponseDTO.getTokenType(),\n        requestUri.getAuthority(),\n        requestUri.getPath());\n    return tokenResponseDTO;\n  }\n\n  private static String createDefaultRedirectUri() throws IOException {\n    try (ServerSocket socket = new ServerSocket(0)) {\n      return String.format(\"%s:%s\", DEFAULT_REDIRECT_HOST, socket.getLocalPort());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/RandomStateProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.nimbusds.oauth2.sdk.id.State;\n\npublic class RandomStateProvider implements StateProvider<String> {\n\n  private static final int STATE_BYTE_SIZE = 256;\n\n  @Override\n  public String getState() {\n    return new State(STATE_BYTE_SIZE).getValue();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/StateProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\npublic interface StateProvider<T> {\n  T getState();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/oauth/TokenResponseDTO.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class TokenResponseDTO {\n\n  private final String accessToken;\n  private final String refreshToken;\n  private final String tokenType;\n  private final String scope;\n  private final String username;\n  private final boolean idpInitiated;\n  private final long expiresIn;\n  private final long refreshTokenExpiresIn;\n\n  @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)\n  public TokenResponseDTO(\n      @JsonProperty(value = \"access_token\", required = true) String accessToken,\n      @JsonProperty(\"refresh_token\") String refreshToken,\n      @JsonProperty(\"token_type\") String tokenType,\n      @JsonProperty(\"scope\") String scope,\n      @JsonProperty(\"username\") String username,\n      @JsonProperty(\"idp_initiated\") boolean idpInitiated,\n      @JsonProperty(\"expires_in\") long expiresIn,\n      @JsonProperty(\"refresh_token_expires_in\") long refreshTokenExpiresIn) {\n    this.accessToken = accessToken;\n    this.tokenType = tokenType;\n    this.refreshToken = refreshToken;\n    this.scope = scope;\n    this.username = username;\n    this.idpInitiated = idpInitiated;\n    this.expiresIn = expiresIn;\n    this.refreshTokenExpiresIn = refreshTokenExpiresIn;\n  }\n\n  public String getAccessToken() {\n    return accessToken;\n  }\n\n  public String getTokenType() {\n    return tokenType;\n  }\n\n  public String getRefreshToken() {\n    return refreshToken;\n  }\n\n  public String getScope() {\n    return scope;\n  }\n\n  public long getExpiresIn() {\n    return expiresIn;\n  }\n\n  public String getUsername() {\n    return username;\n  }\n\n  public long getRefreshTokenExpiresIn() {\n    return refreshTokenExpiresIn;\n  }\n\n  public boolean isIdpInitiated() {\n    return idpInitiated;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/AwsAttestationService.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport java.time.Duration;\nimport java.util.List;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.jdbc.EnvironmentVariables;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport software.amazon.awssdk.auth.credentials.AwsCredentials;\nimport software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;\nimport software.amazon.awssdk.auth.credentials.AwsSessionCredentials;\nimport software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.http.SdkHttpRequest;\nimport software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner;\nimport software.amazon.awssdk.http.auth.spi.signer.SignRequest;\nimport software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;\nimport software.amazon.awssdk.identity.spi.AwsSessionCredentialsIdentity;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;\nimport software.amazon.awssdk.services.sts.StsClient;\nimport software.amazon.awssdk.services.sts.model.AssumeRoleRequest;\nimport software.amazon.awssdk.services.sts.model.AssumeRoleResponse;\nimport software.amazon.awssdk.services.sts.model.Credentials;\nimport software.amazon.awssdk.services.sts.model.GetCallerIdentityResponse;\n\npublic class AwsAttestationService {\n  public static final SFLogger logger = SFLoggerFactory.getLogger(AwsAttestationService.class);\n  public static final int TIMEOUT_MS = 10_000;\n  private static Region region;\n\n  private final AwsV4HttpSigner aws4Signer;\n\n  public AwsAttestationService() {\n    aws4Signer = AwsV4HttpSigner.create();\n  }\n\n  void initializeSignerRegion() {\n    logger.debug(\"Getting AWS region from environment variable\");\n    String envRegion = SnowflakeUtil.systemGetEnv(EnvironmentVariables.AWS_REGION.getName());\n    if (envRegion != null) {\n      region = Region.of(envRegion);\n    } else {\n      logger.debug(\"Getting AWS region from default region provider chain\");\n      region = DefaultAwsRegionProviderChain.builder().build().getRegion();\n    }\n  }\n\n  public AwsCredentials getAWSCredentials() {\n    try {\n      AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.create();\n      return credentialsProvider.resolveCredentials();\n    } catch (Exception e) {\n      logger.debug(\"Failed to retrieve AWS credentials: {}\", e.getMessage());\n      return null;\n    }\n  }\n\n  Region getAWSRegion() {\n    return region;\n  }\n\n  SdkHttpRequest signRequestWithSigV4(\n      SdkHttpRequest signableRequest, AwsCredentials awsCredentials) {\n    AwsCredentialsIdentity credentialsIdentity;\n    if (awsCredentials instanceof AwsSessionCredentials) {\n      AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) awsCredentials;\n\n      // Create AwsSessionCredentialsIdentity that properly includes the session token\n      credentialsIdentity =\n          AwsSessionCredentialsIdentity.create(\n              sessionCredentials.accessKeyId(),\n              sessionCredentials.secretAccessKey(),\n              sessionCredentials.sessionToken());\n    } else {\n      // For basic credentials, use AwsCredentialsIdentity\n      credentialsIdentity =\n          AwsCredentialsIdentity.create(\n              awsCredentials.accessKeyId(), awsCredentials.secretAccessKey());\n    }\n\n    SignRequest<AwsCredentialsIdentity> signRequest =\n        SignRequest.builder(credentialsIdentity)\n            .request(signableRequest)\n            .putProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, \"sts\")\n            .putProperty(AwsV4HttpSigner.REGION_NAME, getAWSRegion().toString())\n            .build();\n\n    return aws4Signer.sign(signRequest).request();\n  }\n\n  AwsCredentials assumeRole(AwsCredentials currentCredentials, String roleArn, String externalId)\n      throws SFException {\n    try (StsClient stsClient = createStsClient(currentCredentials, TIMEOUT_MS)) {\n\n      AssumeRoleRequest.Builder assumeRoleRequestBuilder =\n          AssumeRoleRequest.builder()\n              .roleArn(roleArn)\n              .roleSessionName(\"identity-federation-session\");\n      if (externalId != null && !externalId.isEmpty()) {\n        assumeRoleRequestBuilder.externalId(externalId);\n      }\n      AssumeRoleRequest assumeRoleRequest = assumeRoleRequestBuilder.build();\n\n      AssumeRoleResponse assumeRoleResponse = stsClient.assumeRole(assumeRoleRequest);\n      Credentials credentials = assumeRoleResponse.credentials();\n\n      logger.debug(\"Successfully assumed role: {}\", roleArn);\n\n      return AwsSessionCredentials.create(\n          credentials.accessKeyId(), credentials.secretAccessKey(), credentials.sessionToken());\n\n    } catch (Exception e) {\n      logger.error(\"Failed to assume role: {} - {}\", roleArn, e.getMessage());\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Failed to assume AWS role \" + roleArn + \": \" + e.getMessage());\n    }\n  }\n\n  AwsCredentials getCredentialsViaRoleChaining(SFLoginInput loginInput) throws SFException {\n    AwsCredentials currentCredentials = getAWSCredentials();\n    if (currentCredentials == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"No initial AWS credentials found for role chaining\");\n    }\n    List<String> impersonationPath = loginInput.getWorkloadIdentityImpersonationPath();\n    for (int i = 0; i < impersonationPath.size(); i++) {\n      String roleArn = impersonationPath.get(i);\n      logger.debug(\"Assuming role: {}\", roleArn);\n      String externalId =\n          (i == impersonationPath.size() - 1)\n              ? loginInput.getWorkloadIdentityAwsExternalId()\n              : null;\n      currentCredentials = assumeRole(currentCredentials, roleArn, externalId);\n      if (currentCredentials == null) {\n        throw new SFException(\n            ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"Failed to assume role: \" + roleArn);\n      }\n    }\n\n    return currentCredentials;\n  }\n\n  public String getCallerIdentityArn(AwsCredentials credentials, int timeoutMs) {\n    if (credentials == null) {\n      logger.debug(\"Cannot get caller identity with null credentials\");\n      return null;\n    }\n\n    Region region = getAWSRegion();\n    if (region == null) {\n      logger.debug(\"Cannot get caller identity without AWS region\");\n      return null;\n    }\n\n    try (StsClient stsClient = createStsClient(credentials, timeoutMs)) {\n      GetCallerIdentityResponse callerIdentity = stsClient.getCallerIdentity();\n\n      if (callerIdentity == null || callerIdentity.arn() == null) {\n        logger.debug(\"GetCallerIdentity returned null or missing ARN\");\n        return null;\n      }\n\n      return callerIdentity.arn();\n\n    } catch (Exception e) {\n      logger.debug(\"Failed to get caller identity ARN: {}\", e.getMessage());\n      return null;\n    }\n  }\n\n  private StsClient createStsClient(AwsCredentials credentials, int timeoutMs) {\n    return StsClient.builder()\n        .credentialsProvider(StaticCredentialsProvider.create(credentials))\n        .overrideConfiguration(config -> config.apiCallTimeout(Duration.ofMillis(timeoutMs)))\n        .region(getAWSRegion())\n        .build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/AwsIdentityAttestationCreator.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport java.io.ByteArrayInputStream;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.Collections;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport software.amazon.awssdk.auth.credentials.AwsCredentials;\nimport software.amazon.awssdk.http.SdkHttpFullRequest;\nimport software.amazon.awssdk.http.SdkHttpMethod;\nimport software.amazon.awssdk.http.SdkHttpRequest;\nimport software.amazon.awssdk.regions.Region;\n\npublic class AwsIdentityAttestationCreator implements WorkloadIdentityAttestationCreator {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(AwsIdentityAttestationCreator.class);\n  public static final String API_VERSION = \"2011-06-15\";\n  public static final String GET_CALLER_IDENTITY_ACTION = \"GetCallerIdentity\";\n\n  private final AwsAttestationService attestationService;\n  private final SFLoginInput loginInput;\n\n  public AwsIdentityAttestationCreator(\n      AwsAttestationService attestationService, SFLoginInput loginInput) {\n    this.attestationService = attestationService;\n    this.loginInput = loginInput;\n  }\n\n  @Override\n  public WorkloadIdentityAttestation createAttestation() throws SFException {\n    attestationService.initializeSignerRegion();\n    AwsCredentials awsCredentials;\n\n    if (loginInput.getWorkloadIdentityImpersonationPath().isEmpty()) {\n      logger.debug(\"Creating AWS identity attestation...\");\n      awsCredentials = attestationService.getAWSCredentials();\n    } else {\n      logger.debug(\"Creating AWS identity attestation with impersonation...\");\n      awsCredentials = attestationService.getCredentialsViaRoleChaining(loginInput);\n    }\n\n    if (awsCredentials == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"No AWS credentials were found\");\n    }\n    Region region = attestationService.getAWSRegion();\n    if (region == null) {\n      throw new SFException(ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"No AWS region was found\");\n    }\n\n    String stsHostname = getStsHostname(region.id());\n    SdkHttpRequest request = createStsRequest(stsHostname);\n    SdkHttpRequest signedRequest = attestationService.signRequestWithSigV4(request, awsCredentials);\n\n    String credential = createBase64EncodedRequestCredential(signedRequest);\n    return new WorkloadIdentityAttestation(\n        WorkloadIdentityProviderType.AWS, credential, Collections.emptyMap());\n  }\n\n  private String getStsHostname(String region) {\n    String domain = region.startsWith(\"cn-\") ? \"amazonaws.com.cn\" : \"amazonaws.com\";\n    return String.format(\"sts.%s.%s\", region, domain);\n  }\n\n  private SdkHttpRequest createStsRequest(String hostname) {\n    String url =\n        String.format(\n            \"https://%s?Action=%s&Version=%s\", hostname, GET_CALLER_IDENTITY_ACTION, API_VERSION);\n\n    SdkHttpFullRequest.Builder requestBuilder =\n        SdkHttpFullRequest.builder()\n            .method(SdkHttpMethod.POST)\n            .uri(URI.create(url))\n            .putHeader(\"Host\", hostname)\n            .putHeader(\n                WorkloadIdentityUtil.SNOWFLAKE_AUDIENCE_HEADER_NAME,\n                WorkloadIdentityUtil.SNOWFLAKE_AUDIENCE)\n            .contentStreamProvider(\n                () -> new ByteArrayInputStream(new byte[0])); // needed to properly sign the request\n\n    return requestBuilder.build();\n  }\n\n  private String createBase64EncodedRequestCredential(SdkHttpRequest request) {\n    JSONObject assertionJson = new JSONObject();\n    JSONObject headers = new JSONObject();\n\n    // AWS SDK 2 headers are Map<String, List<String>>, but backend expects Map<String, String>\n    request.headers().entrySet().stream()\n        .filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())\n        .forEach(entry -> headers.put(entry.getKey(), entry.getValue().get(0)));\n\n    assertionJson.put(\"url\", request.getUri().toString());\n    assertionJson.put(\"method\", request.method().toString());\n    assertionJson.put(\"headers\", headers);\n\n    String assertionJsonString = assertionJson.toString();\n    return Base64.getEncoder().encodeToString(assertionJsonString.getBytes(StandardCharsets.UTF_8));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/AzureAttestationService.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpRequestBase;\n\npublic class AzureAttestationService {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(AzureAttestationService.class);\n\n  // Expected to be set in Azure Functions environment\n  String getIdentityEndpoint() {\n    return SnowflakeUtil.systemGetEnv(\"IDENTITY_ENDPOINT\");\n  }\n\n  // Expected to be set in Azure Functions environment\n  String getIdentityHeader() {\n    return SnowflakeUtil.systemGetEnv(\"IDENTITY_HEADER\");\n  }\n\n  // Expected to be set in Azure Functions environment\n  String getClientId() {\n    return SnowflakeUtil.systemGetEnv(\"MANAGED_IDENTITY_CLIENT_ID\");\n  }\n\n  String fetchTokenFromMetadataService(HttpRequestBase tokenRequest, SFLoginInput loginInput)\n      throws SFException {\n    try {\n      return WorkloadIdentityUtil.performIdentityRequest(tokenRequest, loginInput);\n    } catch (Exception e) {\n      logger.error(\"Azure metadata server request failed\", e);\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Azure metadata server request was not successful: \" + e.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/AzureIdentityAttestationCreator.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static net.snowflake.client.internal.core.auth.wif.WorkloadIdentityUtil.DEFAULT_AZURE_METADATA_SERVICE_BASE_URL;\nimport static net.snowflake.client.internal.core.auth.wif.WorkloadIdentityUtil.SubjectAndIssuer;\nimport static net.snowflake.client.internal.core.auth.wif.WorkloadIdentityUtil.extractClaimsWithoutVerifyingSignature;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.base.Strings;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpGet;\n\npublic class AzureIdentityAttestationCreator implements WorkloadIdentityAttestationCreator {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(AzureIdentityAttestationCreator.class);\n  public static final ObjectMapper objectMapper = new ObjectMapper();\n\n  private static final String DEFAULT_WORKLOAD_IDENTITY_ENTRA_RESOURCE =\n      \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\";\n\n  private final AzureAttestationService azureAttestationService;\n  private final SFLoginInput loginInput;\n  private final String workloadIdentityEntraResource;\n  private final String azureMetadataServiceBaseUrl;\n\n  public AzureIdentityAttestationCreator(\n      AzureAttestationService azureAttestationService, SFLoginInput loginInput) {\n    this.azureAttestationService = azureAttestationService;\n    this.azureMetadataServiceBaseUrl = DEFAULT_AZURE_METADATA_SERVICE_BASE_URL;\n    this.loginInput = loginInput;\n    this.workloadIdentityEntraResource = getEntraResource(loginInput);\n  }\n\n  /** Only for testing purpose */\n  public AzureIdentityAttestationCreator(\n      AzureAttestationService azureAttestationService,\n      SFLoginInput loginInput,\n      String azureMetadataServiceBaseUrl) {\n    this.azureAttestationService = azureAttestationService;\n    this.azureMetadataServiceBaseUrl = azureMetadataServiceBaseUrl;\n    this.loginInput = loginInput;\n    this.workloadIdentityEntraResource = getEntraResource(loginInput);\n  }\n\n  @Override\n  public WorkloadIdentityAttestation createAttestation() throws SFException {\n    logger.debug(\"Creating Azure identity attestation...\");\n    String identityEndpoint = azureAttestationService.getIdentityEndpoint();\n    HttpGet request;\n    if (Strings.isNullOrEmpty(identityEndpoint)) {\n      request = createAzureVMIdentityRequest();\n    } else {\n      String identityHeader = azureAttestationService.getIdentityHeader();\n      if (Strings.isNullOrEmpty(identityHeader)) {\n        throw new SFException(\n            ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n            \"Managed identity is not enabled on this Azure function.\");\n      }\n      request =\n          createAzureFunctionsIdentityRequest(\n              identityEndpoint, identityHeader, azureAttestationService.getClientId());\n    }\n    if (!loginInput.getWorkloadIdentityImpersonationPath().isEmpty()) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Property 'workloadIdentityImpersonationPath' is not empty. Identity impersonation is not available on Azure.\");\n    }\n    String tokenJson = azureAttestationService.fetchTokenFromMetadataService(request, loginInput);\n    if (tokenJson == null) {\n      throw new SFException(ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"Could not fetch Azure token.\");\n    }\n    String token = extractTokenFromJson(tokenJson);\n    if (token == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"No access token found in Azure response.\");\n    }\n    SubjectAndIssuer claims = extractClaimsWithoutVerifyingSignature(token);\n    return new WorkloadIdentityAttestation(\n        WorkloadIdentityProviderType.AZURE, token, claims.toMap());\n  }\n\n  private String getEntraResource(SFLoginInput loginInput) {\n    if (!Strings.isNullOrEmpty(loginInput.getWorkloadIdentityEntraResource())) {\n      return loginInput.getWorkloadIdentityEntraResource();\n    } else {\n      return DEFAULT_WORKLOAD_IDENTITY_ENTRA_RESOURCE;\n    }\n  }\n\n  private String extractTokenFromJson(String tokenJson) throws SFException {\n    try {\n      JsonNode jsonNode = objectMapper.readTree(tokenJson);\n      return jsonNode.get(\"access_token\").asText();\n    } catch (Exception e) {\n      logger.error(\"Unable to extract token from Azure metadata response\", e);\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Unable to extract token from Azure metadata response: \" + e.getMessage());\n    }\n  }\n\n  private HttpGet createAzureFunctionsIdentityRequest(\n      String identityEndpoint, String identityHeader, String managedIdentityClientId) {\n    String queryParams = \"api-version=2019-08-01&resource=\" + workloadIdentityEntraResource;\n    if (managedIdentityClientId != null) {\n      queryParams += \"&client_id=\" + managedIdentityClientId;\n    }\n    HttpGet request = new HttpGet(String.format(\"%s?%s\", identityEndpoint, queryParams));\n    request.addHeader(\"X-IDENTITY-HEADER\", identityHeader);\n    return request;\n  }\n\n  private HttpGet createAzureVMIdentityRequest() {\n    String urlWithoutQueryString = azureMetadataServiceBaseUrl + \"/metadata/identity/oauth2/token?\";\n    String queryParams = \"api-version=2018-02-01&resource=\" + workloadIdentityEntraResource;\n    HttpGet request = new HttpGet(urlWithoutQueryString + queryParams);\n    request.setHeader(\"Metadata\", \"True\");\n    return request;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/GcpIdentityAttestationCreator.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static net.snowflake.client.internal.core.auth.wif.WorkloadIdentityUtil.DEFAULT_GCP_METADATA_SERVICE_BASE_URL;\nimport static net.snowflake.client.internal.core.auth.wif.WorkloadIdentityUtil.performIdentityRequest;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\n\npublic class GcpIdentityAttestationCreator implements WorkloadIdentityAttestationCreator {\n\n  private static final String METADATA_FLAVOR_HEADER_NAME = \"Metadata-Flavor\";\n  private static final String METADATA_FLAVOR = \"Google\";\n  private static final ObjectMapper objectMapper = new ObjectMapper();\n  private static final String DEFAULT_GCP_IAM_CREDENTIALS_URL =\n      \"https://iamcredentials.googleapis.com\";\n\n  private final String gcpMetadataServiceBaseUrl;\n  private final String gcpIamCredentialsBaseUrl;\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(GcpIdentityAttestationCreator.class);\n\n  private final SFLoginInput loginInput;\n\n  public GcpIdentityAttestationCreator(SFLoginInput loginInput) {\n    this.loginInput = loginInput;\n    this.gcpMetadataServiceBaseUrl = DEFAULT_GCP_METADATA_SERVICE_BASE_URL;\n    this.gcpIamCredentialsBaseUrl = DEFAULT_GCP_IAM_CREDENTIALS_URL;\n  }\n\n  /** Only for testing purpose */\n  GcpIdentityAttestationCreator(SFLoginInput loginInput, String gcpBaseUrl, String gcpIamUrl) {\n    this.loginInput = loginInput;\n    this.gcpMetadataServiceBaseUrl = gcpBaseUrl;\n    gcpIamCredentialsBaseUrl = gcpIamUrl;\n  }\n\n  @Override\n  public WorkloadIdentityAttestation createAttestation() throws SFException {\n    String token;\n\n    if (loginInput.getWorkloadIdentityImpersonationPath().isEmpty()) {\n      logger.debug(\"Creating GCP identity attestation...\");\n      token = getGcpIdentityTokenFromMetadataService();\n    } else {\n      logger.debug(\"Creating GCP identity attestation with impersonation...\");\n      token = getGcpIdentityTokenViaImpersonation();\n    }\n\n    if (token == null) {\n      throw new SFException(ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"No GCP token was found.\");\n    }\n    // if the token has been returned, we can assume that we're on GCP environment\n    WorkloadIdentityUtil.SubjectAndIssuer claims =\n        WorkloadIdentityUtil.extractClaimsWithoutVerifyingSignature(token);\n\n    return new WorkloadIdentityAttestation(\n        WorkloadIdentityProviderType.GCP,\n        token,\n        Collections.singletonMap(\"sub\", claims.getSubject()));\n  }\n\n  private String getGcpIdentityTokenFromMetadataService() throws SFException {\n    String uri =\n        gcpMetadataServiceBaseUrl\n            + \"/computeMetadata/v1/instance/service-accounts/default/identity?audience=\"\n            + WorkloadIdentityUtil.SNOWFLAKE_AUDIENCE;\n    return executeGcpTokenRequest(uri);\n  }\n\n  private String fetchTokenFromMetadataService() throws SFException {\n    String uri =\n        gcpMetadataServiceBaseUrl + \"/computeMetadata/v1/instance/service-accounts/default/token\";\n    String response = executeGcpTokenRequest(uri);\n    try {\n      JsonNode responseBody = objectMapper.readTree(response);\n      return responseBody.get(\"access_token\").asText();\n    } catch (JsonProcessingException e) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"GCP metadata server request for token was not successful: \" + e.getMessage());\n    }\n  }\n\n  private String executeGcpTokenRequest(String uri) throws SFException {\n    HttpGet tokenRequest = new HttpGet(uri);\n    tokenRequest.setHeader(METADATA_FLAVOR_HEADER_NAME, METADATA_FLAVOR);\n    try {\n      return performIdentityRequest(tokenRequest, loginInput);\n    } catch (Exception e) {\n      logger.error(\"GCP metadata server request was not successful\", e);\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"GCP metadata server request was not successful: \" + e.getMessage());\n    }\n  }\n\n  private String getGcpIdentityTokenViaImpersonation() throws SFException {\n    String accessToken = fetchTokenFromMetadataService();\n\n    List<String> fullServiceAccountPaths =\n        loginInput.getWorkloadIdentityImpersonationPath().stream()\n            .map(sa -> \"projects/-/serviceAccounts/\" + sa)\n            .collect(Collectors.toList());\n\n    String targetServiceAccount = fullServiceAccountPaths.get(fullServiceAccountPaths.size() - 1);\n    List<String> delegates = fullServiceAccountPaths.subList(0, fullServiceAccountPaths.size() - 1);\n\n    String url =\n        String.format(gcpIamCredentialsBaseUrl + \"/v1/%s:generateIdToken\", targetServiceAccount);\n\n    HttpPost request = new HttpPost(url);\n    request.setHeader(\"Authorization\", \"Bearer \" + accessToken);\n    request.setHeader(\"Content-Type\", \"application/json\");\n\n    Map<String, Object> requestBody = new HashMap<>();\n    requestBody.put(\"delegates\", delegates);\n    requestBody.put(\"audience\", WorkloadIdentityUtil.SNOWFLAKE_AUDIENCE);\n\n    try {\n      String json = objectMapper.writeValueAsString(requestBody);\n      request.setEntity(new StringEntity(json, StandardCharsets.UTF_8));\n\n      String response = performIdentityRequest(request, loginInput);\n      JsonNode responseBody = objectMapper.readTree(response);\n      return responseBody.get(\"token\").asText();\n    } catch (Exception e) {\n      logger.error(\"Error fetching GCP impersonated identity token\", e);\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Error fetching GCP impersonated identity token: \" + e.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/OidcIdentityAttestationCreator.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class OidcIdentityAttestationCreator implements WorkloadIdentityAttestationCreator {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(OidcIdentityAttestationCreator.class);\n\n  private final String token;\n\n  public OidcIdentityAttestationCreator(String token) {\n    this.token = token;\n  }\n\n  @Override\n  public WorkloadIdentityAttestation createAttestation() throws SFException {\n    logger.debug(\"Creating OIDC identity attestation...\");\n    if (token == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"No OIDC token was specified. Please provide it in `token` property.\");\n    }\n    WorkloadIdentityUtil.SubjectAndIssuer claims =\n        WorkloadIdentityUtil.extractClaimsWithoutVerifyingSignature(token);\n    return new WorkloadIdentityAttestation(\n        WorkloadIdentityProviderType.OIDC, token, claims.toMap());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/PlatformDetectionUtil.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport java.io.IOException;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport software.amazon.awssdk.auth.credentials.AwsCredentials;\n\npublic class PlatformDetectionUtil {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(PlatformDetectionUtil.class);\n\n  // Regex patterns for validating AWS ARN for WIF\n  private static final Pattern IAM_USER_ARN_PATTERN =\n      Pattern.compile(\"^arn:[^:]+:iam::[^:]+:user/.+$\");\n  private static final Pattern ASSUMED_ROLE_ARN_PATTERN =\n      Pattern.compile(\"^arn:[^:]+:sts::[^:]+:assumed-role/.+$\");\n\n  public static String performPlatformDetectionRequest(HttpRequestBase httpRequest, int timeoutMs)\n      throws SnowflakeSQLException, IOException {\n    httpRequest.setConfig(\n        RequestConfig.custom()\n            .setConnectTimeout(timeoutMs)\n            .setSocketTimeout(timeoutMs)\n            .setConnectionRequestTimeout(timeoutMs)\n            .build());\n    return HttpUtil.executeGeneralRequestOmitSnowflakeHeaders(\n        httpRequest,\n        1,\n        timeoutMs / 1000,\n        timeoutMs,\n        0,\n        new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS),\n        null);\n  }\n\n  public static boolean hasValidAwsIdentityForWif(\n      AwsAttestationService attestationService, int timeoutMs) {\n    try {\n      AwsCredentials credentials = attestationService.getAWSCredentials();\n      if (!hasValidAwsCredentials(credentials)) {\n        logger.debug(\"No valid AWS credentials available for identity validation\");\n        return false;\n      }\n      String arn = attestationService.getCallerIdentityArn(credentials, timeoutMs);\n      if (arn == null) {\n        logger.debug(\"Failed to retrieve caller identity ARN\");\n        return false;\n      }\n\n      boolean isValid = isValidArnForWif(arn);\n      if (isValid) {\n        logger.debug(\"Valid AWS identity found with ARN: {}\", arn);\n      } else {\n        logger.debug(\"ARN is not valid for WIF: {}\", arn);\n      }\n      return isValid;\n    } catch (Exception e) {\n      logger.debug(\"Failed to validate AWS identity: {}\", e.getMessage());\n      return false;\n    }\n  }\n\n  public static boolean isValidArnForWif(String arn) {\n    if (arn == null || arn.trim().isEmpty()) {\n      return false;\n    }\n    return IAM_USER_ARN_PATTERN.matcher(arn).matches()\n        || ASSUMED_ROLE_ARN_PATTERN.matcher(arn).matches();\n  }\n\n  private static boolean hasValidAwsCredentials(AwsCredentials awsCredentials) {\n    if (awsCredentials == null) {\n      logger.debug(\"No AWS credentials found\");\n      return false;\n    }\n\n    String accessKey = awsCredentials.accessKeyId();\n    String secretKey = awsCredentials.secretAccessKey();\n\n    if (SnowflakeUtil.isNullOrEmpty(accessKey) || SnowflakeUtil.isNullOrEmpty(secretKey)) {\n      logger.debug(\"AWS credentials are incomplete\");\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/WorkloadIdentityAttestation.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport java.util.Map;\n\npublic class WorkloadIdentityAttestation {\n\n  private final WorkloadIdentityProviderType provider;\n  private final String credential;\n  private final Map<String, String> userIdentifierComponents;\n\n  WorkloadIdentityAttestation(\n      WorkloadIdentityProviderType provider,\n      String credential,\n      Map<String, String> userIdentifierComponents) {\n    this.provider = provider;\n    this.credential = credential;\n    this.userIdentifierComponents = userIdentifierComponents;\n  }\n\n  public WorkloadIdentityProviderType getProvider() {\n    return provider;\n  }\n\n  public String getCredential() {\n    return credential;\n  }\n\n  public Map<String, String> getUserIdentifierComponents() {\n    return userIdentifierComponents;\n  }\n\n  @Override\n  public String toString() {\n    return \"WorkloadIdentityAttestation{\"\n        + \"provider=\"\n        + provider\n        + \", credential='\"\n        + credential\n        + '\\''\n        + \", userIdentifierComponents=\"\n        + userIdentifierComponents\n        + '}';\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/WorkloadIdentityAttestationCreator.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport net.snowflake.client.internal.core.SFException;\n\ninterface WorkloadIdentityAttestationCreator {\n\n  /**\n   * @return corresponding attestation or null if it couldn't be loaded\n   */\n  WorkloadIdentityAttestation createAttestation() throws SFException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/WorkloadIdentityAttestationProvider.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport java.util.Arrays;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class WorkloadIdentityAttestationProvider {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(WorkloadIdentityAttestationProvider.class);\n\n  private final AwsIdentityAttestationCreator awsAttestationCreator;\n  private final GcpIdentityAttestationCreator gcpAttestationCreator;\n  private final AzureIdentityAttestationCreator azureAttestationCreator;\n  private final OidcIdentityAttestationCreator oidcAttestationCreator;\n\n  public WorkloadIdentityAttestationProvider(\n      AwsIdentityAttestationCreator awsAttestationCreator,\n      GcpIdentityAttestationCreator gcpAttestationCreator,\n      AzureIdentityAttestationCreator azureAttestationCreator,\n      OidcIdentityAttestationCreator oidcAttestationCreator) {\n    this.awsAttestationCreator = awsAttestationCreator;\n    this.gcpAttestationCreator = gcpAttestationCreator;\n    this.azureAttestationCreator = azureAttestationCreator;\n    this.oidcAttestationCreator = oidcAttestationCreator;\n  }\n\n  public WorkloadIdentityAttestation getAttestation(String identityProvider) throws SFException {\n    return getCreator(identityProvider).createAttestation();\n  }\n\n  WorkloadIdentityAttestationCreator getCreator(String identityProvider) throws SFException {\n    if (WorkloadIdentityProviderType.AWS.name().equalsIgnoreCase(identityProvider)) {\n      return awsAttestationCreator;\n    } else if (WorkloadIdentityProviderType.GCP.name().equalsIgnoreCase(identityProvider)) {\n      return gcpAttestationCreator;\n    } else if (WorkloadIdentityProviderType.AZURE.name().equalsIgnoreCase(identityProvider)) {\n      return azureAttestationCreator;\n    } else if (WorkloadIdentityProviderType.OIDC.name().equalsIgnoreCase(identityProvider)) {\n      return oidcAttestationCreator;\n    } else {\n      String validValues =\n          Arrays.stream(WorkloadIdentityProviderType.values())\n              .map(Enum::name)\n              .collect(Collectors.joining(\", \"));\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR,\n          \"Unknown Workload Identity provider specified: \"\n              + identityProvider\n              + \", valid values are: \"\n              + validValues);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/WorkloadIdentityProviderType.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nenum WorkloadIdentityProviderType {\n  AWS, // Provider that builds an encoded pre-signed GetCallerIdentity request using the current\n  // workload's IAM role.\n  AZURE, // Provider that requests an OAuth access token for the workload's managed identity.\n  GCP, // Provider that requests an ID token for the workload's attached service account.\n  OIDC // Provider that looks for an OIDC ID token.\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/auth/wif/WorkloadIdentityUtil.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport com.nimbusds.jwt.JWT;\nimport com.nimbusds.jwt.JWTParser;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpRequestBase;\n\n/**\n * Utility class for Workload Identity Federation (WIF) specific operations. This class contains\n * functions that are used exclusively within the WIF package.\n */\npublic class WorkloadIdentityUtil {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(WorkloadIdentityUtil.class);\n\n  // GCP metadata service uses a hostname that resolves to both IPv4 and IPv6 via DNS\n  public static final String DEFAULT_GCP_METADATA_SERVICE_BASE_URL =\n      \"http://metadata.google.internal\";\n  // Azure instance metadata service (IPv4 only; Azure does not expose an IPv6 IMDS endpoint)\n  public static final String DEFAULT_AZURE_METADATA_SERVICE_BASE_URL = \"http://169.254.169.254\";\n\n  public static final String SNOWFLAKE_AUDIENCE_HEADER_NAME = \"X-Snowflake-Audience\";\n  public static final String SNOWFLAKE_AUDIENCE = \"snowflakecomputing.com\";\n\n  /**\n   * Performs an HTTP request for WIF identity token retrieval. This method is used by WIF\n   * authentication flows to communicate with cloud metadata services.\n   */\n  public static String performIdentityRequest(HttpRequestBase tokenRequest, SFLoginInput loginInput)\n      throws SnowflakeSQLException, IOException {\n    return HttpUtil.executeGeneralRequestOmitSnowflakeHeaders(\n        tokenRequest,\n        loginInput.getLoginTimeout(),\n        3, // 3s timeout\n        loginInput.getSocketTimeoutInMillis(),\n        0,\n        loginInput.getHttpClientSettingsKey(),\n        null);\n  }\n\n  /**\n   * Extracts claims (subject and issuer) from a JWT token without verifying the signature. This is\n   * used in WIF flows where signature verification is handled elsewhere.\n   */\n  public static SubjectAndIssuer extractClaimsWithoutVerifyingSignature(String token)\n      throws SFException {\n    Map<String, Object> claims = extractClaimsMap(token);\n    if (claims == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"Failed to parse JWT and extract claims\");\n    }\n    String issuer = (String) claims.get(\"iss\");\n    if (issuer == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"Missing issuer claim in JWT token\");\n    }\n    String subject = (String) claims.get(\"sub\");\n    if (subject == null) {\n      throw new SFException(\n          ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR, \"Missing sub claim in JWT token\");\n    }\n    return new SubjectAndIssuer(subject, issuer);\n  }\n\n  private static Map<String, Object> extractClaimsMap(String token) {\n    try {\n      JWT jwt = JWTParser.parse(token);\n      return jwt.getJWTClaimsSet().getClaims();\n    } catch (Exception e) {\n      logger.error(\"Unable to extract JWT claims from token: {}\", e);\n      return null;\n    }\n  }\n\n  /**\n   * Container class for JWT subject and issuer claims. Used in WIF authentication flows to pass\n   * extracted token claims.\n   */\n  public static class SubjectAndIssuer {\n    private final String subject;\n    private final String issuer;\n\n    public SubjectAndIssuer(String subject, String issuer) {\n      this.issuer = issuer;\n      this.subject = subject;\n    }\n\n    public String getIssuer() {\n      return issuer;\n    }\n\n    public String getSubject() {\n      return subject;\n    }\n\n    public Map<String, String> toMap() {\n      Map<String, String> claims = new HashMap<>();\n      claims.put(\"iss\", issuer);\n      claims.put(\"sub\", subject);\n      return claims;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/bind/BindException.java",
    "content": "package net.snowflake.client.internal.core.bind;\n\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\n\npublic class BindException extends Exception {\n  private static final long serialVersionUID = 1L;\n\n  public enum Type {\n    SERIALIZATION(TelemetryField.FAILED_BIND_SERIALIZATION),\n    UPLOAD(TelemetryField.FAILED_BIND_UPLOAD),\n    OTHER(TelemetryField.FAILED_BIND_OTHER);\n\n    public final TelemetryField field;\n\n    Type(TelemetryField field) {\n      this.field = field;\n    }\n  }\n\n  public final Type type;\n\n  public BindException(String msg, Type type) {\n    super(msg);\n    this.type = type;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/bind/BindUploader.java",
    "content": "package net.snowflake.client.internal.core.bind;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.sql.SQLException;\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.time.LocalTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.time.format.DateTimeFormatterBuilder;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SFBaseFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SFPair;\nimport net.snowflake.common.core.SqlState;\n\npublic class BindUploader implements Closeable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(BindUploader.class);\n\n  // session of the uploader\n  private final SFBaseSession session;\n\n  // fully-qualified stage path to upload binds to\n  private final String stagePath;\n\n  // whether the uploader has completed\n  private boolean closed = false;\n\n  // size (bytes) of max input stream (10MB default)\n  private long inputStreamBufferSize = 1024 * 1024 * 10;\n\n  private int fileCount = 0;\n\n  private final DateTimeFormatter timestampFormatter =\n      new DateTimeFormatterBuilder()\n          .append(DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss.SSSSSSSSS \"))\n          .appendOffset(\"+HH:MM\", \"Z\")\n          .toFormatter();\n  private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\");\n  private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(\"HH:mm:ss.SSSSSSSSS\");\n\n  private final String createStageSQL;\n\n  static class ColumnTypeDataPair {\n    public String type;\n    public List<String> data;\n\n    ColumnTypeDataPair(String type, List<String> data) {\n      this.type = type;\n      this.data = data;\n    }\n  }\n\n  /**\n   * Create a new BindUploader which will write binds to the *existing* bindDir and upload them to\n   * the given stageDir\n   *\n   * @param session the session to use for uploading binds\n   * @param stageDir the stage path to upload to\n   */\n  private BindUploader(SFBaseSession session, String stageDir) {\n    this.session = session;\n    this.stagePath = \"@\" + session.getSfConnectionHandler().getBindStageName() + \"/\" + stageDir;\n    this.createStageSQL =\n        \"CREATE TEMPORARY STAGE IF NOT EXISTS \"\n            + session.getSfConnectionHandler().getBindStageName()\n            + \" file_format=(\"\n            + \" type=csv\"\n            + \" field_optionally_enclosed_by='\\\"'\"\n            + \")\";\n  }\n\n  private synchronized String synchronizedDateFormat(String o) {\n    if (o == null) {\n      return null;\n    }\n    long millis = Long.parseLong(o);\n    Instant instant = Instant.ofEpochMilli(millis);\n    LocalDate localDate = instant.atZone(ZoneOffset.UTC).toLocalDate();\n    return localDate.format(this.dateFormatter);\n  }\n\n  private synchronized String synchronizedTimeFormat(String o) {\n    if (o == null) {\n      return null;\n    }\n    SFPair<Long, Integer> times = getNanosAndSecs(o, false);\n    long sec = times.left;\n    int nano = times.right;\n\n    LocalTime time = Instant.ofEpochSecond(sec, nano).atZone(ZoneOffset.UTC).toLocalTime();\n    return time.format(timeFormatter);\n  }\n\n  private SFPair<Long, Integer> getNanosAndSecs(String o, boolean isNegative) {\n    String inpString = o;\n    if (isNegative) {\n      inpString = o.substring(1);\n    }\n\n    long sec;\n    int nano;\n    if (inpString.length() < 10) {\n      sec = 0;\n      nano = Integer.parseInt(inpString);\n    } else {\n      sec = Long.parseLong(inpString.substring(0, inpString.length() - 9));\n      nano = Integer.parseInt(inpString.substring(inpString.length() - 9));\n    }\n    if (isNegative) {\n      // adjust the timestamp\n      sec = -1 * sec;\n      if (nano > 0) {\n        nano = 1000000000 - nano;\n        sec--;\n      }\n    }\n    return SFPair.of(sec, nano);\n  }\n\n  private synchronized String synchronizedTimestampFormat(String o, String type) {\n    if (o == null) {\n      return null;\n    }\n\n    boolean isNegative = o.length() > 0 && o.charAt(0) == '-';\n    SFPair<Long, Integer> times = getNanosAndSecs(o, isNegative);\n    long sec = times.left;\n    int nano = times.right;\n\n    Instant instant = Instant.ofEpochSecond(sec, nano);\n\n    // For timestamp_ntz, use UTC timezone. For timestamp_ltz, use the local timezone to minimise\n    // the gap.\n    if (\"TIMESTAMP_LTZ\".equals(type)) {\n      ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());\n      return zdt.format(timestampFormatter);\n    } else {\n      ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);\n      return zdt.format(timestampFormatter);\n    }\n  }\n\n  /**\n   * Create a new BindUploader which will upload to the given stage path. Note that no temporary\n   * file or directory is created anymore. Instead, streaming uploading is used.\n   *\n   * @param session the session to use for uploading binds\n   * @param stageDir the stage path to upload to\n   * @return BindUploader instance\n   */\n  public static synchronized BindUploader newInstance(SFBaseSession session, String stageDir) {\n    return new BindUploader(session, stageDir);\n  }\n\n  /**\n   * Wrapper around upload() with default compression to true.\n   *\n   * @param bindValues the bind map to upload\n   * @throws BindException if there is an error when uploading bind values\n   * @throws SQLException if any error occurs\n   */\n  public void upload(Map<String, ParameterBindingDTO> bindValues)\n      throws BindException, SQLException {\n    upload(bindValues, true);\n  }\n\n  /**\n   * Upload bind parameters via streaming. This replaces previous function upload function where\n   * binds were written to a file which was then uploaded with a PUT statement.\n   *\n   * @param bindValues the bind map to upload\n   * @param compressData whether or not to compress data\n   * @throws BindException if there is an error when uploading bind values\n   * @throws SQLException if any error occurs\n   */\n  public void upload(Map<String, ParameterBindingDTO> bindValues, boolean compressData)\n      throws BindException, SQLException {\n    if (!closed) {\n      List<ColumnTypeDataPair> columns = getColumnValues(bindValues);\n      List<byte[]> bindingRows = buildRowsAsBytes(columns);\n      int startIndex = 0;\n      int numBytes = 0;\n      int rowNum = 0;\n      fileCount = 0;\n\n      while (rowNum < bindingRows.size()) {\n        // create a list of byte arrays\n        while (numBytes < inputStreamBufferSize && rowNum < bindingRows.size()) {\n          numBytes += bindingRows.get(rowNum).length;\n          rowNum++;\n        }\n        // concatenate all byte arrays into 1 and put into input stream\n        ByteBuffer bb = ByteBuffer.allocate(numBytes);\n        for (int i = startIndex; i < rowNum; i++) {\n          bb.put(bindingRows.get(i));\n        }\n        byte[] finalBytearray = bb.array();\n        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(finalBytearray)) {\n          // do the upload\n          String fileName = Integer.toString(++fileCount);\n          uploadStreamInternal(inputStream, fileName, compressData);\n          startIndex = rowNum;\n          numBytes = 0;\n        } catch (IOException ex) {\n          throw new BindException(\n              String.format(\n                  \"Failure using inputstream to upload bind data. Message: %s\", ex.getMessage()),\n              BindException.Type.SERIALIZATION);\n        }\n      }\n    }\n  }\n\n  /**\n   * Method to put data from a stream at a stage location. The data will be uploaded as one file. No\n   * splitting is done in this method. Similar to uploadStreamInternal() in SnowflakeConnectionImpl.\n   *\n   * <p>Stream size must match the total size of data in the input stream unless compressData\n   * parameter is set to true.\n   *\n   * <p>caller is responsible for passing the correct size for the data in the stream and releasing\n   * the inputStream after the method is called.\n   *\n   * @param inputStream input stream from which the data will be uploaded\n   * @param destFileName destination file name to use\n   * @param compressData whether compression is requested fore uploading data\n   * @throws SQLException raises if any error occurs\n   * @throws BindException if there is an error when uploading bind values\n   */\n  private void uploadStreamInternal(\n      InputStream inputStream, String destFileName, boolean compressData)\n      throws SQLException, BindException {\n\n    createStageIfNeeded();\n    String stageName = stagePath;\n    logger.debug(\n        \"upload data from stream: stageName={}\" + \", destFileName={}\", stageName, destFileName);\n\n    if (stageName == null) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"stage name is null\");\n    }\n\n    if (destFileName == null) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"stage name is null\");\n    }\n\n    SFBaseStatement stmt = session.getSfConnectionHandler().getSFStatement();\n\n    StringBuilder putCommand = new StringBuilder();\n\n    // use a placeholder for source file\n    putCommand.append(\"put file:///tmp/placeholder \");\n\n    // add stage name surrounded by quotations in case special chars are used in directory name\n    putCommand.append(\"'\");\n    putCommand.append(stageName);\n    putCommand.append(\"'\");\n\n    putCommand.append(\" overwrite=true\");\n\n    SFBaseFileTransferAgent transferAgent =\n        session.getSfConnectionHandler().getFileTransferAgent(putCommand.toString(), stmt);\n\n    transferAgent.setDestStagePath(stagePath);\n    transferAgent.setSourceStream(inputStream);\n    transferAgent.setDestFileNameForStreamSource(destFileName);\n    transferAgent.setCompressSourceFromStream(compressData);\n    transferAgent.execute();\n\n    stmt.close();\n  }\n\n  /**\n   * Convert bind map to a list of values for each column Perform necessary type casts and invariant\n   * checks\n   *\n   * @param bindValues the bind map to convert\n   * @return list of values for each column\n   * @throws BindException if bind map is improperly formed\n   */\n  private List<ColumnTypeDataPair> getColumnValues(Map<String, ParameterBindingDTO> bindValues)\n      throws BindException {\n    List<ColumnTypeDataPair> columns = new ArrayList<>(bindValues.size());\n    for (int i = 1; i <= bindValues.size(); i++) {\n      // bindValues should have n entries with string keys 1 ... n and list values\n      String key = Integer.toString(i);\n      if (!bindValues.containsKey(key)) {\n        throw new BindException(\n            String.format(\n                \"Bind map with %d columns should contain key \\\"%d\\\"\", bindValues.size(), i),\n            BindException.Type.SERIALIZATION);\n      }\n\n      ParameterBindingDTO value = bindValues.get(key);\n      try {\n        String type = value.getType();\n        List<?> list = (List<?>) value.getValue();\n        List<String> convertedList = new ArrayList<>(list.size());\n        if (\"TIMESTAMP_LTZ\".equals(type) || \"TIMESTAMP_NTZ\".equals(type)) {\n          for (Object e : list) {\n            convertedList.add(synchronizedTimestampFormat((String) e, type));\n          }\n        } else if (\"DATE\".equals(type)) {\n          for (Object e : list) {\n            convertedList.add(synchronizedDateFormat((String) e));\n          }\n        } else if (\"TIME\".equals(type)) {\n          for (Object e : list) {\n            convertedList.add(synchronizedTimeFormat((String) e));\n          }\n        } else {\n          for (Object e : list) {\n            convertedList.add((String) e);\n          }\n        }\n        columns.add(i - 1, new ColumnTypeDataPair(type, convertedList));\n      } catch (ClassCastException ex) {\n        throw new BindException(\n            \"Value in binding DTO could not be cast to a list\", BindException.Type.SERIALIZATION);\n      }\n    }\n    return columns;\n  }\n\n  /**\n   * Transpose a list of columns and their values to a list of rows in bytes instead of strings\n   *\n   * @param columns the list of columns to transpose\n   * @return list of rows\n   * @throws BindException if columns improperly formed\n   */\n  private List<byte[]> buildRowsAsBytes(List<ColumnTypeDataPair> columns) throws BindException {\n    List<byte[]> rows = new ArrayList<>();\n    int numColumns = columns.size();\n    // columns should have binds\n    if (columns.get(0).data.isEmpty()) {\n      throw new BindException(\"No binds found in first column\", BindException.Type.SERIALIZATION);\n    }\n\n    int numRows = columns.get(0).data.size();\n    // every column should have the same number of binds\n    for (int i = 0; i < numColumns; i++) {\n      int iNumRows = columns.get(i).data.size();\n      if (columns.get(i).data.size() != numRows) {\n        throw new BindException(\n            String.format(\n                \"Column %d has a different number of binds (%d) than column 1 (%d)\",\n                i, iNumRows, numRows),\n            BindException.Type.SERIALIZATION);\n      }\n    }\n\n    for (int rowIdx = 0; rowIdx < numRows; rowIdx++) {\n      String[] row = new String[numColumns];\n      for (int colIdx = 0; colIdx < numColumns; colIdx++) {\n        row[colIdx] = columns.get(colIdx).data.get(rowIdx);\n      }\n      rows.add(createCSVRecord(row));\n    }\n\n    return rows;\n  }\n\n  /**\n   * Serialize row to a csv Duplicated from StreamLoader class\n   *\n   * @param data the row to create a csv record from\n   * @return serialized csv for row\n   */\n  private byte[] createCSVRecord(String[] data) {\n    StringBuilder sb = new StringBuilder(1024);\n\n    for (int i = 0; i < data.length; ++i) {\n      if (i > 0) {\n        sb.append(',');\n      }\n      sb.append(SnowflakeTypeUtil.escapeForCSV(data[i]));\n    }\n    sb.append('\\n');\n    return sb.toString().getBytes(UTF_8);\n  }\n\n  /**\n   * Check whether the session's temporary stage has been created, and create it if not.\n   *\n   * @throws BindException if creating the stage fails\n   */\n  private void createStageIfNeeded() throws BindException {\n    if (session.getArrayBindStage() != null) {\n      return;\n    }\n    synchronized (session) {\n      // another thread may have created the session by the time we enter this block\n      if (session.getArrayBindStage() == null) {\n        try {\n          SFBaseStatement statement = session.getSfConnectionHandler().getSFStatement();\n          statement.execute(createStageSQL, null, null, new ExecTimeTelemetryData());\n          session.setArrayBindStage(session.getSfConnectionHandler().getBindStageName());\n        } catch (SFException | SQLException ex) {\n          // to avoid repeated failures to create stage, disable array bind stage\n          // optimization if we fail to create stage for some reason\n          session.setArrayBindStageThreshold(0);\n          throw new BindException(\n              String.format(\n                  \"Failed to create temporary stage for array binds. %s\", ex.getMessage()),\n              BindException.Type.UPLOAD);\n        }\n      }\n    }\n  }\n\n  /**\n   * Close uploader, deleting the local temporary directory\n   *\n   * <p>This class can be used in a try-with-resources statement, which ensures that the temporary\n   * directory is cleaned up even when exceptions occur\n   */\n  @Override\n  public void close() {\n    if (!closed) {\n      closed = true;\n    }\n  }\n\n  /**\n   * Set the approximate maximum size in bytes for a single bind file\n   *\n   * @param bufferSize size in bytes\n   */\n  public void setInputStreamBufferSize(int bufferSize) {\n    this.inputStreamBufferSize = bufferSize;\n  }\n\n  /**\n   * Return the number of files that binding data is split into on internal stage. Used for testing\n   * purposes.\n   *\n   * @return number of files that binding data is split into on internal stage\n   */\n  public int getFileCount() {\n    return this.fileCount;\n  }\n\n  /**\n   * Return the stage path to which binds are uploaded\n   *\n   * @return the stage path\n   */\n  public String getStagePath() {\n    return this.stagePath;\n  }\n\n  /**\n   * Compute the number of array bind values in the given bind map\n   *\n   * @param bindValues the bind map\n   * @return 0 if bindValues is null, has no binds, or is not an array bind n otherwise, where n is\n   *     the number of binds in the array bind\n   */\n  public static int arrayBindValueCount(Map<String, ParameterBindingDTO> bindValues) {\n    if (!isArrayBind(bindValues)) {\n      return 0;\n    } else {\n      ParameterBindingDTO bindSample = bindValues.values().iterator().next();\n      List<?> bindSampleValues = (List<?>) bindSample.getValue();\n      return bindValues.size() * bindSampleValues.size();\n    }\n  }\n\n  /**\n   * Return whether the bind map uses array binds\n   *\n   * @param bindValues the bind map\n   * @return whether the bind map uses array binds\n   */\n  public static boolean isArrayBind(Map<String, ParameterBindingDTO> bindValues) {\n    if (bindValues == null || bindValues.size() == 0) {\n      return false;\n    }\n    ParameterBindingDTO bindSample = bindValues.values().iterator().next();\n    return bindSample.getValue() instanceof List;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLCache.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\ninterface CRLCache {\n  CRLCacheEntry get(String crlUrl);\n\n  void put(String crlUrl, CRLCacheEntry entry);\n\n  void cleanup();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLCacheConfig.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport net.snowflake.client.internal.core.FileCacheUtil;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class CRLCacheConfig {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CRLCacheConfig.class);\n  public static final String ENABLE_CRL_IN_MEMORY_CACHING = \"ENABLE_CRL_IN_MEMORY_CACHING\";\n  public static final String ENABLE_CRL_DISK_CACHING = \"ENABLE_CRL_DISK_CACHING\";\n  public static final String CRL_CACHE_VALIDITY_TIME = \"CRL_CACHE_VALIDITY_TIME\";\n  public static final String CRL_RESPONSE_CACHE_DIR = \"CRL_RESPONSE_CACHE_DIR\";\n  public static final String CRL_ON_DISK_CACHE_REMOVAL_DELAY = \"CRL_ON_DISK_CACHE_REMOVAL_DELAY\";\n  public static final String CRL_DOWNLOAD_MAX_SIZE_BYTES = \"CRL_DOWNLOAD_MAX_SIZE_BYTES\";\n  private static final long DEFAULT_CRL_DOWNLOAD_MAX_SIZE_BYTES = 20L * 1024 * 1024; // 20 MB\n\n  public static boolean getInMemoryCacheEnabled() {\n    return SnowflakeUtil.convertSystemPropertyToBooleanValue(ENABLE_CRL_IN_MEMORY_CACHING, true);\n  }\n\n  public static boolean getOnDiskCacheEnabled() {\n    return SnowflakeUtil.convertSystemPropertyToBooleanValue(ENABLE_CRL_DISK_CACHING, true);\n  }\n\n  public static Duration getCacheValidityTime() {\n    String validityTime = SnowflakeUtil.systemGetProperty(CRL_CACHE_VALIDITY_TIME);\n    if (!SnowflakeUtil.isNullOrEmpty(validityTime)) {\n      try {\n        long seconds = Long.parseLong(validityTime);\n        if (seconds <= 0) {\n          throw new IllegalArgumentException(\"Cache validity time must be positive\");\n        }\n        return Duration.ofSeconds(seconds);\n      } catch (NumberFormatException e) {\n        throw new IllegalArgumentException(\"Invalid cache validity time: \" + validityTime, e);\n      }\n    } else {\n      return Duration.ofDays(1);\n    }\n  }\n\n  public static Path getOnDiskCacheDir() {\n    String cacheDir = SnowflakeUtil.systemGetProperty(CRL_RESPONSE_CACHE_DIR);\n    if (SnowflakeUtil.isNullOrEmpty(cacheDir)) {\n      File defaultCacheDir = FileCacheUtil.getDefaultCacheDir();\n      if (defaultCacheDir != null) {\n        return Paths.get(defaultCacheDir.getAbsolutePath(), \"crls\");\n      } else {\n        throw new IllegalStateException(\n            \"Default cache dir not set but CRL file cache is enabled. Either fix the environment so that a cache directory can be determined, or disable the CRL file cache in the configuration.\");\n      }\n    } else {\n      return Paths.get(cacheDir);\n    }\n  }\n\n  public static long getCrlDownloadMaxSizeBytes() {\n    String value = SnowflakeUtil.systemGetProperty(CRL_DOWNLOAD_MAX_SIZE_BYTES);\n    if (!SnowflakeUtil.isNullOrEmpty(value)) {\n      try {\n        long bytes = Long.parseLong(value);\n        if (bytes <= 0) {\n          throw new IllegalArgumentException(\"CRL download max size must be positive\");\n        }\n        if (bytes > Integer.MAX_VALUE - 1) {\n          throw new IllegalArgumentException(\n              \"CRL download max size must not exceed \" + (Integer.MAX_VALUE - 1) + \" bytes\");\n        }\n        return bytes;\n      } catch (NumberFormatException e) {\n        throw new IllegalArgumentException(\"Invalid CRL download max size: \" + value, e);\n      }\n    }\n    return DEFAULT_CRL_DOWNLOAD_MAX_SIZE_BYTES;\n  }\n\n  public static Duration getCrlOnDiskCacheRemovalDelay() {\n    String removalDelay = SnowflakeUtil.systemGetProperty(CRL_ON_DISK_CACHE_REMOVAL_DELAY);\n    if (!SnowflakeUtil.isNullOrEmpty(removalDelay)) {\n      try {\n        long seconds = Long.parseLong(removalDelay);\n        if (seconds <= 0) {\n          throw new IllegalArgumentException(\"Cache removal delay time must be positive\");\n        }\n        return Duration.ofSeconds(seconds);\n      } catch (NumberFormatException e) {\n        throw new IllegalArgumentException(\"Invalid cache removal delay: \" + removalDelay, e);\n      }\n    } else {\n      logger.debug(\"Using default on-disk cache removal delay of 7 days\");\n      return Duration.ofDays(7);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLCacheEntry.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\n\nclass CRLCacheEntry {\n  private final X509CRL crl;\n  private final Instant downloadTime;\n\n  CRLCacheEntry(X509CRL crl, Instant downloadTime) {\n    if (crl == null) {\n      throw new IllegalArgumentException(\"CRL cannot be null\");\n    }\n    if (downloadTime == null) {\n      throw new IllegalArgumentException(\"Download time cannot be null\");\n    }\n    this.crl = crl;\n    this.downloadTime = downloadTime;\n  }\n\n  X509CRL getCrl() {\n    return crl;\n  }\n\n  Instant getDownloadTime() {\n    return downloadTime;\n  }\n\n  boolean isCrlExpired(Instant time) {\n    return crl.getNextUpdate() != null && crl.getNextUpdate().toInstant().isBefore(time);\n  }\n\n  boolean isEvicted(Instant time, Duration cacheValidityTime) {\n    return downloadTime.plus(cacheValidityTime).isBefore(time);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLCacheManager.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.nio.file.Path;\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Cache manager that coordinates between in-memory and file-based CRL caches. Provides automatic\n * cleanup of expired entries and proper lifecycle management.\n */\npublic class CRLCacheManager {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CRLCacheManager.class);\n\n  private final CRLCache memoryCache;\n  private final CRLCache fileCache;\n  private final ScheduledExecutorService cleanupScheduler;\n  private final Runnable cleanupTask;\n  private final long cleanupIntervalInMs;\n  private final Duration cacheValidityTime;\n\n  CRLCacheManager(\n      CRLCache memoryCache,\n      CRLCache fileCache,\n      Duration cleanupInterval,\n      Duration cacheValidityTime) {\n    this.memoryCache = memoryCache;\n    this.fileCache = fileCache;\n    this.cleanupIntervalInMs = cleanupInterval.toMillis();\n    this.cacheValidityTime = cacheValidityTime;\n\n    this.cleanupTask =\n        () -> {\n          try {\n            logger.debug(\n                \"Running periodic CRL cache cleanup with interval {} seconds\",\n                cleanupIntervalInMs / 1000.0);\n            memoryCache.cleanup();\n            fileCache.cleanup();\n          } catch (Exception e) {\n            logger.error(\"An error occurred during scheduled CRL cache cleanup.\", e);\n          }\n        };\n    ThreadFactory threadFactory =\n        r -> {\n          Thread t = new Thread(r, \"crl-cache-cleanup\");\n          t.setDaemon(true); // Don't prevent JVM shutdown\n          return t;\n        };\n    this.cleanupScheduler = Executors.newSingleThreadScheduledExecutor(threadFactory);\n  }\n\n  public static CRLCacheManager build(\n      boolean inMemoryCacheEnabled,\n      boolean onDiskCacheEnabled,\n      Path onDiskCacheDir,\n      Duration onDiskCacheRemovalDelay,\n      Duration cacheValidityTime)\n      throws SnowflakeSQLLoggedException {\n    CRLCache memoryCache;\n    if (inMemoryCacheEnabled) {\n      logger.debug(\"Enabling in-memory CRL cache\");\n      memoryCache = new CRLInMemoryCache(cacheValidityTime);\n    } else {\n      logger.debug(\"In-memory CRL cache disabled\");\n      memoryCache = NoopCRLCache.INSTANCE;\n    }\n\n    CRLCache fileCache;\n    if (onDiskCacheEnabled) {\n      logger.debug(\"Enabling file based CRL cache\");\n      fileCache = new CRLFileCache(onDiskCacheDir, onDiskCacheRemovalDelay);\n    } else {\n      logger.debug(\"File based CRL cache disabled\");\n      fileCache = NoopCRLCache.INSTANCE;\n    }\n\n    CRLCacheManager manager =\n        new CRLCacheManager(memoryCache, fileCache, onDiskCacheRemovalDelay, cacheValidityTime);\n    if (inMemoryCacheEnabled || onDiskCacheEnabled) {\n      manager.startPeriodicCleanup();\n    }\n    return manager;\n  }\n\n  CRLCacheEntry get(String crlUrl) {\n    CRLCacheEntry entry = memoryCache.get(crlUrl);\n    if (entry != null) {\n      return entry;\n    }\n\n    entry = fileCache.get(crlUrl);\n    if (entry != null) {\n      // Promote to memory cache\n      memoryCache.put(crlUrl, entry);\n      return entry;\n    }\n\n    logger.debug(\"CRL not found in cache for {}\", crlUrl);\n    return null;\n  }\n\n  void put(String crlUrl, X509CRL crl, Instant downloadTime) {\n    CRLCacheEntry entry = new CRLCacheEntry(crl, downloadTime);\n    memoryCache.put(crlUrl, entry);\n    fileCache.put(crlUrl, entry);\n  }\n\n  private void startPeriodicCleanup() {\n    cleanupScheduler.scheduleAtFixedRate(\n        cleanupTask, cleanupIntervalInMs, cleanupIntervalInMs, TimeUnit.MILLISECONDS);\n\n    logger.debug(\n        \"Scheduled CRL cache cleanup task to run every {} seconds.\", cleanupIntervalInMs / 1000.0);\n  }\n\n  public Duration getCacheValidityTime() {\n    return cacheValidityTime;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLFileCache.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.nio.file.attribute.FileTime;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.security.cert.CRLException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\nclass CRLFileCache implements CRLCache {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CRLFileCache.class);\n\n  private final Path cacheDir;\n  private final Duration removalDelay;\n  private final Lock cacheLock = new ReentrantLock();\n\n  CRLFileCache(Path cacheDir, Duration removalDelay) throws SnowflakeSQLLoggedException {\n    this.cacheDir = cacheDir;\n    this.removalDelay = removalDelay;\n\n    ensureCacheDirectoryExists(cacheDir);\n  }\n\n  public CRLCacheEntry get(String crlUrl) {\n    try {\n      cacheLock.lock();\n      Path crlFilePath = getCrlFilePath(crlUrl);\n      if (Files.exists(crlFilePath)) {\n        logger.debug(\"Found CRL on disk for {}\", crlFilePath);\n\n        BasicFileAttributes attrs = Files.readAttributes(crlFilePath, BasicFileAttributes.class);\n        Instant downloadTime = attrs.lastModifiedTime().toInstant();\n\n        CertificateFactory certFactory = CertificateFactory.getInstance(\"X.509\");\n        try (InputStream crlBytes = Files.newInputStream(crlFilePath)) {\n          X509CRL crl = (X509CRL) certFactory.generateCRL(crlBytes);\n          return new CRLCacheEntry(crl, downloadTime);\n        }\n      }\n    } catch (Exception e) {\n      logger.warn(\"Failed to read CRL from disk cache for {}: {}\", crlUrl, e.getMessage());\n    } finally {\n      cacheLock.unlock();\n    }\n\n    return null;\n  }\n\n  public void put(String crlUrl, CRLCacheEntry entry) {\n    try {\n      cacheLock.lock();\n      Path crlFilePath = getCrlFilePath(crlUrl);\n\n      if (Constants.getOS().isPosix()) {\n        Files.deleteIfExists(crlFilePath);\n        Files.createFile(\n            crlFilePath,\n            PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rw-------\")));\n        Files.write(crlFilePath, entry.getCrl().getEncoded());\n      } else {\n        Files.write(crlFilePath, entry.getCrl().getEncoded());\n      }\n\n      Files.setLastModifiedTime(crlFilePath, FileTime.from(entry.getDownloadTime()));\n\n      logger.debug(\"Updated disk cache for {}\", crlUrl);\n    } catch (Exception e) {\n      logger.warn(\"Failed to write CRL to disk cache for {}: {}\", crlUrl, e.getMessage());\n    } finally {\n      cacheLock.unlock();\n    }\n  }\n\n  public void cleanup() {\n    Instant now = Instant.now();\n    logger.debug(\"Cleaning up on-disk CRL cache at {}\", now);\n\n    try {\n      if (!Files.exists(cacheDir)) {\n        return;\n      }\n\n      int removedCount = 0;\n      try (Stream<Path> files = Files.list(cacheDir)) {\n        cacheLock.lock();\n        for (Path filePath : files.filter(Files::isRegularFile).collect(Collectors.toList())) {\n          try {\n            try (InputStream crlBytes = Files.newInputStream(filePath)) {\n              CertificateFactory certFactory = CertificateFactory.getInstance(\"X.509\");\n              X509CRL crl = (X509CRL) certFactory.generateCRL(crlBytes);\n              CRLCacheEntry entry =\n                  new CRLCacheEntry(crl, Files.getLastModifiedTime(filePath).toInstant());\n\n              boolean expired = entry.isCrlExpired(now);\n              boolean evicted = entry.isEvicted(now, removalDelay);\n              if (expired || evicted) {\n                Files.delete(filePath);\n                removedCount++;\n                logger.debug(\n                    \"Removing file based CRL cache entry for {}: expired={}, evicted={}\",\n                    filePath,\n                    expired,\n                    evicted);\n              }\n            }\n          } catch (IOException | CRLException | CertificateException e) {\n            // If we can't parse the file, it's probably corrupted - remove it\n            try {\n              Files.delete(filePath);\n              removedCount++;\n            } catch (IOException deleteError) {\n              logger.warn(\n                  \"Failed to delete corrupted CRL file {}: {}\", filePath, deleteError.getMessage());\n            }\n          }\n        }\n      } finally {\n        cacheLock.unlock();\n      }\n\n      if (removedCount > 0) {\n        logger.debug(\"Removed {} expired/corrupted files from disk CRL cache\", removedCount);\n      }\n    } catch (Exception e) {\n      logger.warn(\"Failed to cleanup disk CRL cache: {}\", e.getMessage());\n    }\n  }\n\n  private Path getCrlFilePath(String crlUrl) throws UnsupportedEncodingException {\n    String encodedUrl = URLEncoder.encode(crlUrl, StandardCharsets.UTF_8.toString());\n    return cacheDir.resolve(encodedUrl);\n  }\n\n  private static boolean ownerOnlyPermissions(Path cacheDir) throws IOException {\n    return Files.getPosixFilePermissions(cacheDir)\n        .equals(PosixFilePermissions.fromString(\"rwx------\"));\n  }\n\n  private static void ensureCacheDirectoryExists(Path cacheDir) throws SnowflakeSQLLoggedException {\n    try {\n      boolean exists = Files.exists(cacheDir);\n      if (!exists) {\n        if (Constants.getOS().isPosix()) {\n          Files.createDirectories(\n              cacheDir,\n              PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rwx------\")));\n          logger.debug(\"Initialized CRL cache directory: {}\", cacheDir);\n        } else {\n          Files.createDirectories(cacheDir);\n        }\n      }\n\n      if (Constants.getOS().isPosix() && !ownerOnlyPermissions(cacheDir)) {\n        Files.setPosixFilePermissions(cacheDir, PosixFilePermissions.fromString(\"rwx------\"));\n        logger.debug(\"Set CRL cache directory permissions to 'rwx------\");\n      }\n    } catch (Exception e) {\n      throw new SnowflakeSQLLoggedException(\n          null, null, SqlState.INTERNAL_ERROR, \"Failed to create CRL cache directory\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLInMemoryCache.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.concurrent.ConcurrentHashMap;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass CRLInMemoryCache implements CRLCache {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CRLInMemoryCache.class);\n  private final ConcurrentHashMap<String, CRLCacheEntry> cache = new ConcurrentHashMap<>();\n  private final Duration cacheValidityTime;\n\n  CRLInMemoryCache(Duration cacheValidityTime) {\n    this.cacheValidityTime = cacheValidityTime;\n  }\n\n  public CRLCacheEntry get(String crlUrl) {\n    CRLCacheEntry entry = cache.get(crlUrl);\n    if (entry != null) {\n      logger.debug(\"Found CRL in memory cache for {}\", crlUrl);\n    }\n    return entry;\n  }\n\n  public void put(String crlUrl, CRLCacheEntry entry) {\n    cache.put(crlUrl, entry);\n  }\n\n  public void cleanup() {\n    Instant now = Instant.now();\n    logger.debug(\"Cleaning up in-memory CRL cache at {}\", now);\n\n    int initialSize = cache.size();\n    cache\n        .entrySet()\n        .removeIf(\n            entry -> {\n              CRLCacheEntry cacheEntry = entry.getValue();\n              boolean expired = cacheEntry.isCrlExpired(now);\n              boolean evicted = cacheEntry.isEvicted(now, cacheValidityTime);\n              logger.debug(\n                  \"Removing in-memory CRL cache entry for {}: expired={}, evicted={}\",\n                  entry.getKey(),\n                  expired,\n                  evicted);\n              return expired || evicted;\n            });\n\n    int removedCount = initialSize - cache.size();\n    if (removedCount > 0) {\n      logger.debug(\"Removed {} expired/evicted entries from in-memory CRL cache\", removedCount);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLValidationResult.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nenum CRLValidationResult {\n  CHAIN_UNREVOKED,\n  CHAIN_REVOKED,\n  CHAIN_ERROR\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLValidationUtils.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509Certificate;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.bouncycastle.asn1.ASN1OctetString;\nimport org.bouncycastle.asn1.ASN1Primitive;\nimport org.bouncycastle.asn1.DERIA5String;\nimport org.bouncycastle.asn1.x509.CRLDistPoint;\nimport org.bouncycastle.asn1.x509.DistributionPoint;\nimport org.bouncycastle.asn1.x509.DistributionPointName;\nimport org.bouncycastle.asn1.x509.GeneralName;\nimport org.bouncycastle.asn1.x509.GeneralNames;\nimport org.bouncycastle.asn1.x509.IssuingDistributionPoint;\n\nclass CRLValidationUtils {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CRLValidationUtils.class);\n\n  // CA/Browser Forum Baseline Requirements date thresholds (using UTC for consistency)\n  private static final Date MARCH_15_2024 =\n      Date.from(LocalDate.of(2024, 3, 15).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n  private static final Date MARCH_15_2026 =\n      Date.from(LocalDate.of(2026, 3, 15).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n\n  static List<String> extractCRLDistributionPoints(X509Certificate cert) {\n    List<String> crlUrls = new ArrayList<>();\n\n    try {\n      byte[] extensionBytes = cert.getExtensionValue(\"2.5.29.31\");\n      if (extensionBytes == null) {\n        logger.debug(\n            \"No CRL Distribution Points extension found for certificate: {}\",\n            cert.getSubjectX500Principal());\n        return crlUrls;\n      }\n\n      ASN1OctetString octetString = (ASN1OctetString) ASN1Primitive.fromByteArray(extensionBytes);\n      CRLDistPoint crlDistPoint =\n          CRLDistPoint.getInstance(ASN1Primitive.fromByteArray(octetString.getOctets()));\n\n      DistributionPoint[] distributionPoints = crlDistPoint.getDistributionPoints();\n      if (distributionPoints != null) {\n        for (DistributionPoint dp : distributionPoints) {\n          DistributionPointName dpName = dp.getDistributionPoint();\n          if (dpName != null && dpName.getType() == DistributionPointName.FULL_NAME) {\n            GeneralNames generalNames = (GeneralNames) dpName.getName();\n            for (GeneralName generalName : generalNames.getNames()) {\n              if (generalName.getTagNo() == GeneralName.uniformResourceIdentifier) {\n                String url = ((DERIA5String) generalName.getName()).getString();\n                if (url.toLowerCase().startsWith(\"http://\")\n                    || url.toLowerCase().startsWith(\"https://\")) {\n                  logger.debug(\"Found CRL URL: {}\", url);\n                  crlUrls.add(url);\n                }\n              }\n            }\n          }\n        }\n      }\n    } catch (Exception e) {\n      logger.debug(\n          \"Failed to extract CRL distribution points from certificate {}: {}\",\n          cert.getSubjectX500Principal(),\n          e.getMessage());\n    }\n\n    logger.debug(\n        \"Extracted {} CRL URLs for certificate: {}\",\n        crlUrls.size(),\n        cert.getSubjectX500Principal());\n    return crlUrls;\n  }\n\n  /**\n   * Determines if a certificate is short-lived according to CA/Browser Forum Baseline Requirements.\n   */\n  static boolean isShortLived(X509Certificate cert) {\n    Date notBefore = cert.getNotBefore();\n    Date notAfter = cert.getNotAfter();\n\n    // Certificates issued before March 15, 2024 are not considered short-lived\n    if (notBefore.before(MARCH_15_2024)) {\n      return false;\n    }\n\n    // Determine the maximum validity period based on issuance date\n    long maxValidityPeriodMs;\n    if (notBefore.before(MARCH_15_2026)) {\n      maxValidityPeriodMs =\n          10L * 24 * 60 * 60 * 1000; // 10 days for certificates before March 15, 2026\n    } else {\n      maxValidityPeriodMs =\n          7L * 24 * 60 * 60 * 1000; // 7 days for certificates after March 15, 2026\n    }\n\n    // Add 1 minute margin to account for clock differences and inclusive time boundaries\n    maxValidityPeriodMs += 60 * 1000;\n\n    long actualValidityPeriodMs = notAfter.getTime() - notBefore.getTime();\n    return actualValidityPeriodMs <= maxValidityPeriodMs;\n  }\n\n  static boolean verifyIssuingDistributionPoint(X509CRL crl, X509Certificate cert, String crlUrl) {\n    try {\n      byte[] extensionBytes = crl.getExtensionValue(\"2.5.29.28\");\n      if (extensionBytes == null) {\n        logger.debug(\"No IDP extension found - CRL covers all certificates\");\n        return true;\n      }\n\n      ASN1OctetString octetString = (ASN1OctetString) ASN1Primitive.fromByteArray(extensionBytes);\n      IssuingDistributionPoint idp =\n          IssuingDistributionPoint.getInstance(\n              ASN1Primitive.fromByteArray(octetString.getOctets()));\n\n      // Check if this CRL only covers user certificates\n      if (idp.onlyContainsUserCerts() && cert.getBasicConstraints() != -1) {\n        logger.debug(\"CRL only covers user certificates, but certificate is a CA certificate\");\n        return false;\n      }\n\n      // Check if this CRL only covers CA certificates\n      if (idp.onlyContainsCACerts() && cert.getBasicConstraints() == -1) {\n        logger.debug(\"CRL only covers CA certificates, but certificate is not a CA certificate\");\n        return false;\n      }\n\n      // Check if this CRL only covers specific revocation reasons\n      if (idp.getOnlySomeReasons() != null) {\n        logger.debug(\n            \"CRL only covers specific revocation reasons (onlySomeReasons is set) - \"\n                + \"treating as not authoritative for full revocation checking\");\n        return false;\n      }\n\n      DistributionPointName dpName = idp.getDistributionPoint();\n      if (dpName != null) {\n        if (dpName.getType() == DistributionPointName.FULL_NAME) {\n          GeneralNames generalNames = (GeneralNames) dpName.getName();\n          boolean foundMatch = false;\n\n          for (GeneralName generalName : generalNames.getNames()) {\n            if (generalName.getTagNo() == GeneralName.uniformResourceIdentifier) {\n              String idpUrl = ((DERIA5String) generalName.getName()).getString();\n              if (idpUrl.equals(crlUrl)) {\n                foundMatch = true;\n                break;\n              }\n            }\n          }\n\n          if (!foundMatch) {\n            logger.debug(\n                \"CRL URL {} not found in IDP distribution points - this CRL is not authorized for this certificate\",\n                crlUrl);\n            return false;\n          }\n        }\n      }\n\n      logger.debug(\"IDP extension verification passed\");\n      return true;\n    } catch (Exception e) {\n      logger.debug(\"Failed to verify IDP extension: {}\", e.getMessage());\n      return false;\n    }\n  }\n\n  static String getCertChainSubjects(List<X509Certificate[]> certificateChains) {\n    return certificateChains.stream()\n        .flatMap(Arrays::stream)\n        .map(cert -> cert.getSubjectX500Principal().getName())\n        .collect(Collectors.joining(\", \"));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CRLValidator.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.extractCRLDistributionPoints;\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.getCertChainSubjects;\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.isShortLived;\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.verifyIssuingDistributionPoint;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.NoSuchProviderException;\nimport java.security.PublicKey;\nimport java.security.SignatureException;\nimport java.security.cert.CRLException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509CRLEntry;\nimport java.security.cert.X509Certificate;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.jdbc.telemetry.PreSessionTelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.RevocationCheckTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.io.input.BoundedInputStream;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\n\npublic class CRLValidator {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CRLValidator.class);\n  private static final Map<HttpClientSettingsKey, CRLValidator> validatorRegistryForTelemetry =\n      new ConcurrentHashMap<>();\n\n  private final Map<String, Lock> urlLocks = new ConcurrentHashMap<>();\n  private final CloseableHttpClient httpClient;\n  private final CRLCacheManager cacheManager;\n  private final CertRevocationCheckMode certRevocationCheckMode;\n  private final boolean allowCertificatesWithoutCrlUrl;\n  private final Telemetry telemetryClient;\n\n  public CRLValidator(\n      CertRevocationCheckMode revocationCheckMode,\n      boolean allowCertificatesWithoutCrlUrl,\n      CloseableHttpClient httpClient,\n      CRLCacheManager cacheManager,\n      Telemetry telemetryClient) {\n    this.httpClient = httpClient;\n    this.cacheManager = cacheManager;\n    this.certRevocationCheckMode = revocationCheckMode;\n    this.allowCertificatesWithoutCrlUrl = allowCertificatesWithoutCrlUrl;\n    this.telemetryClient = telemetryClient;\n  }\n\n  /**\n   * Validates certificate chains against CRLs.\n   *\n   * @param certificateChains the verified certificate chains to validate\n   * @return true if validation passes, false otherwise\n   */\n  public boolean validateCertificateChains(List<X509Certificate[]> certificateChains) {\n    if (this.certRevocationCheckMode == CertRevocationCheckMode.DISABLED) {\n      logger.debug(\"CRL validation is disabled\");\n      return true; // OPEN\n    }\n\n    if (certificateChains == null || certificateChains.isEmpty()) {\n      throw new IllegalArgumentException(\"Certificate chains cannot be null or empty\");\n    }\n\n    logger.debug(\n        \"Validating {} certificate chains with subjects: {}\",\n        certificateChains.size(),\n        getCertChainSubjects(certificateChains));\n\n    List<CRLValidationResult> crlValidationResults = validateChains(certificateChains);\n\n    if (crlValidationResults.get(crlValidationResults.size() - 1)\n        == CRLValidationResult.CHAIN_UNREVOKED) {\n      logger.debug(\"Found certificate chain with all certificates unrevoked\");\n      return true; // OPEN\n    }\n\n    if (containsOnlyRevokedChains(crlValidationResults)) {\n      logger.debug(\"Every verified certificate chain contained revoked certificates\");\n      return false;\n    }\n\n    logger.debug(\"Some certificate chains didn't pass or driver wasn't able to perform the checks\");\n    if (this.certRevocationCheckMode == CertRevocationCheckMode.ADVISORY) {\n      logger.debug(\"Advisory mode: allowing connection despite validation issues\");\n      return true; // FAIL OPEN\n    }\n\n    return false;\n  }\n\n  private List<CRLValidationResult> validateChains(List<X509Certificate[]> certChains) {\n    List<CRLValidationResult> chainsValidationResults = new ArrayList<>();\n\n    for (X509Certificate[] certChain : certChains) {\n      CRLValidationResult chainResult = CRLValidationResult.CHAIN_UNREVOKED;\n\n      // Validate each certificate in the chain against CRL, skip the root certificate\n      for (int i = 0; i < certChain.length; i++) {\n        X509Certificate cert = certChain[i];\n        boolean isRoot = (i == certChain.length - 1);\n        if (isRoot) {\n          break;\n        }\n\n        X509Certificate parentCert = certChain[i + 1];\n\n        if (isShortLived(cert)) {\n          logger.debug(\"Skipping short-lived certificate: {}\", cert.getSubjectX500Principal());\n          continue;\n        }\n\n        List<String> crlUrls = extractCRLDistributionPoints(cert);\n        if (crlUrls.isEmpty()) {\n          if (this.allowCertificatesWithoutCrlUrl) {\n            logger.debug(\n                \"Certificate has missing CRL Distribution Point URLs: {}\",\n                cert.getSubjectX500Principal());\n            continue;\n          }\n          chainResult = CRLValidationResult.CHAIN_ERROR;\n          continue;\n        }\n\n        CertificateValidationResult certStatus = validateCert(cert, parentCert);\n\n        if (certStatus == CertificateValidationResult.CERT_REVOKED) {\n          chainResult = CRLValidationResult.CHAIN_REVOKED;\n          break;\n        }\n\n        if (certStatus == CertificateValidationResult.CERT_ERROR) {\n          chainResult = CRLValidationResult.CHAIN_ERROR;\n        }\n      }\n\n      chainsValidationResults.add(chainResult);\n\n      if (chainResult == CRLValidationResult.CHAIN_UNREVOKED) {\n        logger.debug(\"Found valid certificate chain, stopping validation of remaining chains\");\n        break;\n      }\n    }\n\n    return chainsValidationResults;\n  }\n\n  private CertificateValidationResult validateCert(\n      X509Certificate cert, X509Certificate parentCert) {\n    List<String> crlUrls = extractCRLDistributionPoints(cert);\n\n    Set<CertificateValidationResult> results = new HashSet<>();\n\n    for (String url : crlUrls) {\n      CertificateValidationResult result = validateCert(cert, url, parentCert);\n\n      if (result == CertificateValidationResult.CERT_REVOKED) {\n        return result;\n      }\n\n      results.add(result);\n    }\n\n    if (results.contains(CertificateValidationResult.CERT_ERROR)) {\n      return CertificateValidationResult.CERT_ERROR;\n    } else {\n      return CertificateValidationResult.CERT_UNREVOKED;\n    }\n  }\n\n  private CertificateValidationResult validateCert(\n      X509Certificate cert, String crlUrl, X509Certificate parentCert) {\n    // Thread-safe processing of CRL for given crlUrl\n    Lock lock = urlLocks.computeIfAbsent(crlUrl, k -> new ReentrantLock());\n\n    lock.lock();\n    try {\n      Instant now = Instant.now();\n      RevocationCheckTelemetryData revocationTelemetry = new RevocationCheckTelemetryData();\n      revocationTelemetry.setCrlUrl(crlUrl);\n\n      CRLCacheEntry cacheEntry = cacheManager.get(crlUrl);\n      X509CRL crl = cacheEntry != null ? cacheEntry.getCrl() : null;\n      Instant downloadTime = cacheEntry != null ? cacheEntry.getDownloadTime() : null;\n\n      boolean needsFreshCrl =\n          crl == null\n              || (crl.getNextUpdate() != null && crl.getNextUpdate().toInstant().isBefore(now))\n              || cacheEntry.isEvicted(now, cacheManager.getCacheValidityTime());\n\n      boolean shouldUpdateCache = false;\n\n      if (needsFreshCrl) {\n        X509CRL newCrl = fetchCrl(crlUrl, revocationTelemetry);\n\n        if (newCrl != null) {\n          shouldUpdateCache = crl == null || newCrl.getThisUpdate().after(crl.getThisUpdate());\n\n          if (shouldUpdateCache) {\n            logger.debug(\"Found updated CRL for {}\", crlUrl);\n            crl = newCrl;\n            downloadTime = now;\n          } else {\n            // New CRL isn't newer, check if old one is still valid\n            if (crl.getNextUpdate() != null && crl.getNextUpdate().toInstant().isAfter(now)) {\n              logger.debug(\"CRL for {} is up-to-date, using cached version\", crlUrl);\n            } else {\n              logger.warn(\"CRL for {} is not available or outdated\", crlUrl);\n              return CertificateValidationResult.CERT_ERROR;\n            }\n          }\n        } else {\n          if (crl != null\n              && crl.getNextUpdate() != null\n              && crl.getNextUpdate().toInstant().isAfter(now)) {\n            logger.debug(\n                \"Using cached CRL for {} (fetch failed but cached version still valid)\", crlUrl);\n          } else {\n            logger.error(\n                \"Unable to fetch fresh CRL from {} and no valid cached version available\", crlUrl);\n            return CertificateValidationResult.CERT_ERROR;\n          }\n        }\n      }\n\n      int numberOfRevokedCertificates =\n          crl.getRevokedCertificates() != null ? crl.getRevokedCertificates().size() : 0;\n      logger.debug(\n          \"CRL has {} revoked entries, next update at {}\",\n          numberOfRevokedCertificates,\n          crl.getNextUpdate());\n      revocationTelemetry.setNumberOfRevokedCertificates(numberOfRevokedCertificates);\n\n      if (!isCrlSignatureAndIssuerValid(crl, cert, parentCert, crlUrl)) {\n        logger.debug(\"Unable to verify CRL for {}\", crlUrl);\n        return CertificateValidationResult.CERT_ERROR;\n      }\n\n      // Update cache if we have a new/updated CRL\n      if (shouldUpdateCache) {\n        logger.debug(\"CRL for {} is valid, updating cache\", crlUrl);\n        cacheManager.put(crlUrl, crl, downloadTime);\n      }\n\n      if (isCertificateRevoked(crl, cert)) {\n        logger.debug(\n            \"Certificate {} is revoked according to CRL {}\", cert.getSerialNumber(), crlUrl);\n        return CertificateValidationResult.CERT_REVOKED;\n      }\n\n      telemetryClient.addLogToBatch(revocationTelemetry.buildTelemetry());\n      return CertificateValidationResult.CERT_UNREVOKED;\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  private boolean isCrlSignatureAndIssuerValid(\n      X509CRL crl, X509Certificate cert, X509Certificate parentCert, String crlUrl) {\n    try {\n      if (!crl.getIssuerX500Principal().equals(parentCert.getSubjectX500Principal())) {\n        logger.debug(\n            \"CRL issuer {} does not match parent certificate subject {} for {}\",\n            crl.getIssuerX500Principal(),\n            parentCert.getSubjectX500Principal(),\n            \"validation\");\n        return false;\n      }\n\n      Date now = new Date();\n      if (crl.getNextUpdate() != null && now.after(crl.getNextUpdate())) {\n        logger.debug(\"CRL has expired: nextUpdate={}, now={}\", crl.getNextUpdate(), now);\n        return false;\n      }\n\n      PublicKey parentPublicKey = parentCert.getPublicKey();\n      try {\n        crl.verify(parentPublicKey);\n        logger.debug(\"CRL signature verified successfully using parent certificate\");\n      } catch (InvalidKeyException\n          | NoSuchAlgorithmException\n          | NoSuchProviderException\n          | SignatureException e) {\n        logger.debug(\"CRL signature verification failed: {}\", e.getMessage());\n        return false;\n      }\n\n      if (!verifyIssuingDistributionPoint(crl, cert, crlUrl)) {\n        logger.debug(\"IDP extension verification failed\");\n        return false;\n      }\n\n      return true;\n    } catch (Exception e) {\n      logger.debug(\"CRL validation failed: {}\", e.getMessage());\n      return false;\n    }\n  }\n\n  private boolean isCertificateRevoked(X509CRL crl, X509Certificate cert) {\n    X509CRLEntry entry = crl.getRevokedCertificate(cert.getSerialNumber());\n    return entry != null;\n  }\n\n  private X509CRL fetchCrl(String crlUrl, RevocationCheckTelemetryData revocationTelemetry) {\n    try {\n      logger.debug(\"Fetching CRL from {}\", crlUrl);\n      URL url = new URL(crlUrl);\n      HttpGet get = new HttpGet(url.toString());\n      CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");\n      long start = System.currentTimeMillis();\n      try (CloseableHttpResponse response = this.httpClient.execute(get)) {\n        try (InputStream inputStream = response.getEntity().getContent()) {\n          long maxSize = CRLCacheConfig.getCrlDownloadMaxSizeBytes();\n          InputStream bounded =\n              BoundedInputStream.builder()\n                  .setInputStream(inputStream)\n                  .setMaxCount(maxSize + 1)\n                  .get();\n          byte[] crlData = IOUtils.toByteArray(bounded);\n          if (crlData.length > maxSize) {\n            logger.warn(\n                \"CRL from {} exceeds max download size of {} bytes, aborting\", crlUrl, maxSize);\n            return null;\n          }\n          revocationTelemetry.setTimeDownloadingCrl(System.currentTimeMillis() - start);\n          start = System.currentTimeMillis();\n          X509CRL crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(crlData));\n          long crlBytes = crl.getEncoded().length;\n          revocationTelemetry.setTimeParsingCrl(System.currentTimeMillis() - start);\n          revocationTelemetry.setCrlBytes(crlBytes);\n          return crl;\n        }\n      }\n    } catch (IOException | CRLException | CertificateException e) {\n      logger.debug(\"Failed to fetch CRL from {}: {}\", crlUrl, e.getMessage());\n      return null;\n    }\n  }\n\n  private boolean containsOnlyRevokedChains(List<CRLValidationResult> results) {\n    return !results.isEmpty()\n        && results.stream().allMatch(result -> result == CRLValidationResult.CHAIN_REVOKED);\n  }\n\n  /**\n   * Multiple sessions may share the same HttpClientSettingsKey thus CRL telemetry might be sent for\n   * wrong session. We accept this limitation.\n   */\n  public static void setTelemetryClientForKey(\n      HttpClientSettingsKey key, Telemetry telemetryClient) {\n    CRLValidator result =\n        validatorRegistryForTelemetry.computeIfPresent(\n            key,\n            (k, validator) -> {\n              validator.provideTelemetryClient(telemetryClient);\n              return validator;\n            });\n\n    if (result == null) {\n      logger.debug(\"No CRL validator found for key: {}\", key);\n    }\n  }\n\n  public static void registerValidator(HttpClientSettingsKey key, CRLValidator validator) {\n    validatorRegistryForTelemetry.put(key, validator);\n  }\n\n  private void provideTelemetryClient(Telemetry telemetryClient) {\n    try {\n      PreSessionTelemetryClient preSessionTelemetryClient =\n          (PreSessionTelemetryClient) this.telemetryClient;\n      if (!preSessionTelemetryClient.hasRealTelemetryClient()) {\n        preSessionTelemetryClient.setRealTelemetryClient(telemetryClient);\n      }\n    } catch (Exception e) {\n      logger.warn(\"Failed to set real telemetry client for trust manager: {}\", e.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CertRevocationCheckMode.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\npublic enum CertRevocationCheckMode {\n  DISABLED,\n  ENABLED,\n  ADVISORY\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CertificateValidationResult.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nenum CertificateValidationResult {\n  CERT_UNREVOKED,\n  CERT_REVOKED,\n  CERT_ERROR\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/CrlRevocationManager.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.security.cert.CertPathBuilderException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.List;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetry.PreSessionTelemetryClient;\nimport org.apache.http.impl.client.CloseableHttpClient;\n\npublic class CrlRevocationManager {\n  private static final CRLCacheManager crlCacheManager;\n  private final VerifiedCertPathBuilder certPathBuilder;\n  private final CRLValidator crlValidator;\n\n  static {\n    try {\n      crlCacheManager =\n          CRLCacheManager.build(\n              CRLCacheConfig.getInMemoryCacheEnabled(),\n              CRLCacheConfig.getOnDiskCacheEnabled(),\n              CRLCacheConfig.getOnDiskCacheDir(),\n              CRLCacheConfig.getCrlOnDiskCacheRemovalDelay(),\n              CRLCacheConfig.getCacheValidityTime());\n    } catch (SnowflakeSQLLoggedException e) {\n      throw new ExceptionInInitializerError(e);\n    }\n  }\n\n  public CrlRevocationManager(HttpClientSettingsKey key, X509TrustManager trustManager)\n      throws CertificateException {\n    CloseableHttpClient httpClient = HttpUtil.getHttpClientForCrl(key);\n    this.certPathBuilder = new VerifiedCertPathBuilder(trustManager);\n    this.crlValidator =\n        new CRLValidator(\n            key.getRevocationCheckMode(),\n            key.isAllowCertificatesWithoutCrlUrl(),\n            httpClient,\n            crlCacheManager,\n            new PreSessionTelemetryClient());\n    CRLValidator.registerValidator(key, this.crlValidator);\n  }\n\n  public void validateRevocationStatus(X509Certificate[] chain, String authType)\n      throws CertificateException {\n    try {\n      List<X509Certificate[]> certificates =\n          this.certPathBuilder.buildAllVerifiedPaths(chain, authType);\n      boolean validationResult = this.crlValidator.validateCertificateChains(certificates);\n      if (!validationResult) {\n        throw new CertificateException(\n            \"No not revoked certificate chains found during CRL revocation check or transient error happened and not in advisory mode\");\n      }\n    } catch (CertPathBuilderException e) {\n      throw new CertificateException(\"Certificate revocation check failed\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/NoopCRLCache.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nclass NoopCRLCache implements CRLCache {\n  static final CRLCache INSTANCE = new NoopCRLCache();\n\n  @Override\n  public CRLCacheEntry get(String crlUrl) {\n    return null;\n  }\n\n  @Override\n  public void put(String crlUrl, CRLCacheEntry entry) {}\n\n  @Override\n  public void cleanup() {}\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/crl/VerifiedCertPathBuilder.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertPath;\nimport java.security.cert.CertPathBuilder;\nimport java.security.cert.CertPathBuilderException;\nimport java.security.cert.CertPathBuilderResult;\nimport java.security.cert.CertStore;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CollectionCertStoreParameters;\nimport java.security.cert.PKIXBuilderParameters;\nimport java.security.cert.PKIXCertPathBuilderResult;\nimport java.security.cert.TrustAnchor;\nimport java.security.cert.X509CertSelector;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Builds and verifies certificate paths using a truststore and CertPathBuilder. This class takes a\n * certificate chain presented by a server and returns verified paths that include trust anchors for\n * CRL validation support.\n */\npublic class VerifiedCertPathBuilder {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(VerifiedCertPathBuilder.class);\n  private final X509TrustManager trustManager;\n  private final Set<TrustAnchor> trustAnchors;\n\n  /**\n   * Constructor that initializes the VerifiedCertPathBuilder with the provided trust manager.\n   *\n   * @param trustManager the X509TrustManager to use for certificate validation\n   * @throws IllegalArgumentException if trustManager is null\n   */\n  public VerifiedCertPathBuilder(X509TrustManager trustManager) throws CertificateException {\n    if (trustManager == null) {\n      throw new IllegalArgumentException(\"Trust manager cannot be null\");\n    }\n    this.trustManager = trustManager;\n    this.trustAnchors = createTrustAnchors(trustManager);\n  }\n\n  /**\n   * Builds and verifies all possible certificate paths from leaf certificates to trust anchors.\n   * Unlike standard PKIX path building, this method includes trust anchor certificates at the end\n   * of each path for CRL validation support.\n   *\n   * @param certificateChain the certificate chain presented by the server\n   * @param authType the authentication type used for the connection\n   * @return a list of all verified certificate paths with trust anchors included\n   * @throws CertificateException if certificate validation fails\n   * @throws CertPathBuilderException if no valid certificate paths could be built\n   */\n  public List<X509Certificate[]> buildAllVerifiedPaths(\n      X509Certificate[] certificateChain, String authType)\n      throws CertificateException, CertPathBuilderException {\n\n    if (certificateChain == null || certificateChain.length == 0) {\n      throw new IllegalArgumentException(\"Certificate chain cannot be null or empty\");\n    }\n    if (authType == null || authType.trim().isEmpty()) {\n      throw new IllegalArgumentException(\"Authentication type cannot be null or empty\");\n    }\n\n    logger.debug(\n        \"Building verified paths for chain length: {} with authType: {}\",\n        certificateChain.length,\n        authType);\n\n    List<X509Certificate[]> allVerifiedPaths = new ArrayList<>();\n\n    try {\n      List<Certificate> certCollection = Arrays.asList(certificateChain);\n      CertStore certStore =\n          CertStore.getInstance(\"Collection\", new CollectionCertStoreParameters(certCollection));\n\n      X509Certificate leafCertificate = identifyLeafCertificate(certificateChain);\n      logger.debug(\"Identified leaf certificate: {}\", leafCertificate.getSubjectX500Principal());\n\n      allVerifiedPaths.addAll(\n          findAllPathsForTarget(leafCertificate, trustAnchors, certStore, authType));\n\n    } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {\n      throw new CertificateException(\"Failed to build certificate paths\", e);\n    }\n\n    if (allVerifiedPaths.isEmpty()) {\n      throw new CertPathBuilderException(\"No valid certificate paths could be built\");\n    }\n\n    logger.debug(\"Successfully built {} verified certificate paths\", allVerifiedPaths.size());\n    return allVerifiedPaths;\n  }\n\n  /** Finds all possible valid paths from a leaf certificate to trust anchors. */\n  private List<X509Certificate[]> findAllPathsForTarget(\n      X509Certificate targetCert,\n      Set<TrustAnchor> trustAnchors,\n      CertStore certStore,\n      String authType) {\n\n    List<X509Certificate[]> pathsForTarget = new ArrayList<>();\n\n    for (TrustAnchor trustAnchor : trustAnchors) {\n      try {\n        Set<TrustAnchor> singleTrustAnchor = Collections.singleton(trustAnchor);\n        PKIXBuilderParameters singleAnchorParams =\n            new PKIXBuilderParameters(singleTrustAnchor, null);\n        singleAnchorParams.addCertStore(certStore);\n        singleAnchorParams.setRevocationEnabled(false);\n        X509CertSelector selector = new X509CertSelector();\n        selector.setCertificate(targetCert);\n        singleAnchorParams.setTargetCertConstraints(selector);\n\n        CertPathBuilder builder = CertPathBuilder.getInstance(\"PKIX\");\n        CertPathBuilderResult result = builder.build(singleAnchorParams);\n\n        if (result instanceof PKIXCertPathBuilderResult) {\n          PKIXCertPathBuilderResult pkixResult = (PKIXCertPathBuilderResult) result;\n          CertPath certPath = pkixResult.getCertPath();\n\n          try {\n            X509Certificate[] certArray = convertCertPathToArray(certPath);\n            trustManager.checkServerTrusted(certArray, authType);\n\n            // Create path with trust anchor included for CRL validation\n            X509Certificate[] pathWithTrustAnchor = new X509Certificate[certArray.length + 1];\n            System.arraycopy(certArray, 0, pathWithTrustAnchor, 0, certArray.length);\n            pathWithTrustAnchor[certArray.length] = trustAnchor.getTrustedCert();\n\n            pathsForTarget.add(pathWithTrustAnchor);\n            logger.trace(\n                \"Found valid path via trust anchor {}: length {}\",\n                trustAnchor.getTrustedCert().getSubjectX500Principal(),\n                pathWithTrustAnchor.length);\n\n          } catch (CertificateException e) {\n            logger.trace(\n                \"Path validation failed via trust anchor {}: {}\",\n                trustAnchor.getTrustedCert().getSubjectX500Principal(),\n                e.getMessage());\n          }\n        }\n      } catch (CertPathBuilderException\n          | NoSuchAlgorithmException\n          | InvalidAlgorithmParameterException e) {\n        logger.trace(\n            \"Failed to build path via trust anchor {}: {}\",\n            trustAnchor.getTrustedCert().getSubjectX500Principal(),\n            e.getMessage());\n      }\n    }\n\n    return pathsForTarget;\n  }\n\n  /**\n   * Identifies the leaf certificate (end-entity certificate) in the certificate chain.\n   *\n   * @param certificateChain the certificate chain to analyze\n   * @return the leaf certificate found in the chain\n   * @throws CertificateException if no leaf certificate is found in the chain\n   */\n  private X509Certificate identifyLeafCertificate(X509Certificate[] certificateChain)\n      throws CertificateException {\n    Set<X509Certificate> leafCerts =\n        Arrays.stream(certificateChain)\n            .filter(\n                cert ->\n                    cert != null\n                        && cert.getBasicConstraints()\n                            == -1) // Basic constraints -1 indicates a leaf certificate\n            .collect(Collectors.toSet());\n    if (leafCerts.isEmpty()) {\n      throw new CertificateException(\"No leaf certificate found in the chain\");\n    }\n    if (leafCerts.size() > 1) {\n      throw new CertificateException(\"Multiple leaf certificates found\");\n    }\n    return leafCerts.iterator().next();\n  }\n\n  /** Creates trust anchors from the truststore. */\n  private Set<TrustAnchor> createTrustAnchors(X509TrustManager trustManager)\n      throws CertificateException {\n    Set<TrustAnchor> trustAnchors = new HashSet<>();\n\n    try {\n      X509Certificate[] trustedCerts = trustManager.getAcceptedIssuers();\n      for (X509Certificate cert : trustedCerts) {\n        trustAnchors.add(new TrustAnchor(cert, null));\n      }\n      logger.debug(\"Created {} trust anchors from truststore\", trustAnchors.size());\n    } catch (Exception e) {\n      throw new CertificateException(\"Failed to create trust anchors\", e);\n    }\n\n    return trustAnchors;\n  }\n\n  /** Converts a CertPath to an X509Certificate array. */\n  private X509Certificate[] convertCertPathToArray(CertPath certPath) throws CertificateException {\n    List<? extends Certificate> certificates = certPath.getCertificates();\n\n    if (certificates == null || certificates.isEmpty()) {\n      throw new CertificateException(\"Certificate path is empty\");\n    }\n\n    X509Certificate[] certArray = new X509Certificate[certificates.size()];\n    for (int i = 0; i < certificates.size(); i++) {\n      Certificate cert = certificates.get(i);\n      if (!(cert instanceof X509Certificate)) {\n        throw new CertificateException(\n            \"Certificate path contains non-X509 certificate: \"\n                + cert.getClass().getCanonicalName());\n      }\n      certArray[i] = (X509Certificate) cert;\n    }\n\n    return certArray;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/json/BooleanConverter.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport java.sql.Types;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\npublic class BooleanConverter {\n  public Boolean getBoolean(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return false;\n    }\n    if (obj instanceof Boolean) {\n      return (Boolean) obj;\n    }\n    // if type is an approved type that can be converted to Boolean, do this\n    if (columnType == Types.BOOLEAN\n        || columnType == Types.INTEGER\n        || columnType == Types.SMALLINT\n        || columnType == Types.TINYINT\n        || columnType == Types.BIGINT\n        || columnType == Types.BIT\n        || columnType == Types.VARCHAR\n        || columnType == Types.CHAR\n        || columnType == Types.DECIMAL) {\n      String type = obj.toString();\n      if (\"1\".equals(type) || Boolean.TRUE.toString().equalsIgnoreCase(type)) {\n        return true;\n      }\n      if (\"0\".equals(type) || Boolean.FALSE.toString().equalsIgnoreCase(type)) {\n        return false;\n      }\n    }\n    throw new SFException(\n        ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BOOLEAN_STR, obj);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/json/BytesConverter.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Types;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.arrow.vector.Float8Vector;\n\npublic class BytesConverter {\n  private final Converters converters;\n\n  public BytesConverter(Converters converters) {\n    this.converters = converters;\n  }\n\n  public byte[] getBytes(Object obj, int columnType, int columnSubType, Integer scale)\n      throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    if (obj instanceof byte[]) {\n      return (byte[]) obj;\n    }\n\n    try {\n      // For all types except time/date/timestamp data, convert data into byte array. Different\n      // methods are needed\n      // for different types.\n      switch (columnType) {\n        case Types.FLOAT:\n        case Types.DOUBLE:\n          return ByteBuffer.allocate(Float8Vector.TYPE_WIDTH)\n              .putDouble(0, converters.getNumberConverter().getDouble(obj, columnType))\n              .array();\n        case Types.NUMERIC:\n        case Types.INTEGER:\n        case Types.SMALLINT:\n        case Types.TINYINT:\n        case Types.BIGINT:\n          return converters\n              .getNumberConverter()\n              .getBigDecimal(obj, columnType, scale)\n              .toBigInteger()\n              .toByteArray();\n        case Types.VARCHAR:\n        case Types.CHAR:\n        case Types.STRUCT:\n        case Types.ARRAY:\n        case SnowflakeType.EXTRA_TYPES_VECTOR:\n          return converters\n              .getStringConverter()\n              .getString(obj, columnType, columnSubType, scale)\n              .getBytes(StandardCharsets.UTF_8);\n        case Types.BOOLEAN:\n          return converters.getBooleanConverter().getBoolean(obj, columnType)\n              ? new byte[] {1}\n              : new byte[] {0};\n        case Types.TIMESTAMP:\n        case Types.TIME:\n        case Types.DATE:\n        case Types.DECIMAL:\n          throw new SFException(\n              ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BYTES_STR, obj);\n        default:\n          return SFBinary.fromHex(obj.toString()).getBytes();\n      }\n    } catch (IllegalArgumentException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BYTES_STR, obj);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/json/Converters.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.Instant;\nimport java.time.ZoneOffset;\nimport java.util.Arrays;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SfTimestampUtil;\nimport net.snowflake.client.internal.core.arrow.StructuredTypeDateTimeConverter;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.util.Converter;\nimport net.snowflake.common.core.SFTimestamp;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\npublic class Converters {\n  private final BooleanConverter booleanConverter;\n  private final NumberConverter numberConverter;\n  private final DateTimeConverter dateTimeConverter;\n  private final BytesConverter bytesConverter;\n  private final StringConverter stringConverter;\n  private final StructuredTypeDateTimeConverter structuredTypeDateTimeConverter;\n\n  public Converters(\n      TimeZone sessionTimeZone,\n      SFBaseSession session,\n      long resultVersion,\n      boolean honorClientTZForTimestampNTZ,\n      boolean treatNTZAsUTC,\n      boolean useSessionTimezone,\n      boolean formatDateWithTimeZone,\n      SFBinaryFormat binaryFormatter,\n      SnowflakeDateTimeFormat dateFormatter,\n      SnowflakeDateTimeFormat timeFormatter,\n      SnowflakeDateTimeFormat timestampNTZFormatter,\n      SnowflakeDateTimeFormat timestampLTZFormatter,\n      SnowflakeDateTimeFormat timestampTZFormatter) {\n    booleanConverter = new BooleanConverter();\n    numberConverter = new NumberConverter();\n    dateTimeConverter =\n        new DateTimeConverter(\n            sessionTimeZone,\n            session,\n            resultVersion,\n            honorClientTZForTimestampNTZ,\n            treatNTZAsUTC,\n            useSessionTimezone,\n            formatDateWithTimeZone);\n    bytesConverter = new BytesConverter(this);\n    stringConverter =\n        new StringConverter(\n            sessionTimeZone,\n            binaryFormatter,\n            dateFormatter,\n            timeFormatter,\n            timestampNTZFormatter,\n            timestampLTZFormatter,\n            timestampTZFormatter,\n            resultVersion,\n            session,\n            this);\n    structuredTypeDateTimeConverter =\n        new StructuredTypeDateTimeConverter(\n            sessionTimeZone,\n            resultVersion,\n            honorClientTZForTimestampNTZ,\n            treatNTZAsUTC,\n            useSessionTimezone,\n            formatDateWithTimeZone);\n  }\n\n  public Converters(SFBaseSession session, SnowflakeResultSetSerializableV1 resultSetSerializable) {\n    this(\n        resultSetSerializable.getTimeZone(),\n        session,\n        resultSetSerializable.getResultVersion(),\n        resultSetSerializable.isHonorClientTZForTimestampNTZ(),\n        resultSetSerializable.getTreatNTZAsUTC(),\n        resultSetSerializable.getUseSessionTimezone(),\n        resultSetSerializable.getFormatDateWithTimeZone(),\n        resultSetSerializable.getBinaryFormatter(),\n        resultSetSerializable.getDateFormatter(),\n        resultSetSerializable.getTimeFormatter(),\n        resultSetSerializable.getTimestampNTZFormatter(),\n        resultSetSerializable.getTimestampLTZFormatter(),\n        resultSetSerializable.getTimestampTZFormatter());\n  }\n\n  public BooleanConverter getBooleanConverter() {\n    return booleanConverter;\n  }\n\n  public NumberConverter getNumberConverter() {\n    return numberConverter;\n  }\n\n  public DateTimeConverter getDateTimeConverter() {\n    return dateTimeConverter;\n  }\n\n  public BytesConverter getBytesConverter() {\n    return bytesConverter;\n  }\n\n  public StringConverter getStringConverter() {\n    return stringConverter;\n  }\n\n  public StructuredTypeDateTimeConverter getStructuredTypeDateTimeConverter() {\n    return structuredTypeDateTimeConverter;\n  }\n\n  public Converter integerConverter(int columnType) {\n    return value -> getNumberConverter().getInt(value, columnType);\n  }\n\n  public Converter smallIntConverter(int columnType) {\n    return value -> getNumberConverter().getShort(value, columnType);\n  }\n\n  public Converter tinyIntConverter(int columnType) {\n    return value -> getNumberConverter().getByte(value);\n  }\n\n  public Converter bigIntConverter(int columnType) {\n    return value -> getNumberConverter().getBigInt(value, columnType);\n  }\n\n  public Converter longConverter(int columnType) {\n    return value -> getNumberConverter().getLong(value, columnType);\n  }\n\n  public Converter bigDecimalConverter(int columnType) {\n    return value -> getNumberConverter().getBigDecimal(value, columnType);\n  }\n\n  public Converter floatConverter(int columnType) {\n    return value -> getNumberConverter().getFloat(value, columnType);\n  }\n\n  public Converter doubleConverter(int columnType) {\n    return value -> getNumberConverter().getDouble(value, columnType);\n  }\n\n  public Converter<Byte[]> bytesConverter(int columnType, int scale) {\n    return value -> {\n      byte[] primitiveArray = getBytesConverter().getBytes(value, columnType, Types.BINARY, scale);\n      Byte[] newByteArray = new Byte[primitiveArray.length];\n      Arrays.setAll(newByteArray, n -> primitiveArray[n]);\n      return newByteArray;\n    };\n  }\n\n  public Converter varcharConverter(int columnType, int columnSubType, int scale) {\n    return value -> getStringConverter().getString(value, columnType, columnSubType, scale);\n  }\n\n  public Converter booleanConverter(int columnType) {\n    return value -> getBooleanConverter().getBoolean(value, columnType);\n  }\n\n  public Converter dateStringConverter(SFBaseSession session) {\n    return value -> {\n      SnowflakeDateTimeFormat formatter =\n          SnowflakeDateTimeFormat.fromSqlFormat(\n              (String) session.getCommonParameters().get(\"DATE_OUTPUT_FORMAT\"));\n      SFTimestamp timestamp = formatter.parse((String) value);\n      return Date.valueOf(\n          Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalDate());\n    };\n  }\n\n  public Converter dateFromIntConverter(TimeZone tz) {\n    return value -> structuredTypeDateTimeConverter.getDate((Integer) value, tz);\n  }\n\n  public Converter timeFromStringConverter(SFBaseSession session) {\n    return value -> {\n      SnowflakeDateTimeFormat formatter =\n          SnowflakeDateTimeFormat.fromSqlFormat(\n              (String) session.getCommonParameters().get(\"TIME_OUTPUT_FORMAT\"));\n      SFTimestamp timestamp = formatter.parse((String) value);\n      return Time.valueOf(\n          Instant.ofEpochMilli(timestamp.getTime()).atZone(ZoneOffset.UTC).toLocalTime());\n    };\n  }\n\n  public Converter timeFromIntConverter(int scale) {\n    return value -> structuredTypeDateTimeConverter.getTime((Long) value, scale);\n  }\n\n  public Converter timestampFromStringConverter(\n      int columnSubType,\n      int columnType,\n      int scale,\n      SFBaseSession session,\n      TimeZone tz,\n      TimeZone sessionTimezone) {\n    return value -> {\n      Timestamp result =\n          SfTimestampUtil.getTimestampFromType(\n              columnSubType, (String) value, session, sessionTimezone, tz);\n      if (result != null) {\n        return result;\n      }\n      return getDateTimeConverter()\n          .getTimestamp(value, columnType, columnSubType, TimeZone.getDefault(), scale);\n    };\n  }\n\n  public Converter timestampFromStructConverter(\n      int columnType, int columnSubType, TimeZone tz, int scale) {\n    return value ->\n        structuredTypeDateTimeConverter.getTimestamp(\n            (Map<String, Object>) value, columnType, columnSubType, tz, scale);\n  }\n\n  public Converter structConverter(ObjectMapper objectMapper) {\n    return value -> {\n      try {\n        return objectMapper.readValue((String) value, Map.class);\n      } catch (JsonProcessingException e) {\n        throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA);\n      }\n    };\n  }\n\n  public Converter arrayConverter(ObjectMapper objectMapper) {\n    return value -> {\n      try {\n        return objectMapper.readValue((String) value, Map[].class);\n      } catch (JsonProcessingException e) {\n        throw new SFException(e, ErrorCode.INVALID_STRUCT_DATA);\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/json/DateTimeConverter.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.ArrowResultUtil;\nimport net.snowflake.client.internal.jdbc.SnowflakeDateWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimeWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeTimestampWithTimezone;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.common.core.SFTime;\nimport net.snowflake.common.core.SFTimestamp;\n\npublic class DateTimeConverter {\n  private final TimeZone sessionTimeZone;\n  private final long resultVersion;\n  private final boolean honorClientTZForTimestampNTZ;\n  private final boolean treatNTZAsUTC;\n  private final boolean useSessionTimezone;\n  private final boolean formatDateWithTimeZone;\n  private final SFBaseSession session;\n\n  public DateTimeConverter(\n      TimeZone sessionTimeZone,\n      SFBaseSession session,\n      long resultVersion,\n      boolean honorClientTZForTimestampNTZ,\n      boolean treatNTZAsUTC,\n      boolean useSessionTimezone,\n      boolean formatDateWithTimeZone) {\n    this.sessionTimeZone = sessionTimeZone;\n    this.session = session;\n    this.resultVersion = resultVersion;\n    this.honorClientTZForTimestampNTZ = honorClientTZForTimestampNTZ;\n    this.treatNTZAsUTC = treatNTZAsUTC;\n    this.useSessionTimezone = useSessionTimezone;\n    this.formatDateWithTimeZone = formatDateWithTimeZone;\n  }\n\n  public Timestamp getTimestamp(\n      Object obj, int columnType, int columnSubType, TimeZone tz, int scale) throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) {\n      if (tz == null) {\n        tz = TimeZone.getDefault();\n      }\n      SFTimestamp sfTS =\n          ResultUtil.getSFTimestamp(\n              obj.toString(), scale, columnSubType, resultVersion, sessionTimeZone, session);\n\n      Timestamp res = sfTS.getTimestamp();\n      if (res == null) {\n        return null;\n      }\n      // If we want to display format with no session offset, we have to use session timezone for\n      // ltz and tz types but UTC timezone for ntz type.\n      if (useSessionTimezone) {\n        if (columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ\n            || columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ) {\n          TimeZone specificSessionTimezone = adjustTimezoneForTimestampTZ(obj, columnSubType);\n          res = new SnowflakeTimestampWithTimezone(res, specificSessionTimezone);\n        } else {\n          res = new SnowflakeTimestampWithTimezone(res);\n        }\n      }\n      // If timestamp type is NTZ and JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true, keep\n      // timezone in UTC to avoid daylight savings errors\n      else if (treatNTZAsUTC && columnSubType == Types.TIMESTAMP) {\n        res = new SnowflakeTimestampWithTimezone(res);\n      }\n      // If JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=false, default behavior is to honor\n      // client timezone for NTZ time. Move NTZ timestamp offset to correspond to\n      // client's timezone. JDBC_USE_SESSION_TIMEZONE overrides other params.\n      if (columnSubType == Types.TIMESTAMP\n          && ((!treatNTZAsUTC && honorClientTZForTimestampNTZ) || useSessionTimezone)) {\n        res = sfTS.moveToTimeZone(tz).getTimestamp();\n      }\n      // Adjust time if date happens before year 1582 for difference between\n      // Julian and Gregorian calendars\n      return ResultUtil.adjustTimestamp(res);\n    } else if (Types.DATE == columnType) {\n      Date d = getDate(obj, columnType, columnSubType, tz, scale);\n      if (d == null) {\n        return null;\n      }\n      return new Timestamp(d.getTime());\n    } else if (Types.TIME == columnType) {\n      Time t = getTime(obj, columnType, columnSubType, tz, scale);\n      if (t == null) {\n        return null;\n      }\n      if (useSessionTimezone) {\n        SFTime sfTime = ResultUtil.getSFTime(obj.toString(), scale, session);\n        return new SnowflakeTimestampWithTimezone(\n            sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS),\n            sfTime.getNanosecondsWithinSecond(),\n            TimeZone.getTimeZone(\"UTC\"));\n      }\n      return new Timestamp(t.getTime());\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.TIMESTAMP_STR, obj);\n    }\n  }\n\n  public Time getTime(Object obj, int columnType, int columnSubType, TimeZone tz, int scale)\n      throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    if (Types.TIME == columnType) {\n      SFTime sfTime = ResultUtil.getSFTime(obj.toString(), scale, session);\n      Time ts =\n          new Time(\n              sfTime.getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS));\n      if (useSessionTimezone) {\n        ts =\n            SnowflakeUtil.getTimeInSessionTimezone(\n                SnowflakeUtil.getSecondsFromMillis(ts.getTime()),\n                sfTime.getNanosecondsWithinSecond());\n      }\n      return ts;\n    } else if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) {\n      Timestamp ts = getTimestamp(obj, columnType, columnSubType, tz, scale);\n      if (ts == null) {\n        return null;\n      }\n      if (useSessionTimezone) {\n        ts = getTimestamp(obj, columnType, columnSubType, sessionTimeZone, scale);\n        TimeZone sessionTimeZone = adjustTimezoneForTimestampTZ(obj, columnSubType);\n        return new SnowflakeTimeWithTimezone(ts, sessionTimeZone, useSessionTimezone);\n      }\n      return new Time(ts.getTime());\n    } else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.TIME_STR, obj);\n    }\n  }\n\n  public Date getDate(Object obj, int columnType, int columnSubType, TimeZone tz, int scale)\n      throws SFException {\n    if (obj == null) {\n      return null;\n    }\n\n    if (Types.TIMESTAMP == columnType || Types.TIMESTAMP_WITH_TIMEZONE == columnType) {\n      if (tz == null) {\n        tz = TimeZone.getDefault();\n      }\n      if (columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ\n          || columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ) {\n        TimeZone specificSessionTimeZone = adjustTimezoneForTimestampTZ(obj, columnSubType);\n        return new SnowflakeDateWithTimezone(\n            getTimestamp(obj, columnType, columnSubType, tz, scale).getTime(),\n            specificSessionTimeZone,\n            useSessionTimezone);\n      }\n      return new Date(getTimestamp(obj, columnType, columnSubType, tz, scale).getTime());\n\n    } else if (Types.DATE == columnType) {\n      if (tz == null || !formatDateWithTimeZone) {\n        return ArrowResultUtil.getDate(Integer.parseInt((String) obj));\n      }\n      return ArrowResultUtil.getDate(Integer.parseInt((String) obj), tz, sessionTimeZone);\n    }\n    // for Types.TIME and all other type, throw user error\n    else {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DATE_STR, obj);\n    }\n  }\n\n  private TimeZone adjustTimezoneForTimestampTZ(Object obj, int columnSubType) {\n    // If the timestamp is of type timestamp_tz, use the associated offset timezone instead of the\n    // session timezone for formatting\n    if (obj != null\n        && columnSubType == SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ\n        && resultVersion > 0) {\n      String timestampStr = obj.toString();\n      int indexForSeparator = timestampStr.indexOf(' ');\n      String timezoneIndexStr = timestampStr.substring(indexForSeparator + 1);\n      return SFTimestamp.convertTimezoneIndexToTimeZone(Integer.parseInt(timezoneIndexStr));\n    }\n    // By default, return session timezone\n    return sessionTimeZone;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/json/NumberConverter.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.sql.Types;\nimport java.time.Duration;\nimport java.time.Period;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.ArrowVectorConverterUtil;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\npublic class NumberConverter {\n  // Precision of maximum long value in Java (2^63-1). Precision is 19\n  private static final int LONG_PRECISION = 19;\n\n  private static final BigDecimal MAX_LONG_VAL = new BigDecimal(Long.MAX_VALUE);\n  private static final BigDecimal MIN_LONG_VAL = new BigDecimal(Long.MIN_VALUE);\n  private static final int monthsInYear = 12;\n\n  public byte getByte(Object obj) {\n    if (obj == null) {\n      return 0;\n    }\n\n    if (obj instanceof String) {\n      return Byte.parseByte((String) obj);\n    } else {\n      return ((Number) obj).byteValue();\n    }\n  }\n\n  public Period getPeriod(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    try {\n      long value;\n      if (obj instanceof String) {\n        String objString = (String) obj;\n        value = Long.parseLong(objString);\n      } else {\n        value = ((Number) obj).longValue();\n      }\n      return Period.of((int) (value / monthsInYear), (int) (value % monthsInYear), 0);\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.PERIOD_STR, obj);\n    }\n  }\n\n  public Duration getDuration(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    try {\n      BigDecimal numNanos;\n      if (obj instanceof String) {\n        String objString = (String) obj;\n        numNanos = new BigDecimal(objString);\n      } else {\n        numNanos = getBigDecimal(obj, columnType);\n      }\n      try {\n        return ArrowVectorConverterUtil.getDurationFromNanos(numNanos);\n      } catch (ArithmeticException e) {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, columnType, \"Duration\", numNanos.toPlainString());\n      }\n    } catch (NumberFormatException nfe) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DURATION_STR, obj);\n    }\n  }\n\n  public short getShort(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return 0;\n    }\n    try {\n      if (obj instanceof String) {\n        String objString = (String) obj;\n        if (objString.contains(\".\") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) {\n          objString = objString.substring(0, objString.indexOf(\".\"));\n        }\n        return Short.parseShort(objString);\n      } else {\n        return ((Number) obj).shortValue();\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.SHORT_STR, obj);\n    }\n  }\n\n  public int getInt(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return 0;\n    }\n    try {\n      if (obj instanceof String) {\n        String objString = (String) obj;\n        if (objString.contains(\".\") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) {\n          objString = objString.substring(0, objString.indexOf(\".\"));\n        }\n        return Integer.parseInt(objString);\n      } else {\n        return ((Number) obj).intValue();\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.INT_STR, obj);\n    }\n  }\n\n  public long getLong(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return 0;\n    }\n    try {\n      if (obj instanceof String) {\n        String objString = (String) obj;\n        if (objString.contains(\".\") && (columnType == Types.FLOAT || columnType == Types.DOUBLE)) {\n          objString = objString.substring(0, objString.indexOf(\".\"));\n        }\n        return Long.parseLong(objString);\n      } else {\n        return ((Number) obj).longValue();\n      }\n    } catch (NumberFormatException nfe) {\n\n      if (Types.INTEGER == columnType || Types.SMALLINT == columnType) {\n        throw new SFException(\n            ErrorCode.INTERNAL_ERROR, SnowflakeUtil.LONG_STR + \": \" + obj.toString());\n      } else {\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.LONG_STR, obj);\n      }\n    }\n  }\n\n  public BigDecimal getBigDecimal(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    try {\n      if (columnType != Types.TIME\n          && columnType != Types.TIMESTAMP\n          && columnType != Types.TIMESTAMP_WITH_TIMEZONE) {\n        return new BigDecimal(obj.toString());\n      }\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BIG_DECIMAL_STR, obj);\n\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.BIG_DECIMAL_STR, obj);\n    }\n  }\n\n  public BigDecimal getBigDecimal(Object obj, int columnType, Integer scale) throws SFException {\n    if (obj == null) {\n      return null;\n    }\n    BigDecimal value = getBigDecimal(obj.toString(), columnType);\n    value = value.setScale(scale, RoundingMode.HALF_UP);\n    return value;\n  }\n\n  public float getFloat(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return 0;\n    }\n\n    try {\n      if (obj instanceof String) {\n        if (columnType != Types.TIME\n            && columnType != Types.TIMESTAMP\n            && columnType != Types.TIMESTAMP_WITH_TIMEZONE) {\n          if (\"inf\".equals(obj)) {\n            return Float.POSITIVE_INFINITY;\n          } else if (\"-inf\".equals(obj)) {\n            return Float.NEGATIVE_INFINITY;\n          } else {\n            return Float.parseFloat((String) obj);\n          }\n        }\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.FLOAT_STR, obj);\n      } else {\n        return ((Number) obj).floatValue();\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.FLOAT_STR, obj);\n    }\n  }\n\n  public double getDouble(Object obj, int columnType) throws SFException {\n    if (obj == null) {\n      return 0;\n    }\n    try {\n      if (obj instanceof String) {\n        if (columnType != Types.TIME\n            && columnType != Types.TIMESTAMP\n            && columnType != Types.TIMESTAMP_WITH_TIMEZONE) {\n          if (\"inf\".equals(obj)) {\n            return Double.POSITIVE_INFINITY;\n          } else if (\"-inf\".equals(obj)) {\n            return Double.NEGATIVE_INFINITY;\n          } else {\n            return Double.parseDouble((String) obj);\n          }\n        }\n        throw new SFException(\n            ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DOUBLE_STR, obj);\n      } else {\n        return ((Number) obj).doubleValue();\n      }\n    } catch (NumberFormatException ex) {\n      throw new SFException(\n          ErrorCode.INVALID_VALUE_CONVERT, columnType, SnowflakeUtil.DOUBLE_STR, obj);\n    }\n  }\n\n  public Object getBigInt(Object obj, int columnType) throws SFException {\n    // If precision is < precision of max long precision, we can automatically convert to long.\n    // Otherwise, do a check to ensure it doesn't overflow max long value.\n    if (obj == null) {\n      return null;\n    }\n    String numberAsString = obj.toString();\n    if (numberAsString.length() >= LONG_PRECISION) {\n      BigDecimal bigNum = getBigDecimal(obj, columnType);\n      if (bigNum.compareTo(MAX_LONG_VAL) == 1 || bigNum.compareTo(MIN_LONG_VAL) == -1) {\n        return bigNum;\n      }\n    }\n    return getLong(obj, columnType);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/json/StringConverter.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport java.sql.Date;\nimport java.sql.Types;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SFTime;\nimport net.snowflake.common.core.SFTimestamp;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\n\npublic class StringConverter {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(StringConverter.class);\n  private final TimeZone sessionTimeZone;\n  private final SFBinaryFormat binaryFormatter;\n  private final SnowflakeDateTimeFormat dateFormatter;\n  private final SnowflakeDateTimeFormat timeFormatter;\n  private final SnowflakeDateTimeFormat timestampNTZFormatter;\n  private final SnowflakeDateTimeFormat timestampLTZFormatter;\n  private final SnowflakeDateTimeFormat timestampTZFormatter;\n  private final long resultVersion;\n  private final SFBaseSession session;\n  private final Converters converters;\n\n  public StringConverter(\n      TimeZone sessionTimeZone,\n      SFBinaryFormat binaryFormatter,\n      SnowflakeDateTimeFormat dateFormatter,\n      SnowflakeDateTimeFormat timeFormatter,\n      SnowflakeDateTimeFormat timestampNTZFormatter,\n      SnowflakeDateTimeFormat timestampLTZFormatter,\n      SnowflakeDateTimeFormat timestampTZFormatter,\n      long resultVersion,\n      SFBaseSession session,\n      Converters converters) {\n    this.sessionTimeZone = sessionTimeZone;\n    this.binaryFormatter = binaryFormatter;\n    this.dateFormatter = dateFormatter;\n    this.timeFormatter = timeFormatter;\n    this.timestampNTZFormatter = timestampNTZFormatter;\n    this.timestampLTZFormatter = timestampLTZFormatter;\n    this.timestampTZFormatter = timestampTZFormatter;\n    this.resultVersion = resultVersion;\n    this.session = session;\n    this.converters = converters;\n  }\n\n  public String getString(Object obj, int columnType, int columnSubType, int scale)\n      throws SFException {\n    if (obj == null) {\n      return null;\n    }\n\n    switch (columnType) {\n      case Types.BOOLEAN:\n        return ResultUtil.getBooleanAsString(ResultUtil.getBoolean(obj.toString()));\n\n      case Types.TIMESTAMP:\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ:\n      case SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ:\n        return timestampToString(obj, columnType, columnSubType, scale);\n      case Types.DATE:\n        return dateToString(obj, columnType, columnSubType, scale);\n      case Types.TIME:\n        return timeToString(obj, scale);\n      case Types.BINARY:\n        return binaryToString(obj, columnType, columnSubType, scale);\n      default:\n        break;\n    }\n    return obj.toString();\n  }\n\n  private String timestampToString(Object obj, int columnType, int columnSubType, int scale)\n      throws SFException {\n    SFTimestamp sfTS =\n        ResultUtil.getSFTimestamp(\n            obj.toString(), scale, columnSubType, resultVersion, sessionTimeZone, session);\n\n    String timestampStr =\n        ResultUtil.getSFTimestampAsString(\n            sfTS,\n            columnType,\n            scale,\n            timestampNTZFormatter,\n            timestampLTZFormatter,\n            timestampTZFormatter,\n            session);\n\n    logger.debug(\n        \"Converting timestamp to string from: {} to: {}\",\n        (ArgSupplier) obj::toString,\n        timestampStr);\n\n    return timestampStr;\n  }\n\n  private String dateToString(Object obj, int columnType, int columnSubType, int scale)\n      throws SFException {\n    Date date =\n        converters\n            .getDateTimeConverter()\n            .getDate(obj, columnType, columnSubType, TimeZone.getDefault(), scale);\n\n    if (dateFormatter == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing date formatter\");\n    }\n\n    String dateStr = ResultUtil.getDateAsString(date, dateFormatter);\n\n    logger.debug(\"Converting date to string from: {} to: {}\", (ArgSupplier) obj::toString, dateStr);\n\n    return dateStr;\n  }\n\n  private String timeToString(Object obj, int scale) throws SFException {\n    SFTime sfTime = ResultUtil.getSFTime(obj.toString(), scale, session);\n\n    if (timeFormatter == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing time formatter\");\n    }\n\n    String timeStr = ResultUtil.getSFTimeAsString(sfTime, scale, timeFormatter);\n\n    logger.debug(\"Converting time to string from: {} to: {}\", (ArgSupplier) obj::toString, timeStr);\n\n    return timeStr;\n  }\n\n  private String binaryToString(Object obj, int columnType, int columnSubType, int scale)\n      throws SFException {\n    if (binaryFormatter == null) {\n      throw new SFException(ErrorCode.INTERNAL_ERROR, \"missing binary formatter\");\n    }\n\n    if (binaryFormatter == SFBinaryFormat.HEX) {\n      // Shortcut: the values are already passed with hex encoding, so just\n      // return the string unchanged rather than constructing an SFBinary.\n      return obj.toString();\n    }\n\n    SFBinary sfb =\n        new SFBinary(\n            converters.getBytesConverter().getBytes(obj, columnType, columnSubType, scale));\n    return binaryFormatter.format(sfb);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/Minicore.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport java.util.Collections;\nimport java.util.concurrent.CompletableFuture;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class Minicore {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(Minicore.class);\n\n  public static final String DISABLE_MINICORE_ENV_VAR = \"SNOWFLAKE_DISABLE_MINICORE\";\n  public static final String LIBRARY_BASE_NAME = \"libsf_mini_core\";\n\n  private static volatile Minicore INSTANCE;\n  private static volatile CompletableFuture<Void> INITIALIZATION_FUTURE;\n  private static boolean DISABLED_VIA_ENV_VAR = false;\n\n  private final MinicoreLoadResult loadResult;\n  private final MinicoreLibrary library;\n\n  private Minicore(MinicoreLoadResult loadResult, MinicoreLibrary library) {\n    this.loadResult = loadResult;\n    this.library = library;\n  }\n\n  public static synchronized void initializeAsync() {\n    if (INITIALIZATION_FUTURE != null) {\n      return; // Already started\n    }\n\n    // Check if minicore is disabled via environment variable\n    if (isMinicoreDisabled()) {\n      logger.debug(\n          \"Minicore initialization disabled via {} environment variable\", DISABLE_MINICORE_ENV_VAR);\n      DISABLED_VIA_ENV_VAR = true;\n      INITIALIZATION_FUTURE = CompletableFuture.completedFuture(null);\n      return;\n    }\n\n    INITIALIZATION_FUTURE =\n        CompletableFuture.runAsync(\n            () -> {\n              try {\n                logger.trace(\"Starting async minicore initialization\");\n                MinicoreLoader loader = new MinicoreLoader();\n                MinicoreLoadResult result = loader.loadLibrary();\n                INSTANCE = new Minicore(result, result.getLibrary());\n              } catch (Exception e) {\n                logger.debug(\"Unexpected error during minicore initialization\", e);\n                MinicoreLoadResult failedResult =\n                    MinicoreLoadResult.failure(\n                        \"Unexpected initialization error: \" + e.getMessage(),\n                        null,\n                        e,\n                        Collections.emptyList());\n                INSTANCE = new Minicore(failedResult, null);\n              }\n            });\n  }\n\n  private static boolean isMinicoreDisabled() {\n    String envValue = SnowflakeUtil.systemGetEnv(DISABLE_MINICORE_ENV_VAR);\n    return envValue != null && envValue.equalsIgnoreCase(\"true\");\n  }\n\n  public static synchronized void initialize() {\n    if (INSTANCE != null) {\n      return;\n    }\n\n    if (INITIALIZATION_FUTURE == null) {\n      initializeAsync();\n    }\n\n    try {\n      INITIALIZATION_FUTURE.join();\n    } catch (Exception e) {\n      logger.error(\"Failed to initialize minicore\", e);\n    }\n  }\n\n  public static Minicore getInstance() {\n    return INSTANCE;\n  }\n\n  public MinicoreLibrary getLibrary() {\n    return library;\n  }\n\n  public MinicoreLoadResult getLoadResult() {\n    return loadResult;\n  }\n\n  public static synchronized boolean hasInitializationStarted() {\n    return INITIALIZATION_FUTURE != null;\n  }\n\n  public static boolean isDisabledViaEnvVar() {\n    return DISABLED_VIA_ENV_VAR;\n  }\n\n  // This method is for testing only. Do not use in production code\n  public static synchronized void resetForTesting() {\n    INSTANCE = null;\n    INITIALIZATION_FUTURE = null;\n    DISABLED_VIA_ENV_VAR = false;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicoreLibrary.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport com.sun.jna.Library;\n\n/** JNA interface for the Snowflake minicore native library. */\npublic interface MinicoreLibrary extends Library {\n\n  /**\n   * Get the full version string from the minicore library. This method maps to the C function:\n   * const char* sf_core_full_version();\n   */\n  String sf_core_full_version();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicoreLoadError.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\npublic enum MinicoreLoadError {\n  DISABLED(\"Minicore is disabled with SNOWFLAKE_DISABLE_MINICORE env variable\"),\n  FAILED_TO_LOAD(\"Failed to load binary\"),\n  STILL_LOADING(\"Minicore is still loading\");\n\n  private final String message;\n\n  MinicoreLoadError(String message) {\n    this.message = message;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  @Override\n  public String toString() {\n    return message;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicoreLoadLogger.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class MinicoreLoadLogger {\n\n  private final long startTimeNanos;\n  private final List<String> logs;\n\n  public MinicoreLoadLogger() {\n    this.startTimeNanos = System.nanoTime();\n    this.logs = new ArrayList<>();\n  }\n\n  public void log(String message) {\n    long elapsedNanos = System.nanoTime() - startTimeNanos;\n    double elapsedMs = elapsedNanos / 1_000_000.0;\n    String timestampedMessage = String.format(\"[%.6fms] %s\", elapsedMs, message);\n    logs.add(timestampedMessage);\n  }\n\n  public List<String> getLogs() {\n    return Collections.unmodifiableList(logs);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicoreLoadResult.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class MinicoreLoadResult {\n\n  private final boolean success;\n  private final String errorMessage;\n  private final String libraryFileName;\n  private final MinicoreLibrary library;\n  private final String coreVersion;\n  private final Throwable exception;\n  private final List<String> logs;\n\n  private MinicoreLoadResult(\n      boolean success,\n      String errorMessage,\n      String libraryFileName,\n      MinicoreLibrary library,\n      String coreVersion,\n      Throwable exception,\n      List<String> logs) {\n    this.success = success;\n    this.errorMessage = errorMessage;\n    this.libraryFileName = libraryFileName;\n    this.library = library;\n    this.coreVersion = coreVersion;\n    this.exception = exception;\n    this.logs = logs != null ? logs : new ArrayList<>();\n  }\n\n  public static MinicoreLoadResult success(\n      String libraryFileName, MinicoreLibrary library, String coreVersion, List<String> logs) {\n    return new MinicoreLoadResult(true, null, libraryFileName, library, coreVersion, null, logs);\n  }\n\n  public static MinicoreLoadResult failure(\n      String errorMessage, String libraryFileName, Throwable exception, List<String> logs) {\n    return new MinicoreLoadResult(\n        false, errorMessage, libraryFileName, null, null, exception, logs);\n  }\n\n  public boolean isSuccess() {\n    return success;\n  }\n\n  public String getErrorMessage() {\n    return errorMessage;\n  }\n\n  public Throwable getException() {\n    return exception;\n  }\n\n  public String getLibraryFileName() {\n    return libraryFileName;\n  }\n\n  public MinicoreLibrary getLibrary() {\n    return library;\n  }\n\n  public String getCoreVersion() {\n    return coreVersion;\n  }\n\n  public List<String> getLogs() {\n    return Collections.unmodifiableList(logs);\n  }\n\n  @Override\n  public String toString() {\n    if (success) {\n      return String.format(\n          \"MinicoreLoadResult{success=true, libraryFileName='%s', version='%s'}\",\n          libraryFileName, coreVersion);\n    } else {\n      return String.format(\n          \"MinicoreLoadResult{success=false, error='%s', exception=%s}\",\n          errorMessage, exception != null ? exception.getClass().getSimpleName() : \"none\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicoreLoader.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.sun.jna.Native;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.EnumSet;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.commons.io.IOUtils;\n\npublic class MinicoreLoader {\n  private enum DirectoryType {\n    TEMP(\"temp\"),\n    HOME(\"home cache\"),\n    CWD(\"working\");\n\n    private String name;\n\n    DirectoryType(String name) {\n      this.name = name;\n    }\n  }\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(MinicoreLoader.class);\n  private static final String TEMP_DIR_PREFIX = \"snowflake-minicore-\";\n\n  private MinicoreLoadResult loadResult;\n  private final MinicoreLoadLogger loadLogger = new MinicoreLoadLogger();\n  private final MinicorePlatform platform = new MinicorePlatform();\n\n  public synchronized MinicoreLoadResult loadLibrary() {\n    if (loadResult != null) {\n      return loadResult;\n    }\n\n    loadLogger.log(\"Starting minicore loading\");\n    loadLogger.log(\n        \"Detected platform: OS=\" + platform.getOsName() + \", Arch=\" + platform.getOsArch());\n\n    if (!platform.isSupported()) {\n      loadLogger.log(\"Platform not supported\");\n      loadResult =\n          failure(\n              \"Unsupported platform: OS=\" + platform.getOsName() + \", Arch=\" + platform.getOsArch(),\n              null);\n      return loadResult;\n    }\n\n    loadLogger.log(\"Platform supported: \" + platform.getPlatformIdentifier());\n    loadResult = loadFromJar();\n    return loadResult;\n  }\n\n  private MinicoreLoadResult loadFromJar() {\n    String resourcePath = platform.getLibraryPath();\n    loadLogger.log(\"Library resource path: \" + resourcePath);\n\n    byte[] libraryBytes = readLibraryFromJar(resourcePath);\n    if (libraryBytes == null) {\n      return failure(\"Library resource not found in JAR: \" + resourcePath, null);\n    }\n\n    MinicoreLoadResult result =\n        tryDirectory(DirectoryType.TEMP, libraryBytes, this::createTempDirectory);\n    if (result != null) {\n      return result;\n    }\n\n    result = tryDirectory(DirectoryType.HOME, libraryBytes, this::getOrCreateHomeCacheDirectory);\n    if (result != null) {\n      return result;\n    }\n\n    result = tryDirectory(DirectoryType.CWD, libraryBytes, this::getWorkingDirectory);\n    if (result != null) {\n      return result;\n    }\n\n    loadLogger.log(\"No writable directory found\");\n    return failure(\"No writable directory found (tried: temp, home cache, working dir)\", null);\n  }\n\n  private byte[] readLibraryFromJar(String resourcePath) {\n    try (InputStream stream = MinicoreLoader.class.getResourceAsStream(resourcePath)) {\n      if (stream == null) {\n        loadLogger.log(\"Library resource not found in JAR\");\n        return null;\n      }\n      loadLogger.log(\"Library resource found in JAR\");\n      return IOUtils.toByteArray(stream);\n    } catch (IOException e) {\n      loadLogger.log(\"Failed to read library from JAR: \" + e.getMessage());\n      return null;\n    }\n  }\n\n  private MinicoreLoadResult tryDirectory(\n      DirectoryType directoryType, byte[] libraryBytes, DirectorySupplier supplier) {\n    Path targetPath = null;\n    Path createdTempDir = null;\n    try {\n      Path directory = supplier.get();\n      if (directory == null) {\n        return null;\n      }\n      // Track if this is a temp directory we created (so we can clean it up on failure)\n      if (directoryType == DirectoryType.TEMP) {\n        createdTempDir = directory;\n      }\n\n      targetPath = directory.resolve(platform.getLibraryFileName());\n      loadLogger.log(\"Trying \" + directoryType.name + \" directory: \" + directory);\n      return writeLoadAndCleanup(targetPath, libraryBytes);\n    } catch (Exception e) {\n      loadLogger.log(\"Failed to use \" + directoryType.name + \" directory: \" + e.getMessage());\n      cleanup(targetPath, createdTempDir);\n      return null;\n    }\n  }\n\n  private Path createTempDirectory() throws IOException {\n    Path tempDir = Files.createTempDirectory(TEMP_DIR_PREFIX);\n    setDirectoryPermissions(tempDir);\n    return tempDir;\n  }\n\n  private Path getOrCreateHomeCacheDirectory() throws IOException {\n    Path cacheDir = getHomeCacheDirectory();\n    if (cacheDir == null) {\n      return null;\n    }\n    if (!Files.exists(cacheDir)) {\n      Files.createDirectories(cacheDir);\n    }\n    // Always ensure correct permissions (may have been created by another process)\n    setDirectoryPermissions(cacheDir);\n    return cacheDir;\n  }\n\n  private Path getWorkingDirectory() {\n    String cwd = systemGetProperty(\"user.dir\");\n    return (cwd != null && !cwd.isEmpty()) ? Paths.get(cwd) : null;\n  }\n\n  /**\n   * Returns the OS-specific cache directory path:\n   *\n   * <ul>\n   *   <li>Windows: %USERPROFILE%/AppData/Local/Snowflake/Caches/minicore/\n   *   <li>MacOS: $HOME/Library/Caches/Snowflake/minicore/\n   *   <li>Other: $HOME/.cache/Snowflake/minicore/\n   * </ul>\n   */\n  Path getHomeCacheDirectory() {\n    String home = systemGetProperty(\"user.home\");\n    if (home == null || home.isEmpty()) {\n      return null;\n    }\n\n    switch (platform.getOs()) {\n      case WINDOWS:\n        return Paths.get(home, \"AppData\", \"Local\", \"Snowflake\", \"Caches\", \"minicore\");\n      case MAC:\n        return Paths.get(home, \"Library\", \"Caches\", \"Snowflake\", \"minicore\");\n      default:\n        return Paths.get(home, \".cache\", \"Snowflake\", \"minicore\");\n    }\n  }\n\n  private MinicoreLoadResult writeLoadAndCleanup(Path targetPath, byte[] libraryBytes)\n      throws IOException {\n    Files.write(targetPath, libraryBytes);\n    loadLogger.log(\"Wrote library to: \" + targetPath);\n    setFilePermissions(targetPath);\n\n    try {\n      loadLogger.log(\"Loading library\");\n      MinicoreLibrary library =\n          Native.load(targetPath.toAbsolutePath().toString(), MinicoreLibrary.class);\n      loadLogger.log(\"Library loaded successfully\");\n\n      return getVersionAndCreateResult(library);\n    } finally {\n      deleteQuietly(targetPath);\n      loadLogger.log(\"Deleted library file\");\n    }\n  }\n\n  private MinicoreLoadResult getVersionAndCreateResult(MinicoreLibrary library) {\n    try {\n      String version = library.sf_core_full_version();\n      loadLogger.log(\"Library version: \" + version);\n      return MinicoreLoadResult.success(\n          platform.getLibraryFileName(), library, version, loadLogger.getLogs());\n    } catch (UnsatisfiedLinkError e) {\n      loadLogger.log(\"Library missing sf_core_full_version symbol: \" + e.getMessage());\n      return failure(\"Library missing required symbol: sf_core_full_version\", e);\n    } catch (Exception e) {\n      loadLogger.log(\"Failed to get library version: \" + e.getMessage());\n      return failure(\"Failed to get library version: \" + e.getMessage(), e);\n    }\n  }\n\n  private void cleanup(Path filePath, Path tempDirectory) {\n    deleteQuietly(filePath);\n    deleteQuietly(tempDirectory);\n  }\n\n  private void deleteQuietly(Path path) {\n    try {\n      Files.deleteIfExists(path);\n    } catch (IOException e) {\n      logger.trace(\"Failed to delete: {}\", e.getMessage());\n    }\n  }\n\n  private MinicoreLoadResult failure(String message, Throwable cause) {\n    return MinicoreLoadResult.failure(\n        message, platform.getLibraryFileName(), cause, loadLogger.getLogs());\n  }\n\n  private void setDirectoryPermissions(Path path) throws IOException {\n    setPermissions(path, true);\n  }\n\n  private void setFilePermissions(Path path) throws IOException {\n    setPermissions(path, false);\n  }\n\n  private void setPermissions(Path path, boolean executable) throws IOException {\n    if (platform.getOs() == Constants.OS.WINDOWS) {\n      path.toFile().setReadable(true, true);\n      path.toFile().setWritable(true, true);\n      path.toFile().setExecutable(executable, true);\n    } else {\n      EnumSet<PosixFilePermission> perms =\n          EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE);\n      if (executable) {\n        perms.add(PosixFilePermission.OWNER_EXECUTE);\n      }\n      Files.setPosixFilePermissions(path, perms);\n    }\n  }\n\n  @FunctionalInterface\n  private interface DirectorySupplier {\n    Path get() throws IOException;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicorePlatform.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.Constants.Architecture;\nimport net.snowflake.client.internal.core.Constants.OS;\nimport net.snowflake.client.internal.util.LibcDetails;\n\npublic class MinicorePlatform {\n  private final OS os;\n  private final Architecture architecture;\n  private final String osName;\n  private final String osArch;\n  private final String resourcePath;\n  private final boolean supported;\n\n  public MinicorePlatform() {\n    this.osName = systemGetProperty(\"os.name\");\n    this.osArch = systemGetProperty(\"os.arch\");\n    this.os = Constants.getOS();\n    this.architecture = Constants.getArchitecture();\n    this.resourcePath = buildResourcePath();\n    this.supported =\n        resourcePath != null && MinicorePlatform.class.getResource(resourcePath) != null;\n  }\n\n  public boolean isSupported() {\n    return supported;\n  }\n\n  public String getLibraryPath() {\n    if (!supported) {\n      throw new UnsupportedOperationException(\n          String.format(\n              \"Minicore library not available for platform: OS=%s (%s), Arch=%s (%s)\",\n              os, osName, architecture, osArch));\n    }\n    return resourcePath;\n  }\n\n  private String buildResourcePath() {\n    String fileName = getLibraryFileName();\n    if (fileName == null) {\n      return null;\n    }\n    // Flat structure: /minicore/{filename}\n    return \"/minicore/\" + fileName;\n  }\n\n  public String getPlatformIdentifier() {\n    String osId = getOsIdentifier();\n    if (osId == null || architecture == Architecture.UNKNOWN) {\n      return null;\n    }\n\n    String archId = architecture.getIdentifier();\n\n    // For Linux, add libc family: linux-x86_64-glibc or linux-aarch64-musl\n    String libcFamily = LibcDetails.load().getFamily();\n    if (libcFamily != null) {\n      return osId + \"-\" + archId + \"-\" + libcFamily;\n    }\n\n    // For non-Linux platforms (libcFamily is null), just os-arch\n    return osId + \"-\" + archId;\n  }\n\n  /** Get OS identifier (used in filenames). Returns: macos, linux, windows, aix. */\n  private String getOsIdentifier() {\n    if (os == null) {\n      return null;\n    }\n    switch (os) {\n      case LINUX:\n        if (Constants.isAix()) {\n          return \"aix\";\n        }\n        return \"linux\";\n      case MAC:\n        return \"macos\";\n      case WINDOWS:\n        return \"windows\";\n      default:\n        return null;\n    }\n  }\n\n  private String getLibraryExtension() {\n    if (os == null) {\n      return \"\";\n    }\n    if (Constants.isAix()) {\n      return \".so\";\n    }\n    switch (os) {\n      case WINDOWS:\n        return \".dll\";\n      case MAC:\n        return \".dylib\";\n      default: // Linux included.\n        return \".so\";\n    }\n  }\n\n  /**\n   * Get the library filename with platform encoding.\n   *\n   * <p>Format: {base_name}_{os}_{arch}[_{libc}]{extension}\n   *\n   * <p>Examples:\n   *\n   * <ul>\n   *   <li>Linux x86_64 glibc: {@code libsf_mini_core_linux_x86_64_glibc.so}\n   *   <li>Linux aarch64 musl: {@code libsf_mini_core_linux_aarch64_musl.so}\n   *   <li>macOS x86_64: {@code libsf_mini_core_macos_x86_64.dylib}\n   *   <li>macOS aarch64: {@code libsf_mini_core_macos_aarch64.dylib}\n   *   <li>Windows x86_64: {@code libsf_mini_core_windows_x86_64.dll}\n   *   <li>AIX ppc64: {@code libsf_mini_core_aix_ppc64.so}\n   * </ul>\n   */\n  public String getLibraryFileName() {\n    String osId = getOsIdentifier();\n    if (osId == null || architecture == Architecture.UNKNOWN) {\n      return null;\n    }\n\n    String archId = architecture.getIdentifier();\n\n    StringBuilder fileName = new StringBuilder();\n    fileName.append(Minicore.LIBRARY_BASE_NAME);\n    fileName.append(\"_\").append(osId);\n    fileName.append(\"_\").append(archId);\n\n    // For Linux, add libc family\n    String libcFamily = LibcDetails.load().getFamily();\n    if (libcFamily != null) {\n      fileName.append(\"_\").append(libcFamily);\n    }\n\n    fileName.append(getLibraryExtension());\n    return fileName.toString();\n  }\n\n  public OS getOs() {\n    return os;\n  }\n\n  public String getOsName() {\n    return osName;\n  }\n\n  public String getOsArch() {\n    return osArch;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\n        \"MinicorePlatform{os=%s, arch=%s, osName='%s', osArch='%s', supported=%s}\",\n        os, architecture, osName, osArch, isSupported());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/minicore/MinicoreTelemetry.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\n\n/**\n * Telemetry data for minicore library loading and platform information.\n *\n * <p>This class encapsulates all telemetry information related to minicore that should be included\n * in the CLIENT_ENVIRONMENT section of the login-request.\n *\n * <p>Fields:\n *\n * <ul>\n *   <li><b>ISA</b>: Instruction Set Architecture (e.g., \"amd64\", \"arm64\")\n *   <li><b>CORE_VERSION</b>: Result of sf_core_full_version() if successful\n *   <li><b>CORE_FILE_NAME</b>: Binary library file name that the driver tried to load\n *   <li><b>CORE_LOAD_ERROR</b>: One of three error states from {@link MinicoreLoadError}\n *   <li><b>loadLogs</b>: List of log messages with detailed error info from the loading process\n * </ul>\n *\n * <p>Note: OS, OS_VERSION, and OS_DETAILS are set by SessionUtil.createClientEnvironmentInfo()\n */\npublic class MinicoreTelemetry {\n\n  private final String isa;\n  private final String coreVersion;\n  private final String coreFileName;\n  private final MinicoreLoadError coreLoadError;\n  private final List<String> coreLoadLogs;\n\n  private MinicoreTelemetry(\n      String isa,\n      String coreVersion,\n      String coreFileName,\n      MinicoreLoadError coreLoadError,\n      List<String> coreLoadLogs) {\n    this.isa = isa;\n    this.coreVersion = coreVersion;\n    this.coreFileName = coreFileName;\n    this.coreLoadError = coreLoadError;\n    this.coreLoadLogs = coreLoadLogs != null ? coreLoadLogs : new ArrayList<>();\n  }\n\n  /**\n   * Create telemetry based on current Minicore state.\n   *\n   * <p>Handles three error cases:\n   *\n   * <ul>\n   *   <li>Disabled via env var: {@link MinicoreLoadError#DISABLED}\n   *   <li>Still loading: {@link MinicoreLoadError#STILL_LOADING}\n   *   <li>Failed to load: {@link MinicoreLoadError#FAILED_TO_LOAD}\n   * </ul>\n   */\n  public static MinicoreTelemetry create() {\n    String isa = Constants.getArchitecture().getIdentifier();\n\n    // Check if disabled via environment variable\n    if (Minicore.isDisabledViaEnvVar()) {\n      return new MinicoreTelemetry(isa, null, null, MinicoreLoadError.DISABLED, null);\n    }\n\n    // Check if initialization hasn't started or instance not yet available\n    Minicore minicore = Minicore.getInstance();\n    if (minicore == null) {\n      return new MinicoreTelemetry(isa, null, null, MinicoreLoadError.STILL_LOADING, null);\n    }\n\n    MinicoreLoadResult result = minicore.getLoadResult();\n    if (result == null) {\n      return new MinicoreTelemetry(isa, null, null, MinicoreLoadError.STILL_LOADING, null);\n    }\n\n    return fromLoadResult(result);\n  }\n\n  /** Create telemetry from a successful or failed load result. */\n  public static MinicoreTelemetry fromLoadResult(MinicoreLoadResult loadResult) {\n    String isa = Constants.getArchitecture().getIdentifier();\n    String coreVersion = loadResult.getCoreVersion();\n    String coreFileName = loadResult.getLibraryFileName();\n    List<String> logs = new ArrayList<>(loadResult.getLogs());\n\n    if (loadResult.isSuccess()) {\n      return new MinicoreTelemetry(isa, coreVersion, coreFileName, null, logs);\n    }\n\n    // For failures, add the detailed error message to logs for visibility\n    String detailedError = loadResult.getErrorMessage();\n    if (detailedError != null && !detailedError.isEmpty()) {\n      logs.add(\"Error: \" + detailedError);\n    }\n    Throwable exception = loadResult.getException();\n    if (exception != null) {\n      logs.add(\"Exception: \" + exception.getClass().getName() + \": \" + exception.getMessage());\n    }\n\n    return new MinicoreTelemetry(\n        isa, coreVersion, coreFileName, MinicoreLoadError.FAILED_TO_LOAD, logs);\n  }\n\n  // Convert telemetry data to Map for client environment telemetry. Load logs are not included.\n  public Map<String, Object> toClientEnvironmentTelemetryMap() {\n    Map<String, Object> map = new HashMap<>();\n\n    if (isa != null) {\n      map.put(\"ISA\", SecretDetector.maskSecrets(isa));\n    }\n\n    if (coreVersion != null) {\n      map.put(\"CORE_VERSION\", SecretDetector.maskSecrets(coreVersion));\n    }\n\n    if (coreFileName != null) {\n      map.put(\"CORE_FILE_NAME\", SecretDetector.maskSecrets(coreFileName));\n    }\n\n    if (coreLoadError != null) {\n      map.put(\"CORE_LOAD_ERROR\", SecretDetector.maskSecrets(coreLoadError.getMessage()));\n    }\n\n    return map;\n  }\n\n  // Convert telemetry data to ObjectNode for in-band telemetry.\n  public ObjectNode toInBandTelemetryNode() {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    ObjectNode message = mapper.createObjectNode();\n\n    message.put(\"type\", \"client_minicore_load\");\n    message.put(\"source\", \"JDBC\");\n    message.put(\"success\", coreLoadError == null);\n\n    if (coreFileName != null) {\n      message.put(\"libraryFileName\", SecretDetector.maskSecrets(coreFileName));\n    }\n\n    if (coreVersion != null) {\n      message.put(\"coreVersion\", SecretDetector.maskSecrets(coreVersion));\n    }\n\n    if (coreLoadError != null) {\n      message.put(\"error\", SecretDetector.maskSecrets(coreLoadError.getMessage()));\n    }\n\n    if (!coreLoadLogs.isEmpty()) {\n      ArrayNode logsArray = message.putArray(\"loadLogs\");\n      coreLoadLogs.stream().map(SecretDetector::maskSecrets).forEach(logsArray::add);\n    }\n\n    return message;\n  }\n\n  @Override\n  public String toString() {\n    return String.format(\n        \"MinicoreTelemetry{isa='%s', coreVersion='%s', coreFileName='%s', coreLoadError='%s', logs=%d entries}\",\n        isa,\n        coreVersion,\n        coreFileName,\n        coreLoadError != null ? coreLoadError.getMessage() : null,\n        coreLoadLogs.size());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/structs/SQLDataCreationHelper.java",
    "content": "package net.snowflake.client.internal.core.structs;\n\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.util.Optional;\nimport java.util.function.Supplier;\n\npublic class SQLDataCreationHelper {\n  public static <T> T create(Class<T> type) throws SQLException {\n    Optional<Supplier<SQLData>> typeFactory = SnowflakeObjectTypeFactories.get(type);\n    return (T)\n        typeFactory\n            .map(Supplier::get)\n            .orElseGet(() -> createUsingReflection((Class<SQLData>) type));\n  }\n\n  private static SQLData createUsingReflection(Class<? extends SQLData> type) {\n    try {\n      return type.newInstance();\n    } catch (InstantiationException | IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/core/structs/SnowflakeObjectTypeFactories.java",
    "content": "package net.snowflake.client.internal.core.structs;\n\nimport java.sql.SQLData;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.function.Supplier;\n\npublic class SnowflakeObjectTypeFactories {\n  private static final Map<Class<?>, Supplier<SQLData>> factories = new ConcurrentHashMap<>();\n\n  public static void register(Class<?> type, Supplier<SQLData> factory) {\n    Objects.requireNonNull((Object) type, \"type cannot be null\");\n    Objects.requireNonNull((Object) factory, \"factory cannot be null\");\n    factories.put(type, factory);\n  }\n\n  public static void unregister(Class<?> type) {\n    Objects.requireNonNull((Object) type, \"type cannot be null\");\n    factories.remove(type);\n  }\n\n  public static Optional<Supplier<SQLData>> get(Class<?> type) {\n    Objects.requireNonNull((Object) type, \"type cannot be null\");\n    return Optional.ofNullable(factories.get(type));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/driver/AutoConfigurationHelper.java",
    "content": "package net.snowflake.client.internal.driver;\n\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.config.ConnectionParameters;\nimport net.snowflake.client.internal.config.SFConnectionConfigParser;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Helper for handling JDBC auto-configuration via connections.toml file.\n *\n * <p>Auto-configuration allows users to specify \"jdbc:snowflake:auto\" as the connection URL and\n * have connection parameters loaded from a configuration file. This provides a convenient way to\n * manage connection settings without hardcoding them in application code.\n *\n * <p>Configuration files are typically located in:\n *\n * <ul>\n *   <li>~/.snowflake/connections.toml (user-level)\n *   <li>Project-specific locations as configured\n * </ul>\n */\npublic final class AutoConfigurationHelper {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(AutoConfigurationHelper.class);\n\n  /**\n   * The URL prefix that indicates auto-configuration should be used.\n   *\n   * <p>When a connection URL starts with or equals this prefix, the driver will attempt to load\n   * connection parameters from a configuration file.\n   */\n  public static final String AUTO_CONNECTION_PREFIX = \"jdbc:snowflake:auto\";\n\n  private AutoConfigurationHelper() {\n    // Utility class - prevent instantiation\n  }\n\n  /**\n   * Check if the URL indicates auto-configuration should be used.\n   *\n   * @param url the JDBC connection URL\n   * @return true if auto-configuration is enabled\n   */\n  public static boolean isAutoConfigurationUrl(String url) {\n    return url != null && url.contains(AUTO_CONNECTION_PREFIX);\n  }\n\n  /**\n   * Load connection parameters from configuration file or use provided parameters.\n   *\n   * <p>If the URL contains the auto-configuration prefix, this method attempts to load parameters\n   * from the configuration file. Otherwise, it creates ConnectionParameters from the provided URL\n   * and info Properties.\n   *\n   * @param url JDBC connection URL\n   * @param info connection properties\n   * @return ConnectionParameters with resolved configuration\n   * @throws SnowflakeSQLException if auto-configuration is requested but fails\n   */\n  public static ConnectionParameters resolveConnectionParameters(String url, Properties info)\n      throws SnowflakeSQLException {\n\n    if (isAutoConfigurationUrl(url)) {\n      logger.debug(\n          \"JDBC connection initializing with URL '{}'. Autoconfiguration is enabled.\",\n          AUTO_CONNECTION_PREFIX);\n\n      ConnectionParameters params = SFConnectionConfigParser.buildConnectionParameters(url);\n      if (params == null) {\n        throw new SnowflakeSQLException(\n            \"Unavailable connection configuration parameters expected for \"\n                + \"auto configuration using file\");\n      }\n      return params;\n    } else {\n      return new ConnectionParameters(url, info);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/driver/ConnectionFactory.java",
    "content": "package net.snowflake.client.internal.driver;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.config.ConnectionParameters;\nimport net.snowflake.client.internal.jdbc.SnowflakeConnectString;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Factory for creating Snowflake JDBC connections.\n *\n * <p>This class handles the validation and creation of connections from JDBC URLs and properties.\n * It supports both standard connection URLs and auto-configuration.\n *\n * <p>This class is thread-safe and stateless.\n */\npublic final class ConnectionFactory {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ConnectionFactory.class);\n\n  private ConnectionFactory() {\n    // Utility class - prevent instantiation\n  }\n\n  /**\n   * Creates a connection to the Snowflake database.\n   *\n   * <p>This method validates the URL, resolves connection parameters (including auto-configuration\n   * if applicable), parses the connection string, and creates a new connection instance.\n   *\n   * @param url the database URL\n   * @param info additional connection properties\n   * @return a Connection object, or null if the URL is not accepted by this driver (per JDBC spec)\n   * @throws SQLException if a database access error occurs or the connection parameters are invalid\n   */\n  public static Connection createConnection(String url, Properties info) throws SQLException {\n    // Resolve connection parameters (handles auto-configuration if needed)\n    ConnectionParameters params = AutoConfigurationHelper.resolveConnectionParameters(url, info);\n\n    // Validate URL is not null\n    if (params.getUrl() == null) {\n      throw new SnowflakeSQLException(\"Unable to connect to url of 'null'.\");\n    }\n\n    // Check if URL has supported prefix (return null if not, per JDBC spec)\n    if (!SnowflakeConnectString.hasSupportedPrefix(params.getUrl())) {\n      // Per JDBC spec: Driver.connect() should return null if URL is not recognized\n      return null;\n    }\n\n    // Parse and validate the connection string\n    SnowflakeConnectString connectString =\n        SnowflakeConnectString.parse(params.getUrl(), params.getParams());\n\n    if (!connectString.isValid()) {\n      throw new SnowflakeSQLException(\"Connection string is invalid. Unable to parse.\");\n    }\n\n    // Create and return the connection implementation\n    return new SnowflakeConnectionImpl(params.getUrl(), params.getParams());\n  }\n\n  /**\n   * Creates a connection using auto-configuration.\n   *\n   * <p>This is a convenience method that uses the auto-configuration URL prefix to load connection\n   * parameters from the connections.toml file.\n   *\n   * @return a Connection object\n   * @throws SQLException if a database access error occurs or configuration cannot be loaded\n   */\n  public static Connection createConnectionWithAutoConfig() throws SQLException {\n    logger.debug(\"Creating connection with auto-configuration\");\n    return createConnection(AutoConfigurationHelper.AUTO_CONNECTION_PREFIX, null);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/driver/DriverInitializer.java",
    "content": "package net.snowflake.client.internal.driver;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport net.snowflake.client.internal.core.SecurityUtil;\nimport net.snowflake.client.internal.core.minicore.Minicore;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Handles all one-time initialization for the Snowflake JDBC driver.\n *\n * <p>This includes:\n *\n * <ul>\n *   <li>Arrow result format support\n *   <li>BouncyCastle security provider registration\n *   <li>Out-of-band telemetry configuration\n *   <li>Suppression of illegal reflective access warnings\n * </ul>\n *\n * <p>All initialization is performed once in the static block of {@link\n * net.snowflake.client.api.driver.SnowflakeDriver}. This class is thread-safe and ensures\n * initialization happens exactly once.\n */\npublic final class DriverInitializer {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(DriverInitializer.class);\n\n  private static volatile boolean initialized = false;\n  private static volatile boolean arrowEnabled = true;\n  private static volatile String arrowDisableReason = null;\n\n  private DriverInitializer() {\n    // Utility class - prevent instantiation\n  }\n\n  /**\n   * Perform all driver initialization. This method is idempotent and thread-safe.\n   *\n   * <p>If called multiple times, subsequent calls are no-ops.\n   */\n  public static synchronized void initialize() {\n    if (initialized) {\n      logger.debug(\"Driver already initialized, skipping\");\n      return;\n    }\n\n    logger.debug(\"Initializing Snowflake JDBC Driver...\");\n\n    initializeArrowSupport();\n    initializeSecurityProvider();\n    initializeTelemetry();\n    initializeMinicore();\n\n    initialized = true;\n    logger.debug(\"Snowflake JDBC Driver initialization complete\");\n  }\n\n  /**\n   * Initialize Apache Arrow support for high-performance result sets.\n   *\n   * <p>This method attempts to suppress illegal reflective access warnings from Netty/Arrow. If\n   * initialization fails, Arrow is disabled and the driver will fall back to JSON result format.\n   *\n   * <p>Note: Prior to version 4.x, this method set the system property\n   * io.netty.tryReflectionSetAccessible=true. Testing has shown this is no longer necessary with\n   * Arrow 17.0.0+ and Netty 4.1.130+, as modern versions handle Java module system restrictions\n   * without requiring this property.\n   */\n  private static void initializeArrowSupport() {\n    try {\n      // Suppress reflective access warnings from Netty/Arrow\n      suppressIllegalReflectiveAccessWarnings();\n\n      arrowEnabled = true;\n      logger.debug(\"Arrow result format enabled successfully\");\n    } catch (Throwable t) {\n      arrowEnabled = false;\n      arrowDisableReason = t.getLocalizedMessage();\n      logger.warn(\"Failed to enable Arrow result format: {}\", arrowDisableReason);\n    }\n  }\n\n  /**\n   * Suppress illegal reflective access warnings caused by Netty/Arrow dependencies.\n   *\n   * <p>Only suppresses warnings if not explicitly disabled via the {@code\n   * snowflake.jdbc.enable.illegalAccessWarning} system property.\n   *\n   * <p>This uses sun.misc.Unsafe to set jdk.internal.module.IllegalAccessLogger's logger to null,\n   * effectively disabling the warnings. This is necessary because the Netty dependency of Apache\n   * Arrow causes warnings on Java 9+. Failures are non-fatal and the driver will continue to\n   * function normally.\n   */\n  private static void suppressIllegalReflectiveAccessWarnings() {\n    if (\"true\"\n        .equals(SnowflakeUtil.systemGetProperty(\"snowflake.jdbc.enable.illegalAccessWarning\"))) {\n      logger.debug(\"Keeping illegal access warnings enabled (user requested)\");\n      return;\n    }\n\n    try {\n      // Get sun.misc.Unsafe class and instance\n      Class<?> unsafeClass = Class.forName(\"sun.misc.Unsafe\");\n      Field field = unsafeClass.getDeclaredField(\"theUnsafe\");\n      field.setAccessible(true);\n      Object unsafe = field.get(null);\n\n      // Get Unsafe methods for manipulating static fields\n      Method putObjectVolatile =\n          unsafeClass.getDeclaredMethod(\n              \"putObjectVolatile\", Object.class, long.class, Object.class);\n      Method staticFieldOffset = unsafeClass.getDeclaredMethod(\"staticFieldOffset\", Field.class);\n      Method staticFieldBase = unsafeClass.getDeclaredMethod(\"staticFieldBase\", Field.class);\n\n      // Get the IllegalAccessLogger class and its logger field\n      Class<?> loggerClass = Class.forName(\"jdk.internal.module.IllegalAccessLogger\");\n      Field loggerField = loggerClass.getDeclaredField(\"logger\");\n\n      // Use Unsafe to set the logger to null, effectively disabling warnings\n      Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);\n      Object loggerBase = staticFieldBase.invoke(unsafe, loggerField);\n      putObjectVolatile.invoke(unsafe, loggerBase, loggerOffset, null);\n\n      logger.debug(\"Illegal reflective access warnings suppressed\");\n    } catch (Throwable ex) {\n      // Non-fatal - just log and continue\n      logger.debug(\"Failed to suppress reflective access warnings: {}\", ex.getMessage());\n    }\n  }\n\n  /**\n   * Register BouncyCastle security provider for cryptographic operations.\n   *\n   * <p>BouncyCastle is used for various security operations in the JDBC driver.\n   */\n  private static void initializeSecurityProvider() {\n    try {\n      SecurityUtil.addBouncyCastleProvider();\n      logger.debug(\"BouncyCastle security provider registered\");\n    } catch (Throwable t) {\n      logger.warn(\"Failed to register BouncyCastle provider: {}\", t.getMessage());\n    }\n  }\n\n  /**\n   * Configure telemetry settings for the driver.\n   *\n   * <p>By default, out-of-band telemetry is disabled.\n   */\n  private static void initializeTelemetry() {\n    try {\n      TelemetryService.disableOOBTelemetry();\n      logger.debug(\"Out-of-band telemetry disabled\");\n    } catch (Throwable t) {\n      logger.warn(\"Failed to configure telemetry: {}\", t.getMessage());\n    }\n  }\n\n  /**\n   * Start asynchronous minicore native library loading.\n   *\n   * <p>Minicore is loaded in the background so it can overlap with connection setup. The loading\n   * result is reported via telemetry during session establishment.\n   */\n  private static void initializeMinicore() {\n    try {\n      Minicore.initializeAsync();\n      logger.debug(\"Minicore async initialization started\");\n    } catch (Throwable t) {\n      logger.trace(\"Failed to start minicore initialization\", t);\n    }\n  }\n\n  // Public accessors for Arrow status\n\n  /**\n   * Check if Arrow result format is enabled.\n   *\n   * @return true if Arrow is enabled, false otherwise\n   */\n  public static boolean isArrowEnabled() {\n    return arrowEnabled;\n  }\n\n  /**\n   * Get the reason why Arrow was disabled (if applicable).\n   *\n   * @return error message if Arrow is disabled, null otherwise\n   */\n  public static String getArrowDisableReason() {\n    return arrowDisableReason;\n  }\n\n  /**\n   * Check if driver has been initialized.\n   *\n   * @return true if initialized, false otherwise\n   */\n  public static boolean isInitialized() {\n    return initialized;\n  }\n\n  static synchronized void resetForTesting() {\n    initialized = false;\n    arrowEnabled = true;\n    arrowDisableReason = null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/driver/DriverVersion.java",
    "content": "package net.snowflake.client.internal.driver;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.common.core.SqlState;\n\n/**\n * Manages Snowflake JDBC driver version information.\n *\n * <p>This class is responsible for parsing and providing access to the driver's version numbers\n * (major, minor, and patch). The version is read from {@code version.properties} on the classpath,\n * which is populated by Maven resource filtering at build time.\n *\n * <p>This class is thread-safe and immutable.\n */\npublic final class DriverVersion {\n\n  private static final String implementVersion = readAndNormalizeVersion();\n\n  private final int major;\n\n  private final int minor;\n  private final long patch;\n  private final String fullVersion;\n  private static final DriverVersion INSTANCE = parseFromStaticVersion();\n\n  private DriverVersion(int major, int minor, long patch, String fullVersion) {\n    this.major = major;\n    this.minor = minor;\n    this.patch = patch;\n    this.fullVersion = fullVersion;\n  }\n\n  /**\n   * Gets the singleton instance of DriverVersion.\n   *\n   * @return the driver version instance\n   */\n  public static DriverVersion getInstance() {\n    return INSTANCE;\n  }\n\n  private static String readAndNormalizeVersion() {\n    String version = DriverVersionProperties.get(\"version\");\n    if (version == null) {\n      return null;\n    }\n    if (version.endsWith(\"-SNAPSHOT\")) {\n      version = version.substring(0, version.length() - \"-SNAPSHOT\".length());\n    }\n    return version;\n  }\n\n  /**\n   * Parses a version string in the format \"major.minor.patch\".\n   *\n   * @param versionString the version string to parse\n   * @return a DriverVersion instance\n   * @throws IllegalArgumentException if the version string is invalid\n   */\n  public static DriverVersion parse(String versionString) throws IllegalArgumentException {\n    if (versionString == null || versionString.isEmpty()) {\n      throw new IllegalArgumentException(\"Version string cannot be null or empty\");\n    }\n\n    String[] parts = versionString.split(\"\\\\.\");\n    if (parts.length != 3) {\n      throw new IllegalArgumentException(\n          \"Invalid version format: \" + versionString + \". Expected: major.minor.patch\");\n    }\n\n    try {\n      int major = Integer.parseInt(parts[0]);\n      int minor = Integer.parseInt(parts[1]);\n      long patch = Long.parseLong(parts[2]);\n      return new DriverVersion(major, minor, patch, versionString);\n    } catch (NumberFormatException e) {\n      throw new IllegalArgumentException(\"Invalid version numbers in: \" + versionString, e);\n    }\n  }\n\n  /**\n   * Parses the version from the static version string defined in the driver. This is called during\n   * static initialization.\n   *\n   * @return a DriverVersion instance\n   */\n  private static DriverVersion parseFromStaticVersion() {\n    try {\n      if (implementVersion != null && !implementVersion.isEmpty()) {\n        return parse(implementVersion);\n      } else {\n        throw new SnowflakeSQLException(\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            /*session = */ null,\n            \"Snowflake JDBC Version is not set. \"\n                + \"Ensure static version string was initialized.\");\n      }\n    } catch (IllegalArgumentException ex) {\n      // Re-throw as SnowflakeSQLException for consistency with original code\n      throw new RuntimeException(\n          new SnowflakeSQLException(\n              SqlState.INTERNAL_ERROR,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              /*session = */ null,\n              \"Invalid Snowflake JDBC Version: \" + implementVersion));\n    } catch (SnowflakeSQLException ex) {\n      throw new RuntimeException(ex);\n    }\n  }\n\n  /**\n   * Gets the major version number.\n   *\n   * @return the major version\n   */\n  public int getMajor() {\n    return major;\n  }\n\n  /**\n   * Gets the minor version number.\n   *\n   * @return the minor version\n   */\n  public int getMinor() {\n    return minor;\n  }\n\n  /**\n   * Gets the patch version number.\n   *\n   * @return the patch version\n   */\n  public long getPatch() {\n    return patch;\n  }\n\n  /**\n   * Gets the full version string in the format \"major.minor.patch\".\n   *\n   * @return the full version string\n   */\n  public String getFullVersion() {\n    return fullVersion;\n  }\n\n  @Override\n  public String toString() {\n    return fullVersion;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/driver/DriverVersionProperties.java",
    "content": "package net.snowflake.client.internal.driver;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Properties;\n\n/**\n * Provides access to build-time properties from {@code version.properties}, which is populated by\n * Maven resource filtering.\n */\npublic final class DriverVersionProperties {\n\n  private static final String RESOURCE = \"/net/snowflake/client/jdbc/version.properties\";\n  private static final Properties PROPERTIES = loadProperties();\n\n  private DriverVersionProperties() {}\n\n  public static String get(String key) {\n    return PROPERTIES.getProperty(key);\n  }\n\n  private static Properties loadProperties() {\n    Properties props = new Properties();\n    try (InputStream is = DriverVersionProperties.class.getResourceAsStream(RESOURCE)) {\n      if (is != null) {\n        props.load(is);\n      }\n    } catch (IOException e) {\n      // Fall through with empty properties — callers will get null and fail with clear messages\n    }\n    return props;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/exception/SnowflakeSQLLoggedException.java",
    "content": "package net.snowflake.client.internal.exception;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.jdbc.telemetry.SqlExceptionTelemetryHandler;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\n\n/**\n * This SnowflakeSQLLoggedException class extends the SnowflakeSQLException class to add OOB\n * telemetry data for sql exceptions. Not all sql exceptions require OOB telemetry logging so the\n * exceptions in this class should only be thrown if there is a need for logging the exception with\n * OOB telemetry.\n */\npublic class SnowflakeSQLLoggedException extends SnowflakeSQLException {\n\n  public SnowflakeSQLLoggedException(\n      String queryID, SFSession session, String sqlState, String message, Exception cause) {\n    super(queryID, cause, sqlState, TelemetryUtil.NO_VENDOR_CODE, message);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        queryID, sqlState, TelemetryUtil.NO_VENDOR_CODE, session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param reason exception reason\n   * @param SQLState the SQL state\n   * @param vendorCode the vendor code\n   * @param queryId the query ID\n   */\n  public SnowflakeSQLLoggedException(\n      SFBaseSession session, String reason, String SQLState, int vendorCode, String queryId) {\n    super(queryId, reason, SQLState, vendorCode);\n    SqlExceptionTelemetryHandler.sendTelemetry(queryId, SQLState, vendorCode, session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param vendorCode the vendor code\n   * @param SQLState the SQL state\n   */\n  public SnowflakeSQLLoggedException(SFBaseSession session, int vendorCode, String SQLState) {\n    super((String) null, SQLState, vendorCode);\n    SqlExceptionTelemetryHandler.sendTelemetry(null, SQLState, vendorCode, session, this);\n  }\n\n  /**\n   * @param queryId the query ID\n   * @param session SFBaseSession\n   * @param vendorCode the vendor code\n   * @param SQLState the SQL state\n   */\n  public SnowflakeSQLLoggedException(\n      String queryId, SFBaseSession session, int vendorCode, String SQLState) {\n    super(queryId, SQLState, vendorCode);\n    SqlExceptionTelemetryHandler.sendTelemetry(queryId, SQLState, vendorCode, session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param SQLState the SQL state\n   * @param reason the exception reason\n   */\n  public SnowflakeSQLLoggedException(SFBaseSession session, String SQLState, String reason) {\n    super(null, reason, SQLState);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        null, SQLState, TelemetryUtil.NO_VENDOR_CODE, session, this);\n  }\n\n  /**\n   * @param queryId the query ID\n   * @param session SFBaseSession\n   * @param SQLState the SQL state\n   * @param reason the exception reason\n   */\n  public SnowflakeSQLLoggedException(\n      String queryId, SFBaseSession session, String SQLState, String reason) {\n    super(null, reason, SQLState);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        queryId, SQLState, TelemetryUtil.NO_VENDOR_CODE, session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param vendorCode the vendor code\n   * @param SQLState the SQL state\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(\n      SFBaseSession session, int vendorCode, String SQLState, Object... params) {\n    this(null, session, vendorCode, SQLState, params);\n  }\n\n  /**\n   * @param queryId the query ID\n   * @param session SFBaseSession\n   * @param vendorCode the vendor code\n   * @param SQLState the SQL state\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(\n      String queryId, SFBaseSession session, int vendorCode, String SQLState, Object... params) {\n    super(queryId, SQLState, vendorCode, params);\n    SqlExceptionTelemetryHandler.sendTelemetry(queryId, SQLState, vendorCode, session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param errorCode the error code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(SFBaseSession session, ErrorCode errorCode, Object... params) {\n    super(errorCode, params);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        null, errorCode.getSqlState(), errorCode.getMessageCode(), session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param errorCode the error code\n   * @param ex Throwable exception\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(\n      SFBaseSession session, ErrorCode errorCode, Throwable ex, Object... params) {\n    super(ex, errorCode, params);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        null, errorCode.getSqlState(), errorCode.getMessageCode(), session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param SQLState the SQL state\n   * @param vendorCode the vendor code\n   * @param ex Throwable exception\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(\n      SFBaseSession session, String SQLState, int vendorCode, Throwable ex, Object... params) {\n    super(ex, SQLState, vendorCode, params);\n    SqlExceptionTelemetryHandler.sendTelemetry(null, SQLState, vendorCode, session, this);\n  }\n\n  /**\n   * @param queryId the query ID\n   * @param session SFBaseSession\n   * @param SQLState the SQL state\n   * @param vendorCode the vendor code\n   * @param ex Throwable exception\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(\n      String queryId,\n      SFBaseSession session,\n      String SQLState,\n      int vendorCode,\n      Throwable ex,\n      Object... params) {\n    super(queryId, ex, SQLState, vendorCode, params);\n    SqlExceptionTelemetryHandler.sendTelemetry(queryId, SQLState, vendorCode, session, this);\n  }\n\n  /**\n   * @param queryId the query ID\n   * @param session SFBaseSession\n   * @param errorCode the error code\n   * @param params additional parameters\n   */\n  public SnowflakeSQLLoggedException(\n      String queryId, SFBaseSession session, ErrorCode errorCode, Object... params) {\n    super(queryId, errorCode, params);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        queryId, null, TelemetryUtil.NO_VENDOR_CODE, session, this);\n  }\n\n  /**\n   * @param session SFBaseSession\n   * @param e throwable exception\n   */\n  public SnowflakeSQLLoggedException(SFBaseSession session, SFException e) {\n    super(e);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        null, null, TelemetryUtil.NO_VENDOR_CODE, session, this);\n  }\n\n  /**\n   * @param queryId the query ID\n   * @param session SFBaseSession\n   * @param reason exception reason\n   */\n  public SnowflakeSQLLoggedException(String queryId, SFBaseSession session, String reason) {\n    super(queryId, reason, null);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        queryId, null, TelemetryUtil.NO_VENDOR_CODE, session, this);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/ArrowResultChunk.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.core.arrow.ArrowVectorConverterUtil.initConverter;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.channels.ClosedByInterruptException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.ArrowResultChunkIndexSorter;\nimport net.snowflake.client.internal.core.arrow.ArrowVectorConverter;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.BitVector;\nimport org.apache.arrow.vector.DateDayVector;\nimport org.apache.arrow.vector.DecimalVector;\nimport org.apache.arrow.vector.FieldVector;\nimport org.apache.arrow.vector.Float8Vector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.SmallIntVector;\nimport org.apache.arrow.vector.TinyIntVector;\nimport org.apache.arrow.vector.ValueVector;\nimport org.apache.arrow.vector.VarBinaryVector;\nimport org.apache.arrow.vector.VarCharVector;\nimport org.apache.arrow.vector.VectorSchemaRoot;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.ipc.ArrowStreamReader;\nimport org.apache.arrow.vector.util.TransferPair;\n\npublic class ArrowResultChunk extends SnowflakeResultChunk {\n  /**\n   * A 2-D array of arrow ValueVectors, this list represents data in the whole chunk. Since each\n   * chunk is divided into record batches and each record batch is composed of list of column\n   * vectors.\n   *\n   * <p>So the outer list is list of record batches, inner list represents list of columns\n   */\n  private final ArrayList<List<ValueVector>> batchOfVectors;\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ArrowResultChunk.class);\n\n  /** arrow root allocator used by this resultSet */\n  private final RootAllocator rootAllocator;\n\n  private boolean enableSortFirstResultChunk;\n  private IntVector firstResultChunkSortedIndices;\n  private VectorSchemaRoot root;\n  private SFBaseSession session;\n\n  public ArrowResultChunk(\n      String url,\n      int rowCount,\n      int colCount,\n      int uncompressedSize,\n      RootAllocator rootAllocator,\n      SFBaseSession session) {\n    super(url, rowCount, colCount, uncompressedSize);\n    this.batchOfVectors = new ArrayList<>();\n    this.rootAllocator = rootAllocator;\n    this.session = session;\n  }\n\n  private void addBatchData(List<ValueVector> batch) {\n    batchOfVectors.add(batch);\n  }\n\n  /**\n   * Read an inputStream of arrow data bytes and load them into java vectors of value. Note, there\n   * is no copy of data involved once data is loaded into memory. a.k.a ArrowStreamReader originally\n   * allocates the memory to hold vectors, but those memory ownership is transfer into\n   * ArrowResultChunk class and once ArrowStreamReader is garbage collected, memory will not be\n   * cleared up\n   *\n   * @param is inputStream which contains arrow data file in bytes\n   * @throws IOException if failed to read data as arrow file\n   */\n  public void readArrowStream(InputStream is) throws IOException {\n    ArrayList<ValueVector> valueVectors = new ArrayList<>();\n    try (ArrowStreamReader reader = new ArrowStreamReader(is, rootAllocator)) {\n      root = reader.getVectorSchemaRoot();\n      while (reader.loadNextBatch()) {\n        valueVectors = new ArrayList<>();\n\n        for (FieldVector f : root.getFieldVectors()) {\n          // transfer will not copy data but transfer ownership of memory\n          // from streamReader to resultChunk\n          TransferPair t = f.getTransferPair(rootAllocator);\n          t.transfer();\n          valueVectors.add(t.getTo());\n        }\n\n        addBatchData(valueVectors);\n        root.clear();\n      }\n    } catch (ClosedByInterruptException cbie) {\n      // happens when the statement is closed before finish parsing\n      logger.debug(\"Interrupted when loading Arrow result\", cbie);\n      valueVectors.forEach(ValueVector::close);\n      freeData();\n    } catch (Exception ex) {\n      valueVectors.forEach(ValueVector::close);\n      freeData();\n      throw ex;\n    }\n  }\n\n  @Override\n  public void reset() {\n    freeData();\n    this.batchOfVectors.clear();\n  }\n\n  @Override\n  public long computeNeededChunkMemory() {\n    return getUncompressedSize();\n  }\n\n  @Override\n  public void freeData() {\n    batchOfVectors.forEach(list -> list.forEach(ValueVector::close));\n    this.batchOfVectors.clear();\n    if (firstResultChunkSortedIndices != null) {\n      firstResultChunkSortedIndices.close();\n    }\n    if (root != null) {\n      root.clear();\n      root = null;\n    }\n  }\n\n  /**\n   * @param dataConversionContext DataConversionContext\n   * @return an iterator to iterate over current chunk\n   */\n  public ArrowChunkIterator getIterator(DataConversionContext dataConversionContext) {\n    return new ArrowChunkIterator(dataConversionContext);\n  }\n\n  /**\n   * @return an empty iterator to iterate over current chunk\n   */\n  public static ArrowChunkIterator getEmptyChunkIterator() {\n    return new EmptyArrowResultChunk().new ArrowChunkIterator(null);\n  }\n\n  public void enableSortFirstResultChunk() {\n    enableSortFirstResultChunk = true;\n  }\n\n  /** Iterator class used to go through the arrow chunk row by row */\n  public class ArrowChunkIterator {\n    /** index of record batch that iterator currently points to */\n    private int currentRecordBatchIndex;\n\n    /** total number of record batch */\n    private int totalRecordBatch;\n\n    /** index of row inside current record batch that iterator points to */\n    private int currentRowInRecordBatch;\n\n    /** number of rows inside current record batch */\n    private int rowCountInCurrentRecordBatch;\n\n    /**\n     * list of converters that attached to current record batch Note: this list is updated every\n     * time iterator points to a new record batch\n     */\n    private List<ArrowVectorConverter> currentConverters;\n\n    /** formatters to each data type */\n    private DataConversionContext dataConversionContext;\n\n    ArrowChunkIterator(DataConversionContext dataConversionContext) {\n      this.currentRecordBatchIndex = -1;\n      this.totalRecordBatch = batchOfVectors.size();\n      this.currentRowInRecordBatch = -1;\n      this.rowCountInCurrentRecordBatch = 0;\n      this.dataConversionContext = dataConversionContext;\n    }\n\n    /**\n     * Given a list of arrow vectors (all columns in a single record batch), return list of arrow\n     * vector converter. Note, converter is built on top of arrow vector, so that arrow data can be\n     * converted back to java data\n     *\n     * @param vectors list of arrow vectors\n     * @return list of converters on top of each converters\n     */\n    private List<ArrowVectorConverter> initConverters(List<ValueVector> vectors)\n        throws SnowflakeSQLException {\n      List<ArrowVectorConverter> converters = new ArrayList<>();\n      for (int i = 0; i < vectors.size(); i++) {\n        converters.add(initConverter(vectors.get(i), dataConversionContext, session, i));\n      }\n      return converters;\n    }\n\n    /**\n     * Advance to next row.\n     *\n     * @return true if there is a next row\n     * @throws SnowflakeSQLException if an error is encountered.\n     */\n    public boolean next() throws SnowflakeSQLException {\n      currentRowInRecordBatch++;\n      if (currentRowInRecordBatch < rowCountInCurrentRecordBatch) {\n        // still in current recordbatch\n        return true;\n      } else {\n        currentRecordBatchIndex++;\n        if (currentRecordBatchIndex < totalRecordBatch) {\n          this.currentRowInRecordBatch = 0;\n          if (currentRecordBatchIndex == 0 && sortFirstResultChunkEnabled()) {\n            // perform client-side sorting for the first chunk (only used in Snowflake internal\n            // regression tests)\n            // if first chunk has multiple record batches, merge them into one and sort it\n            if (batchOfVectors.size() > 1) {\n              mergeBatchesIntoOne();\n              totalRecordBatch = 1;\n            }\n            this.rowCountInCurrentRecordBatch =\n                batchOfVectors.get(currentRecordBatchIndex).get(0).getValueCount();\n            currentConverters = initConverters(batchOfVectors.get(currentRecordBatchIndex));\n            sortFirstResultChunk(currentConverters);\n          } else {\n            this.rowCountInCurrentRecordBatch =\n                batchOfVectors.get(currentRecordBatchIndex).get(0).getValueCount();\n            currentConverters = initConverters(batchOfVectors.get(currentRecordBatchIndex));\n          }\n          return true;\n        }\n      }\n      return false;\n    }\n\n    public boolean isLast() {\n      return currentRecordBatchIndex + 1 == totalRecordBatch\n          && currentRowInRecordBatch + 1 == rowCountInCurrentRecordBatch;\n    }\n\n    public boolean isAfterLast() {\n      return currentRecordBatchIndex >= totalRecordBatch\n          && currentRowInRecordBatch >= rowCountInCurrentRecordBatch;\n    }\n\n    public ArrowResultChunk getChunk() {\n      return ArrowResultChunk.this;\n    }\n\n    public ArrowVectorConverter getCurrentConverter(int columnIdx) throws SFException {\n      if (columnIdx < 0 || columnIdx >= currentConverters.size()) {\n        throw new SFException(ErrorCode.COLUMN_DOES_NOT_EXIST, columnIdx + 1);\n      }\n\n      return currentConverters.get(columnIdx);\n    }\n\n    /**\n     * @return index of row in current record batch\n     */\n    public int getCurrentRowInRecordBatch() {\n      if (sortFirstResultChunkEnabled() && currentRecordBatchIndex == 0) {\n        return firstResultChunkSortedIndices.get(currentRowInRecordBatch);\n      } else {\n        return currentRowInRecordBatch;\n      }\n    }\n  }\n\n  /**\n   * merge arrow result chunk with more than one batches into one record batch (Only used for the\n   * first chunk when client side sorting is required)\n   *\n   * @throws SnowflakeSQLException if failed to merge first result chunk\n   */\n  public void mergeBatchesIntoOne() throws SnowflakeSQLException {\n    try {\n      List<ValueVector> first = batchOfVectors.get(0);\n      for (int i = 1; i < batchOfVectors.size(); i++) {\n        List<ValueVector> batch = batchOfVectors.get(i);\n        mergeBatch(first, batch);\n        batch.forEach(ValueVector::close);\n      }\n      batchOfVectors.clear();\n      batchOfVectors.add(first);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          \"Failed to merge first result chunk: \" + ex.getLocalizedMessage());\n    }\n  }\n\n  /**\n   * merge right batch into the left batch\n   *\n   * @param left\n   * @param right\n   */\n  private void mergeBatch(List<ValueVector> left, List<ValueVector> right) throws SFException {\n    for (int i = 0; i < left.size(); i++) {\n      mergeVector(left.get(i), right.get(i));\n    }\n  }\n\n  /**\n   * todo append values from the right vector to the left\n   *\n   * @param left\n   * @param right\n   */\n  private void mergeVector(ValueVector left, ValueVector right) throws SFException {\n    if (left instanceof StructVector) {\n      mergeStructVector((StructVector) left, (StructVector) right);\n    } else {\n      mergeNonStructVector(left, right);\n    }\n  }\n\n  /**\n   * TODO merge StructVector used by Snowflake timestamp types\n   *\n   * @param left\n   * @param right\n   */\n  private void mergeStructVector(StructVector left, StructVector right) throws SFException {\n    int numOfChildren = left.getChildrenFromFields().size();\n    for (int i = 0; i < numOfChildren; i++) {\n      mergeNonStructVector(\n          left.getChildrenFromFields().get(i), right.getChildrenFromFields().get(i));\n    }\n    int offset = left.getValueCount();\n    for (int i = 0; i < right.getValueCount(); i++) {\n      if (right.isNull(i)) {\n        left.setNull(offset + i);\n      }\n    }\n    left.setValueCount(offset + right.getValueCount());\n  }\n\n  /**\n   * merge not struct vectors\n   *\n   * @param left\n   * @param right\n   */\n  private void mergeNonStructVector(ValueVector left, ValueVector right) throws SFException {\n    if (left instanceof BigIntVector) {\n      BigIntVector bigIntVectorLeft = (BigIntVector) left;\n      BigIntVector bigIntVectorRight = (BigIntVector) right;\n      int offset = bigIntVectorLeft.getValueCount();\n      for (int i = 0; i < bigIntVectorRight.getValueCount(); i++) {\n        if (bigIntVectorRight.isNull(i)) {\n          bigIntVectorLeft.setNull(offset + i);\n        } else {\n          bigIntVectorLeft.setSafe(offset + i, bigIntVectorRight.get(i));\n        }\n      }\n      bigIntVectorLeft.setValueCount(offset + bigIntVectorRight.getValueCount());\n    } else if (left instanceof BitVector) {\n      BitVector bitVectorLeft = (BitVector) left;\n      BitVector bitVectorRight = (BitVector) right;\n      int offset = bitVectorLeft.getValueCount();\n      for (int i = 0; i < bitVectorRight.getValueCount(); i++) {\n        if (bitVectorRight.isNull(i)) {\n          bitVectorLeft.setNull(offset + i);\n        } else {\n          try {\n            bitVectorLeft.setSafe(offset + i, bitVectorRight.get(i));\n          } catch (IndexOutOfBoundsException e) {\n            // this can be a bug in arrow that doesn't safely set value for\n            // BitVector so we have to reAlloc manually\n            bitVectorLeft.reAlloc();\n            bitVectorLeft.setSafe(offset + i, bitVectorRight.get(i));\n          }\n        }\n      }\n      bitVectorLeft.setValueCount(offset + bitVectorRight.getValueCount());\n    } else if (left instanceof DateDayVector) {\n      DateDayVector dateDayVectorLeft = (DateDayVector) left;\n      DateDayVector dateDayVectorRight = (DateDayVector) right;\n      int offset = dateDayVectorLeft.getValueCount();\n      for (int i = 0; i < dateDayVectorRight.getValueCount(); i++) {\n        if (dateDayVectorRight.isNull(i)) {\n          dateDayVectorLeft.setNull(offset + i);\n        } else {\n          dateDayVectorLeft.setSafe(offset + i, dateDayVectorRight.get(i));\n        }\n      }\n      dateDayVectorLeft.setValueCount(offset + dateDayVectorRight.getValueCount());\n    } else if (left instanceof DecimalVector) {\n      DecimalVector decimalVectorLeft = (DecimalVector) left;\n      DecimalVector decimalVectorRight = (DecimalVector) right;\n      int offset = decimalVectorLeft.getValueCount();\n      for (int i = 0; i < decimalVectorRight.getValueCount(); i++) {\n        if (decimalVectorRight.isNull(i)) {\n          decimalVectorLeft.setNull(offset + i);\n        } else {\n          decimalVectorLeft.setSafe(offset + i, decimalVectorRight.get(i));\n        }\n      }\n      decimalVectorLeft.setValueCount(offset + decimalVectorRight.getValueCount());\n    } else if (left instanceof Float8Vector) {\n      Float8Vector float8VectorLeft = (Float8Vector) left;\n      Float8Vector float8VectorRight = (Float8Vector) right;\n      int offset = float8VectorLeft.getValueCount();\n      for (int i = 0; i < float8VectorRight.getValueCount(); i++) {\n        if (float8VectorRight.isNull(i)) {\n          float8VectorLeft.setNull(offset + i);\n        } else {\n          float8VectorLeft.setSafe(offset + i, float8VectorRight.get(i));\n        }\n      }\n      float8VectorLeft.setValueCount(offset + float8VectorRight.getValueCount());\n    } else if (left instanceof IntVector) {\n      IntVector intVectorLeft = (IntVector) left;\n      IntVector intVectorRight = (IntVector) right;\n      int offset = intVectorLeft.getValueCount();\n      for (int i = 0; i < intVectorRight.getValueCount(); i++) {\n        if (intVectorRight.isNull(i)) {\n          intVectorLeft.setNull(offset + i);\n        } else {\n          intVectorLeft.setSafe(offset + i, intVectorRight.get(i));\n        }\n      }\n      intVectorLeft.setValueCount(offset + intVectorRight.getValueCount());\n    } else if (left instanceof SmallIntVector) {\n      SmallIntVector smallIntVectorLeft = (SmallIntVector) left;\n      SmallIntVector smallIntVectorRight = (SmallIntVector) right;\n      int offset = smallIntVectorLeft.getValueCount();\n      for (int i = 0; i < smallIntVectorRight.getValueCount(); i++) {\n        if (smallIntVectorRight.isNull(i)) {\n          smallIntVectorLeft.setNull(offset + i);\n        } else {\n          smallIntVectorLeft.setSafe(offset + i, smallIntVectorRight.get(i));\n        }\n      }\n      smallIntVectorLeft.setValueCount(offset + smallIntVectorRight.getValueCount());\n    } else if (left instanceof TinyIntVector) {\n      TinyIntVector tinyIntVectorLeft = (TinyIntVector) left;\n      TinyIntVector tinyIntVectorRight = (TinyIntVector) right;\n      int offset = tinyIntVectorLeft.getValueCount();\n      for (int i = 0; i < tinyIntVectorRight.getValueCount(); i++) {\n        if (tinyIntVectorRight.isNull(i)) {\n          tinyIntVectorLeft.setNull(offset + i);\n        } else {\n          tinyIntVectorLeft.setSafe(offset + i, tinyIntVectorRight.get(i));\n        }\n      }\n      tinyIntVectorLeft.setValueCount(offset + tinyIntVectorRight.getValueCount());\n    } else if (left instanceof VarBinaryVector) {\n      VarBinaryVector varBinaryVectorLeft = (VarBinaryVector) left;\n      VarBinaryVector varBinaryVectorRight = (VarBinaryVector) right;\n      int offset = varBinaryVectorLeft.getValueCount();\n      for (int i = 0; i < varBinaryVectorRight.getValueCount(); i++) {\n        if (varBinaryVectorRight.isNull(i)) {\n          varBinaryVectorLeft.setNull(offset + i);\n        } else {\n          varBinaryVectorLeft.setSafe(offset + i, varBinaryVectorRight.get(i));\n        }\n      }\n      varBinaryVectorLeft.setValueCount(offset + varBinaryVectorRight.getValueCount());\n    } else if (left instanceof VarCharVector) {\n      VarCharVector varCharVectorLeft = (VarCharVector) left;\n      VarCharVector varCharVectorRight = (VarCharVector) right;\n      int offset = varCharVectorLeft.getValueCount();\n      for (int i = 0; i < varCharVectorRight.getValueCount(); i++) {\n        if (varCharVectorRight.isNull(i)) {\n          varCharVectorLeft.setNull(offset + i);\n        } else {\n          varCharVectorLeft.setSafe(offset + i, varCharVectorRight.get(i));\n        }\n      }\n      varCharVectorLeft.setValueCount(offset + varCharVectorRight.getValueCount());\n    } else {\n      throw new SFException(\n          ErrorCode.INTERNAL_ERROR, \"Failed to merge vector due to unknown vector type\");\n    }\n  }\n\n  private void sortFirstResultChunk(List<ArrowVectorConverter> converters)\n      throws SnowflakeSQLException {\n    try {\n      List<ValueVector> firstResultChunk = this.batchOfVectors.get(0);\n      ArrowResultChunkIndexSorter sorter =\n          new ArrowResultChunkIndexSorter(firstResultChunk, converters);\n      firstResultChunkSortedIndices = sorter.sort();\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          \"Failed to sort first result chunk: \" + ex.getLocalizedMessage());\n    }\n  }\n\n  private boolean sortFirstResultChunkEnabled() {\n    return enableSortFirstResultChunk;\n  }\n\n  /**\n   * Empty arrow result chunk implementation. Used when rowset from server is null or empty or in\n   * testing\n   */\n  private static class EmptyArrowResultChunk extends ArrowResultChunk {\n    EmptyArrowResultChunk() {\n      super(\"\", 0, 0, 0, null, null);\n    }\n\n    @Override\n    public final long computeNeededChunkMemory() {\n      return 0;\n    }\n\n    @Override\n    public final void freeData() {\n      // do nothing\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/BindingParameterMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport java.util.List;\n\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class BindingParameterMetadata {\n  private String type;\n  private String name;\n  private Integer length;\n  private Integer byteLength;\n  private Integer precision;\n  private Integer scale;\n\n  private boolean nullable = true;\n  private List<BindingParameterMetadata> fields;\n\n  public BindingParameterMetadata(String type) {\n    this.type = type;\n  }\n\n  public BindingParameterMetadata(String type, String name) {\n    this.type = type;\n    this.name = name;\n  }\n\n  public BindingParameterMetadata(\n      String type,\n      String name,\n      Integer length,\n      Integer byteLength,\n      Integer precision,\n      Integer scale,\n      Boolean nullable) {\n    this.type = type;\n    this.name = name;\n    this.length = length;\n    this.byteLength = byteLength;\n    this.precision = precision;\n    this.scale = scale;\n    this.nullable = nullable;\n  }\n\n  public BindingParameterMetadata() {}\n\n  public String getType() {\n    return type;\n  }\n\n  public void setType(String type) {\n    this.type = type;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public void setName(String name) {\n    this.name = name;\n  }\n\n  public Integer getLength() {\n    return length;\n  }\n\n  public void setLength(Integer length) {\n    this.length = length;\n  }\n\n  public Integer getByteLength() {\n    return byteLength;\n  }\n\n  public void setByteLength(Integer byteLength) {\n    this.byteLength = byteLength;\n  }\n\n  public Integer getPrecision() {\n    return precision;\n  }\n\n  public void setPrecision(Integer precision) {\n    this.precision = precision;\n  }\n\n  public Integer getScale() {\n    return scale;\n  }\n\n  public void setScale(Integer scale) {\n    this.scale = scale;\n  }\n\n  public Boolean isNullable() {\n    return nullable;\n  }\n\n  public void setNullable(Boolean nullable) {\n    this.nullable = nullable;\n  }\n\n  public List<BindingParameterMetadata> getFields() {\n    return fields;\n  }\n\n  public void setFields(List<BindingParameterMetadata> fields) {\n    this.fields = fields;\n  }\n\n  public static class BindingParameterMetadataBuilder {\n    private BindingParameterMetadata bindingParameterMetadata;\n\n    private BindingParameterMetadataBuilder() {\n      bindingParameterMetadata = new BindingParameterMetadata();\n    }\n\n    public BindingParameterMetadataBuilder withType(String type) {\n      bindingParameterMetadata.type = type;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withName(String name) {\n      bindingParameterMetadata.name = name;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withLength(Integer length) {\n      bindingParameterMetadata.length = length;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withByteLength(Integer byteLength) {\n      bindingParameterMetadata.byteLength = byteLength;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withPrecision(Integer precision) {\n      bindingParameterMetadata.precision = precision;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withScale(Integer scale) {\n      bindingParameterMetadata.scale = scale;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withNullable(Boolean nullable) {\n      bindingParameterMetadata.nullable = nullable;\n      return this;\n    }\n\n    public BindingParameterMetadataBuilder withFields(List<BindingParameterMetadata> fields) {\n      bindingParameterMetadata.fields = fields;\n      return this;\n    }\n\n    public static BindingParameterMetadataBuilder bindingParameterMetadata() {\n      return new BindingParameterMetadataBuilder();\n    }\n\n    public BindingParameterMetadata build() {\n      return bindingParameterMetadata;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/ChunkDownloadContext.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.util.Map;\nimport net.snowflake.client.internal.core.SFBaseSession;\n\n/**\n * Simple struct to contain download context for a chunk. This is useful to organize the collection\n * of properties that may be used for containing download information, and allows for the\n * getInputStream() method to be overridden.\n */\npublic class ChunkDownloadContext {\n  private final SnowflakeChunkDownloader chunkDownloader;\n\n  public SnowflakeChunkDownloader getChunkDownloader() {\n    return chunkDownloader;\n  }\n\n  public SnowflakeResultChunk getResultChunk() {\n    return resultChunk;\n  }\n\n  public String getQrmk() {\n    return qrmk;\n  }\n\n  public int getChunkIndex() {\n    return chunkIndex;\n  }\n\n  public Map<String, String> getChunkHeadersMap() {\n    return chunkHeadersMap;\n  }\n\n  public int getNetworkTimeoutInMilli() {\n    return networkTimeoutInMilli;\n  }\n\n  public int getAuthTimeout() {\n    return authTimeout;\n  }\n\n  public int getSocketTimeout() {\n    return socketTimeout;\n  }\n\n  public SFBaseSession getSession() {\n    return session;\n  }\n\n  private final SnowflakeResultChunk resultChunk;\n  private final String qrmk;\n  private final int chunkIndex;\n  private final Map<String, String> chunkHeadersMap;\n  private final int networkTimeoutInMilli;\n  private final int authTimeout;\n  private final int socketTimeout;\n  private final int maxHttpRetries;\n  private final SFBaseSession session;\n\n  public ChunkDownloadContext(\n      SnowflakeChunkDownloader chunkDownloader,\n      SnowflakeResultChunk resultChunk,\n      String qrmk,\n      int chunkIndex,\n      Map<String, String> chunkHeadersMap,\n      int networkTimeoutInMilli,\n      int authTimeout,\n      int socketTimeout,\n      int maxHttpRetries,\n      SFBaseSession session) {\n    this.chunkDownloader = chunkDownloader;\n    this.resultChunk = resultChunk;\n    this.qrmk = qrmk;\n    this.chunkIndex = chunkIndex;\n    this.chunkHeadersMap = chunkHeadersMap;\n    this.networkTimeoutInMilli = networkTimeoutInMilli;\n    this.authTimeout = authTimeout;\n    this.socketTimeout = socketTimeout;\n    this.maxHttpRetries = maxHttpRetries;\n    this.session = session;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/ColumnTypeInfo.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\npublic class ColumnTypeInfo {\n  private int columnType;\n  private String extColTypeName;\n  private SnowflakeType snowflakeType;\n\n  public ColumnTypeInfo(int columnType, String extColTypeName, SnowflakeType snowflakeType) {\n    this.columnType = columnType;\n    this.extColTypeName = extColTypeName;\n    this.snowflakeType = snowflakeType;\n  }\n\n  public int getColumnType() {\n    return columnType;\n  }\n\n  public String getExtColTypeName() {\n    return extColTypeName;\n  }\n\n  public SnowflakeType getSnowflakeType() {\n    return snowflakeType;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/CompressedStreamFactory.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.core.Constants.MB;\nimport static net.snowflake.common.core.FileCompressionType.GZIP;\nimport static net.snowflake.common.core.FileCompressionType.ZSTD;\n\nimport com.github.luben.zstd.ZstdInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.zip.GZIPInputStream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.Header;\n\nclass CompressedStreamFactory {\n\n  private static final int STREAM_BUFFER_SIZE = MB;\n\n  /**\n   * Determine the format of the response, if it is not either plain text or gzip, raise an error.\n   */\n  public InputStream createBasedOnEncodingHeader(InputStream is, Header encoding)\n      throws IOException, SnowflakeSQLException {\n    if (encoding != null) {\n      if (GZIP.name().equalsIgnoreCase(encoding.getValue())) {\n        return new GZIPInputStream(is, STREAM_BUFFER_SIZE);\n      } else if (ZSTD.name().equalsIgnoreCase(encoding.getValue())) {\n        return new ZstdInputStream(is);\n      } else {\n        throw new SnowflakeSQLException(\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            \"Exception: unexpected compression got \" + encoding.getValue());\n      }\n    } else {\n      return DefaultResultStreamProvider.detectGzipAndGetStream(is);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/DBMetadataResultSetMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * For function call getTables/getSchemas, we returned resultset. We stored these resultSetMetadata\n * here\n */\npublic enum DBMetadataResultSetMetadata {\n  GET_CATALOGS(\n      Collections.singletonList(\"TABLE_CAT\"),\n      Collections.singletonList(\"TEXT\"),\n      Collections.singletonList(Types.VARCHAR)),\n\n  GET_SCHEMAS(\n      Arrays.asList(\"TABLE_SCHEM\", \"TABLE_CATALOG\"),\n      Arrays.asList(\"TEXT\", \"TEXT\"),\n      Arrays.asList(Types.VARCHAR, Types.VARCHAR)),\n\n  GET_TABLES(\n      Arrays.asList(\n          \"TABLE_CAT\",\n          \"TABLE_SCHEM\",\n          \"TABLE_NAME\",\n          \"TABLE_TYPE\",\n          \"REMARKS\",\n          \"TYPE_CAT\",\n          \"TYPE_SCHEM\",\n          \"TYPE_NAME\",\n          \"SELF_REFERENCING_COL_NAME\",\n          \"REF_GENERATION\"),\n      Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR)),\n\n  GET_COLUMNS(\n      Arrays.asList(\n          \"TABLE_CAT\",\n          \"TABLE_SCHEM\",\n          \"TABLE_NAME\",\n          \"COLUMN_NAME\",\n          \"DATA_TYPE\",\n          \"TYPE_NAME\",\n          \"COLUMN_SIZE\",\n          \"BUFFER_LENGTH\",\n          \"DECIMAL_DIGITS\",\n          \"NUM_PREC_RADIX\",\n          \"NULLABLE\",\n          \"REMARKS\",\n          \"COLUMN_DEF\",\n          \"SQL_DATA_TYPE\",\n          \"SQL_DATETIME_SUB\",\n          \"CHAR_OCTET_LENGTH\",\n          \"ORDINAL_POSITION\",\n          \"IS_NULLABLE\",\n          \"SCOPE_CATALOG\",\n          \"SCOPE_SCHEMA\",\n          \"SCOPE_TABLE\",\n          \"SOURCE_DATA_TYPE\",\n          \"IS_AUTOINCREMENT\",\n          \"IS_GENERATEDCOLUMN\"),\n      Arrays.asList(\n          \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"INTEGER\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"INTEGER\",\n          \"INTEGER\", \"INTEGER\", \"TEXT\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"INTEGER\", \"INTEGER\", \"TEXT\",\n          \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"TEXT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.VARCHAR,\n          Types.VARCHAR)),\n\n  GET_COLUMNS_EXTENDED_SET(\n      GET_COLUMNS,\n      Collections.singletonList(\"BASE_TYPE\"),\n      Collections.singletonList(\"TEXT\"),\n      Collections.singletonList(Types.VARCHAR)),\n\n  GET_PRIMARY_KEYS(\n      Arrays.asList(\"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\", \"COLUMN_NAME\", \"KEY_SEQ\", \"PK_NAME\"),\n      Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"INTEGER\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.VARCHAR)),\n\n  GET_FOREIGN_KEYS(\n      Arrays.asList(\n          \"PKTABLE_CAT\",\n          \"PKTABLE_SCHEM\",\n          \"PKTABLE_NAME\",\n          \"PKCOLUMN_NAME\",\n          \"FKTABLE_CAT\",\n          \"FKTABLE_SCHEM\",\n          \"FKTABLE_NAME\",\n          \"FKCOLUMN_NAME\",\n          \"KEY_SEQ\",\n          \"UPDATE_RULE\",\n          \"DELETE_RULE\",\n          \"FK_NAME\",\n          \"PK_NAME\",\n          \"DEFERRABILITY\"),\n      Arrays.asList(\n          \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"SHORT\", \"SHORT\",\n          \"TEXT\", \"TEXT\", \"SHORT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.SMALLINT,\n          Types.SMALLINT,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT)),\n\n  GET_FUNCTIONS(\n      Arrays.asList(\n          \"FUNCTION_CAT\",\n          \"FUNCTION_SCHEM\",\n          \"FUNCTION_NAME\",\n          \"REMARKS\",\n          \"FUNCTION_TYPE\",\n          \"SPECIFIC_NAME\"),\n      Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.VARCHAR)),\n\n  GET_FUNCTION_COLUMNS(\n      Arrays.asList(\n          \"FUNCTION_CAT\",\n          \"FUNCTION_SCHEM\",\n          \"FUNCTION_NAME\",\n          \"COLUMN_NAME\",\n          \"COLUMN_TYPE\",\n          \"DATA_TYPE\",\n          \"TYPE_NAME\",\n          \"PRECISION\",\n          \"LENGTH\",\n          \"SCALE\",\n          \"RADIX\",\n          \"NULLABLE\",\n          \"REMARKS\",\n          \"CHAR_OCTET_LENGTH\",\n          \"ORDINAL_POSITION\",\n          \"IS_NULLABLE\",\n          \"SPECIFIC_NAME\"),\n      Arrays.asList(\n          \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"INTEGER\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"SHORT\",\n          \"SHORT\", \"SHORT\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"TEXT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.SMALLINT,\n          Types.SMALLINT,\n          Types.SMALLINT,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.VARCHAR)),\n\n  GET_PROCEDURES(\n      Arrays.asList(\n          \"PROCEDURE_CAT\",\n          \"PROCEDURE_SCHEM\",\n          \"PROCEDURE_NAME\",\n          \"REMARKS\",\n          \"PROCEDURE_TYPE\",\n          \"SPECIFIC_NAME\"),\n      Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.VARCHAR)),\n  GET_PROCEDURE_COLUMNS(\n      Arrays.asList(\n          \"PROCEDURE_CAT\",\n          \"PROCEDURE_SCHEM\",\n          \"PROCEDURE_NAME\",\n          \"COLUMN_NAME\",\n          \"COLUMN_TYPE\",\n          \"DATA_TYPE\",\n          \"TYPE_NAME\",\n          \"PRECISION\",\n          \"LENGTH\",\n          \"SCALE\",\n          \"RADIX\",\n          \"NULLABLE\",\n          \"REMARKS\",\n          \"COLUMN_DEF\",\n          \"SQL_DATA_TYPE\",\n          \"SQL_DATETIME_SUB\",\n          \"CHAR_OCTET_LENGTH\",\n          \"ORDINAL_POSITION\",\n          \"IS_NULLABLE\",\n          \"SPECIFIC_NAME\"),\n      Arrays.asList(\n          \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"SHORT\", \"INTEGER\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"SHORT\",\n          \"SHORT\", \"SHORT\", \"TEXT\", \"TEXT\", \"INTEGER\", \"INTEGER\", \"INTEGER\", \"INTEGER\", \"TEXT\",\n          \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.SMALLINT,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.SMALLINT,\n          Types.SMALLINT,\n          Types.SMALLINT,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.VARCHAR)),\n  GET_TABLE_PRIVILEGES(\n      Arrays.asList(\n          \"TABLE_CAT\",\n          \"TABLE_SCHEM\",\n          \"TABLE_NAME\",\n          \"GRANTOR\",\n          \"GRANTEE\",\n          \"PRIVILEGE\",\n          \"IS_GRANTABLE\"),\n      Arrays.asList(\"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR)),\n\n  GET_STREAMS(\n      Arrays.asList(\n          \"STREAM_NAME\",\n          \"DATABASE_NAME\",\n          \"SCHEMA_NAME\",\n          \"OWNER\",\n          \"COMMENT\",\n          \"TABLE_NAME\",\n          \"SOURCE_TYPE\",\n          \"BASE_TABLES\",\n          \"TYPE\",\n          \"STALE\",\n          \"MODE\"),\n      Arrays.asList(\n          \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\", \"TEXT\"),\n      Arrays.asList(\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR,\n          Types.VARCHAR)),\n  ;\n\n  private List<String> columnNames;\n\n  private List<String> columnTypeNames;\n\n  private List<Integer> columnTypes;\n\n  DBMetadataResultSetMetadata(\n      List<String> columnNames, List<String> columnTypeNames, List<Integer> columnTypes) {\n    this.columnNames = columnNames;\n    this.columnTypeNames = columnTypeNames;\n    this.columnTypes = columnTypes;\n  }\n\n  DBMetadataResultSetMetadata(\n      DBMetadataResultSetMetadata base,\n      List<String> additionalColumnNames,\n      List<String> additionalColumnTypeNames,\n      List<Integer> additionalColumnTypes) {\n    this.columnNames = new ArrayList<>(base.getColumnNames());\n    this.columnTypeNames = new ArrayList<>(base.getColumnTypeNames());\n    this.columnTypes = new ArrayList<>(base.getColumnTypes());\n    columnNames.addAll(additionalColumnNames);\n    columnTypeNames.addAll(additionalColumnTypeNames);\n    columnTypes.addAll(additionalColumnTypes);\n  }\n\n  public List<String> getColumnNames() {\n    return columnNames;\n  }\n\n  public List<String> getColumnTypeNames() {\n    return columnTypeNames;\n  }\n\n  public List<Integer> getColumnTypes() {\n    return columnTypes;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/DefaultResultStreamProvider.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PushbackInputStream;\nimport java.net.URISyntaxException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.zip.GZIPInputStream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.Header;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.impl.client.CloseableHttpClient;\n\npublic class DefaultResultStreamProvider implements ResultStreamProvider {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(DefaultResultStreamProvider.class);\n  // SSE-C algorithm header\n  private static final String SSE_C_ALGORITHM = \"x-amz-server-side-encryption-customer-algorithm\";\n\n  // SSE-C customer key header\n  private static final String SSE_C_KEY = \"x-amz-server-side-encryption-customer-key\";\n\n  // SSE-C algorithm value\n  private static final String SSE_C_AES = \"AES256\";\n\n  private CompressedStreamFactory compressedStreamFactory;\n\n  public DefaultResultStreamProvider() {\n    this.compressedStreamFactory = new CompressedStreamFactory();\n  }\n\n  @Override\n  public InputStream getInputStream(ChunkDownloadContext context) throws Exception {\n    HttpResponse response;\n    try {\n      response = getResultChunk(context);\n    } catch (URISyntaxException | IOException ex) {\n      throw new SnowflakeSQLLoggedException(\n          context.getSession(),\n          ErrorCode.NETWORK_ERROR.getMessageCode(),\n          SqlState.IO_ERROR,\n          \"Error encountered when request a result chunk URL: \"\n              + context.getResultChunk().getUrl()\n              + \" \"\n              + ex.getLocalizedMessage());\n    }\n\n    /*\n     * return error if we don't get a response or the response code\n     * means failure.\n     */\n    if (response == null || response.getStatusLine().getStatusCode() != 200) {\n      logger.error(\"Error fetching chunk from: {}\", context.getResultChunk().getScrubbedUrl());\n\n      SnowflakeUtil.logResponseDetails(response, logger);\n\n      throw new SnowflakeSQLException(\n          SqlState.IO_ERROR,\n          ErrorCode.NETWORK_ERROR.getMessageCode(),\n          \"Error encountered when downloading a result chunk: HTTP \"\n              + \"status: \"\n              + ((response != null) ? response.getStatusLine().getStatusCode() : \"null response\"));\n    }\n\n    InputStream inputStream;\n    final HttpEntity entity = response.getEntity();\n    Header encoding = response.getFirstHeader(\"Content-Encoding\");\n    try {\n      // create stream based on compression type\n      inputStream =\n          compressedStreamFactory.createBasedOnEncodingHeader(entity.getContent(), encoding);\n    } catch (Exception ex) {\n      logger.error(\"Failed to decompress data: {}\", response);\n\n      throw new SnowflakeSQLLoggedException(\n          context.getSession(),\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Failed to decompress data: \" + response.toString());\n    }\n\n    // trace the response if requested\n    logger.debug(\"Json response: {}\", response);\n\n    return inputStream;\n  }\n\n  private HttpResponse getResultChunk(ChunkDownloadContext context) throws Exception {\n    URIBuilder uriBuilder = new URIBuilder(context.getResultChunk().getUrl());\n\n    HttpGet httpRequest = new HttpGet(uriBuilder.build());\n\n    if (context.getChunkHeadersMap() != null && context.getChunkHeadersMap().size() != 0) {\n      for (Map.Entry<String, String> entry : context.getChunkHeadersMap().entrySet()) {\n        logger.debug(\"Adding header key: {}\", entry.getKey());\n        httpRequest.addHeader(entry.getKey(), entry.getValue());\n      }\n    }\n    // Add SSE-C headers\n    else if (context.getQrmk() != null) {\n      httpRequest.addHeader(SSE_C_ALGORITHM, SSE_C_AES);\n      httpRequest.addHeader(SSE_C_KEY, context.getQrmk());\n      logger.debug(\"Adding SSE-C headers\", false);\n    }\n\n    logger.debug(\n        \"Thread {} Fetching result chunk#{}: {}\",\n        Thread.currentThread().getId(),\n        context.getChunkIndex(),\n        context.getResultChunk().getScrubbedUrl());\n\n    SFBaseSession session = context.getSession();\n    List<HttpHeadersCustomizer> headersCustomizers = null;\n    if (session instanceof SFSession) {\n      headersCustomizers = ((SFSession) session).getHttpHeadersCustomizers();\n    }\n    CloseableHttpClient httpClient =\n        HttpUtil.getHttpClient(\n            context.getChunkDownloader().getHttpClientSettingsKey(), headersCustomizers);\n\n    // fetch the result chunk\n    HttpResponse response =\n        RestRequest.executeWithRetries(\n                httpClient,\n                httpRequest,\n                context.getNetworkTimeoutInMilli() / 1000, // retry timeout\n                0,\n                context.getSocketTimeout(),\n                0,\n                0, // no socket timeout injection\n                null, // no canceling\n                false, // no cookie\n                false, // no retry parameters in url\n                false, // no request_guid\n                true, // retry on HTTP403 for AWS S3\n                true, // no retry on http request\n                false,\n                new ExecTimeTelemetryData(),\n                session,\n                context.getChunkDownloader().getHttpClientSettingsKey(),\n                headersCustomizers,\n                false)\n            .getHttpResponse();\n\n    logger.debug(\n        \"Thread {} Call chunk#{} returned for URL: {}, response: {}\",\n        Thread.currentThread().getId(),\n        context.getChunkIndex(),\n        (ArgSupplier) () -> SecretDetector.maskSASToken(context.getResultChunk().getUrl()),\n        response);\n    return response;\n  }\n\n  public static InputStream detectGzipAndGetStream(InputStream is) throws IOException {\n    PushbackInputStream pb = new PushbackInputStream(is, 2);\n    byte[] signature = new byte[2];\n    int len = pb.read(signature);\n    pb.unread(signature, 0, len);\n    // https://tools.ietf.org/html/rfc1952\n    if (signature[0] == (byte) 0x1f && signature[1] == (byte) 0x8b) {\n      return new GZIPInputStream(pb);\n    } else {\n      return pb;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/DefaultSFConnectionHandler.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.config.SFClientConfigParser.checkConfigFilePermissions;\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_SFSQL;\nimport static net.snowflake.client.internal.core.SessionUtil.JVM_PARAMS_TO_PARAMS;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLNonTransientConnectionException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.config.SFClientConfig;\nimport net.snowflake.client.internal.config.SFClientConfigParser;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.jdbc.util.DriverUtil;\nimport net.snowflake.client.internal.log.JDK14Logger;\nimport net.snowflake.client.internal.log.SFLogLevel;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.log.SFToJavaLogMapper;\nimport net.snowflake.common.core.LoginInfoDTO;\n\n/**\n * The default ConnectionHandler used by SnowflakeConnectionV(x). Unless a separate implementation\n * is provided, a DefaultConnectionHandler will be constructed automatically by the Connection\n * class.\n */\npublic class DefaultSFConnectionHandler implements SFConnectionHandler {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(DefaultSFConnectionHandler.class);\n\n  private final SFSession sfSession;\n  private final SnowflakeConnectString conStr;\n  private final boolean skipOpen;\n\n  /**\n   * Constructs a DefaultConnectionHandler using a SnowflakeConnectString. This can be done by using\n   * SnowflakeConnectString.parse(url, info), where url is a connection url and info is a\n   * java.util.Properties\n   *\n   * @param conStr A SnowflakeConnectString object\n   */\n  public DefaultSFConnectionHandler(SnowflakeConnectString conStr) {\n    this(conStr, false);\n  }\n\n  /**\n   * Constructs a DefaultConnectionHandler using a SnowflakeConnectString. This can be done by using\n   * SnowflakeConnectString.parse(url, info), where url is a connection url and info is a\n   * java.util.Properties\n   *\n   * @param conStr A SnowflakeConnectString object\n   * @param skipOpen Skip calling open() on the session (for test-use only)\n   */\n  public DefaultSFConnectionHandler(SnowflakeConnectString conStr, boolean skipOpen) {\n    this.sfSession = new SFSession(this);\n    this.conStr = conStr;\n    this.skipOpen = skipOpen;\n    sfSession.setSnowflakeConnectionString(conStr);\n  }\n\n  /**\n   * Processes parameters given in the connection string. This extracts accountName, databaseName,\n   * schemaName from the URL if it is specified there.\n   *\n   * @param conStr Connection string object\n   * @return a map containing accountName, databaseName and schemaName if specified\n   */\n  public static Map<String, Object> mergeProperties(SnowflakeConnectString conStr) {\n    conStr.getParameters().remove(\"SSL\");\n    conStr\n        .getParameters()\n        .put(\n            \"SERVERURL\",\n            conStr.getScheme() + \"://\" + conStr.getHost() + \":\" + conStr.getPort() + \"/\");\n    return conStr.getParameters();\n  }\n\n  @Override\n  public boolean supportsAsyncQuery() {\n    return true;\n  }\n\n  @Override\n  public void initializeConnection(String url, Properties info) throws SQLException {\n    initialize(\n        conStr, LoginInfoDTO.SF_JDBC_APP_ID, SnowflakeDriver.getImplementationVersion(), info);\n  }\n\n  /** Returns the default SFSession client implementation. */\n  @Override\n  public SFBaseSession getSFSession() {\n    return sfSession;\n  }\n\n  /** Returns the default SFStatement client implementation. */\n  @Override\n  public SFBaseStatement getSFStatement() {\n    return new SFStatement(sfSession);\n  }\n\n  protected void initialize(SnowflakeConnectString conStr, String appID, String appVersion)\n      throws SQLException {\n    this.initialize(conStr, appID, appVersion, null);\n  }\n\n  protected void initialize(\n      SnowflakeConnectString conStr, String appID, String appVersion, Properties properties)\n      throws SQLException {\n    TelemetryService.getInstance().updateContext(conStr);\n\n    try {\n      // pass the parameters to sfSession\n      initSessionProperties(conStr, appID, appVersion);\n      setClientConfig();\n      initLogger();\n      initHttpHeaderCustomizers(properties);\n      logger.debug(\"Trying to establish session, JDBC driver: {}\", DriverUtil.getJdbcJarname());\n      if (!skipOpen) {\n        sfSession.open(internalCallMarker());\n      }\n\n    } catch (SFException ex) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession, ex.getSqlState(), ex.getVendorCode(), ex.getCause(), ex.getParams());\n    }\n  }\n\n  private void setClientConfig() throws SnowflakeSQLLoggedException {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = sfSession.getConnectionPropertiesMap();\n    String clientConfigFilePath =\n        (String) connectionPropertiesMap.getOrDefault(SFSessionProperty.CLIENT_CONFIG_FILE, null);\n\n    SFClientConfig sfClientConfig = sfSession.getSfClientConfig();\n    if (sfClientConfig == null) {\n      try {\n        sfClientConfig = SFClientConfigParser.loadSFClientConfig(clientConfigFilePath);\n      } catch (IOException e) {\n        throw new SnowflakeSQLLoggedException(\n            sfSession, ErrorCode.INTERNAL_ERROR, e.getMessage(), e.getCause());\n      }\n      sfSession.setSfClientConfig(sfClientConfig);\n    }\n  }\n\n  /**\n   * This method instantiates a JDK14Logger. This will be used if the java.util.logging.config.file\n   * properties file is missing. The method performs the following actions: 1. Check if the\n   * CLIENT_CONFIG_FILE is present. If it is, the method loads the logLevel and logPath from the\n   * client config. 2. Check if the Tracing parameter is present in the URL or connection\n   * properties. If it is, the method will overwrite the logLevel obtained from step 1. 3.\n   * Instantiate java.util.logging with the specified logLevel and logPath. 4. If both the logLevel\n   * and logPath are null, this method doesn't do anything.\n   */\n  private void initLogger() throws SnowflakeSQLLoggedException {\n    if (logger instanceof JDK14Logger\n        && systemGetProperty(\"java.util.logging.config.file\") == null) {\n      Map<SFSessionProperty, Object> connectionPropertiesMap =\n          sfSession.getConnectionPropertiesMap();\n      String tracingLevelFromConnectionProp =\n          (String) connectionPropertiesMap.getOrDefault(SFSessionProperty.TRACING, null);\n\n      Level logLevel = null;\n      String logPattern = \"%h/snowflake_jdbc%u.log\"; // default pattern.\n      SFClientConfig sfClientConfig = sfSession.getSfClientConfig();\n      Path logPath = null;\n\n      if (sfClientConfig != null) {\n        String logPathFromConfig = sfClientConfig.getCommonProps().getLogPath();\n        logPath = getLogPath(logPathFromConfig);\n        logPattern = constructLogPattern(logPath, logPathFromConfig);\n        String levelStr = sfClientConfig.getCommonProps().getLogLevel();\n        SFLogLevel sfLogLevel = SFLogLevel.getLogLevel(levelStr);\n        logLevel = SFToJavaLogMapper.toJavaUtilLoggingLevel(sfLogLevel);\n      }\n\n      if (tracingLevelFromConnectionProp != null) {\n        // Log level from connection param will overwrite the log level from sf config file.\n        logLevel = Level.parse(tracingLevelFromConnectionProp.toUpperCase());\n      }\n\n      if (logLevel != null && logPattern != null) {\n        try {\n          logger.debug(\"Setting logger with log level {} and log pattern {}\", logLevel, logPattern);\n          JDK14Logger.instantiateLogger(logLevel, logPattern);\n          if (sfClientConfig != null) {\n            logger.debug(\n                \"SF Client config found at location: {}.\", sfClientConfig.getConfigFilePath());\n            checkConfigFilePermissions(sfClientConfig.getConfigFilePath());\n          }\n          if (logPath != null) {\n            checkLogFolderPermissions(logPath);\n          }\n        } catch (IOException ex) {\n          throw new SnowflakeSQLLoggedException(\n              sfSession, ErrorCode.INTERNAL_ERROR, ex.getMessage());\n        }\n        logger.debug(\n            \"Instantiating JDK14Logger with level: {}, output path: {}\", logLevel, logPattern);\n      }\n    }\n  }\n\n  private Path getLogPath(String logPathFromConfig) throws SnowflakeSQLLoggedException {\n    if (JDK14Logger.STDOUT.equalsIgnoreCase(logPathFromConfig)) {\n      return null;\n    }\n\n    Path logPath;\n    if (logPathFromConfig != null && !logPathFromConfig.isEmpty()) {\n      // Get log path from configuration\n      logPath = Paths.get(logPathFromConfig);\n      if (!Files.exists(logPath)) {\n        try {\n          Files.createDirectories(logPath);\n        } catch (IOException ex) {\n          throw new SnowflakeSQLLoggedException(\n              sfSession,\n              ErrorCode.INTERNAL_ERROR,\n              String.format(\n                  \"Unable to create log path mentioned in configfile %s ,%s\",\n                  logPathFromConfig, ex.getMessage()));\n        }\n      }\n    } else {\n      // Get log path from home directory\n      String homePath = systemGetProperty(\"user.home\");\n      if (homePath == null || homePath.isEmpty()) {\n        throw new SnowflakeSQLLoggedException(\n            sfSession,\n            ErrorCode.INTERNAL_ERROR,\n            String.format(\n                \"Log path not set in configfile %s and home directory not set.\",\n                logPathFromConfig));\n      }\n      logPath = Paths.get(homePath);\n    }\n\n    return createLogPathSubDirectory(logPath);\n  }\n\n  private String constructLogPattern(Path logPath, String logPathFromConfig) {\n    if (JDK14Logger.STDOUT.equalsIgnoreCase(logPathFromConfig)) {\n      return JDK14Logger.STDOUT;\n    }\n\n    String logPattern = \"%t/snowflake_jdbc%u.log\"; // java.tmpdir\n    logPattern = Paths.get(logPath.toString(), \"snowflake_jdbc%u.log\").toString();\n    return logPattern;\n  }\n\n  private Path createLogPathSubDirectory(Path logPath) throws SnowflakeSQLLoggedException {\n    Path path = Paths.get(logPath.toString(), \"jdbc\");\n    if (!Files.exists(path)) {\n      createLogFolder(path);\n    }\n    return path;\n  }\n\n  private void createLogFolder(Path path) throws SnowflakeSQLLoggedException {\n    try {\n      if (Constants.getOS() == Constants.OS.WINDOWS) {\n        Files.createDirectories(path);\n      } else {\n        Files.createDirectories(\n            path,\n            PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rwx------\")));\n      }\n    } catch (IOException ex) {\n      throw new SnowflakeSQLLoggedException(\n          sfSession,\n          ErrorCode.INTERNAL_ERROR,\n          String.format(\n              \"Unable to create jdbc subfolder in configfile %s ,%s\",\n              path.toString(), ex.getMessage(), ex.getCause()));\n    }\n  }\n\n  private void checkLogFolderPermissions(Path path) throws SnowflakeSQLLoggedException {\n    if (!isWindows()) {\n      try {\n        Set<PosixFilePermission> folderPermissions = Files.getPosixFilePermissions(path);\n        if (folderPermissions.contains(PosixFilePermission.GROUP_WRITE)\n            || folderPermissions.contains(PosixFilePermission.GROUP_READ)\n            || folderPermissions.contains(PosixFilePermission.GROUP_EXECUTE)\n            || folderPermissions.contains(PosixFilePermission.OTHERS_WRITE)\n            || folderPermissions.contains(PosixFilePermission.OTHERS_READ)\n            || folderPermissions.contains(PosixFilePermission.OTHERS_EXECUTE)) {\n          logger.warn(\n              \"Access permission for the logs directory {} is currently {} and is potentially \"\n                  + \"accessible to users other than the owner of the logs directory.\",\n              path.toString(),\n              folderPermissions.toString());\n        }\n      } catch (IOException ex) {\n        throw new SnowflakeSQLLoggedException(\n            sfSession,\n            ErrorCode.INTERNAL_ERROR,\n            String.format(\n                \"Unable to get permissions of log directory %s ,%s\",\n                path.toString(), ex.getMessage(), ex.getCause()));\n      }\n    }\n  }\n\n  private void initSessionProperties(SnowflakeConnectString conStr, String appID, String appVersion)\n      throws SFException {\n    Map<String, Object> properties = mergeProperties(conStr);\n\n    for (Map.Entry<String, Object> property : properties.entrySet()) {\n      if (\"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\".equals(property.getKey())) {\n        try {\n          Object v0 = property.getValue();\n          int intV;\n          if (v0 instanceof Integer) {\n            intV = (Integer) v0;\n          } else {\n            intV = Integer.parseInt((String) v0);\n          }\n          if (intV > 3600) {\n            properties.replace(property.getKey(), \"3600\");\n          }\n          if (intV < 900) {\n            properties.replace(property.getKey(), \"900\");\n          }\n        } catch (NumberFormatException ex) {\n          logger.warn(\n              \"Invalid data type for CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY: {}\",\n              property.getValue());\n          continue;\n        }\n      } else if (CLIENT_SFSQL.equals(property.getKey())) {\n        Object v0 = property.getValue();\n        boolean booleanV = v0 instanceof Boolean ? (Boolean) v0 : Boolean.parseBoolean((String) v0);\n        sfSession.setSfSQLMode(booleanV);\n      }\n      sfSession.addSFSessionProperty(property.getKey(), property.getValue());\n    }\n    sfSession.overrideConsoleHandlerWhenNecessary();\n\n    // populate app id and version\n    sfSession.addProperty(SFSessionProperty.APP_ID, appID);\n    sfSession.addProperty(SFSessionProperty.APP_VERSION, appVersion);\n\n    // Set the corresponding session parameters to the JVM properties\n    for (Map.Entry<String, String> entry : JVM_PARAMS_TO_PARAMS.entrySet()) {\n      String value = systemGetProperty(entry.getKey());\n      if (value != null && !sfSession.containProperty(entry.getValue())) {\n        sfSession.addSFSessionProperty(entry.getValue(), value);\n      }\n    }\n  }\n\n  @Override\n  public ResultSet createResultSet(String queryID, Statement statement) throws SQLException {\n    SFAsyncResultSet rs = new SFAsyncResultSet(queryID, statement);\n    rs.setSession(sfSession);\n    rs.setStatement(statement);\n    return rs;\n  }\n\n  @Override\n  public SnowflakeBaseResultSet createResultSet(SFBaseResultSet resultSet, Statement statement)\n      throws SQLException {\n    return new SnowflakeResultSetV1(resultSet, statement);\n  }\n\n  @Override\n  public SnowflakeBaseResultSet createAsyncResultSet(SFBaseResultSet resultSet, Statement statement)\n      throws SQLException {\n    return new SFAsyncResultSet(resultSet, statement);\n  }\n\n  @Override\n  public SFBaseFileTransferAgent getFileTransferAgent(String command, SFBaseStatement statement)\n      throws SQLNonTransientConnectionException, SnowflakeSQLException {\n    if (!(statement instanceof SFStatement)) {\n      throw new SnowflakeSQLException(\n          \"getFileTransferAgent() called with an incompatible SFBaseStatement type. Requires an\"\n              + \" SFStatement.\");\n    }\n    return new SnowflakeFileTransferAgent(\n        command, sfSession, (SFStatement) statement, internalCallMarker());\n  }\n\n  private void initHttpHeaderCustomizers(Properties properties) {\n    if (properties == null) {\n      return;\n    }\n    Object httpHeadersCustomizers =\n        properties.get(HttpHeadersCustomizer.HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY);\n    if (httpHeadersCustomizers instanceof List<?>) {\n      List<HttpHeadersCustomizer> typedCustomizers = new ArrayList<>();\n      for (Object customizer : (List<?>) httpHeadersCustomizers) {\n        if (customizer instanceof HttpHeadersCustomizer) {\n          typedCustomizers.add((HttpHeadersCustomizer) customizer);\n        } else if (customizer != null) {\n          logger.warn(\n              \"Invalid object type found in HttpHeadersCustomizer list: {}\",\n              customizer.getClass().getName());\n        }\n      }\n      logger.debug(\"Registering {} HttpHeadersCustomizer\", typedCustomizers.size());\n      this.sfSession.setHttpHeadersCustomizers(typedCustomizers);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/EnvironmentVariables.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\npublic enum EnvironmentVariables {\n  AWS_REGION(\"AWS_REGION\");\n\n  private final String name;\n\n  EnvironmentVariables(String name) {\n    this.name = name;\n  }\n\n  public String getName() {\n    return name;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/FileBackedOutputStream.java",
    "content": "/*\n * Copyright (C) 2008 The Guava Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * 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 net.snowflake.client.internal.jdbc;\n\nimport com.google.common.annotations.Beta;\nimport com.google.common.io.ByteSource;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport net.snowflake.client.internal.core.FileUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * An {@link OutputStream} that starts buffering to a byte array, but switches to file buffering\n * once the data reaches a configurable size.\n *\n * <p>This class is thread-safe.\n *\n * <p>Adapted by Snowflake to return File object when file is spilled to disk.\n *\n * @author Chris Nokleberg\n * @since 1.0\n */\n@Beta\npublic final class FileBackedOutputStream extends OutputStream {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(FileBackedOutputStream.class);\n\n  private final int fileThreshold;\n  private final boolean resetOnFinalize;\n  private final ByteSource source;\n\n  private OutputStream out;\n  private MemoryOutput memory;\n  private File file;\n\n  /** ByteArrayOutputStream that exposes its internals. */\n  private static class MemoryOutput extends ByteArrayOutputStream {\n    byte[] getBuffer() {\n      return buf;\n    }\n\n    int getCount() {\n      return count;\n    }\n  }\n\n  /**\n   * @return the file holding the data (possibly null).\n   */\n  public synchronized File getFile() {\n    return file;\n  }\n\n  /**\n   * Creates a new instance that uses the given file threshold, and does not reset the data when the\n   * {@link ByteSource} returned by {@link #asByteSource} is finalized.\n   *\n   * @param fileThreshold the number of bytes before the stream should switch to buffering to a file\n   */\n  public FileBackedOutputStream(int fileThreshold) {\n    this(fileThreshold, false);\n  }\n\n  /**\n   * Creates a new instance that uses the given file threshold, and optionally resets the data when\n   * the {@link ByteSource} returned by {@link #asByteSource} is finalized.\n   *\n   * @param fileThreshold the number of bytes before the stream should switch to buffering to a file\n   * @param resetOnFinalize if true, the {@link #reset} method will be called when the {@link\n   *     ByteSource} returned by {@link #asByteSource} is finalized\n   */\n  public FileBackedOutputStream(int fileThreshold, boolean resetOnFinalize) {\n    this.fileThreshold = fileThreshold;\n    this.resetOnFinalize = resetOnFinalize;\n    memory = new MemoryOutput();\n    out = memory;\n\n    if (resetOnFinalize) {\n      source =\n          new ByteSource() {\n            @Override\n            public InputStream openStream() throws IOException {\n              return openInputStream();\n            }\n\n            @Override\n            protected void finalize() {\n              try {\n                reset();\n              } catch (Throwable t) {\n                logger.error(\"Exception occurred on finalize\", t);\n              }\n            }\n          };\n    } else {\n      source =\n          new ByteSource() {\n            @Override\n            public InputStream openStream() throws IOException {\n              return openInputStream();\n            }\n          };\n    }\n  }\n\n  /**\n   * @return a readable {@link ByteSource} view of the data that has been written to this stream.\n   * @since 15.0\n   */\n  public ByteSource asByteSource() {\n    return source;\n  }\n\n  private synchronized InputStream openInputStream() throws IOException {\n    if (file != null) {\n      FileUtil.logFileUsage(file, \"Data buffering stream\", false);\n      return new FileInputStream(file);\n    } else {\n      return new ByteArrayInputStream(memory.getBuffer(), 0, memory.getCount());\n    }\n  }\n\n  /**\n   * Calls {@link #close} if not already closed, and then resets this object back to its initial\n   * state, for reuse. If data was buffered to a file, it will be deleted.\n   *\n   * @throws IOException if an I/O error occurred while deleting the file buffer\n   */\n  public synchronized void reset() throws IOException {\n    try {\n      close();\n    } finally {\n      if (memory == null) {\n        memory = new MemoryOutput();\n      } else {\n        memory.reset();\n      }\n      out = memory;\n      if (file != null) {\n        File deleteMe = file;\n        file = null;\n        if (!deleteMe.delete()) {\n          throw new IOException(\"Could not delete: \" + deleteMe);\n        }\n      }\n    }\n  }\n\n  @Override\n  public synchronized void write(int b) throws IOException {\n    update(1);\n    out.write(b);\n  }\n\n  @Override\n  public synchronized void write(byte[] b) throws IOException {\n    write(b, 0, b.length);\n  }\n\n  @Override\n  public synchronized void write(byte[] b, int off, int len) throws IOException {\n    update(len);\n    out.write(b, off, len);\n  }\n\n  @Override\n  public synchronized void close() throws IOException {\n    out.close();\n  }\n\n  @Override\n  public synchronized void flush() throws IOException {\n    out.flush();\n  }\n\n  /**\n   * Checks if writing {@code len} bytes would go over threshold, and switches to file buffering if\n   * so.\n   */\n  private void update(int len) throws IOException {\n    if (file == null && (memory.getCount() + len > fileThreshold)) {\n      File temp = File.createTempFile(\"FileBackedOutputStream\", null);\n      if (resetOnFinalize) {\n        // Finalizers are not guaranteed to be called on system shutdown;\n        // this is insurance.\n        temp.deleteOnExit();\n      }\n      FileOutputStream transfer = new FileOutputStream(temp);\n      transfer.write(memory.getBuffer(), 0, memory.getCount());\n      transfer.flush();\n\n      // We've successfully transferred the data; switch to writing to file\n      out = transfer;\n      file = temp;\n      memory = null;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/JsonResultChunk.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.lang.ref.SoftReference;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.BitSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\npublic class JsonResultChunk extends SnowflakeResultChunk {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(JsonResultChunk.class);\n\n  private ResultChunkData data;\n\n  private int currentRow;\n\n  private SFBaseSession session;\n\n  public JsonResultChunk(\n      String url, int rowCount, int colCount, int uncompressedSize, SFBaseSession session) {\n    super(url, rowCount, colCount, uncompressedSize);\n    data = new BlockResultChunkDataV2(computeCharactersNeeded(), rowCount, colCount, session);\n    this.session = session;\n  }\n\n  public static Object extractCell(JsonNode resultData, int rowIdx, int colIdx) {\n    JsonNode currentRow = resultData.get(rowIdx);\n\n    JsonNode colNode = currentRow.get(colIdx);\n\n    if (colNode.isTextual()) {\n      return colNode.asText();\n    } else if (colNode.isNumber()) {\n      return colNode.numberValue();\n    } else if (colNode.isNull()) {\n      return null;\n    }\n    throw new RuntimeException(\"Unknow json type\");\n  }\n\n  public void tryReuse(ResultChunkDataCache cache) {\n    // Allocate chunk data, double necessary amount for later reuse\n    cache.reuseOrCreateResultData(data);\n  }\n\n  /**\n   * Creates a String object for the given cell\n   *\n   * @param rowIdx zero based row\n   * @param colIdx zero based column\n   * @return String\n   */\n  public final Object getCell(int rowIdx, int colIdx) {\n    return data.get(colCount * rowIdx + colIdx);\n  }\n\n  public final void addRow(Object[] row) throws SnowflakeSQLException {\n    if (row.length != colCount) {\n      throw new SnowflakeSQLLoggedException(\n          this.session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Exception: expected \" + colCount + \" columns and received \" + row.length);\n    }\n\n    for (Object cell : row) {\n      if (cell == null) {\n        data.add(null);\n      } else {\n        if (cell instanceof String) {\n          data.add((String) cell);\n        } else if (cell instanceof Boolean) {\n          data.add((boolean) cell ? \"1\" : \"0\");\n        } else {\n          throw new SnowflakeSQLLoggedException(\n              this.session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"unknown data type in JSON row \" + cell.getClass().toString());\n        }\n      }\n    }\n    currentRow++;\n  }\n\n  /**\n   * Checks that all data has been added after parsing.\n   *\n   * @throws SnowflakeSQLException when rows are not all downloaded\n   */\n  public final void ensureRowsComplete() throws SnowflakeSQLException {\n    // Check that all the rows have been decoded, raise an error if not\n    if (rowCount != currentRow) {\n      throw new SnowflakeSQLException(\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          \"Exception: expected \" + rowCount + \" rows and received \" + currentRow);\n    }\n  }\n\n  @Override\n  public void reset() {\n    this.currentRow = 0;\n    this.data.reset();\n  }\n\n  /**\n   * Compute the memory necessary to store the data of this chunk\n   *\n   * @return necessary memory in bytes\n   */\n  @Override\n  public final long computeNeededChunkMemory() {\n    if (data != null) {\n      return data.computeNeededChunkMemory();\n    }\n    return 0;\n  }\n\n  @Override\n  public final void freeData() {\n    if (data != null) {\n      data.freeData();\n    }\n  }\n\n  public int computeCharactersNeeded() {\n    // remove [ , ] characters, they won't be stored\n    return uncompressedSize\n        - (rowCount * 2) // opening [ and komma separating rows\n        - (rowCount * colCount); // komma separating cells and closing ]\n  }\n\n  public void addOffset(int offset) throws SnowflakeSQLException {\n    data.addOffset(offset);\n  }\n\n  public void setIsNull() throws SnowflakeSQLException {\n    data.setIsNull();\n  }\n\n  public void setLastLength(int len) throws SnowflakeSQLException {\n    data.setLastLength(len);\n  }\n\n  public void nextIndex() throws SnowflakeSQLException {\n    data.nextIndex();\n  }\n\n  public byte get(int offset) throws SnowflakeSQLException {\n    return data.getByte(offset);\n  }\n\n  public void addByte(byte b, int pos) throws SnowflakeSQLException {\n    data.addByte(b, pos);\n  }\n\n  public void addBytes(byte[] src, int offset, int pos, int length) throws SnowflakeSQLException {\n    data.addBytes(src, offset, pos, length);\n  }\n\n  /**\n   * This class abstracts the storage of the strings in one chunk. To the user the class behaves\n   * similar to an ArrayList.\n   */\n  private interface ResultChunkData {\n    /**\n     * Add the string to the data list\n     *\n     * @param string value to add\n     */\n    void add(String string) throws SnowflakeSQLException;\n\n    /**\n     * Access an element by an index\n     *\n     * @param index determines the element\n     * @return String containing the same data as the one passed to add()\n     */\n    String get(int index);\n\n    /**\n     * Compute the necessary memory to store this chunk\n     *\n     * @return memory in bytes\n     */\n    long computeNeededChunkMemory();\n\n    /** Let GC collect the memory */\n    void freeData();\n\n    /**\n     * add offset into offset buffer\n     *\n     * @param offset\n     * @throws SnowflakeSQLException\n     */\n    void addOffset(int offset) throws SnowflakeSQLException;\n\n    /**\n     * label current value as null\n     *\n     * @throws SnowflakeSQLException\n     */\n    void setIsNull() throws SnowflakeSQLException;\n\n    /**\n     * increase index\n     *\n     * @throws SnowflakeSQLException\n     */\n    void nextIndex() throws SnowflakeSQLException;\n\n    /**\n     * set the last length\n     *\n     * @param len\n     * @throws SnowflakeSQLException\n     */\n    void setLastLength(int len) throws SnowflakeSQLException;\n\n    /**\n     * get one byte from the byte array\n     *\n     * @param offset\n     * @return\n     * @throws SnowflakeSQLException\n     */\n    byte getByte(int offset) throws SnowflakeSQLException;\n\n    /**\n     * add one byte to the byte array at nextIndex\n     *\n     * @param b\n     * @param pos\n     * @throws SnowflakeSQLException\n     */\n    void addByte(byte b, int pos) throws SnowflakeSQLException;\n\n    /**\n     * add bytes to the byte array\n     *\n     * @param src\n     * @param src_offset\n     * @param pos\n     * @param length\n     * @throws SnowflakeSQLException\n     */\n    void addBytes(byte[] src, int src_offset, int pos, int length) throws SnowflakeSQLException;\n\n    void reset();\n  }\n\n  /**\n   * BlockResultChunkDataV2: This implementation copies the strings to byte arrays and stores the\n   * offsets and bitmaps. This design can save half of the memory usage compared to the original one\n   */\n  private static class BlockResultChunkDataV2 implements ResultChunkData {\n    BlockResultChunkDataV2(int totalLength, int rowCount, int colCount, SFBaseSession session) {\n      this.blockCount = getBlock(totalLength - 1) + 1;\n      this.rowCount = rowCount;\n      this.colCount = colCount;\n      this.metaBlockCount = getMetaBlock(this.rowCount * this.colCount - 1) + 1;\n      this.session = session;\n    }\n\n    @Override\n    public void reset() {\n      freeData();\n      this.lastLength = 0;\n      this.nextIndex = 0;\n    }\n\n    @Override\n    public void addOffset(int offset) {\n      if (data.size() < blockCount || offsets.size() < metaBlockCount) {\n        allocateArrays();\n      }\n      offsets.get(getMetaBlock(nextIndex))[getMetaBlockIndex(nextIndex)] = offset;\n    }\n\n    @Override\n    public void setIsNull() {\n      isNulls.get(getMetaBlock(nextIndex)).set(getMetaBlockIndex(nextIndex));\n    }\n\n    @Override\n    public void setLastLength(int len) {\n      lastLength = len;\n    }\n\n    @Override\n    public byte getByte(int offset) {\n      return data.get(getBlock(offset))[getBlockOffset(offset)];\n    }\n\n    @Override\n    public void addByte(byte b, int pos) {\n      if (data.size() < blockCount || offsets.size() < metaBlockCount) {\n        allocateArrays();\n      }\n      data.get(getBlock(pos))[getBlockOffset(pos)] = b;\n    }\n\n    @Override\n    public void addBytes(byte[] src, int src_offset, int pos, int length) {\n      if (data.size() < blockCount || offsets.size() < metaBlockCount) {\n        allocateArrays();\n      }\n\n      final int offset = pos;\n\n      // copy string to the char array\n      int copied = 0;\n      if (spaceLeftOnBlock(offset) < length) {\n        while (copied < length) {\n          final int copySize = Math.min(length - copied, spaceLeftOnBlock(offset + copied));\n          System.arraycopy(\n              src,\n              src_offset + copied,\n              data.get(getBlock(offset + copied)),\n              getBlockOffset(offset + copied),\n              copySize);\n          copied += copySize;\n        }\n      } else {\n        System.arraycopy(\n            src, src_offset, data.get(getBlock(offset)), getBlockOffset(offset), length);\n      }\n    }\n\n    @Override\n    public void nextIndex() {\n      nextIndex++;\n    }\n\n    @Override\n    public void add(String string) throws SnowflakeSQLException {\n      throw new SnowflakeSQLLoggedException(\n          this.session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Unimplemented\");\n    }\n\n    private int getLength(int index, int offset) {\n      if (index == rowCount * colCount - 1) {\n        // last one\n        return lastLength;\n      } else {\n        int nextOffset = offsets.get(getMetaBlock(index + 1))[getMetaBlockIndex(index + 1)];\n        return nextOffset - offset;\n      }\n    }\n\n    @Override\n    public String get(int index) {\n      final boolean isNull = isNulls.get(getMetaBlock(index)).get(getMetaBlockIndex(index));\n      if (isNull) {\n        return null;\n      } else {\n        final int offset = offsets.get(getMetaBlock(index))[getMetaBlockIndex(index)];\n        final int length = getLength(index, offset);\n\n        // Create string from the char arrays\n        if (spaceLeftOnBlock(offset) < length) {\n          int copied = 0;\n          byte[] cell = new byte[length];\n          while (copied < length) {\n            final int copySize = Math.min(length - copied, spaceLeftOnBlock(offset + copied));\n            System.arraycopy(\n                data.get(getBlock(offset + copied)),\n                getBlockOffset(offset + copied),\n                cell,\n                copied,\n                copySize);\n\n            copied += copySize;\n          }\n          return new String(cell, StandardCharsets.UTF_8);\n        } else {\n          return new String(\n              data.get(getBlock(offset)), getBlockOffset(offset), length, StandardCharsets.UTF_8);\n        }\n      }\n    }\n\n    @Override\n    public long computeNeededChunkMemory() {\n      long dataRequirement = blockCount * blockLength * 1L;\n      long metadataRequirement =\n          metaBlockCount * metaBlockLength * 4L // offsets\n              + metaBlockCount * metaBlockLength / 8L // isNulls\n              + 1L; // lastLength\n\n      return dataRequirement + metadataRequirement;\n    }\n\n    @Override\n    public void freeData() {\n      data.clear();\n      offsets.clear();\n      isNulls.clear();\n    }\n\n    private static int getBlock(int offset) {\n      return offset >> blockLengthBits;\n    }\n\n    private static int getBlockOffset(int offset) {\n      return offset & (blockLength - 1);\n    }\n\n    private static int spaceLeftOnBlock(int offset) {\n      return blockLength - getBlockOffset(offset);\n    }\n\n    private static int getMetaBlock(int index) {\n      return index >> metaBlockLengthBits;\n    }\n\n    private static int getMetaBlockIndex(int index) {\n      return index & (metaBlockLength - 1);\n    }\n\n    private void allocateArrays() {\n      logger.debug(\"allocating {} B for ResultChunk\", computeNeededChunkMemory());\n      while (data.size() < blockCount) {\n        data.add(new byte[1 << blockLengthBits]);\n      }\n      while (offsets.size() < metaBlockCount) {\n        offsets.add(new int[1 << metaBlockLengthBits]);\n        isNulls.add(new BitSet(1 << metaBlockLengthBits));\n      }\n      logger.debug(\"allocated {} B for ResultChunk\", computeNeededChunkMemory());\n    }\n\n    // blocks for storing the string data\n    int blockCount;\n    private static final int blockLengthBits = 23;\n    private static int blockLength = 1 << blockLengthBits;\n    private final ArrayList<byte[]> data = new ArrayList<>();\n    SFBaseSession session;\n\n    // blocks for storing offsets and lengths\n    int metaBlockCount;\n    private static int metaBlockLengthBits = 15;\n    private static int metaBlockLength = 1 << metaBlockLengthBits;\n    private final ArrayList<int[]> offsets = new ArrayList<>();\n    private final ArrayList<BitSet> isNulls = new ArrayList<>();\n    private int lastLength;\n    private int rowCount, colCount;\n    private int nextIndex = 0;\n  }\n\n  /** Cache the data, offset and length blocks */\n  static class ResultChunkDataCache {\n    /**\n     * Add the data to the cache. CAUTION: The result chunk is not usable afterward\n     *\n     * @param chunk add this to the cache\n     */\n    void add(JsonResultChunk chunk) {\n      cache.add(new SoftReference<>(chunk.data));\n      chunk.data = null;\n    }\n\n    /**\n     * Creates a new ResultChunkData which reuses as much blocks as possible\n     *\n     * @param data fill this with reused blocks\n     */\n    void reuseOrCreateResultData(ResultChunkData data) {\n      List<SoftReference<ResultChunkData>> remove = new ArrayList<>();\n      try {\n        for (SoftReference<ResultChunkData> ref : cache) {\n          ResultChunkData dat = ref.get();\n          if (dat == null) {\n            remove.add(ref);\n            continue;\n          }\n          if (dat instanceof BlockResultChunkDataV2) {\n            BlockResultChunkDataV2 bTargetData = (BlockResultChunkDataV2) data;\n            BlockResultChunkDataV2 bCachedDat = (BlockResultChunkDataV2) dat;\n            if (bCachedDat.data.size() == 0 && bCachedDat.offsets.size() == 0) {\n              remove.add(ref);\n              continue;\n            }\n\n            while (bTargetData.data.size() < bTargetData.blockCount && bCachedDat.data.size() > 0) {\n              bTargetData.data.add(bCachedDat.data.remove(bCachedDat.data.size() - 1));\n            }\n            while (bTargetData.offsets.size() < bTargetData.metaBlockCount\n                && bCachedDat.offsets.size() > 0) {\n              bTargetData.offsets.add(bCachedDat.offsets.remove(bCachedDat.offsets.size() - 1));\n              BitSet isNulls = bCachedDat.isNulls.remove(bCachedDat.isNulls.size() - 1);\n              isNulls.clear(); // SNOW-80208 have to clear isNulls explicitly\n              bTargetData.isNulls.add(isNulls);\n            }\n            if (bTargetData.data.size() == bTargetData.blockCount\n                && bTargetData.offsets.size() == bTargetData.metaBlockCount) {\n              return;\n            }\n          } else {\n            remove.add(ref);\n          }\n        }\n      } finally {\n        cache.removeAll(remove);\n      }\n    }\n\n    /** Let GC collect all data hold by the cache */\n    void clear() {\n      for (SoftReference<ResultChunkData> ref : cache) {\n        ResultChunkData dat = ref.get();\n        if (dat != null) {\n          dat.freeData();\n        }\n      }\n      cache.clear();\n    }\n\n    List<SoftReference<ResultChunkData>> cache = new LinkedList<>();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/MatDesc.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\n\n/** A class to handle S3 material descriptor metadata entries (matdesc). */\npublic class MatDesc {\n  /** MatDesc key for query ID */\n  public static String QUERY_ID = \"queryId\";\n\n  /** MatDesc key for stage master key ID */\n  public static String SMK_ID = \"smkId\";\n\n  /** MatDesc key for the length of the key in bits */\n  public static String KEY_SIZE = \"keySize\";\n\n  /** If key size is not explicitly specified, assume DEFAULT_KEY_SIZE */\n  public static int DEFAULT_KEY_SIZE = 256;\n\n  /** The JSON parser for matdesc entries */\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  /** The Stage Master Key ID */\n  private final long smkId;\n\n  /** The query ID */\n  private final String queryId;\n\n  /** The key length in bits */\n  private final int keySize;\n\n  public MatDesc(long smkId, String queryId, int keySize) {\n    this.smkId = smkId;\n    this.queryId = queryId;\n    this.keySize = keySize;\n  }\n\n  public MatDesc(long smkId, String queryId) {\n    this(smkId, queryId, DEFAULT_KEY_SIZE);\n  }\n\n  public long getSmkId() {\n    return this.smkId;\n  }\n\n  public String getQueryId() {\n    return this.queryId;\n  }\n\n  public int getKeySize() {\n    return this.keySize;\n  }\n\n  /**\n   * Try to parse the material descriptor string.\n   *\n   * @param matdesc string\n   * @return The material description or null\n   */\n  public static MatDesc parse(String matdesc) {\n    if (matdesc == null) {\n      return null;\n    }\n    try {\n      JsonNode jsonNode = mapper.readTree(matdesc);\n      JsonNode queryIdNode = jsonNode.path(QUERY_ID);\n      if (queryIdNode.isMissingNode() || queryIdNode.isNull()) {\n        return null;\n      }\n      JsonNode smkIdNode = jsonNode.path(SMK_ID);\n      if (smkIdNode.isMissingNode() || smkIdNode.isNull()) {\n        return null;\n      }\n      String queryId = queryIdNode.asText();\n      long smkId = smkIdNode.asLong();\n      JsonNode keySizeNode = jsonNode.path(KEY_SIZE);\n      if (!keySizeNode.isMissingNode() && !keySizeNode.isNull()) {\n        return new MatDesc(smkId, queryId, keySizeNode.asInt());\n      }\n      return new MatDesc(smkId, queryId);\n    } catch (Exception ex) {\n      return null;\n    }\n  }\n\n  @Override\n  public String toString() {\n    ObjectNode obj = mapper.createObjectNode();\n    obj.put(QUERY_ID, this.queryId);\n    obj.put(SMK_ID, Long.toString(this.smkId));\n    obj.put(KEY_SIZE, Integer.toString(this.keySize));\n    return obj.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/OCSPErrorCode.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\npublic enum OCSPErrorCode {\n  CERTIFICATE_STATUS_GOOD,\n  CERTIFICATE_STATUS_REVOKED,\n  CERTIFICATE_STATUS_UNKNOWN,\n  OCSP_CACHE_DOWNLOAD_TIMEOUT,\n  OCSP_RESPONSE_FETCH_TIMEOUT,\n  OCSP_RESPONSE_FETCH_FAILURE,\n  INVALID_CACHE_SERVER_URL,\n  EXPIRED_OCSP_SIGNING_CERTIFICATE,\n  INVALID_CERTIFICATE_SIGNATURE,\n  INVALID_OCSP_RESPONSE_SIGNATURE,\n  INVALID_OCSP_RESPONSE_VALIDITY,\n  INVALID_OCSP_RESPONSE,\n  REVOCATION_CHECK_FAILURE,\n  INVALID_SSD,\n  NO_OCSP_URL_ATTACHED,\n  NO_ROOTCA_FOUND\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/QueryIdValidator.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.util.regex.Pattern;\n\npublic class QueryIdValidator {\n  private static final Pattern QUERY_ID_REGEX =\n      Pattern.compile(\"[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}\");\n\n  public static boolean isValid(String queryId) {\n    return queryId != null && QUERY_ID_REGEX.matcher(queryId).matches();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/RestRequest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.net.URISyntaxException;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Function;\nimport javax.net.ssl.SSLHandshakeException;\nimport javax.net.ssl.SSLKeyException;\nimport javax.net.ssl.SSLPeerUnverifiedException;\nimport javax.net.ssl.SSLProtocolException;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.Event;\nimport net.snowflake.client.internal.core.EventUtil;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpExecutingContext;\nimport net.snowflake.client.internal.core.HttpExecutingContextBuilder;\nimport net.snowflake.client.internal.core.HttpResponseContextDto;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFOCSPException;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.core.URLUtil;\nimport net.snowflake.client.internal.core.UUIDUtils;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.DecorrelatedJitterBackoff;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.StatusLine;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.util.EntityUtils;\n\n/**\n * This is an abstraction on top of http client.\n *\n * <p>Currently it only has one method for retrying http request execution so that the same logic\n * doesn't have to be replicated at difference places where retry is needed.\n */\npublic class RestRequest {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(RestRequest.class);\n\n  // Request guid per HTTP request\n  private static final String SF_REQUEST_GUID = \"request_guid\";\n\n  // min backoff in milli before we retry due to transient issues\n  private static final long minBackoffInMilli = 1000;\n\n  // max backoff in milli before we retry due to transient issues\n  // we double the backoff after each retry till we reach the max backoff\n  private static final long maxBackoffInMilli = 16000;\n\n  // retry at least once even if timeout limit has been reached\n  private static final int MIN_RETRY_COUNT = 1;\n\n  static final String ERROR_FIELD_NAME = \"error\";\n  static final String ERROR_USE_DPOP_NONCE = \"use_dpop_nonce\";\n  static final String DPOP_NONCE_HEADER_NAME = \"dpop-nonce\";\n\n  static final Set<Class<?>> sslExceptions =\n      new HashSet<>(\n          Arrays.asList(\n              SSLHandshakeException.class,\n              SSLKeyException.class,\n              SSLPeerUnverifiedException.class,\n              SSLProtocolException.class));\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not should be executed before and/or after\n   *     the retry\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  @Deprecated\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      ExecTimeTelemetryData execTimeTelemetryData)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        execTimeTelemetryData,\n        (SFBaseSession) null);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not should be executed before and/or after\n   *     the retry\n   * @param sfSession the session associated with the request\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      ExecTimeTelemetryData execTimeTelemetryData,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        false, // noRetry\n        execTimeTelemetryData,\n        null,\n        sfSession,\n        null,\n        null,\n        false);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not should be executed before and/or after\n   *     the retry\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  @Deprecated\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      boolean noRetry,\n      ExecTimeTelemetryData execTimeData)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        noRetry,\n        execTimeData,\n        (SFBaseSession) null);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not should be executed before and/or after\n   *     the retry\n   * @param sfSession the session associated with the request\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      boolean noRetry,\n      ExecTimeTelemetryData execTimeData,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        noRetry,\n        execTimeData,\n        null,\n        sfSession,\n        null,\n        null,\n        false);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not\n   * @param execTimeData ExecTimeTelemetryData should be executed before and/or after the retry\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  @Deprecated\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        execTimeData,\n        retryContextManager,\n        null);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not\n   * @param execTimeData ExecTimeTelemetryData should be executed before and/or after the retry\n   * @param sfSession the session associated with the request\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryContextManager,\n      SFBaseSession sfSession)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        false, // noRetry\n        execTimeData,\n        retryContextManager,\n        sfSession,\n        null,\n        null,\n        false);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not\n   * @param noRetry should we disable retry on non-successful http resp code\n   * @param execTimeData ExecTimeTelemetryData\n   * @param retryManager RetryContextManager - object allowing to optionally pass custom logic that\n   *     should be executed before and/or after the retry\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  @Deprecated\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      boolean noRetry,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryManager)\n      throws SnowflakeSQLException {\n    return execute(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        noRetry,\n        execTimeData,\n        retryManager,\n        null,\n        null,\n        null,\n        false);\n  }\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not\n   * @param noRetry should we disable retry on non-successful http resp code\n   * @param execTimeData ExecTimeTelemetryData\n   * @param retryManager RetryContextManager - object allowing to optionally pass custom logic that\n   *     should be executed before and/or after the retry\n   * @param sfSession the session associated with the request\n   * @param key HttpClientSettingsKey object\n   * @param httpHeaderCustomizer HttpHeadersCustomizer object for customization of HTTP headers for\n   *     requests sent by the Snowflake JDBC driver.\n   * @param isHttpClientWithoutDecompression flag for create client without Decompression\n   * @return HttpResponse Object get from server\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static CloseableHttpResponse execute(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      boolean noRetry,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryManager,\n      SFBaseSession sfSession,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException {\n    return executeWithRetries(\n            httpClient,\n            httpRequest,\n            retryTimeout,\n            authTimeout,\n            socketTimeout,\n            maxRetries,\n            injectSocketTimeout,\n            canceling, // no canceling\n            withoutCookies, // no cookie\n            includeRetryParameters, // no retry\n            includeSnowflakeHeaders,\n            retryHTTP403, // retry on HTTP 403\n            noRetry,\n            new ExecTimeTelemetryData(),\n            sfSession,\n            key,\n            httpHeaderCustomizer,\n            isHttpClientWithoutDecompression)\n        .getHttpResponse();\n  }\n\n  static long getNewBackoffInMilli(\n      long previousBackoffInMilli,\n      boolean isLoginRequest,\n      DecorrelatedJitterBackoff decorrelatedJitterBackoff,\n      int retryCount,\n      long retryTimeoutInMilliseconds,\n      long elapsedMilliForTransientIssues) {\n    long backoffInMilli;\n    if (isLoginRequest) {\n      long jitteredBackoffInMilli =\n          decorrelatedJitterBackoff.getJitterForLogin(previousBackoffInMilli);\n      backoffInMilli =\n          (long)\n              decorrelatedJitterBackoff.chooseRandom(\n                  jitteredBackoffInMilli + previousBackoffInMilli,\n                  Math.pow(2, retryCount) + jitteredBackoffInMilli);\n    } else {\n\n      backoffInMilli = decorrelatedJitterBackoff.nextSleepTime(previousBackoffInMilli);\n    }\n\n    backoffInMilli = Math.min(maxBackoffInMilli, Math.max(previousBackoffInMilli, backoffInMilli));\n\n    if (retryTimeoutInMilliseconds > 0\n        && (elapsedMilliForTransientIssues + backoffInMilli) > retryTimeoutInMilliseconds) {\n      // If the timeout will be reached before the next backoff, just use the remaining\n      // time (but cannot be negative) - this is the only place when backoff is not in range\n      // min-max.\n      backoffInMilli =\n          Math.max(\n              0,\n              Math.min(\n                  backoffInMilli, retryTimeoutInMilliseconds - elapsedMilliForTransientIssues));\n      logger.debug(\n          \"We are approaching retry timeout {}ms, setting backoff to {}ms\",\n          retryTimeoutInMilliseconds,\n          backoffInMilli);\n    }\n    return backoffInMilli;\n  }\n\n  static boolean isNonRetryableHTTPCode(CloseableHttpResponse response, boolean retryHTTP403) {\n    return (response != null)\n        && (response.getStatusLine().getStatusCode() < 500\n            || // service unavailable\n            response.getStatusLine().getStatusCode() >= 600)\n        && // gateway timeout\n        response.getStatusLine().getStatusCode() != 408\n        && // retry\n        response.getStatusLine().getStatusCode() != 429\n        && // request timeout\n        (!retryHTTP403 || response.getStatusLine().getStatusCode() != 403);\n  }\n\n  private static boolean isCertificateRevoked(Exception ex) {\n    if (ex == null) {\n      return false;\n    }\n    Throwable ex0 = getRootCause(ex);\n    if (!(ex0 instanceof SFOCSPException)) {\n      return false;\n    }\n    SFOCSPException cause = (SFOCSPException) ex0;\n    return cause.getErrorCode() == OCSPErrorCode.CERTIFICATE_STATUS_REVOKED;\n  }\n\n  private static Throwable getRootCause(Throwable ex) {\n    Throwable ex0 = ex;\n    while (ex0.getCause() != null) {\n      ex0 = ex0.getCause();\n    }\n    return ex0;\n  }\n\n  private static boolean isTransientHandshakeEOF(Exception ex) {\n    Throwable root = getRootCause(ex);\n    return root instanceof EOFException;\n  }\n\n  private static void setRequestConfig(\n      HttpRequestBase httpRequest,\n      boolean withoutCookies,\n      int injectSocketTimeout,\n      String requestIdStr,\n      long authTimeoutInMilli) {\n    if (withoutCookies) {\n      httpRequest.setConfig(HttpUtil.getRequestConfigWithoutCookies());\n    }\n\n    // For first call, simulate a socket timeout by setting socket timeout\n    // to the injected socket timeout value\n    if (injectSocketTimeout != 0) {\n      // test code path\n      logger.debug(\n          \"{}Injecting socket timeout by setting socket timeout to {} ms\",\n          requestIdStr,\n          injectSocketTimeout);\n      httpRequest.setConfig(\n          HttpUtil.getDefaultRequestConfigWithSocketTimeout(injectSocketTimeout, withoutCookies));\n    }\n\n    // When the auth timeout is set, set the socket timeout as the authTimeout\n    // so that it can be renewed in time and pass it to the http request configuration.\n    if (authTimeoutInMilli > 0) {\n      int requestSocketAndConnectTimeout = (int) authTimeoutInMilli;\n      logger.debug(\n          \"{}Setting auth timeout as the socket timeout: {} ms\", requestIdStr, authTimeoutInMilli);\n      httpRequest.setConfig(\n          HttpUtil.getDefaultRequestConfigWithSocketAndConnectTimeout(\n              requestSocketAndConnectTimeout, withoutCookies));\n    }\n  }\n\n  private static void setRequestURI(\n      HttpRequestBase httpRequest,\n      String requestIdStr,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      int retryCount,\n      String lastStatusCodeForRetry,\n      long startTime,\n      String requestInfoScrubbed)\n      throws URISyntaxException {\n    /*\n     * Add retryCount if the first request failed\n     * GS can use the parameter for optimization. Specifically GS\n     * will only check metadata database to see if a query has been running\n     * for a retry request. This way for the majority of query requests\n     * which are not part of retry we don't have to pay the performance\n     * overhead of looking up in metadata database.\n     */\n    URIBuilder builder = new URIBuilder(httpRequest.getURI());\n    // If HTAP\n    if (\"true\".equalsIgnoreCase(systemGetEnv(\"HTAP_SIMULATION\"))\n        && builder.getPathSegments().contains(\"query-request\")) {\n      logger.debug(\"{}Setting htap simulation\", requestIdStr);\n      builder.setParameter(\"target\", \"htap_simulation\");\n    }\n    if (includeRetryParameters && retryCount > 0) {\n      updateRetryParameters(builder, retryCount, lastStatusCodeForRetry, startTime);\n    }\n\n    if (includeSnowflakeHeaders) {\n      UUID guid = UUIDUtils.getUUID();\n      logger.debug(\"{}Request {} guid: {}\", requestIdStr, requestInfoScrubbed, guid.toString());\n      // Add request_guid for better tracing\n      builder.setParameter(SF_REQUEST_GUID, guid.toString());\n    }\n\n    httpRequest.setURI(builder.build());\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not\n   * @param sfSession the session associated with the request\n   * @return HttpResponseContextDto Object get from server or exception\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static HttpResponseContextDto executeWithRetries(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      boolean unpackResponse,\n      ExecTimeTelemetryData execTimeTelemetryData,\n      SFBaseSession sfSession,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException {\n    return executeWithRetries(\n        httpClient,\n        httpRequest,\n        retryTimeout,\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        injectSocketTimeout,\n        canceling,\n        withoutCookies,\n        includeRetryParameters,\n        includeSnowflakeHeaders,\n        retryHTTP403,\n        false,\n        unpackResponse,\n        execTimeTelemetryData,\n        sfSession,\n        key,\n        httpHeaderCustomizer,\n        isHttpClientWithoutDecompression);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param retryTimeout : retry timeout (in seconds)\n   * @param authTimeout : authenticator specific timeout (in seconds)\n   * @param socketTimeout : curl timeout (in ms)\n   * @param maxRetries : max retry count for the request\n   * @param injectSocketTimeout : simulate socket timeout\n   * @param canceling canceling flag\n   * @param withoutCookies whether the cookie spec should be set to IGNORE or not\n   * @param includeRetryParameters whether to include retry parameters in retried requests. Only\n   *     needs to be true for JDBC statement execution (query requests to Snowflake server).\n   * @param includeSnowflakeHeaders whether to include Snowflake headers (incl. request_guid)\n   * @param retryHTTP403 whether to retry on HTTP 403 or not\n   * @param execTimeTelemetryData ExecTimeTelemetryData should be executed before and/or after the\n   *     retry\n   * @param sfSession the session associated with the request\n   * @return HttpResponseContextDto Object get from server or exception\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static HttpResponseContextDto executeWithRetries(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      long retryTimeout,\n      long authTimeout,\n      int socketTimeout,\n      int maxRetries,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeSnowflakeHeaders,\n      boolean retryHTTP403,\n      boolean noRetry,\n      boolean unpackResponse,\n      ExecTimeTelemetryData execTimeTelemetryData,\n      SFBaseSession sfSession,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException {\n    String requestIdStr = URLUtil.getRequestIdLogStr(httpRequest.getURI());\n    String requestInfoScrubbed = SecretDetector.maskSASToken(httpRequest.toString());\n    HttpExecutingContext context =\n        HttpExecutingContextBuilder.withRequest(requestIdStr, requestInfoScrubbed)\n            .retryTimeout(retryTimeout)\n            .authTimeout(authTimeout)\n            .origSocketTimeout(socketTimeout)\n            .maxRetries(maxRetries)\n            .injectSocketTimeout(injectSocketTimeout)\n            .canceling(canceling)\n            .withoutCookies(withoutCookies)\n            .includeRetryParameters(includeRetryParameters)\n            .includeSnowflakeHeaders(includeSnowflakeHeaders)\n            .retryHTTP403(retryHTTP403)\n            .noRetry(noRetry)\n            .unpackResponse(unpackResponse)\n            .loginRequest(SessionUtil.isNewRetryStrategyRequest(httpRequest))\n            .withSfSession(sfSession)\n            .build();\n    return executeWithRetries(\n        httpClient,\n        httpRequest,\n        context,\n        execTimeTelemetryData,\n        null,\n        key,\n        httpHeaderCustomizer,\n        isHttpClientWithoutDecompression);\n  }\n\n  /**\n   * Execute an HTTP request with retry logic.\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param httpRequest request object contains all the request information\n   * @param execTimeData ExecTimeTelemetryData should be executed before and/or after the retry\n   * @param retryManager RetryManager containing extra actions used during retries\n   * @return HttpResponseContextDto Object get from server or exception\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException Request timeout Exception or\n   *     Illegal State Exception i.e. connection is already shutdown etc\n   */\n  public static HttpResponseContextDto executeWithRetries(\n      CloseableHttpClient httpClient,\n      HttpRequestBase httpRequest,\n      HttpExecutingContext httpExecutingContext,\n      ExecTimeTelemetryData execTimeData,\n      RetryContextManager retryManager,\n      HttpClientSettingsKey key,\n      List<HttpHeadersCustomizer> httpHeaderCustomizer,\n      boolean isHttpClientWithoutDecompression)\n      throws SnowflakeSQLException {\n    Stopwatch networkComunnicationStapwatch = null;\n    Stopwatch requestReponseStopWatch = null;\n    HttpResponseContextDto responseDto = new HttpResponseContextDto();\n\n    if (logger.isDebugEnabled()) {\n      networkComunnicationStapwatch = new Stopwatch();\n      networkComunnicationStapwatch.start();\n      logger.debug(\n          \"{}Executing rest request: {}, retry timeout: {}, socket timeout: {}, max retries: {},\"\n              + \" inject socket timeout: {}, canceling: {}, without cookies: {}, include retry parameters: {},\"\n              + \" include request guid: {}, retry http 403: {}, no retry: {}\",\n          httpExecutingContext.getRequestId(),\n          httpExecutingContext.getRequestInfoScrubbed(),\n          httpExecutingContext.getRetryTimeoutInMilliseconds(),\n          httpExecutingContext.getOrigSocketTimeout(),\n          httpExecutingContext.getMaxRetries(),\n          httpExecutingContext.isInjectSocketTimeout(),\n          httpExecutingContext.getCanceling(),\n          httpExecutingContext.isWithoutCookies(),\n          httpExecutingContext.isIncludeRetryParameters(),\n          httpExecutingContext.isIncludeSnowflakeHeaders(),\n          httpExecutingContext.isRetryHTTP403(),\n          httpExecutingContext.isNoRetry());\n    }\n    if (httpExecutingContext.isLoginRequest()) {\n      logger.debug(\n          \"{}Request is a login/auth request. Using new retry strategy\",\n          httpExecutingContext.getRequestId());\n    }\n\n    RestRequest.setRequestConfig(\n        httpRequest,\n        httpExecutingContext.isWithoutCookies(),\n        httpExecutingContext.getInjectSocketTimeout(),\n        httpExecutingContext.getRequestId(),\n        httpExecutingContext.getAuthTimeoutInMilliseconds());\n\n    execTimeData.setExecuteToSendQueryEnd();\n\n    // try request till we get a good response or retry timeout\n    while (true) {\n      logger.debug(\n          \"{}Retry count: {}, max retries: {}, retry timeout: {} s, backoff: {} ms. Attempting request: {}\",\n          httpExecutingContext.getRequestId(),\n          httpExecutingContext.getRetryCount(),\n          httpExecutingContext.getMaxRetries(),\n          httpExecutingContext.getRetryTimeout(),\n          httpExecutingContext.getMinBackoffInMillis(),\n          httpExecutingContext.getRequestInfoScrubbed());\n      try {\n        // update start time\n        httpExecutingContext.setStartTimePerRequest(System.currentTimeMillis());\n\n        RestRequest.setRequestURI(\n            httpRequest,\n            httpExecutingContext.getRequestId(),\n            httpExecutingContext.isIncludeRetryParameters(),\n            httpExecutingContext.isIncludeSnowflakeHeaders(),\n            httpExecutingContext.getRetryCount(),\n            httpExecutingContext.getLastStatusCodeForRetry(),\n            httpExecutingContext.getStartTime(),\n            httpExecutingContext.getRequestInfoScrubbed());\n\n        SFBaseSession session = httpExecutingContext.getSfSession();\n        if (httpExecutingContext.isIncludeSnowflakeHeaders() && session != null) {\n          if (session.getStickyHttpHeaders() != null) {\n            for (Map.Entry<String, String> entry : session.getStickyHttpHeaders().entrySet()) {\n              httpRequest.setHeader(entry.getKey(), entry.getValue());\n            }\n          }\n        }\n\n        execTimeData.setHttpClientStart();\n        CloseableHttpResponse response = httpClient.execute(httpRequest);\n        responseDto.setHttpResponse(response);\n        execTimeData.setHttpClientEnd();\n      } catch (Exception ex) {\n        if (ex instanceof IllegalStateException) {\n          // if exception is caused by illegal state, e.g shutdown of http client\n          // because of closing of connection, then recreate the http client and remove existing\n          // from the cache.\n          logger.warn(\n              \"IllegalStateException encountered while processing the HTTP request.\"\n                  + \" The HttpClient was shut down due to connection closure. \"\n                  + \"Attempting to rebuild the HttpClient and retry the request.\");\n          // Clear the httpClient cache.\n          try {\n            httpClient.close();\n          } catch (IOException e) {\n            logger.warn(\"Cannot close http client\", e);\n          }\n          HttpUtil.httpClient.remove(key);\n          // rebuild the http client.\n          if (isHttpClientWithoutDecompression) {\n            httpClient = HttpUtil.getHttpClientWithoutDecompression(key, httpHeaderCustomizer);\n          } else {\n            httpClient = HttpUtil.getHttpClient(key, httpHeaderCustomizer);\n          }\n          continue;\n        } else if (ex instanceof SSLHandshakeException && isTransientHandshakeEOF(ex)) {\n          // Treat transient EOF during TLS handshake as retryable:\n          // set saved exception for logging/telemetry and allow the retry loop to proceed.\n          responseDto.setSavedEx(ex);\n        } else {\n          responseDto.setSavedEx(handlingNotRetryableException(ex, httpExecutingContext));\n        }\n      } finally {\n        // Reset the socket timeout to its original value if it is not the\n        // very first iteration.\n        if (httpExecutingContext.getInjectSocketTimeout() != 0\n            && httpExecutingContext.getRetryCount() == 0) {\n          // test code path\n          httpRequest.setConfig(\n              HttpUtil.getDefaultRequestConfigWithSocketTimeout(\n                  httpExecutingContext.getOrigSocketTimeout(),\n                  httpExecutingContext.isWithoutCookies()));\n        }\n      }\n      boolean shouldSkipRetry =\n          shouldSkipRetryWithLoggedReason(httpRequest, responseDto, httpExecutingContext);\n      httpExecutingContext.setShouldRetry(!shouldSkipRetry);\n\n      if (httpExecutingContext.isUnpackResponse()\n          && responseDto.getHttpResponse() != null\n          && responseDto.getHttpResponse().getStatusLine().getStatusCode()\n              == 200) { // todo extract getter for statusCode\n        processHttpResponse(httpExecutingContext, execTimeData, responseDto);\n      }\n\n      if (!httpExecutingContext.isShouldRetry()) {\n        if (responseDto.getHttpResponse() == null) {\n          if (responseDto.getSavedEx() != null) {\n            logger.error(\n                \"{}Returning null response. Cause: {}, request: {}\",\n                httpExecutingContext.getRequestId(),\n                getRootCause(responseDto.getSavedEx()),\n                httpExecutingContext.getRequestInfoScrubbed());\n          } else {\n            logger.error(\n                \"{}Returning null response for request: {}\",\n                httpExecutingContext.getRequestId(),\n                httpExecutingContext.getRequestInfoScrubbed());\n          }\n        } else if (responseDto.getHttpResponse().getStatusLine().getStatusCode() != 200) {\n          logger.error(\n              \"{}Error response: HTTP Response code: {}, request: {}\",\n              httpExecutingContext.getRequestId(),\n              responseDto.getHttpResponse().getStatusLine().getStatusCode(),\n              httpExecutingContext.getRequestInfoScrubbed());\n          responseDto.setSavedEx(\n              new SnowflakeSQLException(\n                  SqlState.IO_ERROR,\n                  ErrorCode.NETWORK_ERROR.getMessageCode(),\n                  \"HTTP status=\"\n                      + ((responseDto.getHttpResponse() != null)\n                          ? responseDto.getHttpResponse().getStatusLine().getStatusCode()\n                          : \"null response\")));\n        } else if ((responseDto.getHttpResponse() == null\n            || responseDto.getHttpResponse().getStatusLine().getStatusCode() != 200)) {\n          sendTelemetryEvent(\n              httpRequest,\n              httpExecutingContext,\n              responseDto.getHttpResponse(),\n              responseDto.getSavedEx());\n        }\n        break;\n      } else {\n        prepareRetry(httpRequest, httpExecutingContext, retryManager, responseDto);\n      }\n    }\n\n    logger.debug(\n        \"{}Execution of request {} took {} ms with total of {} retries\",\n        httpExecutingContext.getRequestId(),\n        httpExecutingContext.getRequestInfoScrubbed(),\n        networkComunnicationStapwatch == null\n            ? \"n/a\"\n            : networkComunnicationStapwatch.elapsedMillis(),\n        httpExecutingContext.getRetryCount());\n\n    httpExecutingContext.resetRetryCount();\n    if (logger.isDebugEnabled() && networkComunnicationStapwatch != null) {\n      networkComunnicationStapwatch.stop();\n    }\n    if (responseDto.getSavedEx() != null) {\n      Exception savedEx = responseDto.getSavedEx();\n      sendIBHttpErrorEvent(httpRequest, responseDto.getHttpResponse(), httpExecutingContext);\n      if (savedEx instanceof SnowflakeSQLException) {\n        throw (SnowflakeSQLException) savedEx;\n      } else {\n        throw new SnowflakeSQLException(\n            savedEx,\n            ErrorCode.NETWORK_ERROR,\n            \"Exception encountered for HTTP request: \" + savedEx.getMessage());\n      }\n    }\n    return responseDto;\n  }\n\n  private static void processHttpResponse(\n      HttpExecutingContext httpExecutingContext,\n      ExecTimeTelemetryData execTimeData,\n      HttpResponseContextDto responseDto) {\n    CloseableHttpResponse response = responseDto.getHttpResponse();\n    try {\n      String responseText;\n      responseText = verifyAndUnpackResponse(response, execTimeData);\n      updateSessionWithStickyHeaders(httpExecutingContext.getSfSession(), response);\n      httpExecutingContext.setShouldRetry(false);\n      responseDto.setUnpackedCloseableHttpResponse(responseText);\n    } catch (IOException ex) {\n      boolean skipRetriesBecauseOf200 = httpExecutingContext.isSkipRetriesBecauseOf200();\n      boolean retryReasonDifferentThan200 =\n          !httpExecutingContext.isShouldRetry() && skipRetriesBecauseOf200;\n      httpExecutingContext.setShouldRetry(retryReasonDifferentThan200);\n      responseDto.setSavedEx(ex);\n    }\n  }\n\n  private static void updateSessionWithStickyHeaders(\n      SFBaseSession sfSession, CloseableHttpResponse response) {\n    if (sfSession != null && response != null) {\n      Map<String, String> responseHeaders = HttpUtil.extractHeadersAsMap(response);\n      sfSession.extractAndUpdateStickyHttpHeaders(responseHeaders);\n    }\n  }\n\n  private static void updateRetryParameters(\n      URIBuilder builder, int retryCount, String lastStatusCodeForRetry, long startTime) {\n    builder.setParameter(\"retryCount\", String.valueOf(retryCount));\n    builder.setParameter(\"retryReason\", lastStatusCodeForRetry);\n    builder.setParameter(\"clientStartTime\", String.valueOf(startTime));\n  }\n\n  private static void prepareRetry(\n      HttpRequestBase httpRequest,\n      HttpExecutingContext httpExecutingContext,\n      RetryContextManager retryManager,\n      HttpResponseContextDto dto)\n      throws SnowflakeSQLException {\n    //        Potentially retryable error\n    logRequestResult(\n        dto.getHttpResponse(),\n        httpExecutingContext.getRequestId(),\n        httpExecutingContext.getRequestInfoScrubbed(),\n        dto.getSavedEx());\n\n    // get the elapsed time for the last request\n    // elapsed in millisecond for last call, used for calculating the\n    // remaining amount of time to sleep:\n    // (backoffInMilli - elapsedMilliForLastCall)\n    long elapsedMilliForLastCall =\n        System.currentTimeMillis() - httpExecutingContext.getStartTimePerRequest();\n\n    if (httpExecutingContext.socketOrConnectTimeoutReached())\n    /* socket timeout not reached */ {\n      /* connect timeout not reached */\n      // check if this is a login-request\n      if (String.valueOf(httpRequest.getURI()).contains(\"login-request\")) {\n        throw new SnowflakeSQLExceptionWithRetryContext(\n            ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT,\n            httpExecutingContext.getRetryCount(),\n            true,\n            httpExecutingContext.getElapsedMilliForTransientIssues() / 1000);\n      }\n    }\n\n    // sleep for backoff - elapsed amount of time\n    sleepForBackoffAndPrepareNext(elapsedMilliForLastCall, httpExecutingContext);\n\n    httpExecutingContext.incrementRetryCount();\n    httpExecutingContext.setLastStatusCodeForRetry(\n        dto.getHttpResponse() == null\n            ? \"0\"\n            : String.valueOf(dto.getHttpResponse().getStatusLine().getStatusCode()));\n    // If the request failed with any other retry-able error and auth timeout is reached\n    // increase the retry count and throw special exception to renew the token before retrying.\n\n    RetryContextManager.RetryHook retryManagerHook = null;\n    if (retryManager != null) {\n      retryManagerHook = retryManager.getRetryHook();\n      retryManager\n          .getRetryContext()\n          .setElapsedTimeInMillis(httpExecutingContext.getElapsedMilliForTransientIssues())\n          .setRetryTimeoutInMillis(httpExecutingContext.getRetryTimeoutInMilliseconds());\n    }\n\n    // Make sure that any authenticator specific info that needs to be\n    // updated gets updated before the next retry. Ex - OKTA OTT, JWT token\n    // Aim is to achieve this using RetryContextManager, but raising\n    // AUTHENTICATOR_REQUEST_TIMEOUT Exception is still supported as well. In both cases the\n    // retried request must be aware of the elapsed time not to exceed the timeout limit.\n    if (retryManagerHook == RetryContextManager.RetryHook.ALWAYS_BEFORE_RETRY) {\n      retryManager.executeRetryCallbacks(httpRequest);\n    }\n\n    if (httpExecutingContext.getAuthTimeout() > 0\n        && httpExecutingContext.getElapsedMilliForTransientIssues()\n            >= httpExecutingContext.getAuthTimeout()) {\n      throw new SnowflakeSQLExceptionWithRetryContext(\n          ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT,\n          httpExecutingContext.getRetryCount(),\n          false,\n          httpExecutingContext.getElapsedMilliForTransientIssues() / 1000);\n    }\n\n    int numOfRetryToTriggerTelemetry =\n        TelemetryService.getInstance().getNumOfRetryToTriggerTelemetry();\n    if (httpExecutingContext.getRetryCount() == numOfRetryToTriggerTelemetry) {\n      TelemetryService.getInstance()\n          .logHttpRequestTelemetryEvent(\n              String.format(\"HttpRequestRetry%dTimes\", numOfRetryToTriggerTelemetry),\n              httpRequest,\n              httpExecutingContext.getInjectSocketTimeout(),\n              httpExecutingContext.getCanceling(),\n              httpExecutingContext.isWithoutCookies(),\n              httpExecutingContext.isIncludeRetryParameters(),\n              httpExecutingContext.isIncludeSnowflakeHeaders(),\n              dto.getHttpResponse(),\n              dto.getSavedEx(),\n              httpExecutingContext.getBreakRetryReason(),\n              httpExecutingContext.getRetryTimeout(),\n              httpExecutingContext.getRetryCount(),\n              SqlState.IO_ERROR,\n              ErrorCode.NETWORK_ERROR.getMessageCode());\n    }\n    dto.setSavedEx(null);\n    httpExecutingContext.setSkipRetriesBecauseOf200(false);\n\n    // release connection before retry\n    httpRequest.releaseConnection();\n  }\n\n  private static void sendTelemetryEvent(\n      HttpRequestBase httpRequest,\n      HttpExecutingContext httpExecutingContext,\n      CloseableHttpResponse response,\n      Exception savedEx) {\n    String eventName;\n    if (response == null) {\n      eventName = \"NullResponseHttpError\";\n    } else {\n      if (response.getStatusLine() == null) {\n        eventName = \"NullResponseStatusLine\";\n      } else {\n        eventName = String.format(\"HttpError%d\", response.getStatusLine().getStatusCode());\n      }\n    }\n    TelemetryService.getInstance()\n        .logHttpRequestTelemetryEvent(\n            eventName,\n            httpRequest,\n            httpExecutingContext.getInjectSocketTimeout(),\n            httpExecutingContext.getCanceling(),\n            httpExecutingContext.isWithoutCookies(),\n            httpExecutingContext.isIncludeRetryParameters(),\n            httpExecutingContext.isIncludeSnowflakeHeaders(),\n            response,\n            savedEx,\n            httpExecutingContext.getBreakRetryReason(),\n            httpExecutingContext.getRetryTimeout(),\n            httpExecutingContext.getRetryCount(),\n            null,\n            0);\n  }\n\n  private static void sleepForBackoffAndPrepareNext(\n      long elapsedMilliForLastCall, HttpExecutingContext context) {\n    if (context.getMinBackoffInMillis() > elapsedMilliForLastCall) {\n      try {\n        logger.debug(\n            \"{}Retry request {}: sleeping for {} ms\",\n            context.getRequestId(),\n            context.getRequestInfoScrubbed(),\n            context.getBackoffInMillis());\n        Thread.sleep(context.getBackoffInMillis());\n      } catch (InterruptedException ex1) {\n        logger.debug(\n            \"{}Backoff sleep before retrying login got interrupted\", context.getRequestId());\n      }\n      context.increaseElapsedMilliForTransientIssues(context.getBackoffInMillis());\n      context.setBackoffInMillis(\n          getNewBackoffInMilli(\n              context.getBackoffInMillis(),\n              context.isLoginRequest(),\n              context.getBackoff(),\n              context.getRetryCount(),\n              context.getRetryTimeoutInMilliseconds(),\n              context.getElapsedMilliForTransientIssues()));\n    }\n  }\n\n  private static void logRequestResult(\n      CloseableHttpResponse response,\n      String requestIdStr,\n      String requestInfoScrubbed,\n      Exception savedEx) {\n    if (response != null) {\n      logger.debug(\n          \"{}HTTP response not ok: status code: {}, request: {}\",\n          requestIdStr,\n          response.getStatusLine().getStatusCode(),\n          requestInfoScrubbed);\n    } else if (savedEx != null) {\n      logger.debug(\n          \"{}Null response for cause: {}, request: {}\",\n          requestIdStr,\n          getRootCause(savedEx).getMessage(),\n          requestInfoScrubbed);\n    } else {\n      logger.debug(\"{}Null response for request: {}\", requestIdStr, requestInfoScrubbed);\n    }\n  }\n\n  private static void checkForDPoPNonceError(CloseableHttpResponse response) throws IOException {\n    String errorResponse = EntityUtils.toString(response.getEntity());\n    if (!isNullOrEmpty(errorResponse)) {\n      ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();\n      JsonNode rootNode = objectMapper.readTree(errorResponse);\n      JsonNode errorNode = rootNode.get(ERROR_FIELD_NAME);\n      if (errorNode != null\n          && errorNode.isValueNode()\n          && errorNode.isTextual()\n          && errorNode.textValue().equals(ERROR_USE_DPOP_NONCE)) {\n        throw new SnowflakeUseDPoPNonceException(\n            response.getFirstHeader(DPOP_NONCE_HEADER_NAME).getValue());\n      }\n    }\n  }\n\n  static Exception handlingNotRetryableException(\n      Exception ex, HttpExecutingContext httpExecutingContext) throws SnowflakeSQLLoggedException {\n    Exception savedEx = null;\n    if (ex instanceof IllegalStateException) {\n      throw new SnowflakeSQLLoggedException(\n          null, ErrorCode.INVALID_STATE, ex, /* session= */ ex.getMessage());\n    } else if (isExceptionInGroup(ex, sslExceptions) && !isProtocolVersionError(ex)) {\n      String formattedMsg =\n          ex.getMessage()\n              + \"\\n\"\n              + \"Verify that the hostnames and portnumbers in SYSTEM$ALLOWLIST are added to your firewall's allowed list.\\n\"\n              + \"To troubleshoot your connection further, you can refer to this article:\\n\"\n              + \"https://docs.snowflake.com/en/user-guide/client-connectivity-troubleshooting/overview\";\n\n      Throwable rootCause = getRootCause(ex);\n      if (rootCause instanceof SFOCSPException) {\n        sendIBOCSPErrorEvent(httpExecutingContext, (SFOCSPException) rootCause);\n      }\n\n      throw new SnowflakeSQLLoggedException(null, ErrorCode.NETWORK_ERROR, ex, formattedMsg);\n    } else if (ex instanceof Exception) {\n      savedEx = ex;\n      // if the request took more than socket timeout log a warning\n      long currentMillis = System.currentTimeMillis();\n      if ((currentMillis - httpExecutingContext.getStartTimePerRequest())\n          > HttpUtil.getSocketTimeout().toMillis()) {\n        logger.warn(\n            \"{}HTTP request took longer than socket timeout {} ms: {} ms\",\n            httpExecutingContext.getRequestId(),\n            HttpUtil.getSocketTimeout().toMillis(),\n            (currentMillis - httpExecutingContext.getStartTimePerRequest()));\n      }\n      StringWriter sw = new StringWriter();\n      savedEx.printStackTrace(new PrintWriter(sw));\n      logger.debug(\n          \"{}Exception encountered for: {}, {}, {}\",\n          httpExecutingContext.getRequestId(),\n          httpExecutingContext.getRequestInfoScrubbed(),\n          ex.getLocalizedMessage(),\n          (ArgSupplier) sw::toString);\n    }\n    return ex;\n  }\n\n  static boolean isExceptionInGroup(Exception e, Set<Class<?>> group) {\n    for (Class<?> clazz : group) {\n      if (clazz.isInstance(e)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  static boolean isProtocolVersionError(Exception e) {\n    return e.getMessage() != null\n        && e.getMessage().contains(\"Received fatal alert: protocol_version\");\n  }\n\n  private static boolean handleCertificateRevoked(\n      Exception savedEx, HttpExecutingContext httpExecutingContext, boolean skipRetrying) {\n    if (!skipRetrying && RestRequest.isCertificateRevoked(savedEx)) {\n      String msg = \"Unknown reason\";\n      Throwable rootCause = RestRequest.getRootCause(savedEx);\n      msg =\n          rootCause.getMessage() != null && !rootCause.getMessage().isEmpty()\n              ? rootCause.getMessage()\n              : msg;\n      logger.debug(\n          \"{}Error response not retryable, \" + msg + \", request: {}\",\n          httpExecutingContext.getRequestId(),\n          httpExecutingContext.getRequestInfoScrubbed());\n      EventUtil.triggerBasicEvent(\n          Event.EventType.NETWORK_ERROR,\n          msg + \", Request: \" + httpExecutingContext.getRequestInfoScrubbed(),\n          false);\n\n      httpExecutingContext.setBreakRetryReason(\"certificate revoked error\");\n      httpExecutingContext.setBreakRetryEventName(\"HttpRequestRetryVertificateRevoked\");\n      httpExecutingContext.setShouldRetry(false);\n      return true;\n    }\n    return skipRetrying;\n  }\n\n  private static boolean handleNonRetryableHttpCode(\n      HttpResponseContextDto dto, HttpExecutingContext httpExecutingContext, boolean skipRetrying) {\n    CloseableHttpResponse response = dto.getHttpResponse();\n    if (!skipRetrying && isNonRetryableHTTPCode(response, httpExecutingContext.isRetryHTTP403())) {\n      String msg = \"Unknown reason\";\n      if (response != null) {\n        logger.debug(\n            \"{}HTTP response code for request {}: {}\",\n            httpExecutingContext.getRequestId(),\n            httpExecutingContext.getRequestInfoScrubbed(),\n            response.getStatusLine().getStatusCode());\n        msg =\n            \"StatusCode: \"\n                + response.getStatusLine().getStatusCode()\n                + \", Reason: \"\n                + response.getStatusLine().getReasonPhrase();\n      } else if (dto.getSavedEx() != null) // may be null.\n      {\n        Throwable rootCause = RestRequest.getRootCause(dto.getSavedEx());\n        msg = rootCause.getMessage();\n      }\n\n      if (response == null || response.getStatusLine().getStatusCode() != 200) {\n        logger.debug(\n            \"{}Error response not retryable, \" + msg + \", request: {}\",\n            httpExecutingContext.getRequestId(),\n            httpExecutingContext.getRequestInfoScrubbed());\n        EventUtil.triggerBasicEvent(\n            Event.EventType.NETWORK_ERROR,\n            msg + \", Request: \" + httpExecutingContext.getRequestInfoScrubbed(),\n            false);\n      }\n      httpExecutingContext.setBreakRetryReason(\"status code does not need retry\");\n      httpExecutingContext.setShouldRetry(false);\n      httpExecutingContext.setSkipRetriesBecauseOf200(\n          response.getStatusLine().getStatusCode() == 200);\n\n      try {\n        if (response == null || response.getStatusLine().getStatusCode() != 200) {\n          logger.error(\n              \"Error executing request: {}\", httpExecutingContext.getRequestInfoScrubbed());\n\n          if (response != null\n              && response.getStatusLine().getStatusCode() == 400\n              && response.getEntity() != null) {\n            checkForDPoPNonceError(response);\n          }\n\n          SnowflakeUtil.logResponseDetails(response, logger);\n\n          if (response != null) {\n            EntityUtils.consume(response.getEntity());\n          }\n\n          //           We throw here exception if timeout was reached for login\n          dto.setSavedEx(\n              new SnowflakeSQLException(\n                  SqlState.IO_ERROR,\n                  ErrorCode.NETWORK_ERROR.getMessageCode(),\n                  \"HTTP status=\"\n                      + ((response != null)\n                          ? response.getStatusLine().getStatusCode()\n                          : \"null response\")));\n        }\n      } catch (IOException e) {\n        dto.setSavedEx(\n            new SnowflakeSQLException(\n                SqlState.IO_ERROR,\n                ErrorCode.NETWORK_ERROR.getMessageCode(),\n                \"Exception details: \" + e.getMessage()));\n      }\n      return true;\n    }\n    return skipRetrying;\n  }\n\n  private static void logTelemetryEvent(\n      HttpRequestBase request,\n      CloseableHttpResponse response,\n      Exception savedEx,\n      HttpExecutingContext httpExecutingContext) {\n    TelemetryService.getInstance()\n        .logHttpRequestTelemetryEvent(\n            httpExecutingContext.getBreakRetryEventName(),\n            request,\n            httpExecutingContext.getInjectSocketTimeout(),\n            httpExecutingContext.getCanceling(),\n            httpExecutingContext.isWithoutCookies(),\n            httpExecutingContext.isIncludeRetryParameters(),\n            httpExecutingContext.isIncludeSnowflakeHeaders(),\n            response,\n            savedEx,\n            httpExecutingContext.getBreakRetryReason(),\n            httpExecutingContext.getRetryTimeout(),\n            httpExecutingContext.getRetryCount(),\n            SqlState.IO_ERROR,\n            ErrorCode.NETWORK_ERROR.getMessageCode());\n  }\n\n  private static void sendIBHttpErrorEvent(\n      HttpRequestBase request,\n      CloseableHttpResponse response,\n      HttpExecutingContext httpExecutingContext) {\n    SFBaseSession session = httpExecutingContext.getSfSession();\n\n    if (session == null) {\n      logger.debug(\"Not sending telemetry event as the request is sessionless (session is null)\");\n      return;\n    }\n\n    if (response == null) {\n      logger.debug(\n          \"Not sending telemetry event as the response is null (request failed before receiving response)\");\n      return;\n    }\n\n    StatusLine statusLine = response.getStatusLine();\n    logger.debug(\n        \"Preparing telemetry event for HTTP error: {} {}\",\n        statusLine.getStatusCode(),\n        statusLine.getReasonPhrase());\n    int calculatedErrorNumber =\n        ErrorCode.HTTP_GENERAL_ERROR.getMessageCode() + statusLine.getStatusCode();\n    String errorMessage =\n        \"HTTP \"\n            + statusLine.getStatusCode()\n            + \" \"\n            + statusLine.getReasonPhrase()\n            + \": \"\n            + request.getMethod()\n            + \" \"\n            + request.getURI().getHost()\n            + request.getURI().getPath();\n    ObjectNode ibValue =\n        TelemetryUtil.createIBValue(\n            null,\n            SqlState.INTERNAL_ERROR,\n            calculatedErrorNumber,\n            TelemetryField.HTTP_EXCEPTION,\n            errorMessage,\n            null);\n    TelemetryData td = TelemetryUtil.buildJobData(ibValue);\n    session.getTelemetryClient(internalCallMarker()).addLogToBatch(td);\n  }\n\n  private static void sendIBOCSPErrorEvent(\n      HttpExecutingContext httpExecutingContext, SFOCSPException ex) {\n    SFBaseSession session = httpExecutingContext.getSfSession();\n\n    if (session == null) {\n      return;\n    }\n\n    String errorMessage = ex.toString();\n    ObjectNode ibValue =\n        TelemetryUtil.createIBValue(\n            null,\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.OCSP_GENERAL_ERROR.getMessageCode(),\n            TelemetryField.OCSP_EXCEPTION,\n            errorMessage,\n            ex.toString());\n    TelemetryData td = TelemetryUtil.buildJobData(ibValue);\n    session.getTelemetryClient(internalCallMarker()).addLogToBatch(td);\n  }\n\n  private static boolean handleMaxRetriesExceeded(\n      HttpExecutingContext httpExecutingContext, boolean skipRetrying) {\n    if (!skipRetrying && httpExecutingContext.maxRetriesExceeded()) {\n      logger.error(\n          \"{}Stop retrying as max retries have been reached for request: {}! Max retry count: {}\",\n          httpExecutingContext.getRequestId(),\n          httpExecutingContext.getRequestInfoScrubbed(),\n          httpExecutingContext.getMaxRetries());\n\n      httpExecutingContext.setBreakRetryReason(\"max retries reached\");\n      httpExecutingContext.setBreakRetryEventName(\"HttpRequestRetryLimitExceeded\");\n      httpExecutingContext.setShouldRetry(false);\n      return true;\n    }\n    return skipRetrying;\n  }\n\n  private static boolean handleElapsedTimeoutExceeded(\n      HttpExecutingContext httpExecutingContext, boolean skipRetrying) {\n    if (!skipRetrying && httpExecutingContext.getRetryTimeoutInMilliseconds() > 0) {\n      // Check for retry time-out.\n      // increment total elapsed due to transient issues\n      long elapsedMilliForLastCall =\n          System.currentTimeMillis() - httpExecutingContext.getStartTimePerRequest();\n      httpExecutingContext.increaseElapsedMilliForTransientIssues(elapsedMilliForLastCall);\n\n      // check if the total elapsed time for transient issues has exceeded\n      // the retry timeout and we retry at least the min, if so, we will not\n      // retry\n      if (httpExecutingContext.elapsedTimeExceeded() && httpExecutingContext.moreThanMinRetries()) {\n        logger.error(\n            \"{}Stop retrying since elapsed time due to network \"\n                + \"issues has reached timeout. \"\n                + \"Elapsed: {} ms, timeout: {} ms\",\n            httpExecutingContext.getRequestId(),\n            httpExecutingContext.getElapsedMilliForTransientIssues(),\n            httpExecutingContext.getRetryTimeoutInMilliseconds());\n\n        httpExecutingContext.setBreakRetryReason(\"retry timeout\");\n        httpExecutingContext.setBreakRetryEventName(\"HttpRequestRetryTimeout\");\n        httpExecutingContext.setShouldRetry(false);\n        return true;\n      }\n    }\n    return skipRetrying;\n  }\n\n  private static boolean handleCancelingSignal(\n      HttpExecutingContext httpExecutingContext, boolean skipRetrying) {\n    if (!skipRetrying\n        && httpExecutingContext.getCanceling() != null\n        && httpExecutingContext.getCanceling().get()) {\n      logger.debug(\n          \"{}Stop retrying since canceling is requested\", httpExecutingContext.getRequestId());\n      httpExecutingContext.setBreakRetryReason(\"canceling is requested\");\n      httpExecutingContext.setShouldRetry(false);\n      return true;\n    }\n    return skipRetrying;\n  }\n\n  private static boolean handleNoRetryFlag(\n      HttpExecutingContext httpExecutingContext, boolean skipRetrying) {\n    if (!skipRetrying && httpExecutingContext.isNoRetry()) {\n      logger.debug(\n          \"{}HTTP retry disabled for this request. noRetry: {}\",\n          httpExecutingContext.getRequestId(),\n          httpExecutingContext.isNoRetry());\n      httpExecutingContext.setBreakRetryReason(\"retry is disabled\");\n      httpExecutingContext.resetRetryCount();\n      httpExecutingContext.setShouldRetry(false);\n      return true;\n    }\n    return skipRetrying;\n  }\n\n  private static boolean shouldSkipRetryWithLoggedReason(\n      HttpRequestBase request,\n      HttpResponseContextDto responseDto,\n      HttpExecutingContext httpExecutingContext) {\n    CloseableHttpResponse response = responseDto.getHttpResponse();\n    Exception savedEx = responseDto.getSavedEx();\n    List<Function<Boolean, Boolean>> conditions =\n        Arrays.asList(\n            skipRetrying -> handleNoRetryFlag(httpExecutingContext, skipRetrying),\n            skipRetrying -> handleCancelingSignal(httpExecutingContext, skipRetrying),\n            skipRetrying -> handleElapsedTimeoutExceeded(httpExecutingContext, skipRetrying),\n            skipRetrying -> handleMaxRetriesExceeded(httpExecutingContext, skipRetrying),\n            skipRetrying -> handleCertificateRevoked(savedEx, httpExecutingContext, skipRetrying),\n            skipRetrying ->\n                handleNonRetryableHttpCode(responseDto, httpExecutingContext, skipRetrying));\n\n    // Process each condition using Stream\n    boolean skipRetrying =\n        conditions.stream().reduce(Function::andThen).orElse(Function.identity()).apply(false);\n\n    // Log telemetry\n    logTelemetryEvent(request, response, savedEx, httpExecutingContext);\n\n    return skipRetrying;\n  }\n\n  private static String verifyAndUnpackResponse(\n      CloseableHttpResponse response, ExecTimeTelemetryData execTimeData) throws IOException {\n    try (StringWriter writer = new StringWriter()) {\n      execTimeData.setResponseIOStreamStart();\n      try (InputStream ins = response.getEntity().getContent()) {\n        IOUtils.copy(ins, writer, \"UTF-8\");\n      }\n\n      execTimeData.setResponseIOStreamEnd();\n      return writer.toString();\n    } finally {\n      IOUtils.closeQuietly(response);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/ResultJsonParserV2.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.nio.Buffer;\nimport java.nio.ByteBuffer;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.common.core.SqlState;\n\n/** This is the Java version of the ODBC's ResultJsonParserV2 class */\npublic class ResultJsonParserV2 {\n\n  private enum State {\n    UNINITIALIZED, // no parsing in progress\n    NEXT_ROW, // Waiting for [ to start the next row\n    ROW_FINISHED, // Waiting for , to separate the next row\n    WAIT_FOR_VALUE, // Waiting for the next value to start\n    IN_VALUE, // Copy the value and wait for its end\n    IN_STRING, // Copy the string and wait for its end\n    ESCAPE, // Expect escaped character next\n    WAIT_FOR_NEXT // Wait for , to separate next column\n  }\n\n  private static final byte[] BNULL = {0x6e, 0x75, 0x6c, 0x6c};\n  private State state = State.UNINITIALIZED;\n  private int currentColumn;\n  private int outputCurValuePosition;\n  private int outputPosition;\n\n  // Temporarily store unicode escape sequence when buffer is empty\n  // contains \\\\u as well\n  private ByteBuffer partialEscapedUnicode;\n\n  private int outputDataLength;\n  //  private int currentRow;\n  private JsonResultChunk resultChunk;\n\n  public void startParsing(JsonResultChunk resultChunk, SFBaseSession session)\n      throws SnowflakeSQLException {\n    this.resultChunk = resultChunk;\n    if (state != State.UNINITIALIZED) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Json parser is already used!\");\n    }\n    state = State.NEXT_ROW;\n    outputPosition = 0;\n    outputCurValuePosition = 0;\n    currentColumn = 0;\n\n    // outputDataLength can be smaller as no ',' and '[' are stored\n    outputDataLength = resultChunk.computeCharactersNeeded();\n  }\n\n  /**\n   * Check if the chunk has been parsed correctly. After calling this it is safe to acquire the\n   * output data\n   *\n   * @param in byte buffer\n   * @param session SFBaseSession\n   * @throws SnowflakeSQLException if parsing fails\n   */\n  public void endParsing(ByteBuffer in, SFBaseSession session) throws SnowflakeSQLException {\n    continueParsingInternal(in, true, session);\n\n    if (state != State.ROW_FINISHED) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"SFResultJsonParser2Failed: Chunk is truncated!\");\n    }\n    currentColumn = 0;\n    state = State.UNINITIALIZED;\n  }\n\n  /**\n   * Continue parsing with the given data\n   *\n   * @param in readOnly byteBuffer backed by an array (the data to be reed is from position to\n   *     limit)\n   * @param session SFBaseSession\n   * @return int remaining number of elements in byteBuffer\n   * @throws SnowflakeSQLException if an error is encountered during parsing\n   */\n  public int continueParsing(ByteBuffer in, SFBaseSession session) throws SnowflakeSQLException {\n    if (state == State.UNINITIALIZED) {\n      throw new SnowflakeSQLLoggedException(\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Json parser hasn't been initialized!\");\n    }\n    continueParsingInternal(in, false, session);\n    return in.remaining();\n  }\n\n  /**\n   * @param in readOnly byteBuffer backed by an array (the data is from position to limit)\n   * @param lastData If true, this signifies this is the last data in parsing\n   * @param session SFBaseSession\n   * @throws SnowflakeSQLException Will be thrown if parsing the chunk data fails\n   */\n  private void continueParsingInternal(ByteBuffer in, boolean lastData, SFBaseSession session)\n      throws SnowflakeSQLException {\n    /*\n     * This function parses a Snowflake result chunk json, copies the data\n     * to one block of memory and creates a vector of vectors with the offsets\n     * and lengths. There's one vector for each column that contains all its\n     * rows.\n     *\n     * Result json looks like this [ \"text\", null, \"text2\" ], [...\n     * The parser keeps state at which element it currently is.\n     *\n     */\n\n    while (in.hasRemaining()) {\n      if (outputPosition >= outputDataLength) {\n        throw new SnowflakeSQLLoggedException(\n            session,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            SqlState.INTERNAL_ERROR,\n            \"column chunk longer than expected\");\n      }\n      switch (state) {\n        case UNINITIALIZED:\n          throw new SnowflakeSQLLoggedException(\n              session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"parser is in inconsistent state\");\n        case NEXT_ROW:\n          switch (in.get()) {\n            case 0x20: // ' '\n            case 0x9: // '\\t'\n            case 0xa: // '\\n'\n            case 0xd: // '\\r\\\n              // skip the whitespaces\n              break;\n            case 0x5b: // '['\n              // beginning of the next row\n              state = State.WAIT_FOR_VALUE;\n              break;\n            default:\n              {\n                throw new SnowflakeSQLLoggedException(\n                    session,\n                    ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                    SqlState.INTERNAL_ERROR,\n                    String.format(\n                        \"encountered unexpected character 0x%x between rows\",\n                        in.get(((Buffer) in).position() - 1)));\n              }\n          }\n          break;\n        case ROW_FINISHED:\n          switch (in.get()) {\n            case 0x2c: // ','\n              state = State.NEXT_ROW;\n              break;\n            case 0x20: // ' '\n            case 0x9: // '\\t'\n            case 0xa: // '\\n'\n            case 0xd: // '\\r\\\n              // skip the whitespaces\n              break;\n            default:\n              {\n                throw new SnowflakeSQLLoggedException(\n                    session,\n                    ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                    SqlState.INTERNAL_ERROR,\n                    String.format(\n                        \"encountered unexpected character 0x%x after array\",\n                        in.get(((Buffer) in).position() - 1)));\n              }\n          }\n          break;\n        case WAIT_FOR_VALUE:\n          switch (in.get()) {\n            case 0x20: // ' '\n            case 0x9: // '\\t'\n            case 0xa: // '\\n'\n            case 0xd: // '\\r\\\n              // skip the whitespaces\n              break;\n            case 0x2c: // ','\n              // null value\n              addNullValue();\n              state = State.WAIT_FOR_NEXT;\n              // reread the comma in the WAIT_FOR_NEXT state\n              ((Buffer) in).position(((Buffer) in).position() - 1);\n              continue;\n            case 0x5d: // ']'\n              // null value (only saw spaces)\n              addNullValue();\n              currentColumn = 0;\n              state = State.ROW_FINISHED;\n              break;\n            case 0x22: // '\"'\n              outputCurValuePosition = outputPosition;\n              // String starts, we do not copy the parenthesis\n              resultChunk.addOffset(outputPosition);\n              state = State.IN_STRING;\n              break;\n            default:\n              outputCurValuePosition = outputPosition;\n              // write\n              resultChunk.addOffset(outputPosition);\n              addByteToOutput(in.get(((Buffer) in).position() - 1));\n\n              state = State.IN_VALUE;\n              break;\n          }\n          break;\n        case IN_VALUE:\n          switch (in.get()) {\n            case 0x20: // ' '\n            case 0x9: // '\\t'\n            case 0xa: // '\\n'\n            case 0xd: // '\\r\\\n            case 0x2c: // ','\n            case 0x5d: // ']'\n              {\n                // value ended\n                int length = outputPosition - outputCurValuePosition;\n\n                // Check if value is null\n                if (length == 4 && isNull()) {\n                  resultChunk.setIsNull();\n                  outputPosition = outputCurValuePosition;\n                } else {\n                  resultChunk.setLastLength(length);\n                }\n                state = State.WAIT_FOR_NEXT;\n                ((Buffer) in).position(((Buffer) in).position() - 1);\n                continue; // reread this char in WAIT_FOR_NEXT\n              }\n            default:\n              addByteToOutput(in.get(((Buffer) in).position() - 1));\n              break;\n          }\n          break;\n        case IN_STRING:\n          switch (in.get()) {\n            case 0x22: // '\"'\n              resultChunk.setLastLength(outputPosition - outputCurValuePosition);\n              state = State.WAIT_FOR_NEXT;\n              break;\n            case 0x5c: // '\\\\'\n              state = State.ESCAPE;\n              break;\n            default:\n              // Check how many characters don't have escape characters\n              // copy those with one memcpy\n              int inputPositionStart = ((Buffer) in).position() - 1;\n              while (in.hasRemaining()) {\n                byte cur = in.get();\n\n                if (cur == 0x22 /* '\"' */ || cur == 0x5c /* '\\\\' */) {\n                  // end of string chunk\n                  ((Buffer) in).position(((Buffer) in).position() - 1);\n                  break;\n                }\n              }\n\n              addByteArrayToOutput(\n                  in.array(),\n                  in.arrayOffset() + inputPositionStart,\n                  ((Buffer) in).position() - inputPositionStart);\n\n              if (in.hasRemaining()\n                  && (in.get(((Buffer) in).position()) == 0x22 /* '\"' */\n                      || in.get(((Buffer) in).position()) == 0x5c /* '\\\\' */)) {\n                // Those need special parsing\n                continue;\n              }\n          }\n          break;\n        case ESCAPE:\n          switch (in.get()) {\n            case 0x22 /* '\"' */:\n              addByteToOutput((byte) 0x22);\n              state = State.IN_STRING;\n              break;\n            case 0x5c /* '\\\\' */:\n              addByteToOutput((byte) 0x5c /* '\\\\' */);\n              state = State.IN_STRING;\n              break;\n            case 0x2f: // '/'\n              addByteToOutput((byte) 0x2f);\n              state = State.IN_STRING;\n              break;\n            case 0x62: // 'b'\n              addByteToOutput((byte) 0x0b /*'\\b'*/);\n              state = State.IN_STRING;\n              break;\n            case 0x66: // 'f'\n              addByteToOutput((byte) 0x0c /*'\\f'*/);\n              state = State.IN_STRING;\n              break;\n            case 0x6e: // 'n'\n              addByteToOutput((byte) 0xa /* '\\n' */);\n              state = State.IN_STRING;\n              break;\n            case 0x72: // 'r'\n              addByteToOutput((byte) 0xd /*'\\r'*/);\n              state = State.IN_STRING;\n              break;\n            case 0x74: // 't'\n              addByteToOutput((byte) 0x9 /*'\\t'*/);\n              state = State.IN_STRING;\n              break;\n            case 0x75: // 'u'\n              // UTF-16 hex encoded, can be up to 12 bytes\n              // when in doesn't have that many left, cache them and parse at the\n              // next invocation of continueParsing()\n\n              // have to have at least 4+2+4=10 chars left to read\n              // already saw \"\\\\u\", now missing \"AAAA\\\\uAAAA\"\n              if (in.remaining() >= 9 || (lastData && in.remaining() >= 3)) {\n                if (!parseCodepoint(in)) {\n                  throw new SnowflakeSQLLoggedException(\n                      session,\n                      ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                      SqlState.INTERNAL_ERROR,\n                      \"SFResultJsonParser2Failed: invalid escaped unicode character\");\n                }\n                state = State.IN_STRING;\n              } else {\n                // if the number of bytes left un-parsed in the buffer is less than 9 (unless it is\n                // the last remaining data in the buffer),\n                // there is not enough bytes to parse the codepoint. Move the position back 1,\n                // so we can re-enter parsing at this position with the ESCAPE state.\n                ((Buffer) in).position(((Buffer) in).position() - 1);\n                state = State.ESCAPE;\n                return;\n              }\n              break;\n            default:\n              {\n                throw new SnowflakeSQLLoggedException(\n                    session,\n                    ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                    SqlState.INTERNAL_ERROR,\n                    \"SFResultJsonParser2Failed: encountered unexpected escape character \" + \"0x%x\",\n                    in.get(((Buffer) in).position() - 1));\n              }\n          }\n          break;\n        case WAIT_FOR_NEXT:\n          switch (in.get()) {\n            case 0x2c: // ',':\n              ++currentColumn;\n              resultChunk.nextIndex();\n              if (currentColumn >= resultChunk.getColCount()) {\n                throw new SnowflakeSQLLoggedException(\n                    session,\n                    ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                    SqlState.INTERNAL_ERROR,\n                    \"SFResultJsonParser2Failed: Too many columns!\");\n              }\n              state = State.WAIT_FOR_VALUE;\n              break;\n            case 0x5d: // ']'\n              currentColumn = 0;\n              resultChunk.nextIndex();\n              state = State.ROW_FINISHED;\n              break;\n            case 0x20: // ' '\n            case 0x9: // '\\t'\n            case 0xa: // '\\n'\n            case 0xd: // '\\r\\\n              // skip whitespace\n              break;\n            default:\n              {\n                throw new SnowflakeSQLLoggedException(\n                    session,\n                    ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                    SqlState.INTERNAL_ERROR,\n                    String.format(\n                        \"encountered unexpected character 0x%x between columns\",\n                        in.get(((Buffer) in).position() - 1)));\n              }\n          }\n          break;\n      }\n    }\n  }\n\n  private boolean isNull() throws SnowflakeSQLException {\n    int pos = outputPosition;\n    if (resultChunk.get(--pos) == BNULL[3]\n        && resultChunk.get(--pos) == BNULL[2]\n        && resultChunk.get(--pos) == BNULL[1]\n        && resultChunk.get(--pos) == BNULL[0]) {\n      return true;\n    }\n    return false;\n  }\n\n  private int parseQuadhex(ByteBuffer s) {\n    // function from picojson\n    int uni_ch = 0, hex;\n    for (int i = 0; i < 4; i++) {\n      if ((hex = s.get()) == -1) {\n        return -1;\n      }\n      if (0x30 /*0*/ <= hex && hex <= 0x39 /*'9'*/) {\n        hex -= 0x30 /*0*/;\n      } else if (0x41 /*'A'*/ <= hex && hex <= 0x46 /*'F'*/) {\n        hex -= 0x41 /*'A'*/ - 0xa;\n      } else if (0x61 /*'a'*/ <= hex && hex <= 0x66 /*'f'*/) {\n        hex -= 0x61 /*'a'*/ - 0xa;\n      } else {\n        return -1;\n      }\n      uni_ch = uni_ch * 16 + hex;\n    }\n    return uni_ch;\n  }\n\n  private void addNullValue() throws SnowflakeSQLException {\n    resultChunk.addOffset(outputPosition);\n  }\n\n  private void addByteToOutput(byte c) throws SnowflakeSQLException {\n    resultChunk.addByte(c, outputPosition);\n    outputPosition++;\n  }\n\n  private void addByteArrayToOutput(byte[] src, int offset, int length)\n      throws SnowflakeSQLException {\n    resultChunk.addBytes(src, offset, outputPosition, length);\n    outputPosition += length;\n  }\n\n  private boolean parseCodepoint(ByteBuffer s) throws SnowflakeSQLException {\n    int uni_ch;\n    if ((uni_ch = parseQuadhex(s)) == -1) {\n      return false;\n    }\n    if (0xd800 <= uni_ch && uni_ch <= 0xdfff) {\n      if (0xdc00 <= uni_ch) {\n        // a second 16-bit of a surrogate pair appeared\n        return false;\n      }\n      // first 16-bit of surrogate pair, get the next one\n      if (2 >= s.remaining()) {\n        // not long enough for \\\\u\n        return false;\n      }\n      if (s.get() != 0x5c /* '\\\\' */ || s.get() != 0x75 /* 'u' */) {\n        return false;\n      }\n      if (4 > s.remaining()) {\n        // not long enough for the next four hex chars\n        return false;\n      }\n      int second = parseQuadhex(s);\n      if (!(0xdc00 <= second && second <= 0xdfff)) {\n        return false;\n      }\n      uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff);\n      uni_ch += 0x10000;\n    }\n    if (uni_ch < 0x80) {\n      addByteToOutput((byte) uni_ch);\n    } else {\n      if (uni_ch < 0x800) {\n        addByteToOutput((byte) (0xc0 | (uni_ch >> 6)));\n      } else {\n        if (uni_ch < 0x10000) {\n          addByteToOutput((byte) (0xe0 | (uni_ch >> 12)));\n        } else {\n          addByteToOutput((byte) (0xf0 | (uni_ch >> 18)));\n          addByteToOutput((byte) (0x80 | ((uni_ch >> 12) & 0x3f)));\n        }\n        addByteToOutput((byte) (0x80 | ((uni_ch >> 6) & 0x3f)));\n      }\n      addByteToOutput((byte) (0x80 | (uni_ch & 0x3f)));\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/ResultStreamProvider.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.InputStream;\n\n// Defines how the underlying data stream is to be fetched; i.e.\n// allows large resultset data to come from a different source\npublic interface ResultStreamProvider {\n  InputStream getInputStream(ChunkDownloadContext context) throws Exception;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/RetryContext.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\n/** RetryContext stores information about an ongoing request's retrying process. */\npublic class RetryContext {\n  static final int SECONDS_TO_MILLIS_FACTOR = 1000;\n  private long elapsedTimeInMillis;\n  private long retryTimeoutInMillis;\n  private long retryCount;\n\n  public RetryContext() {}\n\n  public RetryContext setElapsedTimeInMillis(long elapsedTimeInMillis) {\n    this.elapsedTimeInMillis = elapsedTimeInMillis;\n    return this;\n  }\n\n  public RetryContext setRetryTimeoutInMillis(long retryTimeoutInMillis) {\n    this.retryTimeoutInMillis = retryTimeoutInMillis;\n    return this;\n  }\n\n  public RetryContext setRetryCount(long retryCount) {\n    this.retryCount = retryCount;\n    return this;\n  }\n\n  private long getRemainingRetryTimeoutInMillis() {\n    return retryTimeoutInMillis - elapsedTimeInMillis;\n  }\n\n  public long getRemainingRetryTimeoutInSeconds() {\n    return (getRemainingRetryTimeoutInMillis()) / SECONDS_TO_MILLIS_FACTOR;\n  }\n\n  public long getRetryCount() {\n    return retryCount;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/RetryContextManager.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.util.ThrowingBiFunction;\nimport org.apache.http.client.methods.HttpRequestBase;\n\n/**\n * RetryContextManager lets you register logic (as callbacks) that will be re-executed during a\n * retry of a request.\n */\npublic class RetryContextManager {\n\n  // List of retry callbacks that will be executed in the order they were registered.\n  private final List<\n          ThrowingBiFunction<HttpRequestBase, RetryContext, RetryContext, SnowflakeSQLException>>\n      retryCallbacks = new ArrayList<>();\n\n  // A RetryHook flag that can be used by client code to decide when (or if) callbacks should be\n  // executed.\n  private final RetryHook retryHook;\n  private RetryContext retryContext;\n\n  /** Enumeration for different retry hook strategies. */\n  public enum RetryHook {\n    /** Always execute the registered retry callbacks on every retry. */\n    ALWAYS_BEFORE_RETRY,\n  }\n\n  /** Default constructor using ALWAYS_BEFORE_RETRY as the default retry hook. */\n  public RetryContextManager() {\n    this(RetryHook.ALWAYS_BEFORE_RETRY);\n  }\n\n  /**\n   * Constructor that accepts a specific RetryHook.\n   *\n   * @param retryHook the retry hook strategy.\n   */\n  public RetryContextManager(RetryHook retryHook) {\n    this.retryHook = retryHook;\n    this.retryContext = new RetryContext();\n  }\n\n  /**\n   * Registers a retry callback that will be executed on each retry.\n   *\n   * @param callback A RetryCallback encapsulating the logic to be replayed on retry.\n   * @return the current instance for fluent chaining.\n   */\n  public RetryContextManager registerRetryCallback(\n      ThrowingBiFunction<HttpRequestBase, RetryContext, RetryContext, SnowflakeSQLException>\n          callback) {\n    retryCallbacks.add(callback);\n    return this;\n  }\n\n  /**\n   * Executes all registered retry callbacks in the order they were added, before reattempting the\n   * operation.\n   *\n   * @param requestToRetry the HTTP request to retry.\n   * @throws SnowflakeSQLException if an error occurs during callback execution.\n   */\n  public void executeRetryCallbacks(HttpRequestBase requestToRetry) throws SnowflakeSQLException {\n    for (ThrowingBiFunction<HttpRequestBase, RetryContext, RetryContext, SnowflakeSQLException>\n        callback : retryCallbacks) {\n      retryContext = callback.apply(requestToRetry, retryContext);\n    }\n  }\n\n  /**\n   * Returns the configured RetryHook.\n   *\n   * @return the retry hook.\n   */\n  public RetryHook getRetryHook() {\n    return retryHook;\n  }\n\n  public RetryContext getRetryContext() {\n    return retryContext;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SFAsyncResultSet.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.api.resultset.QueryStatus.Status.NO_DATA;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.List;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.api.resultset.SnowflakeAsyncResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\n/** SFAsyncResultSet implementation. Note: For Snowflake internal use */\npublic class SFAsyncResultSet extends SnowflakeBaseResultSet\n    implements SnowflakeAsyncResultSet, ResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFAsyncResultSet.class);\n\n  private ResultSet resultSetForNext = new SnowflakeResultSetV1.EmptyResultSet();\n  private boolean resultSetForNextInitialized = false;\n  private String queryID;\n  private SFBaseSession session;\n  private Statement extraStatement;\n  private QueryStatus lastQueriedStatus = QueryStatus.empty();\n\n  /**\n   * Constructor takes an inputstream from the API response that we get from executing a SQL\n   * statement.\n   *\n   * <p>The constructor will fetch the first row (if any) so that it can initialize the\n   * ResultSetMetaData.\n   *\n   * @param sfBaseResultSet snowflake core base result rest object\n   * @param statement query statement that generates this result set\n   * @throws SQLException if failed to construct snowflake result set metadata\n   */\n  SFAsyncResultSet(SFBaseResultSet sfBaseResultSet, Statement statement) throws SQLException {\n\n    super(statement);\n    this.queryID = sfBaseResultSet.getQueryId();\n    this.session = sfBaseResultSet.getSession();\n    this.extraStatement = statement;\n    this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData());\n    this.resultSetMetaData.setQueryIdForAsyncResults(this.queryID);\n    this.resultSetMetaData.setQueryType(SnowflakeResultSetMetaDataV1.QueryType.ASYNC);\n  }\n\n  /**\n   * Constructor takes a result set serializable object to create a sessionless result set.\n   *\n   * @param sfBaseResultSet snowflake core base result rest object\n   * @param resultSetSerializable The result set serializable object which includes all metadata to\n   *     create the result set\n   * @throws SQLException if fails to create the result set object\n   */\n  public SFAsyncResultSet(\n      SFBaseResultSet sfBaseResultSet, SnowflakeResultSetSerializableV1 resultSetSerializable)\n      throws SQLException {\n    super(resultSetSerializable);\n    this.queryID = sfBaseResultSet.getQueryId();\n    this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData());\n    this.resultSetMetaData.setQueryIdForAsyncResults(this.queryID);\n    this.resultSetMetaData.setQueryType(SnowflakeResultSetMetaDataV1.QueryType.ASYNC);\n  }\n\n  public SFAsyncResultSet(String queryID, Statement statement) throws SQLException {\n    super(statement);\n    queryID.trim();\n    if (!QueryIdValidator.isValid(queryID)) {\n      throw new SQLException(\n          \"The provided query ID \" + queryID + \" is invalid.\", SqlState.INVALID_PARAMETER_VALUE);\n    }\n    this.queryID = queryID;\n  }\n\n  @Override\n  protected void raiseSQLExceptionIfResultSetIsClosed() throws SQLException {\n    if (isClosed()) {\n      throw new SnowflakeSQLException(ErrorCode.RESULTSET_ALREADY_CLOSED);\n    }\n  }\n\n  @Override\n  public QueryStatus getStatus() throws SQLException {\n    if (session == null) {\n      throw new SQLException(\"Session not set\");\n    }\n    if (this.queryID == null) {\n      throw new SQLException(\"QueryID unknown\");\n    }\n    if (this.lastQueriedStatus.isSuccess()) {\n      return this.lastQueriedStatus;\n    }\n    this.lastQueriedStatus = session.getQueryStatus(this.queryID);\n    // if query has completed successfully, cache its metadata to avoid unnecessary future server\n    // calls\n    return this.lastQueriedStatus;\n  }\n\n  /**\n   * helper function for next() and getMetaData(). Calls result_scan to get resultSet after\n   * asynchronous query call\n   *\n   * @throws SQLException\n   */\n  private void getRealResults() throws SQLException {\n    if (!resultSetForNextInitialized) {\n      // If query has already succeeded, go straight to result scan to get results\n      if (!this.lastQueriedStatus.isSuccess()) {\n        QueryStatus queryStatus = this.lastQueriedStatus;\n        int noDataRetry = 0;\n        final int noDataMaxRetries = 30;\n        final int[] retryPattern = {1, 1, 2, 3, 4, 8, 10};\n        final int maxIndex = retryPattern.length - 1;\n        int retry = 0;\n        while (!queryStatus.isSuccess()) {\n          // if query is not running due to a failure (Aborted, failed with error, etc), generate\n          // exception\n          if (!queryStatus.isStillRunning()) {\n            String errorMessage = this.lastQueriedStatus.getErrorMessage();\n            if (isNullOrEmpty(errorMessage)) {\n              errorMessage = \"No error message available\";\n            }\n            throw new SQLException(\n                \"Status of query associated with resultSet is \"\n                    + queryStatus.getDescription()\n                    + \". \"\n                    + errorMessage\n                    + \" Results not generated.\");\n          }\n          // if no data about the query is returned after about 2 minutes, give up\n          if (queryStatus.getStatus() == NO_DATA) {\n            noDataRetry++;\n            if (noDataRetry >= noDataMaxRetries) {\n              throw new SQLException(\n                  \"Cannot retrieve data on the status of this query. No information returned from server for queryID={}.\",\n                  this.queryID);\n            }\n          }\n          try {\n            // Sleep for an amount before trying again. Exponential backoff up to 5 seconds\n            // implemented.\n            Thread.sleep(500 * retryPattern[retry]);\n          } catch (InterruptedException e) {\n            e.printStackTrace();\n          }\n          if (retry < maxIndex) {\n            retry++;\n          }\n          queryStatus = this.getStatus();\n        }\n      }\n\n      resultSetForNext =\n          extraStatement.executeQuery(\"select * from table(result_scan('\" + this.queryID + \"'))\");\n      resultSetForNextInitialized = true;\n    }\n  }\n\n  /**\n   * Advance to next row\n   *\n   * @return true if next row exists, false otherwise\n   * @throws SQLException if failed to move to the next row\n   */\n  @Override\n  public boolean next() throws SQLException {\n    getMetaData();\n    return resultSetForNext.next();\n  }\n\n  @Override\n  public void close() throws SQLException {\n    close(true);\n  }\n\n  public void close(boolean removeClosedResultSetFromStatement) throws SQLException {\n    // no SQLException is raised.\n    resultSetForNext.close();\n    if (sfBaseResultSet != null) {\n      sfBaseResultSet.close();\n    }\n    if (removeClosedResultSetFromStatement\n        && statement.isWrapperFor(SnowflakeStatementImpl.class)) {\n      statement.unwrap(SnowflakeStatementImpl.class).removeClosedResultSet(this);\n    }\n  }\n\n  public String getQueryID() {\n    return this.queryID;\n  }\n\n  public boolean wasNull() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.wasNull();\n  }\n\n  public String getString(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getString(columnIndex);\n  }\n\n  public void setSession(SFSession session) {\n    this.session = session;\n  }\n\n  public void setStatement(Statement statement) {\n    this.extraStatement = statement;\n  }\n\n  public boolean getBoolean(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getBoolean(columnIndex);\n  }\n\n  @Override\n  public byte getByte(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getByte(columnIndex);\n  }\n\n  public short getShort(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getShort(columnIndex);\n  }\n\n  public int getInt(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getInt(columnIndex);\n  }\n\n  public long getLong(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getLong(columnIndex);\n  }\n\n  public float getFloat(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getFloat(columnIndex);\n  }\n\n  public double getDouble(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getDouble(columnIndex);\n  }\n\n  public Date getDate(int columnIndex, TimeZone tz) throws SQLException {\n    // Note: currently we provide this API but it does not use TimeZone tz.\n    // TODO: use the time zone passed from the arguments\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getDate(columnIndex);\n  }\n\n  public Time getTime(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getTime(columnIndex);\n  }\n\n  public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.unwrap(SnowflakeResultSetV1.class).getTimestamp(columnIndex, tz);\n  }\n\n  public ResultSetMetaData getMetaData() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    getRealResults();\n    this.resultSetMetaData =\n        (SnowflakeResultSetMetaDataV1)\n            resultSetForNext.unwrap(SnowflakeResultSetV1.class).getMetaData();\n    this.resultSetMetaData.setQueryIdForAsyncResults(this.queryID);\n    this.resultSetMetaData.setQueryType(SnowflakeResultSetMetaDataV1.QueryType.ASYNC);\n    return resultSetMetaData;\n  }\n\n  public Object getObject(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getObject(columnIndex);\n  }\n\n  public BigDecimal getBigDecimal(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getBigDecimal(columnIndex);\n  }\n\n  @Deprecated\n  public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getBigDecimal(columnIndex, scale);\n  }\n\n  public byte[] getBytes(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.getBytes(columnIndex);\n  }\n\n  public int getRow() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    return resultSetForNext.getRow();\n  }\n\n  public boolean isFirst() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.isFirst();\n  }\n\n  public boolean isClosed() throws SQLException {\n    // no exception is raised.\n    if (sfBaseResultSet != null) {\n      return (resultSetForNext.isClosed() && sfBaseResultSet.isClosed());\n    }\n    return resultSetForNext.isClosed();\n  }\n\n  @Override\n  public boolean isLast() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.isLast();\n  }\n\n  @Override\n  public boolean isAfterLast() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return resultSetForNext.isAfterLast();\n  }\n\n  @Override\n  public boolean isBeforeFirst() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    // if ResultSet is not initialized yet, this means neither next() nor getMetaData() has been\n    // called.\n    // If next() hasn't been called, we are at the beginning of the ResultSet so should return true.\n    return !resultSetForNextInitialized || resultSetForNext.isBeforeFirst();\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"boolean isWrapperFor(Class<?> iface)\", false);\n\n    return iface.isInstance(this);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"<T> T unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  /**\n   * Get a list of ResultSetSerializables for the ResultSet in order to parallel processing\n   *\n   * <p>Not currently supported for asynchronous result sets.\n   */\n  @Override\n  public List<SnowflakeResultSetSerializable> getResultSetSerializables(long maxSizeInBytes)\n      throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    getRealResults();\n    return resultSetForNext\n        .unwrap(SnowflakeResultSet.class)\n        .getResultSetSerializables(maxSizeInBytes);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SFBaseFileTransferAgent.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.InputStream;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.common.util.ClassUtil;\nimport net.snowflake.common.util.FixedViewColumn;\n\n/**\n * Base class for file transfers: given a SnowflakeConnection, files may be uploaded or downloaded\n * from/to an InputStream.\n *\n * <p>Note that while SnowflakeFileTransferAgent is used liberally throughout the codebase for\n * performing uploads, the \"alternative\" implementations may have quite different ways of uploading\n * to cloud storage, so this is a rather \"thin\" abstract class that leaves much of the\n * implementation up to the implementing class.\n *\n * <p>It is also expected that a command (GET/PUT) is parsed by the FileTransferAgent before either\n * execute() or downloadStream() is called. This is not enforced by the abstract class's interface,\n * but will be passed to the SFConnectionHandler's getFileTransferAgent().\n *\n * <p>In general, besides the abstract methods execute() and downloadStream(), an implementing class\n * needs to also populate the statusRows List with the file-metadata rows forming the fixed view, as\n * well as set the showEncryptionParameter boolean (usually returned by the session parameter;\n * default is false).\n */\npublic abstract class SFBaseFileTransferAgent implements SnowflakeFixedView {\n\n  /** A class for encapsulating the columns to return for the upload command */\n  public enum UploadColumns {\n    source,\n    target,\n    source_size,\n    target_size,\n    source_compression,\n    target_compression,\n    status,\n    encryption,\n    message\n  };\n\n  public class UploadCommandFacade {\n\n    @FixedViewColumn(name = \"source\", ordinal = 10)\n    private String srcFile;\n\n    @FixedViewColumn(name = \"target\", ordinal = 20)\n    private String destFile;\n\n    @FixedViewColumn(name = \"source_size\", ordinal = 30)\n    private long srcSize;\n\n    @FixedViewColumn(name = \"target_size\", ordinal = 40)\n    private long destSize = -1;\n\n    @FixedViewColumn(name = \"source_compression\", ordinal = 50)\n    private String srcCompressionType;\n\n    @FixedViewColumn(name = \"target_compression\", ordinal = 60)\n    private String destCompressionType;\n\n    @FixedViewColumn(name = \"status\", ordinal = 70)\n    private String resultStatus;\n\n    @FixedViewColumn(name = \"message\", ordinal = 80)\n    private String errorDetails;\n\n    public UploadCommandFacade(\n        String srcFile,\n        String destFile,\n        String resultStatus,\n        String errorDetails,\n        long srcSize,\n        long destSize,\n        String srcCompressionType,\n        String destCompressionType) {\n      this.srcFile = srcFile;\n      this.destFile = destFile;\n      this.resultStatus = resultStatus;\n      this.errorDetails = errorDetails;\n      this.srcSize = srcSize;\n      this.destSize = destSize;\n      this.srcCompressionType = srcCompressionType;\n      this.destCompressionType = destCompressionType;\n    }\n\n    public String getSrcFile() {\n      return srcFile;\n    }\n  }\n\n  public class UploadCommandEncryptionFacade extends UploadCommandFacade {\n    @FixedViewColumn(name = \"encryption\", ordinal = 75)\n    private String encryption;\n\n    public UploadCommandEncryptionFacade(\n        String srcFile,\n        String destFile,\n        String resultStatus,\n        String errorDetails,\n        long srcSize,\n        long destSize,\n        String srcCompressionType,\n        String destCompressionType,\n        boolean isEncrypted) {\n      super(\n          srcFile,\n          destFile,\n          resultStatus,\n          errorDetails,\n          srcSize,\n          destSize,\n          srcCompressionType,\n          destCompressionType);\n      this.encryption = isEncrypted ? \"ENCRYPTED\" : \"\";\n    }\n  }\n\n  /** A class for encapsulating the columns to return for the download command */\n  public class DownloadCommandFacade {\n    @FixedViewColumn(name = \"file\", ordinal = 10)\n    private String file;\n\n    @FixedViewColumn(name = \"size\", ordinal = 20)\n    private long size;\n\n    @FixedViewColumn(name = \"status\", ordinal = 30)\n    private String resultStatus;\n\n    @FixedViewColumn(name = \"message\", ordinal = 40)\n    private String errorDetails;\n\n    public DownloadCommandFacade(String file, String resultStatus, String errorDetails, long size) {\n      this.file = file;\n      this.resultStatus = resultStatus;\n      this.errorDetails = errorDetails;\n      this.size = size;\n    }\n\n    public String getFile() {\n      return file;\n    }\n  }\n\n  public class DownloadCommandEncryptionFacade extends DownloadCommandFacade {\n    @FixedViewColumn(name = \"encryption\", ordinal = 35)\n    private String encryption;\n\n    public DownloadCommandEncryptionFacade(\n        String file, String resultStatus, String errorDetails, long size, boolean isEncrypted) {\n      super(file, resultStatus, errorDetails, size);\n      this.encryption = isEncrypted ? \"DECRYPTED\" : \"\";\n    }\n  }\n\n  protected boolean compressSourceFromStream;\n  protected String destStagePath;\n  protected String destFileNameForStreamSource;\n  protected InputStream sourceStream;\n  protected boolean sourceFromStream;\n  protected boolean showEncryptionParameter;\n  protected List<Object> statusRows = new ArrayList<>();\n  protected CommandType commandType = CommandType.UPLOAD;\n  private int currentRowIndex;\n\n  /**\n   * Gets the total number of rows that this GET/PUT command's output returns in the fixed-view\n   * result. The statusRows list must be populated with the FileMetadata.\n   *\n   * @return The number of rows that this fixed-view represents.\n   */\n  @Override\n  public int getTotalRows() {\n    return statusRows.size();\n  }\n\n  /**\n   * Move on to the next row of file metadata. The statusRows list must be populated with the\n   * file-metadata output of the GET/PUT command.\n   *\n   * @return The row, represented as a list of Object.\n   */\n  @Override\n  public List<Object> getNextRow() throws Exception {\n    if (currentRowIndex < statusRows.size()) {\n      return ClassUtil.getFixedViewObjectAsRow(\n          commandType == CommandType.UPLOAD\n              ? (showEncryptionParameter\n                  ? UploadCommandEncryptionFacade.class\n                  : UploadCommandFacade.class)\n              : (showEncryptionParameter\n                  ? DownloadCommandEncryptionFacade.class\n                  : DownloadCommandFacade.class),\n          statusRows.get(currentRowIndex++));\n    } else {\n      return null;\n    }\n  }\n\n  /**\n   * Describe the metadata of a fixed view.\n   *\n   * @return list of column meta data\n   * @throws Exception failed to construct list\n   */\n  @Override\n  public List<SnowflakeColumnMetadata> describeColumns(SFBaseSession session) throws Exception {\n    return SnowflakeUtil.describeFixedViewColumns(\n        commandType == CommandType.UPLOAD\n            ? (showEncryptionParameter\n                ? UploadCommandEncryptionFacade.class\n                : UploadCommandFacade.class)\n            : (showEncryptionParameter\n                ? DownloadCommandEncryptionFacade.class\n                : DownloadCommandFacade.class),\n        session);\n  }\n\n  /**\n   * Sets the source data stream to be uploaded.\n   *\n   * @param sourceStream The source data to upload.\n   */\n  public void setSourceStream(InputStream sourceStream) {\n    this.sourceStream = sourceStream;\n    this.sourceFromStream = true;\n  }\n\n  /**\n   * Sets the destination stage path\n   *\n   * @param destStagePath The target destination stage path.\n   */\n  public void setDestStagePath(String destStagePath) {\n    this.destStagePath = destStagePath;\n  }\n\n  /**\n   * Sets the target filename for uploading\n   *\n   * @param destFileNameForStreamSource The target destination filename once the file is uploaded.\n   */\n  public void setDestFileNameForStreamSource(String destFileNameForStreamSource) {\n    this.destFileNameForStreamSource = destFileNameForStreamSource;\n  }\n\n  /**\n   * Whether to compress the source stream before upload.\n   *\n   * @param compressSourceFromStream boolean for whether to compress the data stream before upload.\n   */\n  public void setCompressSourceFromStream(boolean compressSourceFromStream) {\n    this.compressSourceFromStream = compressSourceFromStream;\n  }\n\n  /**\n   * Run the PUT/GET command, if a command has been set.\n   *\n   * @return Whether the operation was completed successfully, and completely.\n   * @throws SQLException for SQL or upload errors\n   */\n  public abstract boolean execute() throws SQLException;\n\n  /**\n   * Download data from a stage.\n   *\n   * @param fileName A file on a stage to download.\n   * @return An InputStream for the requested file.\n   * @throws SnowflakeSQLException If the file does not exist, or if an error occurred during\n   *     transport.\n   */\n  public abstract InputStream downloadStream(String fileName) throws SnowflakeSQLException;\n\n  /** The types of file transfer: upload and download. */\n  public enum CommandType {\n    UPLOAD,\n    DOWNLOAD\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SFConnectionHandler.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLNonTransientConnectionException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\n\n/**\n * Class that presents the implementation of a Snowflake Connection. This allows for alternate\n * definitions of SFSession, SFStatement, and SFResultSet, (representing the 'physical'\n * implementation layer) that can share high-level code.\n */\npublic interface SFConnectionHandler {\n\n  /**\n   * @return Whether this Connection supports asynchronous queries. If yes, createAsyncResultSet may\n   *     be called.\n   */\n  boolean supportsAsyncQuery();\n\n  /**\n   * Initializes the SnowflakeConnection\n   *\n   * @param url url string\n   * @param info connection parameters\n   * @throws SQLException if any error is encountered\n   */\n  void initializeConnection(String url, Properties info) throws SQLException;\n\n  /**\n   * @return Gets the SFBaseSession implementation for this connection implementation\n   */\n  SFBaseSession getSFSession();\n\n  /**\n   * @return Returns the SFStatementInterface implementation for this connection implementation\n   * @throws SQLException if any error occurs\n   */\n  SFBaseStatement getSFStatement() throws SQLException;\n\n  /**\n   * Creates a result set from a query id.\n   *\n   * @param queryID the query ID\n   * @param statement Statement object\n   * @return ResultSet\n   * @throws SQLException if any error occurs\n   */\n  ResultSet createResultSet(String queryID, Statement statement) throws SQLException;\n\n  /**\n   * @param resultSet SFBaseResultSet\n   * @param statement Statement\n   * @return Creates a SnowflakeResultSet from a base SFBaseResultSet for this connection\n   *     implementation.\n   * @throws SQLException if an error occurs\n   */\n  SnowflakeBaseResultSet createResultSet(SFBaseResultSet resultSet, Statement statement)\n      throws SQLException;\n\n  /**\n   * Creates an asynchronous result set from a base SFBaseResultSet for this connection\n   * implementation.\n   *\n   * @param resultSet SFBaseResultSet\n   * @param statement Statement\n   * @return An asynchronous result set from SFBaseResultSet\n   * @throws SQLException if an error occurs\n   */\n  SnowflakeBaseResultSet createAsyncResultSet(SFBaseResultSet resultSet, Statement statement)\n      throws SQLException;\n\n  /**\n   * @param command The command to parse for this file transfer (e.g., PUT/GET)\n   * @param statement The statement to use for this file transfer\n   * @return SFBaseFileTransferAgent\n   * @throws SQLNonTransientConnectionException if a connection error occurs\n   * @throws SnowflakeSQLException if any other exception occurs\n   */\n  SFBaseFileTransferAgent getFileTransferAgent(String command, SFBaseStatement statement)\n      throws SQLNonTransientConnectionException, SnowflakeSQLException;\n\n  /**\n   * Overridable method that allows for different connection implementations to use different stage\n   * names for binds uploads. By default, it uses SYSTEM$BIND\n   *\n   * @return The name of the identifier with which a temporary stage is created in the Session for\n   *     uploading array bind values.\n   */\n  default String getBindStageName() {\n    return \"SYSTEM$BIND\";\n  }\n  ;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeChunkDownloader.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.core.Constants.MB;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.core.JsonFactory;\nimport com.fasterxml.jackson.databind.MappingJsonFactory;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Random;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicLong;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.ChunkDownloader;\nimport net.snowflake.client.internal.core.DownloaderMetrics;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.QueryResultFormat;\nimport net.snowflake.client.internal.core.SFArrowResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultChunk.DownloadState;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.arrow.memory.RootAllocator;\n\n/** Class for managing async download of offline result chunks */\npublic class SnowflakeChunkDownloader implements ChunkDownloader {\n\n  // object mapper for deserialize JSON\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n  /** a shared JSON parser factory. */\n  private static final JsonFactory jsonFactory = new MappingJsonFactory();\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeChunkDownloader.class);\n  private static final int STREAM_BUFFER_SIZE = MB;\n  private static final long SHUTDOWN_TIME = 3;\n  private final SnowflakeConnectString snowflakeConnectionString;\n  private final OCSPMode ocspMode;\n  private final HttpClientSettingsKey ocspModeAndProxyKey;\n\n  // Session object, used solely for throwing exceptions. CAUTION: MAY BE NULL!\n  private SFBaseSession session;\n\n  private JsonResultChunk.ResultChunkDataCache chunkDataCache =\n      new JsonResultChunk.ResultChunkDataCache();\n  private List<SnowflakeResultChunk> chunks;\n\n  // index of next chunk to be consumed (it may not be ready yet)\n  private int nextChunkToConsume = 0;\n\n  // index of next chunk to be downloaded\n  private int nextChunkToDownload = 0;\n\n  // number of prefetch slots\n  private final int prefetchSlots;\n\n  // thread pool\n  private final ThreadPoolExecutor executor;\n\n  // number of millis main thread waiting for chunks from downloader\n  private long numberMillisWaitingForChunks = 0;\n\n  // is the downloader terminated\n  private final AtomicBoolean terminated = new AtomicBoolean(false);\n\n  // number of millis spent on downloading result chunks\n  private final AtomicLong totalMillisDownloadingChunks = new AtomicLong(0);\n\n  // number of millis spent on parsing result chunks\n  private final AtomicLong totalMillisParsingChunks = new AtomicLong(0);\n\n  // The query result master key\n  private final String qrmk;\n\n  private Map<String, String> chunkHeadersMap;\n\n  private final int networkTimeoutInMilli;\n\n  private final int authTimeout;\n\n  private final int socketTimeout;\n\n  private final int maxHttpRetries;\n  private long memoryLimit;\n\n  // the current memory usage across JVM\n  private static final AtomicLong currentMemoryUsage = new AtomicLong();\n\n  // used to track the downloading threads\n  private Map<Integer, Future> downloaderFutures = new ConcurrentHashMap<>();\n\n  /** query result format */\n  private QueryResultFormat queryResultFormat;\n\n  /** Arrow memory allocator for the current resultSet */\n  private RootAllocator rootAllocator;\n\n  private final String queryId;\n\n  private final int firstChunkRowCount;\n\n  static long getCurrentMemoryUsage() {\n    synchronized (currentMemoryUsage) {\n      return currentMemoryUsage.longValue();\n    }\n  }\n\n  // The parameters used to wait for available memory:\n  // starting waiting time will be BASE_WAITING_MS * WAITING_SECS_MULTIPLIER = 100 ms\n  private long BASE_WAITING_MS = 50;\n  private long WAITING_SECS_MULTIPLIER = 2;\n  // the maximum waiting time\n  private long MAX_WAITING_MS = 30 * 1000;\n  // the default jitter ratio 10%\n  private long WAITING_JITTER_RATIO = 10;\n\n  private final ResultStreamProvider resultStreamProvider;\n\n  /** Timeout that the main thread waits for downloading the current chunk */\n  private static final long downloadedConditionTimeoutInSeconds =\n      HttpUtil.getDownloadedConditionTimeoutInSeconds();\n\n  private static final int MAX_RETRY_JITTER = 1000; // milliseconds\n\n  // Only controls the max retry number when prefetch runs out of memory\n  // Will wait a while then retry to see if we can allocate the required memory\n  // Default value is 1\n  private int prefetchMaxRetry = 1;\n\n  private static Throwable injectedDownloaderException = null; // for testing purpose\n\n  // This function should only be used for testing purpose\n  static void setInjectedDownloaderException(Throwable th) {\n    injectedDownloaderException = th;\n  }\n\n  public OCSPMode getOCSPMode() {\n    return ocspMode;\n  }\n\n  public HttpClientSettingsKey getHttpClientSettingsKey() {\n    return ocspModeAndProxyKey;\n  }\n\n  public ResultStreamProvider getResultStreamProvider() {\n    return resultStreamProvider;\n  }\n\n  /**\n   * Create a pool of downloader threads.\n   *\n   * @param threadNamePrefix name of threads in pool\n   * @param parallel number of thread in pool\n   * @return new thread pool\n   */\n  private static ThreadPoolExecutor createChunkDownloaderExecutorService(\n      final String threadNamePrefix, final int parallel) {\n    ThreadFactory threadFactory =\n        new ThreadFactory() {\n          private int threadCount = 1;\n\n          public Thread newThread(final Runnable r) {\n            final Thread thread = new Thread(r);\n            thread.setName(threadNamePrefix + threadCount++);\n\n            thread.setUncaughtExceptionHandler(\n                new Thread.UncaughtExceptionHandler() {\n                  public void uncaughtException(Thread t, Throwable e) {\n                    logger.error(\"Uncaught Exception in thread {}: {}\", t, e);\n                  }\n                });\n\n            thread.setDaemon(true);\n\n            return thread;\n          }\n        };\n    return (ThreadPoolExecutor) Executors.newFixedThreadPool(parallel, threadFactory);\n  }\n\n  /**\n   * Constructor to initialize downloader, which uses the default stream provider\n   *\n   * @param resultSetSerializable the result set serializable object which includes required\n   *     metadata to start chunk downloader\n   * @throws SnowflakeSQLException if an error is encountered\n   */\n  public SnowflakeChunkDownloader(SnowflakeResultSetSerializableV1 resultSetSerializable)\n      throws SnowflakeSQLException {\n    this.queryId = resultSetSerializable.getQueryId();\n    this.firstChunkRowCount = resultSetSerializable.getFirstChunkRowCount();\n    this.snowflakeConnectionString = resultSetSerializable.getSnowflakeConnectString();\n    this.ocspMode = resultSetSerializable.getOCSPMode();\n    this.ocspModeAndProxyKey = resultSetSerializable.getHttpClientKey();\n    this.qrmk = resultSetSerializable.getQrmk();\n    this.networkTimeoutInMilli = resultSetSerializable.getNetworkTimeoutInMilli();\n    this.authTimeout = resultSetSerializable.getAuthTimeout();\n    this.socketTimeout = resultSetSerializable.getSocketTimeout();\n    this.maxHttpRetries = resultSetSerializable.getMaxHttpRetries();\n    this.prefetchSlots = resultSetSerializable.getResultPrefetchThreads() * 2;\n    this.queryResultFormat = resultSetSerializable.getQueryResultFormat();\n    logger.debug(\"qrmk: {}\", this.qrmk);\n    this.chunkHeadersMap = resultSetSerializable.getChunkHeadersMap();\n    // session may be null. Its only use is for in-band telemetry in this class\n    this.session =\n        (resultSetSerializable.getSession(internalCallMarker()) != null)\n            ? resultSetSerializable.getSession(internalCallMarker()).orElse(null)\n            : null;\n    if (this.session != null) {\n      Object prefetchMaxRetry =\n          this.session.getOtherParameter(SessionUtil.JDBC_CHUNK_DOWNLOADER_MAX_RETRY);\n      if (prefetchMaxRetry != null) {\n        this.prefetchMaxRetry = (int) prefetchMaxRetry;\n      }\n    }\n\n    if (resultSetSerializable.getServerURL() != null) {\n      try {\n        SessionUtil.resetOCSPUrlIfNecessary(resultSetSerializable.getServerURL());\n      } catch (IOException e) {\n        logger.debug(\"Exception while resetting OCSP URL\", e);\n      }\n    }\n\n    this.memoryLimit = resultSetSerializable.getMemoryLimit();\n    if (this.session != null\n        && session.getMemoryLimitForTesting() != SFBaseSession.MEMORY_LIMIT_UNSET) {\n      this.memoryLimit = session.getMemoryLimitForTesting();\n    }\n\n    // create the chunks array\n    this.chunks = new ArrayList<>(resultSetSerializable.getChunkFileCount());\n\n    this.resultStreamProvider = resultSetSerializable.getResultStreamProvider(internalCallMarker());\n\n    if (resultSetSerializable.getChunkFileCount() < 1) {\n      throw new SnowflakeSQLLoggedException(\n          this.session,\n          ErrorCode.INTERNAL_ERROR,\n          \"Incorrect chunk count: \" + resultSetSerializable.getChunkFileCount());\n    }\n\n    // initialize chunks with url and row count\n    for (SnowflakeResultSetSerializableV1.ChunkFileMetadata chunkFileMetadata :\n        resultSetSerializable.getChunkFileMetadatas()) {\n      SnowflakeResultChunk chunk;\n      switch (this.queryResultFormat) {\n        case ARROW:\n          this.rootAllocator = resultSetSerializable.getRootAllocator();\n          chunk =\n              new ArrowResultChunk(\n                  chunkFileMetadata.getFileURL(),\n                  chunkFileMetadata.getRowCount(),\n                  resultSetSerializable.getColumnCount(),\n                  chunkFileMetadata.getUncompressedByteSize(),\n                  this.rootAllocator,\n                  this.session);\n          break;\n\n        case JSON:\n          chunk =\n              new JsonResultChunk(\n                  chunkFileMetadata.getFileURL(),\n                  chunkFileMetadata.getRowCount(),\n                  resultSetSerializable.getColumnCount(),\n                  chunkFileMetadata.getUncompressedByteSize(),\n                  this.session);\n          break;\n\n        default:\n          throw new SnowflakeSQLLoggedException(\n              this.session,\n              ErrorCode.INTERNAL_ERROR,\n              \"Invalid result format: \" + queryResultFormat.name());\n      }\n\n      logger.debug(\n          \"Add chunk: url: {} rowCount: {} uncompressedSize: {} neededChunkMemory: {}, chunkResultFormat: {}\",\n          chunk.getScrubbedUrl(),\n          chunk.getRowCount(),\n          chunk.getUncompressedSize(),\n          chunk.computeNeededChunkMemory(),\n          queryResultFormat.name());\n\n      chunks.add(chunk);\n    }\n    // prefetch threads and slots from parameter settings\n    int effectiveThreads =\n        Math.min(\n            resultSetSerializable.getResultPrefetchThreads(),\n            resultSetSerializable.getChunkFileCount());\n\n    logger.debug(\n        \"#chunks: {} #threads: {} #slots: {} -> pool: {}\",\n        resultSetSerializable.getChunkFileCount(),\n        resultSetSerializable.getResultPrefetchThreads(),\n        prefetchSlots,\n        effectiveThreads);\n\n    // create thread pool\n    executor = createChunkDownloaderExecutorService(\"result-chunk-downloader-\", effectiveThreads);\n\n    try {\n      startNextDownloaders();\n    } catch (OutOfMemoryError outOfMemoryError) {\n      logOutOfMemoryError();\n      StringWriter errors = new StringWriter();\n      outOfMemoryError.printStackTrace(new PrintWriter(errors));\n      throw new SnowflakeSQLLoggedException(\n          this.session, ErrorCode.INTERNAL_ERROR.getMessageCode(), SqlState.INTERNAL_ERROR, errors);\n    }\n  }\n\n  /** Submit download chunk tasks to executor. Number depends on thread and memory limit */\n  private void startNextDownloaders() throws SnowflakeSQLException {\n    long waitingTime = BASE_WAITING_MS;\n    long getPrefetchMemRetry = 0;\n\n    // submit the chunks to be downloaded up to the prefetch slot capacity\n    // and limited by memory\n    while (nextChunkToDownload - nextChunkToConsume < prefetchSlots\n        && nextChunkToDownload < chunks.size()) {\n      // check if memory limit allows more prefetching\n      final SnowflakeResultChunk nextChunk = chunks.get(nextChunkToDownload);\n      final long neededChunkMemory = nextChunk.computeNeededChunkMemory();\n\n      // make sure memoryLimit > neededChunkMemory; otherwise, the thread hangs\n      if (neededChunkMemory > memoryLimit) {\n        logger.debug(\n            \"Thread {}: reset memoryLimit from {} MB to current chunk size {} MB\",\n            (ArgSupplier) () -> Thread.currentThread().getId(),\n            (ArgSupplier) () -> memoryLimit / 1024 / 1024,\n            (ArgSupplier) () -> neededChunkMemory / 1024 / 1024);\n\n        memoryLimit = neededChunkMemory;\n      }\n\n      // try to reserve the needed memory\n      long curMem = currentMemoryUsage.addAndGet(neededChunkMemory);\n      // If there is not enough memory available for prefetch, cancel the memory allocation. It's\n      // cancelled when:\n      // 1. We haven't consumed enough chunks to download next chunk (nextChunkToDownload >\n      // nextChunkToConsume)\n      // 2. There is not enough memory for prefetching to begin with (nextChunkToDownload=0 &&\n      // nextChunkToConsume=0)\n      // In all other cases, don't cancel but wait until memory frees up.\n      if (curMem > memoryLimit\n          && (nextChunkToDownload - nextChunkToConsume > 0\n              || (nextChunkToDownload == 0 && nextChunkToConsume == 0))) {\n        // cancel the reserved memory and this downloader too\n        logger.debug(\n            \"Not enough memory available for prefetch. Cancel reserved memory. MemoryLimit: {},\"\n                + \" curMem: {}, nextChunkToDownload: {}, nextChunkToConsume: {}, retry: {}\",\n            memoryLimit,\n            curMem,\n            nextChunkToDownload,\n            nextChunkToConsume,\n            getPrefetchMemRetry);\n        currentMemoryUsage.addAndGet(-neededChunkMemory);\n        nextChunk.getLock().lock();\n        try {\n          nextChunk.setDownloadState(DownloadState.FAILURE);\n        } finally {\n          nextChunk.getLock().unlock();\n        }\n        break;\n      }\n\n      // only allocate memory when the future usage is less than the limit\n      if (curMem <= memoryLimit) {\n        if (queryResultFormat == QueryResultFormat.JSON) {\n          ((JsonResultChunk) nextChunk).tryReuse(chunkDataCache);\n        }\n\n        logger.debug(\n            \"Thread {}: currentMemoryUsage in MB: {}, nextChunkToDownload: {}, \"\n                + \"nextChunkToConsume: {}, newReservedMemory in B: {} \",\n            (ArgSupplier) () -> Thread.currentThread().getId(),\n            curMem / MB,\n            nextChunkToDownload,\n            nextChunkToConsume,\n            neededChunkMemory);\n\n        logger.debug(\n            \"Submit chunk #{} for downloading, url: {}\",\n            this.nextChunkToDownload,\n            nextChunk.getScrubbedUrl());\n\n        // SNOW-615824 Imagine this scenario to understand the root cause of this issue:\n        // When consuming chunk N, we try to prefetch chunk N+1. The prefetching failed due to\n        // hitting memoryLimit. We will mark the chunk N+1 as FAILED.\n        // After we are done with chunk N, we try to consume chunk N+1.\n        // In getNextChunkToConsume, we first call startNextDownloaders then call waitForChunkReady.\n        // startNextDownloaders sees that the next chunk to download is N+1. With enough memory at\n        // this time, it will try to download the chunk. waitForChunkReady sees that chunk N+1 is\n        // marked as FAILED, it will also try to download the chunk because it thinks that no\n        // prefetching will download the chunk.\n        // Thus we will submit two download jobs, causing chunk N+1 appears to be lost.\n        // Therefore the fix is to only prefetch chunks that are marked as NOT_STARTED here.\n        nextChunk.getLock().lock();\n        try {\n          if (nextChunk.getDownloadState() != DownloadState.NOT_STARTED) {\n            break;\n          }\n        } finally {\n          nextChunk.getLock().unlock();\n        }\n\n        Future downloaderFuture =\n            executor.submit(\n                getDownloadChunkCallable(\n                    this,\n                    nextChunk,\n                    qrmk,\n                    nextChunkToDownload,\n                    chunkHeadersMap,\n                    networkTimeoutInMilli,\n                    authTimeout,\n                    socketTimeout,\n                    maxHttpRetries,\n                    this.session,\n                    chunks.size(),\n                    queryId));\n        downloaderFutures.put(nextChunkToDownload, downloaderFuture);\n        // increment next chunk to download\n        nextChunkToDownload++;\n        // make sure reset waiting time\n        waitingTime = BASE_WAITING_MS;\n        // go to next chunk\n        continue;\n      } else {\n        // cancel the reserved memory\n        logger.debug(\"Cancel the reserved memory.\", false);\n        curMem = currentMemoryUsage.addAndGet(-neededChunkMemory);\n        if (getPrefetchMemRetry > prefetchMaxRetry) {\n          logger.debug(\n              \"Retry limit for prefetch has been reached. Cancel reserved memory and prefetch\"\n                  + \" attempt.\",\n              false);\n          nextChunk.getLock().lock();\n          try {\n            nextChunk.setDownloadState(DownloadState.FAILURE);\n          } finally {\n            nextChunk.getLock().unlock();\n          }\n          break;\n        }\n      }\n\n      // waiting when nextChunkToDownload is equal to nextChunkToConsume but reach memory limit\n      try {\n        waitingTime *= WAITING_SECS_MULTIPLIER;\n        waitingTime = waitingTime > MAX_WAITING_MS ? MAX_WAITING_MS : waitingTime;\n        long jitter = ThreadLocalRandom.current().nextLong(0, waitingTime / WAITING_JITTER_RATIO);\n        waitingTime += jitter;\n        getPrefetchMemRetry++;\n        if (logger.isDebugEnabled()) {\n          logger.debug(\n              \"Thread {} waiting for {} s: currentMemoryUsage in MB: {}, neededChunkMemory in MB:\"\n                  + \" {}, nextChunkToDownload: {}, nextChunkToConsume: {}, retry: {}\",\n              (ArgSupplier) () -> Thread.currentThread().getId(),\n              waitingTime / 1000.0,\n              curMem / MB,\n              neededChunkMemory / MB,\n              nextChunkToDownload,\n              nextChunkToConsume,\n              getPrefetchMemRetry);\n        }\n        Thread.sleep(waitingTime);\n      } catch (InterruptedException ie) {\n        throw new SnowflakeSQLException(\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            \"Waiting SnowflakeChunkDownloader has been interrupted.\");\n      }\n    }\n\n    // clear the cache, we can't download more at the moment\n    // so we won't need them in the near future\n    chunkDataCache.clear();\n  }\n\n  /**\n   * release the memory usage from currentMemoryUsage\n   *\n   * @param chunkId chunk ID\n   * @param optionalReleaseSize if present, then release the specified size\n   */\n  private void releaseCurrentMemoryUsage(int chunkId, Optional<Long> optionalReleaseSize) {\n    long releaseSize =\n        optionalReleaseSize.isPresent()\n            ? optionalReleaseSize.get()\n            : chunks.get(chunkId).computeNeededChunkMemory();\n    if (releaseSize > 0 && !chunks.get(chunkId).isReleased()) {\n      // has to be before reusing the memory\n      long curMem = currentMemoryUsage.addAndGet(-releaseSize);\n      logger.debug(\n          \"Thread {} - currentMemoryUsage in MB: {}, released in MB: {}, \"\n              + \"chunk: {}, optionalReleaseSize: {}, JVMFreeMem: {}\",\n          (ArgSupplier) () -> Thread.currentThread().getId(),\n          (ArgSupplier) () -> curMem / MB,\n          releaseSize,\n          chunkId,\n          optionalReleaseSize.isPresent(),\n          Runtime.getRuntime().freeMemory());\n      chunks.get(chunkId).setReleased();\n    }\n  }\n\n  /** release all existing chunk memory usage before close */\n  private void releaseAllChunkMemoryUsage() {\n    if (chunks == null || chunks.size() == 0) {\n      return;\n    }\n\n    // only release the chunks has been downloading or downloaded\n    for (int i = 0; i < nextChunkToDownload; i++) {\n      releaseCurrentMemoryUsage(i, Optional.empty());\n    }\n  }\n\n  /**\n   * The method does the following:\n   *\n   * <p>1. free the previous chunk data and submit a new chunk to be downloaded\n   *\n   * <p>2. get next chunk to consume, if it is not ready for consumption, it waits until it is ready\n   *\n   * @return next SnowflakeResultChunk to be consumed\n   * @throws InterruptedException if downloading thread was interrupted\n   * @throws SnowflakeSQLException if downloader encountered an error\n   */\n  public SnowflakeResultChunk getNextChunkToConsume()\n      throws InterruptedException, SnowflakeSQLException {\n    // free previous chunk data and submit a new chunk for downloading\n    if (this.nextChunkToConsume > 0) {\n      int prevChunk = this.nextChunkToConsume - 1;\n\n      // free the chunk data for previous chunk\n      logger.debug(\"Free chunk data for chunk #{}\", prevChunk);\n\n      long chunkMemUsage = chunks.get(prevChunk).computeNeededChunkMemory();\n\n      // reuse chunkcache if json result\n      if (this.queryResultFormat == QueryResultFormat.JSON) {\n        if (this.nextChunkToDownload < this.chunks.size()) {\n          // Reuse the set of object to avoid reallocation\n          // It is important to do this BEFORE starting the next download\n          chunkDataCache.add((JsonResultChunk) this.chunks.get(prevChunk));\n        } else {\n          // clear the cache if we don't need it anymore\n          chunkDataCache.clear();\n        }\n      }\n\n      // Free any memory the previous chunk might hang on\n      this.chunks.get(prevChunk).freeData();\n\n      releaseCurrentMemoryUsage(prevChunk, Optional.of(chunkMemUsage));\n    }\n\n    // if no more chunks, return null\n    if (this.nextChunkToConsume >= this.chunks.size()) {\n      logger.debug(\"No more chunk\", false);\n      return null;\n    }\n\n    // prefetch next chunks\n    try {\n      startNextDownloaders();\n    } catch (OutOfMemoryError outOfMemoryError) {\n      logOutOfMemoryError();\n      StringWriter errors = new StringWriter();\n      outOfMemoryError.printStackTrace(new PrintWriter(errors));\n      throw new SnowflakeSQLLoggedException(\n          this.session, ErrorCode.INTERNAL_ERROR.getMessageCode(), SqlState.INTERNAL_ERROR, errors);\n    }\n\n    SnowflakeResultChunk currentChunk = this.chunks.get(nextChunkToConsume);\n\n    if (currentChunk.getDownloadState() == DownloadState.SUCCESS) {\n      logger.debug(\"Chunk #{} is ready to consume\", nextChunkToConsume);\n      nextChunkToConsume++;\n      if (nextChunkToConsume == this.chunks.size()) {\n        // make sure to release the last chunk\n        releaseCurrentMemoryUsage(nextChunkToConsume - 1, Optional.empty());\n      }\n      return currentChunk;\n    } else {\n      // the chunk we want to consume is not ready yet, wait for it\n      currentChunk.getLock().lock();\n      try {\n        logger.debug(\"Chunk#{} is not ready to consume\", nextChunkToConsume);\n        logger.debug(\"Consumer get lock to check chunk state\", false);\n\n        waitForChunkReady(currentChunk);\n\n        // downloader thread encountered an error\n        if (currentChunk.getDownloadState() == DownloadState.FAILURE) {\n          releaseAllChunkMemoryUsage();\n          logger.error(\"Downloader encountered error: {}\", currentChunk.getDownloadError());\n\n          if (currentChunk\n              .getDownloadError()\n              .contains(\"java.lang.OutOfMemoryError: Java heap space\")) {\n            logOutOfMemoryError();\n          }\n\n          throw new SnowflakeSQLLoggedException(\n              this.session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              currentChunk.getDownloadError());\n        }\n\n        logger.debug(\"Chunk#{} is ready to consume\", nextChunkToConsume);\n\n        nextChunkToConsume++;\n\n        // next chunk to consume is ready for consumption\n        return currentChunk;\n      } finally {\n        logger.debug(\"Consumer free lock\", false);\n\n        boolean terminateDownloader = (currentChunk.getDownloadState() == DownloadState.FAILURE);\n        // release the unlock always\n        currentChunk.getLock().unlock();\n        if (nextChunkToConsume == this.chunks.size()) {\n          // make sure to release the last chunk\n          releaseCurrentMemoryUsage(nextChunkToConsume - 1, Optional.empty());\n        }\n        if (terminateDownloader) {\n          logger.debug(\"Download result fail. Shut down the chunk downloader\", false);\n          terminate();\n        }\n      }\n    }\n  }\n\n  /**\n   * wait for the current chunk to be ready to consume if the downloader fails then let it retry for\n   * at most 10 times if the downloader is in progress for at most one hour or the downloader has\n   * already retried more than 10 times, then throw an exception.\n   *\n   * @param currentChunk\n   * @throws InterruptedException\n   */\n  private void waitForChunkReady(SnowflakeResultChunk currentChunk) throws InterruptedException {\n    int retry = 0;\n    long startTime = System.currentTimeMillis();\n    while (true) {\n      logger.debug(\n          \"Thread {} is waiting for chunk#{} to be ready, current chunk state is: {}, retry: {}\",\n          Thread.currentThread().getId(),\n          nextChunkToConsume,\n          currentChunk.getDownloadState(),\n          retry);\n\n      if (currentChunk.getDownloadState() != DownloadState.FAILURE\n          && currentChunk.getDownloadState() != DownloadState.SUCCESS) {\n        // if the state is not failure, we should keep waiting; otherwise, we skip\n        // waiting\n        if (!currentChunk\n            .getDownloadCondition()\n            .await(downloadedConditionTimeoutInSeconds, TimeUnit.SECONDS)) {\n          // if the current chunk has not condition change over the timeout (which is rare)\n          logger.debug(\n              \"Thread {} is timeout for waiting chunk#{} to be ready, current\"\n                  + \" chunk state is: {}, retry: {}, scrubbedUrl: {}\",\n              Thread.currentThread().getId(),\n              nextChunkToConsume,\n              currentChunk.getDownloadState(),\n              retry,\n              currentChunk.getScrubbedUrl());\n\n          currentChunk.setDownloadState(DownloadState.FAILURE);\n          currentChunk.setDownloadError(\n              String.format(\n                  \"Timeout waiting for the download of chunk#%d(Total chunks: %d) retry: %d scrubbedUrl: %s\",\n                  nextChunkToConsume, this.chunks.size(), retry, currentChunk.getScrubbedUrl()));\n          break;\n        }\n      }\n\n      // retry if chunk is not successfully downloaded\n      if (currentChunk.getDownloadState() != DownloadState.SUCCESS) {\n        retry++;\n        // timeout or failed\n        logger.debug(\n            \"Since downloadState is {} Thread {} decides to retry {} time(s) for chunk#{}\",\n            currentChunk.getDownloadState(),\n            Thread.currentThread().getId(),\n            retry,\n            nextChunkToConsume);\n        Future downloaderFuture = downloaderFutures.get(nextChunkToConsume);\n        if (downloaderFuture != null) {\n          downloaderFuture.cancel(true);\n        }\n\n        chunks.get(nextChunkToConsume).getLock().lock();\n        try {\n          chunks.get(nextChunkToConsume).setDownloadState(DownloadState.IN_PROGRESS);\n          chunks.get(nextChunkToConsume).reset();\n        } finally {\n          chunks.get(nextChunkToConsume).getLock().unlock();\n        }\n\n        // random jitter before start next retry\n        Thread.sleep(new Random().nextInt(MAX_RETRY_JITTER));\n\n        downloaderFuture =\n            executor.submit(\n                getDownloadChunkCallable(\n                    this,\n                    chunks.get(nextChunkToConsume),\n                    qrmk,\n                    nextChunkToConsume,\n                    chunkHeadersMap,\n                    networkTimeoutInMilli,\n                    authTimeout,\n                    socketTimeout,\n                    maxHttpRetries,\n                    session,\n                    chunks.size(),\n                    queryId));\n        downloaderFutures.put(nextChunkToConsume, downloaderFuture);\n        // Only when prefetch fails due to internal memory limitation, nextChunkToDownload\n        // equals nextChunkToConsume. In that case we need to increment nextChunkToDownload\n        if (nextChunkToDownload == nextChunkToConsume) {\n          nextChunkToDownload = nextChunkToConsume + 1;\n        }\n      }\n\n      // exit if chunk has downloaded or we have hit max retry\n      // maxHttpRetries = 0 will retry indefinitely\n      if (currentChunk.getDownloadState() == DownloadState.SUCCESS\n          || (maxHttpRetries > 0 && retry >= maxHttpRetries)) {\n        break;\n      }\n    }\n    if (currentChunk.getDownloadState() == DownloadState.SUCCESS) {\n      logger.debug(\"Ready to consume chunk#{}, succeed retry={}\", nextChunkToConsume, retry);\n    } else if (retry >= maxHttpRetries) {\n      // stop retrying and report failure\n      currentChunk.setDownloadState(DownloadState.FAILURE);\n      currentChunk.setDownloadError(\n          String.format(\n              \"Max retry reached for the download of chunk#%d \"\n                  + \"(Total chunks: %d) retry: %d, error: %s\",\n              nextChunkToConsume,\n              this.chunks.size(),\n              retry,\n              chunks.get(nextChunkToConsume).getDownloadError()));\n    }\n    this.numberMillisWaitingForChunks += (System.currentTimeMillis() - startTime);\n  }\n\n  /** log out of memory error and provide the suggestion to avoid this error */\n  private void logOutOfMemoryError() {\n    logger.error(\n        \"Dump some crucial information below:\\n\"\n            + \"Total milliseconds waiting for chunks: {},\\n\"\n            + \"Total memory used: {}, Max heap size: {}, total download time: {} millisec,\\n\"\n            + \"total parsing time: {} milliseconds, total chunks: {},\\n\"\n            + \"currentMemoryUsage in Byte: {}, currentMemoryLimit in Bytes: {} \\n\"\n            + \"nextChunkToDownload: {}, nextChunkToConsume: {}\\n\"\n            + \"Several suggestions to try to resolve the OOM issue:\\n\"\n            + \"1. increase the JVM heap size if you have more space; or \\n\"\n            + \"2. use CLIENT_MEMORY_LIMIT to reduce the memory usage by the JDBC driver \"\n            + \"(https://docs.snowflake.net/manuals/sql-reference/parameters.html#client-memory-limit)3.\"\n            + \" please make sure 2 * CLIENT_PREFETCH_THREADS * CLIENT_RESULT_CHUNK_SIZE <\"\n            + \" CLIENT_MEMORY_LIMIT. If not, please reduce CLIENT_PREFETCH_THREADS and\"\n            + \" CLIENT_RESULT_CHUNK_SIZE too.\",\n        numberMillisWaitingForChunks,\n        Runtime.getRuntime().totalMemory(),\n        Runtime.getRuntime().maxMemory(),\n        totalMillisDownloadingChunks.get(),\n        totalMillisParsingChunks.get(),\n        chunks.size(),\n        currentMemoryUsage,\n        memoryLimit,\n        nextChunkToDownload,\n        nextChunkToConsume);\n  }\n\n  /**\n   * terminate the downloader\n   *\n   * @return chunk downloader metrics collected over instance lifetime\n   */\n  @Override\n  public DownloaderMetrics terminate() throws InterruptedException {\n    if (!terminated.getAndSet(true)) {\n      try {\n        if (executor != null) {\n          if (!executor.isShutdown()) {\n            // cancel running downloaders\n            downloaderFutures.forEach((k, v) -> v.cancel(true));\n            // shutdown executor\n            executor.shutdown();\n\n            if (!executor.awaitTermination(SHUTDOWN_TIME, TimeUnit.SECONDS)) {\n              logger.debug(\"Executor did not terminate in the specified time.\", false);\n              List<Runnable> droppedTasks = executor.shutdownNow(); // optional **\n              logger.debug(\n                  \"Executor was abruptly shut down. {} tasks will not be executed.\",\n                  droppedTasks.size()); // optional **\n            }\n          }\n          // Normal flow will never hit here. This is only for testing purposes\n          if (SnowflakeChunkDownloader.injectedDownloaderException != null\n              && injectedDownloaderException instanceof InterruptedException) {\n            throw (InterruptedException) SnowflakeChunkDownloader.injectedDownloaderException;\n          }\n        }\n\n        long totalUncompressedSize =\n            chunks.stream()\n                .reduce(0L, (acc, chunk) -> acc + chunk.getUncompressedSize(), Long::sum);\n        long rowsInChunks =\n            chunks.stream().reduce(0L, (acc, chunk) -> acc + chunk.getRowCount(), Long::sum);\n        long chunksSize = chunks.size();\n\n        logger.debug(\n            \"Completed processing {} {} chunks for query {} in {} ms. Download took {} ms (average: {} ms),\"\n                + \" parsing took {} ms (average: {} ms). Chunks uncompressed size: {} MB (average: {} MB),\"\n                + \" rows in chunks: {} (total: {}, average in chunk: {}), total memory used: {} MB\",\n            chunksSize,\n            queryResultFormat == QueryResultFormat.ARROW ? \"ARROW\" : \"JSON\",\n            queryId,\n            totalMillisParsingChunks.get() + totalMillisDownloadingChunks.get(),\n            totalMillisDownloadingChunks.get(),\n            totalMillisDownloadingChunks.get() / chunksSize,\n            totalMillisParsingChunks,\n            totalMillisParsingChunks.get() / chunksSize,\n            totalUncompressedSize / MB,\n            totalUncompressedSize / MB / chunksSize,\n            rowsInChunks,\n            firstChunkRowCount + rowsInChunks,\n            rowsInChunks / chunksSize,\n            Runtime.getRuntime().totalMemory() / MB);\n\n        return new DownloaderMetrics(\n            numberMillisWaitingForChunks,\n            totalMillisDownloadingChunks.get(),\n            totalMillisParsingChunks.get());\n      } finally {\n        for (SnowflakeResultChunk chunk : chunks) {\n          // explicitly free each chunk since Arrow chunk may hold direct memory\n          chunk.freeData();\n        }\n        if (queryResultFormat == QueryResultFormat.ARROW) {\n          SFArrowResultSet.closeRootAllocator(rootAllocator);\n        } else {\n          chunkDataCache.clear();\n        }\n        releaseAllChunkMemoryUsage();\n        chunks = null;\n      }\n    }\n    return null;\n  }\n\n  /**\n   * add download time\n   *\n   * @param downloadTime Time for downloading a single chunk\n   */\n  private void addDownloadTime(long downloadTime) {\n    this.totalMillisDownloadingChunks.addAndGet(downloadTime);\n  }\n\n  /**\n   * add parsing time\n   *\n   * @param parsingTime Time for parsing a single chunk\n   */\n  private void addParsingTime(long parsingTime) {\n    this.totalMillisParsingChunks.addAndGet(parsingTime);\n  }\n\n  /**\n   * Create a download callable that will be run in download thread\n   *\n   * @param downloader object to download the chunk\n   * @param resultChunk object contains information about the chunk will be downloaded\n   * @param qrmk Query Result Master Key\n   * @param chunkIndex the index of the chunk which will be downloaded in array chunks. This is\n   *     mainly for logging purpose\n   * @param chunkHeadersMap contains headers needed to be added when downloading from s3\n   * @param networkTimeoutInMilli network timeout\n   * @param totalChunks used to log the information of total chunks\n   * @param queryId used to log the queryId to which the chunk belongs to\n   * @return A callable responsible for downloading chunk\n   */\n  private static Callable<Void> getDownloadChunkCallable(\n      final SnowflakeChunkDownloader downloader,\n      final SnowflakeResultChunk resultChunk,\n      final String qrmk,\n      final int chunkIndex,\n      final Map<String, String> chunkHeadersMap,\n      final int networkTimeoutInMilli,\n      final int authTimeout,\n      final int socketTimeout,\n      final int maxHttpRetries,\n      final SFBaseSession session,\n      final int totalChunks,\n      final String queryId) {\n    ChunkDownloadContext downloadContext =\n        new ChunkDownloadContext(\n            downloader,\n            resultChunk,\n            qrmk,\n            chunkIndex,\n            chunkHeadersMap,\n            networkTimeoutInMilli,\n            authTimeout,\n            socketTimeout,\n            maxHttpRetries,\n            session);\n\n    return new Callable<Void>() {\n\n      /**\n       * Read the input stream and parse chunk data into memory\n       *\n       * @param inputStream\n       * @throws SnowflakeSQLException\n       */\n      private void downloadAndParseChunk(InputStream inputStream) throws SnowflakeSQLException {\n        // remember the download time\n        resultChunk.setDownloadTime(System.currentTimeMillis() - startTime);\n        downloader.addDownloadTime(resultChunk.getDownloadTime());\n\n        startTime = System.currentTimeMillis();\n\n        // parse the result json\n        try {\n          if (downloader.queryResultFormat == QueryResultFormat.ARROW) {\n            ((ArrowResultChunk) resultChunk).readArrowStream(inputStream);\n          } else {\n            parseJsonToChunkV2(inputStream, resultChunk);\n          }\n        } catch (Exception ex) {\n          logger.debug(\n              \"Thread {} Exception when parsing result chunk#{}: {}\",\n              Thread.currentThread().getId(),\n              chunkIndex,\n              ex.getLocalizedMessage());\n\n          throw new SnowflakeSQLLoggedException(\n              session,\n              SqlState.INTERNAL_ERROR,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              ex,\n              \"Exception: \" + ex.getLocalizedMessage());\n        } finally {\n          // close the buffer reader will close underlying stream\n          logger.debug(\n              \"Thread {} close input stream for chunk#{}\",\n              Thread.currentThread().getId(),\n              chunkIndex);\n          try {\n            inputStream.close();\n          } catch (IOException ex) {\n            throw new SnowflakeSQLLoggedException(\n                session,\n                SqlState.INTERNAL_ERROR,\n                ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                ex,\n                \"Exception: \" + ex.getLocalizedMessage());\n          }\n        }\n\n        // add parsing time\n        resultChunk.setParseTime(System.currentTimeMillis() - startTime);\n        downloader.addParsingTime(resultChunk.getParseTime());\n      }\n\n      private long startTime;\n\n      public Void call() {\n        resultChunk.getLock().lock();\n        try {\n          resultChunk.setDownloadState(DownloadState.IN_PROGRESS);\n        } finally {\n          resultChunk.getLock().unlock();\n        }\n\n        logger.debug(\n            \"Downloading chunk#{}, url: {}, Thread {}\",\n            chunkIndex,\n            resultChunk.getUrl(),\n            Thread.currentThread().getId());\n\n        startTime = System.currentTimeMillis();\n\n        // initialize the telemetry service for this downloader thread using the main telemetry\n        // service\n        TelemetryService.getInstance().updateContext(downloader.snowflakeConnectionString);\n\n        try {\n          if (SnowflakeChunkDownloader.injectedDownloaderException != null) {\n            // Normal flow will never hit here. This is only for testing purpose\n            throw SnowflakeChunkDownloader.injectedDownloaderException;\n          }\n\n          InputStream is = downloader.getResultStreamProvider().getInputStream(downloadContext);\n          logger.debug(\n              \"Thread {} start downloading chunk#{}\", Thread.currentThread().getId(), chunkIndex);\n          downloadAndParseChunk(is);\n          logger.debug(\n              \"Thread {} finish downloading chunk#{}\", Thread.currentThread().getId(), chunkIndex);\n          downloader.downloaderFutures.remove(chunkIndex);\n          if (chunkIndex % 5 == 0) {\n            logger.debug(\n                \"Processed {} chunk#{} in {} ms ({} out of {}) for query {}. Download took {} ms, \"\n                    + \"parsing took {} ms. Chunk uncompressed size: {} kB, cols: {}, rows: {}, scrubbed URL: {}\",\n                downloader.queryResultFormat == QueryResultFormat.ARROW ? \"ARROW\" : \"JSON\",\n                chunkIndex,\n                resultChunk.getTotalTime(),\n                chunkIndex + 1,\n                totalChunks,\n                queryId,\n                resultChunk.getDownloadTime(),\n                resultChunk.getParseTime(),\n                resultChunk.getUncompressedSize() / 1024,\n                resultChunk.colCount,\n                resultChunk.rowCount,\n                resultChunk.getScrubbedUrl());\n          } else {\n            logger.trace(\n                \"Processed {} chunk#{} in {} ms ({} out of {}) for query {}. Download took {} ms, \"\n                    + \"parsing took {} ms. Chunk uncompressed size: {} kB, cols: {}, rows: {}, scrubbed URL: {}\",\n                downloader.queryResultFormat == QueryResultFormat.ARROW ? \"ARROW\" : \"JSON\",\n                chunkIndex,\n                resultChunk.getTotalTime(),\n                chunkIndex + 1,\n                totalChunks,\n                queryId,\n                resultChunk.getDownloadTime(),\n                resultChunk.getParseTime(),\n                resultChunk.getUncompressedSize() / 1024,\n                resultChunk.colCount,\n                resultChunk.rowCount,\n                resultChunk.getScrubbedUrl());\n          }\n\n          resultChunk.getLock().lock();\n          try {\n            logger.debug(\"Get lock to change the chunk to be ready to consume\", false);\n\n            logger.debug(\"Wake up consumer if it is waiting for a chunk to be ready\", false);\n\n            resultChunk.setDownloadState(DownloadState.SUCCESS);\n            resultChunk.getDownloadCondition().signal();\n          } finally {\n            logger.debug(\"Downloaded chunk#{}, free lock\", chunkIndex);\n\n            resultChunk.getLock().unlock();\n          }\n        } catch (Throwable th) {\n          resultChunk.getLock().lock();\n          try {\n            logger.debug(\"Get lock to set chunk download error\", false);\n            resultChunk.setDownloadState(DownloadState.FAILURE);\n            downloader.releaseCurrentMemoryUsage(chunkIndex, Optional.empty());\n            StringWriter errors = new StringWriter();\n            th.printStackTrace(new PrintWriter(errors));\n            resultChunk.setDownloadError(errors.toString());\n\n            logger.debug(\"Wake up consumer if it is waiting for a chunk to be ready\", false);\n\n            resultChunk.getDownloadCondition().signal();\n          } finally {\n            logger.debug(\"Failed to download chunk#{}, free lock\", chunkIndex);\n            resultChunk.getLock().unlock();\n          }\n\n          logger.debug(\n              \"Thread {} Exception encountered ({}:{}) fetching chunk#{} from: {}, Error {}\",\n              Thread.currentThread().getId(),\n              th.getClass().getName(),\n              th.getLocalizedMessage(),\n              chunkIndex,\n              resultChunk.getScrubbedUrl(),\n              resultChunk.getDownloadError());\n        }\n\n        return null;\n      }\n\n      private void parseJsonToChunkV2(InputStream jsonInputStream, SnowflakeResultChunk resultChunk)\n          throws IOException, SnowflakeSQLException {\n        /*\n         * This is a hand-written binary parser that\n         * handle.\n         *   [ \"c1\", \"c2\", null, ... ],\n         *   [ null, \"c2\", \"c3\", ... ],\n         *   ...\n         *   [ \"c1\", \"c2\", \"c3\", ... ],\n         * in UTF-8\n         * The number of rows is known and the number of expected columns\n         * is also known.\n         */\n        ResultJsonParserV2 jp = new ResultJsonParserV2();\n        jp.startParsing((JsonResultChunk) resultChunk, session);\n\n        byte[] buf = new byte[STREAM_BUFFER_SIZE];\n\n        // To be used to copy the leftover buffer data in the case of buffer ending in escape state\n        // during parsing.\n        byte[] prevBuffer = null;\n        ByteBuffer bBuf = null;\n        int len;\n        logger.debug(\n            \"Thread {} start to read inputstream for chunk#{}\",\n            Thread.currentThread().getId(),\n            chunkIndex);\n        while ((len = jsonInputStream.read(buf)) != -1) {\n          if (prevBuffer != null) {\n            // if parsing stopped during an escape sequence in jp.continueParsing() and there is\n            // leftover data in the buffer,\n            // prepend the copied data to the next buffer read from the output stream.\n            ByteArrayOutputStream os = new ByteArrayOutputStream();\n            os.write(prevBuffer);\n            os.write(buf);\n            buf = os.toByteArray();\n            len += prevBuffer.length;\n          }\n          bBuf = ByteBuffer.wrap(buf, 0, len);\n          jp.continueParsing(bBuf, session);\n          if (bBuf.remaining() > 0) {\n            // if there is any data left un-parsed, it will be prepended to the next buffer read.\n            prevBuffer = new byte[bBuf.remaining()];\n            bBuf.get(prevBuffer);\n          } else {\n            prevBuffer = null;\n          }\n        }\n        logger.debug(\n            \"Thread {} finish reading inputstream for chunk#{}\",\n            Thread.currentThread().getId(),\n            chunkIndex);\n        if (prevBuffer != null) {\n          bBuf = ByteBuffer.wrap(prevBuffer);\n        } else {\n          bBuf = ByteBuffer.wrap(new byte[0]);\n        }\n        jp.endParsing(bBuf, session);\n      }\n    };\n  }\n\n  /** This is a No Operation chunk downloader to avoid potential null pointer exception */\n  public static class NoOpChunkDownloader implements ChunkDownloader {\n    @Override\n    public SnowflakeResultChunk getNextChunkToConsume() throws SnowflakeSQLException {\n      return null;\n    }\n\n    @Override\n    public DownloaderMetrics terminate() {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeClob.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.io.Writer;\nimport java.sql.Clob;\nimport java.sql.SQLException;\n\n/** A simple Clob implementation using String */\npublic class SnowflakeClob implements Clob {\n  private StringBuffer buffer;\n\n  private class StringBufferWriter extends Writer {\n    private StringBuffer main;\n    private StringBuffer current;\n\n    /** */\n    public StringBufferWriter(StringBuffer buffer, int pos) {\n      super();\n      this.main = buffer;\n      this.current = new StringBuffer();\n    }\n\n    @Override\n    public void write(final char[] cbuf, final int off, final int len) throws IOException {\n      for (int i = 0; i < len; i++) {\n        this.current.append(cbuf[off + i]);\n      }\n    }\n\n    @Override\n    public void flush() throws IOException {\n      this.main.append(this.current);\n      this.current.delete(0, this.current.length());\n    }\n\n    @Override\n    public void close() throws IOException {\n      if (this.current == null) {\n        throw new IOException();\n      }\n      flush();\n      this.current = null;\n    }\n  }\n\n  private class StringBufferOutputStream extends OutputStream {\n    private StringBuffer buffer;\n    private int offset;\n\n    /** */\n    public StringBufferOutputStream(StringBuffer buffer, int pos) {\n      super();\n      this.buffer = buffer;\n      this.offset = pos - 1;\n    }\n\n    /*\n     * @see java.io.OutputStream#write(int)\n     */\n    public void write(int c) throws IOException {\n      if (this.offset >= this.buffer.length()) {\n        buffer.append((char) c);\n      } else {\n        buffer.replace(this.offset, this.offset + 1, Integer.toString(c));\n      }\n    }\n\n    public String toString() {\n      return buffer.toString();\n    }\n\n    public void clear() {\n      buffer.delete(0, buffer.length());\n    }\n  }\n\n  public SnowflakeClob() {\n    buffer = new StringBuffer();\n  }\n\n  public SnowflakeClob(String content) {\n    buffer = new StringBuffer(content);\n  }\n\n  @Override\n  public long length() throws SQLException {\n    return buffer.length();\n  }\n\n  @Override\n  public String getSubString(final long pos, final int length) throws SQLException {\n    if (pos < 1 || length < 0) {\n      throw new SQLException();\n    }\n    return buffer.substring((int) pos - 0, (int) pos - 0 + length);\n  }\n\n  @Override\n  public Reader getCharacterStream() throws SQLException {\n    return new StringReader(buffer.toString());\n  }\n\n  @Override\n  public InputStream getAsciiStream() throws SQLException {\n    return new ByteArrayInputStream(buffer.toString().getBytes());\n  }\n\n  @Override\n  public long position(final String searchstr, final long start) throws SQLException {\n    if (start < 1) {\n      throw new SQLException();\n    }\n    return (long) buffer.lastIndexOf(searchstr, (int) start - 1);\n  }\n\n  @Override\n  public long position(final Clob searchstr, final long start) throws SQLException {\n    if (start < 1) {\n      throw new SQLException();\n    }\n    return (long) buffer.lastIndexOf(searchstr.toString(), (int) start - 1);\n  }\n\n  @Override\n  public int setString(final long pos, final String str) throws SQLException {\n    if (pos < 1) {\n      throw new SQLException();\n    }\n    buffer.insert((int) pos - 1, str);\n    return str.length();\n  }\n\n  @Override\n  public int setString(final long pos, final String str, final int offset, final int len)\n      throws SQLException {\n    if (pos < 1) {\n      throw new SQLException();\n    }\n    String substring = str.substring(offset, len);\n    buffer.insert((int) pos - 1, substring);\n    return substring.length();\n  }\n\n  @Override\n  public OutputStream setAsciiStream(final long pos) throws SQLException {\n    return new StringBufferOutputStream(buffer, (int) pos);\n  }\n\n  @Override\n  public Writer setCharacterStream(final long pos) throws SQLException {\n    return new StringBufferWriter(buffer, (int) pos);\n  }\n\n  @Override\n  public void truncate(final long len) throws SQLException {\n    if (buffer.length() > len) {\n      buffer.delete((int) len, buffer.length());\n    }\n  }\n\n  @Override\n  public void free() throws SQLException {\n    buffer = new StringBuffer();\n  }\n\n  @Override\n  public Reader getCharacterStream(final long pos, final long length) throws SQLException {\n    return new StringReader(buffer.substring((int) pos - 1, (int) pos - 1 + (int) length));\n  }\n\n  @Override\n  public String toString() {\n    return buffer.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeColumn.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static java.lang.annotation.ElementType.FIELD;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Target({FIELD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface SnowflakeColumn {\n\n  /**\n   * (Optional) The name for a column in database,\n   *\n   * @return The default value is empty string. Provided name can override SqlData field name.\n   */\n  String name() default \"\";\n\n  /**\n   * (Optional) The snowflake type for a column\n   *\n   * @return The default value is empty string Provided type can override default type.\n   */\n  String type() default \"\";\n\n  /**\n   * (Optional) The snowflake nullable flag for a column\n   *\n   * @return The default value is true Provided value can override default nullable value.\n   */\n  boolean nullable() default true;\n\n  /**\n   * (Optional) The length for a column of SQL type {@code varchar} or {@code binary}, or of similar\n   * database-native type.\n   *\n   * <p>Applies only to columns of exact varchar and binary type.\n   *\n   * @return The default value {@code -1} indicates that a provider-determined length should be\n   *     inferred.\n   */\n  int length() default -1;\n  /**\n   * (Optional) The length for a column of SQL type {@code binary}, or of similar database-native\n   * type.\n   *\n   * <p>Applies only to columns of exact varchar and binary type.\n   *\n   * @return The default value {@code -1} indicates that a provider-determined byteLength should be\n   *     inferred.\n   */\n  int byteLength() default -1;\n\n  /**\n   * (Optional) The precision for a column of SQL type {@code decimal} or {@code numeric}, or of\n   * similar database-native type.\n   *\n   * <p>Applies only to columns of exact numeric type.\n   *\n   * @return The default value {@code -1} indicates that a provider-determined precision should be\n   *     inferred.\n   */\n  int precision() default -1;\n\n  /**\n   * (Optional) The scale for a column of SQL type {@code decimal} or {@code numeric}, or of similar\n   * database-native type.\n   *\n   * <p>Applies only to columns of exact numeric type.\n   *\n   * @return The default value {@code 0} indicates that a provider-determined scale should be\n   *     inferred.\n   */\n  int scale() default -1;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeColumnMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.getFieldMetadata;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.getSnowflakeType;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isVectorType;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.io.Serializable;\nimport java.sql.Types;\nimport java.util.List;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\n\npublic class SnowflakeColumnMetadata implements Serializable {\n  private static final long serialVersionUID = 1L;\n  private String name;\n  private String typeName;\n  private int type;\n  private boolean nullable;\n  private int length;\n  private int precision;\n  private int scale;\n  private boolean fixed;\n  private SnowflakeType base;\n  private List<FieldMetadata> fields;\n  private String columnSrcTable;\n  private String columnSrcSchema;\n  private String columnSrcDatabase;\n\n  private boolean isAutoIncrement;\n  private int dimension; // vector type contains dimension\n\n  public SnowflakeColumnMetadata(\n      String name,\n      int type,\n      boolean nullable,\n      int length,\n      int precision,\n      int scale,\n      String typeName,\n      boolean fixed,\n      SnowflakeType base,\n      List<FieldMetadata> fields,\n      String columnSrcDatabase,\n      String columnSrcSchema,\n      String columnSrcTable,\n      boolean isAutoIncrement,\n      int dimension) {\n    this.name = name;\n    this.type = type;\n    this.nullable = nullable;\n    this.length = length;\n    this.precision = precision;\n    this.scale = scale;\n    this.typeName = typeName;\n    this.fixed = fixed;\n    this.base = base;\n    this.fields = fields;\n    this.columnSrcDatabase = columnSrcDatabase;\n    this.columnSrcSchema = columnSrcSchema;\n    this.columnSrcTable = columnSrcTable;\n    this.isAutoIncrement = isAutoIncrement;\n    this.dimension = dimension;\n  }\n\n  /**\n   * @deprecated Use {@link SnowflakeColumnMetadata#SnowflakeColumnMetadata(String, int, boolean,\n   *     int, int, int, String, boolean, SnowflakeType, List, String, String, String, boolean, int)}\n   *     instead\n   * @param name name\n   * @param type type\n   * @param nullable is nullable\n   * @param length length\n   * @param precision precision\n   * @param scale scale\n   * @param typeName type name\n   * @param fixed is fixed\n   * @param base SnowflakeType\n   * @param columnSrcDatabase column source database\n   * @param columnSrcSchema column source schema\n   * @param columnSrcTable column source table\n   * @param isAutoIncrement is auto-increment\n   */\n  @Deprecated\n  public SnowflakeColumnMetadata(\n      String name,\n      int type,\n      boolean nullable,\n      int length,\n      int precision,\n      int scale,\n      String typeName,\n      boolean fixed,\n      SnowflakeType base,\n      String columnSrcDatabase,\n      String columnSrcSchema,\n      String columnSrcTable,\n      boolean isAutoIncrement) {\n    this.name = name;\n    this.type = type;\n    this.nullable = nullable;\n    this.length = length;\n    this.precision = precision;\n    this.scale = scale;\n    this.typeName = typeName;\n    this.fixed = fixed;\n    this.base = base;\n    this.columnSrcDatabase = columnSrcDatabase;\n    this.columnSrcSchema = columnSrcSchema;\n    this.columnSrcTable = columnSrcTable;\n    this.isAutoIncrement = isAutoIncrement;\n  }\n\n  public SnowflakeColumnMetadata(\n      JsonNode colNode, boolean jdbcTreatDecimalAsInt, SFBaseSession session)\n      throws SnowflakeSQLLoggedException {\n    this.name = colNode.path(\"name\").asText();\n    this.nullable = colNode.path(\"nullable\").asBoolean();\n    this.precision = colNode.path(\"precision\").asInt();\n    this.scale = colNode.path(\"scale\").asInt();\n    this.length = colNode.path(\"length\").asInt();\n    int dimension =\n        colNode\n            .path(\"dimension\")\n            .asInt(); // vector dimension when checking columns via connection.getMetadata\n    int vectorDimension =\n        colNode\n            .path(\"vectorDimension\")\n            .asInt(); // dimension when checking columns via resultSet.getMetadata\n    this.dimension = dimension > 0 ? dimension : vectorDimension;\n    this.fixed = colNode.path(\"fixed\").asBoolean();\n    JsonNode udtOutputType = colNode.path(\"outputType\");\n    JsonNode extColTypeNameNode = colNode.path(\"extTypeName\");\n    String extColTypeName = null;\n    if (!extColTypeNameNode.isMissingNode() && !isNullOrEmpty(extColTypeNameNode.asText())) {\n      extColTypeName = extColTypeNameNode.asText();\n    }\n    String internalColTypeName = colNode.path(\"type\").asText();\n    List<FieldMetadata> fieldsMetadata =\n        getFieldMetadata(jdbcTreatDecimalAsInt, internalColTypeName, colNode);\n\n    int fixedColType = jdbcTreatDecimalAsInt && scale == 0 ? Types.BIGINT : Types.DECIMAL;\n    ColumnTypeInfo columnTypeInfo =\n        getSnowflakeType(\n            internalColTypeName,\n            extColTypeName,\n            udtOutputType,\n            session,\n            fixedColType,\n            !fieldsMetadata.isEmpty(),\n            isVectorType(internalColTypeName));\n\n    this.typeName = columnTypeInfo.getExtColTypeName();\n    this.type = columnTypeInfo.getColumnType();\n    this.base = columnTypeInfo.getSnowflakeType();\n    this.fields = fieldsMetadata;\n    this.columnSrcDatabase = colNode.path(\"database\").asText();\n    this.columnSrcSchema = colNode.path(\"schema\").asText();\n    this.columnSrcTable = colNode.path(\"table\").asText();\n    this.isAutoIncrement = colNode.path(\"isAutoIncrement\").asBoolean();\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public void setName(String name) {\n    this.name = name;\n  }\n\n  public int getType() {\n    return type;\n  }\n\n  public void setType(int type) {\n    this.type = type;\n  }\n\n  public boolean isNullable() {\n    return nullable;\n  }\n\n  public void setNullable(boolean nullable) {\n    this.nullable = nullable;\n  }\n\n  public int getLength() {\n    return length;\n  }\n\n  public void setLength(int length) {\n    this.length = length;\n  }\n\n  public int getPrecision() {\n    return precision;\n  }\n\n  public void setPrecision(int precision) {\n    this.precision = precision;\n  }\n\n  public int getScale() {\n    return scale;\n  }\n\n  public void setScale(int scale) {\n    this.scale = scale;\n  }\n\n  public String getTypeName() {\n    return typeName;\n  }\n\n  public void setTypeName(String typeName) {\n    this.typeName = typeName;\n  }\n\n  public boolean isFixed() {\n    return fixed;\n  }\n\n  public void setFixed(boolean fixed) {\n    this.fixed = fixed;\n  }\n\n  public SnowflakeType getBase() {\n    return this.base;\n  }\n\n  public List<FieldMetadata> getFields() {\n    return fields;\n  }\n\n  public void setFields(List<FieldMetadata> fields) {\n    this.fields = fields;\n  }\n\n  public String getColumnSrcTable() {\n    return this.columnSrcTable;\n  }\n\n  public String getColumnSrcSchema() {\n    return this.columnSrcSchema;\n  }\n\n  public String getColumnSrcDatabase() {\n    return this.columnSrcDatabase;\n  }\n\n  public boolean isAutoIncrement() {\n    return isAutoIncrement;\n  }\n\n  public void setAutoIncrement(boolean autoIncrement) {\n    isAutoIncrement = autoIncrement;\n  }\n\n  public int getDimension() {\n    return dimension;\n  }\n\n  public String toString() {\n    StringBuilder sBuilder = new StringBuilder();\n\n    sBuilder.append(\"name=\").append(name);\n    sBuilder.append(\",typeName=\").append(typeName);\n    sBuilder.append(\",type=\").append(type);\n    sBuilder.append(\",nullable=\").append(nullable);\n    sBuilder.append(\",length=\").append(length);\n    sBuilder.append(\",precision=\").append(precision);\n    sBuilder.append(\",scale=\").append(scale);\n    sBuilder.append(\",fixed=\").append(fixed);\n    sBuilder.append(\",database=\").append(columnSrcDatabase);\n    sBuilder.append(\",schema=\").append(columnSrcSchema);\n    sBuilder.append(\",table=\").append(columnSrcTable);\n    sBuilder.append((\",isAutoIncrement=\")).append(isAutoIncrement);\n    sBuilder.append((\",dimension=\")).append(dimension);\n\n    return sBuilder.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeConnectString.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport java.io.Serializable;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\n\npublic class SnowflakeConnectString implements Serializable {\n  private static final long serialVersionUID = 1L;\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeConnectString.class);\n\n  private final String scheme;\n  private final String host;\n  private final int port;\n  private final Map<String, Object> parameters;\n  private final String account;\n  private static SnowflakeConnectString INVALID_CONNECT_STRING =\n      new SnowflakeConnectString(\"\", \"\", -1, Collections.emptyMap(), \"\");\n\n  private static final String PREFIX = \"jdbc:snowflake://\";\n\n  public static boolean hasSupportedPrefix(String url) {\n    return url.startsWith(PREFIX);\n  }\n\n  public static SnowflakeConnectString parse(String url, Properties info) {\n    if (url == null) {\n      logger.debug(\"Connect strings must be non-null\");\n      return INVALID_CONNECT_STRING;\n    }\n    int pos = url.indexOf(PREFIX);\n    if (pos != 0) {\n      logger.debug(\"Connect strings must start with jdbc:snowflake://\");\n      return INVALID_CONNECT_STRING; // not start with jdbc:snowflake://\n    }\n    String afterPrefix = url.substring(pos + PREFIX.length());\n    String scheme;\n    String host = null;\n    int port = -1;\n    Map<String, Object> parameters = new HashMap<>();\n    try {\n      URI uri;\n\n      if (!afterPrefix.startsWith(\"http://\") && !afterPrefix.startsWith(\"https://\")) {\n        // not explicitly specified\n        afterPrefix = url.substring(url.indexOf(\"snowflake:\"));\n      }\n      uri = new URI(afterPrefix);\n      scheme = uri.getScheme();\n      String authority = uri.getRawAuthority();\n      String[] hostAndPort = authority.split(\":\");\n      if (hostAndPort.length == 2) {\n        host = hostAndPort[0];\n        port = Integer.parseInt(hostAndPort[1]);\n      } else if (hostAndPort.length == 1) {\n        host = hostAndPort[0];\n      }\n      String queryData = uri.getRawQuery();\n\n      if (!scheme.equals(\"snowflake\") && !scheme.equals(\"http\") && !scheme.equals(\"https\")) {\n        logger.debug(\"Connect strings must have a valid scheme: 'snowflake' or 'http' or 'https'\");\n        return INVALID_CONNECT_STRING;\n      }\n      if (isNullOrEmpty(host)) {\n        logger.debug(\"Connect strings must have a valid host: found null or empty host\");\n        return INVALID_CONNECT_STRING;\n      }\n      if (port == -1) {\n        port = 443;\n      }\n      String path = uri.getPath();\n      if (!isNullOrEmpty(path) && !\"/\".equals(path)) {\n        logger.debug(\"Connect strings must have no path: expecting empty or null or '/'\");\n        return INVALID_CONNECT_STRING;\n      }\n      String account = null;\n      if (!isNullOrEmpty(queryData)) {\n        String[] params = queryData.split(\"&\");\n        for (String p : params) {\n          String[] keyVals = p.split(\"=\");\n          if (keyVals.length != 2) {\n            continue; // ignore invalid pair of parameters.\n          }\n          try {\n            String k = URLDecoder.decode(keyVals[0], \"UTF-8\");\n            String v = URLDecoder.decode(keyVals[1], \"UTF-8\");\n            if (\"ssl\".equalsIgnoreCase(k) && !getBooleanTrueByDefault(v)) {\n              scheme = \"http\";\n            } else if (\"account\".equalsIgnoreCase(k)) {\n              account = v;\n            }\n            parameters.put(k.toUpperCase(Locale.US), v);\n          } catch (UnsupportedEncodingException ex0) {\n            logger.warn(\"Failed to decode a parameter {}. Ignored.\", p);\n          }\n        }\n      }\n      if (\"snowflake\".equals(scheme)) {\n        scheme = \"https\"; // by default\n      }\n\n      if (info.size() > 0) {\n        // NOTE: value in info could be any data type.\n        // overwrite the properties\n        for (Map.Entry<Object, Object> entry : info.entrySet()) {\n          String k = entry.getKey().toString();\n          Object v = entry.getValue();\n          if (\"ssl\".equalsIgnoreCase(k) && !getBooleanTrueByDefault(v)) {\n            scheme = \"http\";\n          } else if (\"account\".equalsIgnoreCase(k)) {\n            account = (String) v;\n          }\n          parameters.put(k.toUpperCase(Locale.US), v);\n        }\n      }\n\n      if (parameters.get(\"ACCOUNT\") == null && account == null && host.indexOf(\".\") > 0) {\n        account = host.substring(0, host.indexOf(\".\"));\n        // If this is a global URL, then extract out the external ID part\n        if (host.contains(\".global.\")) {\n          account = account.substring(0, account.lastIndexOf('-'));\n        }\n        // Account names should not be altered. Set it to a value without org name\n        // if it's a global url\n        parameters.put(\"ACCOUNT\", account);\n      }\n\n      if (isNullOrEmpty(account)) {\n        logger.debug(\"Connect strings must contain account identifier\");\n        return INVALID_CONNECT_STRING;\n      }\n\n      // By default, don't allow underscores in host name unless the property is set to true via\n      // connection properties.\n      boolean allowUnderscoresInHost = false;\n      if (\"true\"\n          .equalsIgnoreCase(\n              (String)\n                  parameters.get(\n                      SFSessionProperty.ALLOW_UNDERSCORES_IN_HOST\n                          .getPropertyKey()\n                          .toUpperCase()))) {\n        allowUnderscoresInHost = true;\n      }\n      if (account.contains(\"_\") && !allowUnderscoresInHost && host.startsWith(account)) {\n        // The account needs to have underscores in it and the host URL needs to start\n        // with the account name. There are cases where the host URL might not have the\n        // the account name in it, ex - ip address instead of host name.\n        // The property allowUnderscoresInHost needs to be set to false.\n        // Update the Host URL to remove underscores if there are any\n        String account_wo_uscores = account.replaceAll(\"_\", \"-\");\n        host = host.replaceFirst(account, account_wo_uscores);\n      }\n\n      return new SnowflakeConnectString(scheme, host, port, parameters, account);\n    } catch (URISyntaxException uriEx) {\n      logger.warn(\n          \"Exception thrown while parsing Snowflake connect string. Illegal character in url.\");\n      return INVALID_CONNECT_STRING;\n    } catch (Exception ex) {\n      logger.warn(\"Exception thrown while parsing Snowflake connect string\", ex);\n      return INVALID_CONNECT_STRING;\n    }\n  }\n\n  private SnowflakeConnectString(\n      String scheme, String host, int port, Map<String, Object> parameters, String account) {\n    this.scheme = scheme;\n    this.host = host;\n    this.port = port;\n    this.parameters = parameters;\n    this.account = account;\n  }\n\n  public String toString() {\n    return toString(true);\n  }\n\n  public String toString(boolean maskSensitiveValue) {\n    StringBuilder urlStr = new StringBuilder();\n    urlStr.append(scheme);\n    urlStr.append(\"://\");\n    urlStr.append(host);\n    urlStr.append(\":\");\n    urlStr.append(port);\n\n    urlStr.append(parameters.size() > 0 ? \"?\" : \"\");\n\n    int cnt = 0;\n    for (Map.Entry<String, Object> entry : parameters.entrySet()) {\n      if (cnt > 0) {\n        urlStr.append('&');\n      }\n      try {\n        String k = URLEncoder.encode(entry.getKey(), \"UTF-8\");\n        String v = URLEncoder.encode(entry.getValue().toString(), \"UTF-8\");\n        urlStr.append(k).append('=');\n        if (maskSensitiveValue) {\n          urlStr.append(SecretDetector.maskParameterValue(k, v));\n        } else {\n          urlStr.append(v);\n        }\n      } catch (UnsupportedEncodingException ex) {\n        logger.warn(\"Failed to encode a parameter {}. Ignored.\", entry.getKey());\n      }\n      ++cnt;\n    }\n    return urlStr.toString();\n  }\n\n  public boolean isValid() {\n    // invalid if host name is null or empty\n    return !isNullOrEmpty(host);\n  }\n\n  public String getScheme() {\n    return scheme;\n  }\n\n  public String getHost() {\n    return host;\n  }\n\n  public int getPort() {\n    return port;\n  }\n\n  public Map<String, Object> getParameters() {\n    return parameters;\n  }\n\n  public String getAccount() {\n    return account;\n  }\n\n  private static boolean getBooleanTrueByDefault(Object value) {\n    if (value instanceof Boolean) {\n      return (Boolean) value;\n    }\n    String vs = value.toString();\n    return !\"off\".equalsIgnoreCase(vs) && !Boolean.FALSE.toString().equalsIgnoreCase(vs);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeDatabaseMetaDataQueryResultSet.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\n/** Database Metadata query based result set. */\npublic class SnowflakeDatabaseMetaDataQueryResultSet extends SnowflakeDatabaseMetaDataResultSet {\n\n  public SnowflakeDatabaseMetaDataQueryResultSet(\n      DBMetadataResultSetMetadata metadataType, ResultSet resultSet, Statement statement)\n      throws SQLException {\n    super(\n        metadataType.getColumnNames(),\n        metadataType.getColumnTypeNames(),\n        metadataType.getColumnTypes(),\n        resultSet,\n        statement);\n  }\n\n  /**\n   * Query result set cannot tell the last row.\n   *\n   * @return n/a\n   * @throws SQLException if the result set is closed or SQLFeatureNotSupportedException\n   */\n  @Override\n  public boolean isLast() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  /**\n   * Query result set cannot tell after the last row.\n   *\n   * @return n/a\n   * @throws SQLException if the result set is closed or SQLFeatureNotSupportedException\n   */\n  @Override\n  public boolean isAfterLast() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeDatabaseMetaDataResultSet.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFResultSetMetaData;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\n\npublic class SnowflakeDatabaseMetaDataResultSet extends SnowflakeBaseResultSet {\n  protected ResultSet showObjectResultSet;\n  protected Object[] nextRow;\n  private boolean wasNull = false;\n  protected Object[][] rows;\n  protected int row = -1;\n\n  private String queryId;\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeDatabaseMetaDataResultSet.class);\n\n  /**\n   * DatabaseMetadataResultSet based on result from show command\n   *\n   * @param columnNames column names\n   * @param columnTypeNames column type names\n   * @param columnTypes column types\n   * @param showObjectResultSet result set after issuing a show command\n   * @param statement show command statement\n   * @throws SQLException if failed to construct snowflake database metadata result set\n   */\n  public SnowflakeDatabaseMetaDataResultSet(\n      final List<String> columnNames,\n      final List<String> columnTypeNames,\n      final List<Integer> columnTypes,\n      final ResultSet showObjectResultSet,\n      final Statement statement)\n      throws SQLException {\n    super(statement);\n    this.showObjectResultSet = showObjectResultSet;\n\n    SFBaseSession session =\n        statement\n            .getConnection()\n            .unwrap(SnowflakeConnectionImpl.class)\n            .getSFBaseSession(internalCallMarker());\n\n    SFResultSetMetaData sfset =\n        new SFResultSetMetaData(\n            columnNames.size(), columnNames, columnTypeNames, columnTypes, session);\n\n    this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfset);\n\n    this.nextRow = new Object[columnNames.size()];\n  }\n\n  /**\n   * DatabaseMetadataResultSet based on a constant rowset.\n   *\n   * @param columnNames column name\n   * @param columnTypeNames column types name\n   * @param columnTypes column type\n   * @param rows returned value of database metadata\n   * @param statement show command statement\n   * @throws SQLException if failed to construct snowflake database metadata result set\n   */\n  public SnowflakeDatabaseMetaDataResultSet(\n      final List<String> columnNames,\n      final List<String> columnTypeNames,\n      final List<Integer> columnTypes,\n      final Object[][] rows,\n      final Statement statement)\n      throws SQLException {\n    super(statement);\n    this.rows = rows;\n\n    SFBaseSession session =\n        statement\n            .getConnection()\n            .unwrap(SnowflakeConnectionImpl.class)\n            .getSFBaseSession(internalCallMarker());\n\n    SFResultSetMetaData sfset =\n        new SFResultSetMetaData(\n            columnNames.size(), columnNames, columnTypeNames, columnTypes, session);\n\n    this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfset);\n\n    this.nextRow = new Object[columnNames.size()];\n  }\n\n  public SnowflakeDatabaseMetaDataResultSet(\n      DBMetadataResultSetMetadata metadataType, Object[][] rows, Statement statement)\n      throws SQLException {\n    this(\n        metadataType.getColumnNames(),\n        metadataType.getColumnTypeNames(),\n        metadataType.getColumnTypes(),\n        rows,\n        statement);\n  }\n\n  public SnowflakeDatabaseMetaDataResultSet(\n      DBMetadataResultSetMetadata metadataType,\n      Object[][] rows,\n      Statement statement,\n      String queryId)\n      throws SQLException {\n    this(\n        metadataType.getColumnNames(),\n        metadataType.getColumnTypeNames(),\n        metadataType.getColumnTypes(),\n        rows,\n        statement);\n    this.queryId = queryId;\n  }\n\n  @Override\n  public boolean isClosed() throws SQLException {\n    // no exception is raised.\n    return statement.isClosed();\n  }\n\n  @Override\n  public boolean next() throws SQLException {\n    logger.trace(\"boolean next()\", false);\n    incrementRow();\n\n    // no exception is raised even after the result set is closed.\n    if (row < rows.length) {\n      nextRow = rows[row];\n      return true;\n    }\n\n    return false;\n  }\n\n  /**\n   * Increments result set row pointer. Mainly used to check the result set isBeforeFirst or\n   * isFirst.\n   */\n  protected void incrementRow() {\n    ++row;\n  }\n\n  @Override\n  public void close() throws SQLException {\n    // no exception\n    try {\n      getStatement().close(); // should close both result set and statement.\n    } catch (SQLException ex) {\n      logger.debug(\"Failed to close\", ex);\n    }\n  }\n\n  @Override\n  public boolean isFirst() throws SQLException {\n    logger.trace(\"boolean isFirst()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return row == 0;\n  }\n\n  @Override\n  public boolean isBeforeFirst() throws SQLException {\n    logger.trace(\"boolean isBeforeFirst()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return row == -1;\n  }\n\n  @Override\n  public boolean isLast() throws SQLException {\n    logger.trace(\"boolean isLast()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return !isBeforeFirst() && row == rows.length - 1;\n  }\n\n  @Override\n  public boolean isAfterLast() throws SQLException {\n    logger.trace(\"boolean isAfterLast()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return row == rows.length;\n  }\n\n  @Override\n  public int getRow() throws SQLException {\n    logger.trace(\"int getRow()\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return row;\n  }\n\n  @Override\n  public byte[] getBytes(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    String str = this.getString(columnIndex);\n    if (str != null) {\n      return str.getBytes(StandardCharsets.UTF_8);\n    } else {\n      throw new SQLException(\"Cannot get bytes on null column\");\n    }\n  }\n\n  @Override\n  public Time getTime(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj instanceof Time) {\n      return (Time) obj;\n    } else {\n      throw new SnowflakeSQLException(\n          ErrorCode.INVALID_VALUE_CONVERT, obj.getClass().getName(), \"TIME\", obj);\n    }\n  }\n\n  @Override\n  public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj instanceof Timestamp) {\n      return (Timestamp) obj;\n    } else {\n      throw new SnowflakeSQLException(\n          ErrorCode.INVALID_VALUE_CONVERT, obj.getClass().getName(), \"TIMESTAMP\", obj);\n    }\n  }\n\n  @Override\n  public Date getDate(int columnIndex, TimeZone tz) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj instanceof Date) {\n      return (Date) obj;\n    } else {\n      throw new SnowflakeSQLException(\n          ErrorCode.INVALID_VALUE_CONVERT, obj.getClass().getName(), \"DATE\", obj);\n    }\n  }\n\n  public static ResultSet getEmptyResult(\n      DBMetadataResultSetMetadata metadataType, Statement statement, String queryId)\n      throws SQLException {\n    return new SnowflakeDatabaseMetaDataResultSet(\n        metadataType, new Object[][] {}, statement, queryId);\n  }\n\n  public static ResultSet getEmptyResultSet(\n      DBMetadataResultSetMetadata metadataType, Statement statement) throws SQLException {\n    return new SnowflakeDatabaseMetaDataResultSet(metadataType, new Object[][] {}, statement);\n  }\n\n  Object getObjectInternal(int columnIndex) throws SQLException {\n    logger.trace(\"Object getObjectInternal(int columnIndex)\", false);\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    if (nextRow == null) {\n      throw new SQLException(\"No row found.\");\n    }\n\n    if (columnIndex > nextRow.length) {\n      throw new SQLException(\"Invalid column index: \" + columnIndex);\n    }\n\n    wasNull = nextRow[columnIndex - 1] == null;\n\n    logger.debug(\"Returning column: \" + columnIndex + \": \" + nextRow[columnIndex - 1]);\n\n    return nextRow[columnIndex - 1];\n  }\n\n  @Override\n  public boolean wasNull() throws SQLException {\n    logger.trace(\"boolean wasNull() returning {}\", wasNull);\n    raiseSQLExceptionIfResultSetIsClosed();\n    return wasNull;\n  }\n\n  @Override\n  public String getString(int columnIndex) throws SQLException {\n    logger.trace(\"String getString(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    return obj == null ? null : obj.toString();\n  }\n\n  @Override\n  public boolean getBoolean(int columnIndex) throws SQLException {\n    logger.trace(\"boolean getBoolean(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return false;\n    }\n\n    if (obj instanceof String) {\n      if (obj.toString().equals(\"1\")) {\n        return Boolean.TRUE;\n      }\n      return Boolean.FALSE;\n    } else if (obj instanceof Integer) {\n      int i = (Integer) obj;\n      if (i > 0) {\n        return Boolean.TRUE;\n      }\n      return Boolean.FALSE;\n    } else {\n      return ((Boolean) obj).booleanValue();\n    }\n  }\n\n  @Override\n  public byte getByte(int columnIndex) throws SQLException {\n    logger.trace(\"byte getByte(int columnIndex)\", false);\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return 0;\n    }\n\n    if (obj instanceof String) {\n      return Byte.valueOf((String) obj);\n    } else {\n      return (Byte) obj;\n    }\n  }\n\n  @Override\n  public short getShort(int columnIndex) throws SQLException {\n    logger.trace(\"short getShort(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return 0;\n    }\n\n    if (obj instanceof String) {\n      return (Short.valueOf((String) obj)).shortValue();\n    } else {\n      return ((Number) obj).shortValue();\n    }\n  }\n\n  @Override\n  public int getInt(int columnIndex) throws SQLException {\n    logger.trace(\"int getInt(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return 0;\n    }\n\n    if (obj instanceof String) {\n      return (Integer.valueOf((String) obj)).intValue();\n    } else {\n      return ((Number) obj).intValue();\n    }\n  }\n\n  @Override\n  public long getLong(int columnIndex) throws SQLException {\n    logger.trace(\"long getLong(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return 0;\n    }\n\n    try {\n      if (obj instanceof String) {\n        return (Long.valueOf((String) obj)).longValue();\n      } else {\n        return ((Number) obj).longValue();\n      }\n    } catch (NumberFormatException nfe) {\n      throw new SnowflakeSQLException(\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          \"Invalid long: \" + (String) obj);\n    }\n  }\n\n  @Override\n  public float getFloat(int columnIndex) throws SQLException {\n    logger.trace(\"float getFloat(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return 0;\n    }\n\n    if (obj instanceof String) {\n      return (Float.valueOf((String) obj)).floatValue();\n    } else {\n      return ((Number) obj).floatValue();\n    }\n  }\n\n  @Override\n  public double getDouble(int columnIndex) throws SQLException {\n    logger.trace(\"double getDouble(int columnIndex)\", false);\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    // snow-11974: null for getDouble should return 0\n    if (obj == null) {\n      return 0;\n    }\n\n    if (obj instanceof String) {\n      return (Double.valueOf((String) obj)).doubleValue();\n    } else {\n      return ((Number) obj).doubleValue();\n    }\n  }\n\n  public String getQueryID() {\n    return queryId;\n  }\n\n  /**\n   * @deprecated\n   */\n  @Deprecated\n  @Override\n  public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {\n    logger.trace(\"BigDecimal getBigDecimal(int columnIndex, int scale)\", false);\n\n    BigDecimal value;\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return null;\n    }\n\n    if (obj instanceof String) {\n      value = new BigDecimal((String) obj);\n    } else {\n      value = new BigDecimal(obj.toString());\n    }\n\n    value = value.setScale(scale, RoundingMode.HALF_UP);\n\n    return value;\n  }\n\n  @Override\n  public BigDecimal getBigDecimal(int columnIndex) throws SQLException {\n    logger.trace(\"BigDecimal getBigDecimal(int columnIndex)\", false);\n\n    BigDecimal value = null;\n\n    // Column index starts from 1, not 0.\n    Object obj = getObjectInternal(columnIndex);\n\n    if (obj == null) {\n      return null;\n    }\n\n    if (obj instanceof String) {\n      value = new BigDecimal((String) obj);\n    } else {\n      value = new BigDecimal(obj.toString());\n    }\n\n    return value;\n  }\n\n  @Override\n  public Object getObject(int columnIndex) throws SQLException {\n    logger.trace(\"Object getObject(int columnIndex)\", false);\n\n    int type = resultSetMetaData.getColumnType(columnIndex);\n\n    Object internalObj = getObjectInternal(columnIndex);\n    if (internalObj == null) {\n      return null;\n    }\n\n    switch (type) {\n      case Types.VARCHAR:\n      case Types.CHAR:\n        return getString(columnIndex);\n\n      case Types.BINARY:\n        return getBytes(columnIndex);\n\n      case Types.INTEGER:\n      case Types.SMALLINT:\n        return Integer.valueOf(getInt(columnIndex));\n\n      case Types.DECIMAL:\n        return getBigDecimal(columnIndex);\n\n      case Types.BIGINT:\n        return getLong(columnIndex);\n\n      case Types.DOUBLE:\n        return Double.valueOf(getDouble(columnIndex));\n\n      case Types.TIMESTAMP:\n        return getTimestamp(columnIndex);\n\n      case Types.DATE:\n        return getDate(columnIndex);\n\n      case Types.TIME:\n        return getTime(columnIndex);\n\n      case Types.BOOLEAN:\n        return getBoolean(columnIndex);\n\n      default:\n        throw new SnowflakeLoggedFeatureNotSupportedException(session);\n    }\n  }\n\n  @Override\n  public List<SnowflakeResultSetSerializable> getResultSetSerializables(long maxSizeInBytes)\n      throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public <T> T[] getArray(int columnIndex, Class<T> type) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public <T> List<T> getList(int columnIndex, Class<T> type) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public <T> Map<String, T> getMap(int columnIndex, Class<T> type) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeDateWithTimezone.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Date;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.TimeZone;\n\n/**\n * Date with toString() overridden to display date values in session timezone. Only relevant for\n * timestamp objects fetched as dates. Normal date objects do not have a timezone associated with\n * them.\n */\npublic class SnowflakeDateWithTimezone extends Date {\n\n  TimeZone timezone = TimeZone.getDefault();\n  boolean useSessionTimezone = false;\n\n  public SnowflakeDateWithTimezone(long date, TimeZone timezone, boolean useSessionTimezone) {\n    super(date);\n    this.timezone = timezone;\n    this.useSessionTimezone = useSessionTimezone;\n  }\n\n  /**\n   * Returns a string representation in UTC so as to display \"wallclock time\"\n   *\n   * @return a string representation of the object\n   */\n  public synchronized String toString() {\n    if (!useSessionTimezone) {\n      return super.toString();\n    }\n    String baseFormat = \"yyyy-MM-dd\";\n    DateFormat formatter = new SimpleDateFormat(baseFormat);\n    formatter.setTimeZone(this.timezone);\n    return formatter.format(this);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeFileTransferAgent.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.core.Constants.NO_SPACE_LEFT_ON_DEVICE_ERR;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.createOwnerOnlyPermissionDir;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.recordIfExternal;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.google.common.io.ByteStreams;\nimport com.google.common.io.CountingOutputStream;\nimport java.io.File;\nimport java.io.FileFilter;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.security.DigestOutputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.zip.GZIPOutputStream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.FileUtil;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFFixedViewResultSet;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeStorageClient;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StageInfo;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageClientFactory;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageObjectMetadata;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageObjectSummary;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageObjectSummaryCollection;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageProviderException;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.common.core.FileCompressionType;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.codec.digest.DigestUtils;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.io.filefilter.WildcardFileFilter;\n\n/** Class for uploading/downloading files */\npublic class SnowflakeFileTransferAgent extends SFBaseFileTransferAgent {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeFileTransferAgent.class);\n\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  // We will allow buffering of upto 128M data before spilling to disk during\n  // compression and digest computation\n  static final int MAX_BUFFER_SIZE = 1 << 27;\n  public static final String SRC_FILE_NAME_FOR_STREAM = \"stream\";\n\n  private static final String FILE_PROTOCOL = \"file://\";\n\n  private static final String localFSFileSep = systemGetProperty(\"file.separator\");\n  private static final int DEFAULT_PARALLEL = 10;\n\n  private final String command;\n\n  // list of files specified. Wildcard should be expanded already for uploading\n  // For downloading, it the list of stage file names\n  private Set<String> sourceFiles;\n\n  // big source files >=16MB, for which we will not upload them in serial mode\n  // since TransferManager will parallelize upload\n  private Set<String> bigSourceFiles;\n\n  // big source files < 16MB, for which we will upload them in parallel mode\n  // with 4 threads by default\n  private Set<String> smallSourceFiles;\n\n  // Threshold for splitting a file to upload multiple parts in parallel\n  private int bigFileThreshold = 200 * 1024 * 1024;\n\n  private Map<String, FileMetadata> fileMetadataMap;\n\n  // stage related info\n  private StageInfo stageInfo;\n\n  // local location for where to download files to\n  private String localLocation;\n\n  // Query ID of PUT or GET statement\n  private String queryID = null;\n\n  // default parallelism\n  private int parallel = DEFAULT_PARALLEL;\n  private SFSession session;\n  private SFStatement statement;\n  private static Throwable injectedFileTransferException = null; // for testing purpose\n\n  // This function should only be used for testing purpose\n  static void setInjectedFileTransferException(Throwable th) {\n    injectedFileTransferException = th;\n  }\n\n  static boolean isInjectedFileTransferExceptionEnabled() {\n    return injectedFileTransferException != null;\n  }\n\n  public StageInfo getStageInfo() {\n    return this.stageInfo;\n  }\n\n  /**\n   * Get value of big file threshold. For testing purposes.\n   *\n   * @return integer value in bytes of threshold\n   */\n  int getBigFileThreshold() {\n    return this.bigFileThreshold;\n  }\n\n  // Encryption material\n  private List<RemoteStoreFileEncryptionMaterial> encryptionMaterial;\n\n  // Presigned URLs\n  private List<String> presignedUrls;\n\n  // Index: Source file to encryption material\n  HashMap<String, RemoteStoreFileEncryptionMaterial> srcFileToEncMat;\n\n  // Index: Source file to presigned URL\n  HashMap<String, String> srcFileToPresignedUrl;\n\n  public Map<?, ?> getStageCredentials() {\n    return new HashMap<>(stageInfo.getCredentials());\n  }\n\n  public List<RemoteStoreFileEncryptionMaterial> getEncryptionMaterial() {\n    return new ArrayList<>(encryptionMaterial);\n  }\n\n  public Map<String, RemoteStoreFileEncryptionMaterial> getSrcToMaterialsMap() {\n    return new HashMap<>(srcFileToEncMat);\n  }\n\n  public Map<String, String> getSrcToPresignedUrlMap() {\n    return new HashMap<>(srcFileToPresignedUrl);\n  }\n\n  public String getStageLocation() {\n    return stageInfo.getLocation();\n  }\n\n  private void initEncryptionMaterial(CommandType commandType, JsonNode jsonNode)\n      throws SnowflakeSQLException, JsonProcessingException {\n    encryptionMaterial = getEncryptionMaterial(commandType, jsonNode);\n  }\n\n  /**\n   * Get the encryption information for an UPLOAD or DOWNLOAD given a PUT command response JsonNode\n   *\n   * @param commandType CommandType of action (e.g UPLOAD or DOWNLOAD)\n   * @param jsonNode JsonNod of PUT call response\n   * @return List of RemoteStoreFileEncryptionMaterial objects\n   */\n  static List<RemoteStoreFileEncryptionMaterial> getEncryptionMaterial(\n      CommandType commandType, JsonNode jsonNode)\n      throws SnowflakeSQLException, JsonProcessingException {\n    List<RemoteStoreFileEncryptionMaterial> encryptionMaterial = new ArrayList<>();\n    JsonNode rootNode = jsonNode.path(\"data\").path(\"encryptionMaterial\");\n    if (commandType == CommandType.UPLOAD) {\n      logger.debug(\"InitEncryptionMaterial: UPLOAD\", false);\n\n      RemoteStoreFileEncryptionMaterial encMat = null;\n      if (!rootNode.isMissingNode() && !rootNode.isNull()) {\n        encMat = mapper.treeToValue(rootNode, RemoteStoreFileEncryptionMaterial.class);\n      }\n      encryptionMaterial.add(encMat);\n\n    } else {\n      logger.debug(\"InitEncryptionMaterial: DOWNLOAD\", false);\n\n      if (!rootNode.isMissingNode() && !rootNode.isNull()) {\n        encryptionMaterial =\n            Arrays.asList(mapper.treeToValue(rootNode, RemoteStoreFileEncryptionMaterial[].class));\n      }\n    }\n    return encryptionMaterial;\n  }\n\n  private void initPresignedUrls(CommandType commandType, JsonNode jsonNode)\n      throws SnowflakeSQLException, JsonProcessingException, IOException {\n    presignedUrls = getPresignedUrls(commandType, jsonNode);\n  }\n\n  private static List<String> getPresignedUrls(CommandType commandType, JsonNode jsonNode)\n      throws SnowflakeSQLException, JsonProcessingException, IOException {\n    List<String> presignedUrls = new ArrayList<>();\n    JsonNode rootNode = jsonNode.path(\"data\").path(\"presignedUrls\");\n    if (commandType == CommandType.DOWNLOAD) {\n      logger.debug(\"InitEncryptionMaterial: DOWNLOAD\", false);\n\n      if (!rootNode.isMissingNode() && !rootNode.isNull()) {\n        presignedUrls = Arrays.asList(mapper.readValue(rootNode.toString(), String[].class));\n      }\n    }\n    return presignedUrls;\n  }\n\n  private boolean autoCompress = true;\n\n  private boolean overwrite = false;\n\n  private SnowflakeStorageClient storageClient = null;\n\n  private static final String SOURCE_COMPRESSION_AUTO_DETECT = \"auto_detect\";\n  private static final String SOURCE_COMPRESSION_NONE = \"none\";\n\n  private String sourceCompression = SOURCE_COMPRESSION_AUTO_DETECT;\n\n  private ExecutorService threadExecutor = null;\n  private Boolean canceled = false;\n\n  /** Result status enum */\n  public enum ResultStatus {\n    UNKNOWN(\"Unknown status\"),\n    UPLOADED(\"File uploaded\"),\n    UNSUPPORTED(\"File type not supported\"),\n    ERROR(\"Error encountered\"),\n    SKIPPED(\"Skipped since file exists\"),\n    NONEXIST(\"File does not exist\"),\n    COLLISION(\"File name collides with another file\"),\n    DIRECTORY(\"Not a file, but directory\"),\n    DOWNLOADED(\"File downloaded\");\n\n    private String desc;\n\n    public String getDesc() {\n      return desc;\n    }\n\n    private ResultStatus(String desc) {\n      this.desc = desc;\n    }\n  }\n\n  /** Remote object location location: \"bucket\" for S3, \"container\" for Azure BLOB */\n  private static class remoteLocation {\n    String location;\n    String path;\n\n    public remoteLocation(String remoteStorageLocation, String remotePath) {\n      location = remoteStorageLocation;\n      path = remotePath;\n    }\n  }\n\n  /**\n   * File metadata with everything we care so we don't need to repeat same processing to get these\n   * info.\n   */\n  private class FileMetadata {\n    public String srcFileName;\n    public long srcFileSize;\n    public String destFileName;\n    public long destFileSize;\n    public boolean requireCompress;\n    public ResultStatus resultStatus = ResultStatus.UNKNOWN;\n    public String errorDetails = \"\";\n    public FileCompressionType srcCompressionType;\n    public FileCompressionType destCompressionType;\n    public boolean isEncrypted = false;\n  }\n\n  static class InputStreamWithMetadata {\n    long size;\n    String digest;\n\n    // FileBackedOutputStream that should be destroyed when\n    // the input stream has been consumed entirely\n    FileBackedOutputStream fileBackedOutputStream;\n\n    InputStreamWithMetadata(\n        long size, String digest, FileBackedOutputStream fileBackedOutputStream) {\n      this.size = size;\n      this.digest = digest;\n      this.fileBackedOutputStream = fileBackedOutputStream;\n    }\n  }\n\n  /**\n   * Compress an input stream with GZIP and return the result size, digest and compressed stream.\n   *\n   * @param inputStream data input\n   * @param session the session\n   * @return result size, digest and compressed stream\n   * @throws SnowflakeSQLException if encountered exception when compressing\n   */\n  private static InputStreamWithMetadata compressStreamWithGZIP(\n      InputStream inputStream, SFBaseSession session, String queryId) throws SnowflakeSQLException {\n    FileBackedOutputStream tempStream = new FileBackedOutputStream(MAX_BUFFER_SIZE, true);\n\n    try {\n\n      DigestOutputStream digestStream =\n          new DigestOutputStream(tempStream, MessageDigest.getInstance(\"SHA-256\"));\n\n      CountingOutputStream countingStream = new CountingOutputStream(digestStream);\n\n      // construct a gzip stream with sync_flush mode\n      GZIPOutputStream gzipStream;\n\n      gzipStream = new GZIPOutputStream(countingStream, true);\n\n      IOUtils.copy(inputStream, gzipStream);\n\n      inputStream.close();\n\n      gzipStream.finish();\n      gzipStream.flush();\n\n      countingStream.flush();\n\n      // Normal flow will never hit here. This is only for testing purposes\n      if (isInjectedFileTransferExceptionEnabled()\n          && SnowflakeFileTransferAgent.injectedFileTransferException\n              instanceof NoSuchAlgorithmException) {\n        throw (NoSuchAlgorithmException) SnowflakeFileTransferAgent.injectedFileTransferException;\n      }\n\n      return new InputStreamWithMetadata(\n          countingStream.getCount(),\n          Base64.getEncoder().encodeToString(digestStream.getMessageDigest().digest()),\n          tempStream);\n\n    } catch (IOException | NoSuchAlgorithmException ex) {\n      logger.error(\"Exception compressing input stream\", ex);\n\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          \"error encountered for compression\");\n    }\n  }\n\n  /**\n   * Compress an input stream with GZIP and return the result size, digest and compressed stream.\n   *\n   * @param inputStream The input stream to compress\n   * @return the compressed stream\n   * @throws SnowflakeSQLException Will be thrown if there is a problem with compression\n   * @deprecated Can be removed when all accounts are encrypted\n   */\n  @Deprecated\n  private static InputStreamWithMetadata compressStreamWithGZIPNoDigest(\n      InputStream inputStream, SFBaseSession session, String queryId) throws SnowflakeSQLException {\n    try {\n      FileBackedOutputStream tempStream = new FileBackedOutputStream(MAX_BUFFER_SIZE, true);\n\n      CountingOutputStream countingStream = new CountingOutputStream(tempStream);\n\n      // construct a gzip stream with sync_flush mode\n      GZIPOutputStream gzipStream;\n\n      gzipStream = new GZIPOutputStream(countingStream, true);\n\n      IOUtils.copy(inputStream, gzipStream);\n\n      inputStream.close();\n\n      gzipStream.finish();\n      gzipStream.flush();\n\n      countingStream.flush();\n\n      // Normal flow will never hit here. This is only for testing purposes\n      if (isInjectedFileTransferExceptionEnabled()) {\n        throw (IOException) SnowflakeFileTransferAgent.injectedFileTransferException;\n      }\n      return new InputStreamWithMetadata(countingStream.getCount(), null, tempStream);\n\n    } catch (IOException ex) {\n      logger.error(\"Exception compressing input stream\", ex);\n\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          \"error encountered for compression\");\n    }\n  }\n\n  private static InputStreamWithMetadata computeDigest(InputStream is, boolean resetStream)\n      throws NoSuchAlgorithmException, IOException {\n    MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n    if (resetStream) {\n      FileBackedOutputStream tempStream = new FileBackedOutputStream(MAX_BUFFER_SIZE, true);\n\n      CountingOutputStream countingOutputStream = new CountingOutputStream(tempStream);\n\n      DigestOutputStream digestStream = new DigestOutputStream(countingOutputStream, md);\n\n      IOUtils.copy(is, digestStream);\n\n      return new InputStreamWithMetadata(\n          countingOutputStream.getCount(),\n          Base64.getEncoder().encodeToString(digestStream.getMessageDigest().digest()),\n          tempStream);\n    } else {\n      CountingOutputStream countingOutputStream =\n          new CountingOutputStream(ByteStreams.nullOutputStream());\n\n      DigestOutputStream digestStream = new DigestOutputStream(countingOutputStream, md);\n      IOUtils.copy(is, digestStream);\n      return new InputStreamWithMetadata(\n          countingOutputStream.getCount(),\n          Base64.getEncoder().encodeToString(digestStream.getMessageDigest().digest()),\n          null);\n    }\n  }\n\n  /**\n   * A callable that can be executed in a separate thread using executor service.\n   *\n   * <p>The callable does compression if needed and upload the result to the table's staging area.\n   *\n   * @deprecated use {@link #getUploadFileCallable(StageInfo, String, FileMetadata,\n   *     SnowflakeStorageClient, SFSession, String, InputStream, boolean, int, File,\n   *     RemoteStoreFileEncryptionMaterial, String)}\n   * @param stage information about the stage\n   * @param srcFilePath source file path\n   * @param metadata file metadata\n   * @param client client object used to communicate with c3\n   * @param session session object\n   * @param command command string\n   * @param inputStream null if upload source is file\n   * @param sourceFromStream whether upload source is file or stream\n   * @param parallel number of threads for parallel uploading\n   * @param srcFile source file name\n   * @param encMat not null if encryption is required\n   * @return a callable that uploading file to the remote store\n   */\n  @Deprecated\n  public static Callable<Void> getUploadFileCallable(\n      final StageInfo stage,\n      final String srcFilePath,\n      final FileMetadata metadata,\n      final SnowflakeStorageClient client,\n      final SFSession session,\n      final String command,\n      final InputStream inputStream,\n      final boolean sourceFromStream,\n      final int parallel,\n      final File srcFile,\n      final RemoteStoreFileEncryptionMaterial encMat) {\n    return getUploadFileCallable(\n        stage,\n        srcFilePath,\n        metadata,\n        client,\n        session,\n        command,\n        inputStream,\n        sourceFromStream,\n        parallel,\n        srcFile,\n        encMat,\n        null);\n  }\n\n  /**\n   * A callable that can be executed in a separate thread using executor service.\n   *\n   * <p>The callable does compression if needed and upload the result to the table's staging area.\n   *\n   * @param stage information about the stage\n   * @param srcFilePath source file path\n   * @param metadata file metadata\n   * @param client client object used to communicate with c3\n   * @param session session object\n   * @param command command string\n   * @param inputStream null if upload source is file\n   * @param sourceFromStream whether upload source is file or stream\n   * @param parallel number of threads for parallel uploading\n   * @param srcFile source file name\n   * @param encMat not null if encryption is required\n   * @param queryId last executed query id (for forwarding in possible exceptions)\n   * @return a callable that uploading file to the remote store\n   */\n  public static Callable<Void> getUploadFileCallable(\n      final StageInfo stage,\n      final String srcFilePath,\n      final FileMetadata metadata,\n      final SnowflakeStorageClient client,\n      final SFSession session,\n      final String command,\n      final InputStream inputStream,\n      final boolean sourceFromStream,\n      final int parallel,\n      final File srcFile,\n      final RemoteStoreFileEncryptionMaterial encMat,\n      final String queryId) {\n    return new Callable<Void>() {\n      public Void call() throws Exception {\n\n        logger.trace(\"Entering getUploadFileCallable...\", false);\n\n        // make sure initialize context for the telemetry service for this thread\n        TelemetryService.getInstance().updateContext(session.getSnowflakeConnectionString());\n\n        InputStream uploadStream = inputStream;\n\n        File fileToUpload = null;\n\n        if (uploadStream == null) {\n          try {\n\n            // Normal flow will never hit here. This is only for testing purposes\n            if (isInjectedFileTransferExceptionEnabled()\n                && SnowflakeFileTransferAgent.injectedFileTransferException\n                    instanceof FileNotFoundException) {\n              throw (FileNotFoundException)\n                  SnowflakeFileTransferAgent.injectedFileTransferException;\n            }\n\n            FileUtil.logFileUsage(srcFilePath, \"Get file to upload\", false);\n            uploadStream = new FileInputStream(srcFilePath);\n          } catch (FileNotFoundException ex) {\n            metadata.resultStatus = ResultStatus.ERROR;\n            metadata.errorDetails = ex.getMessage();\n            throw ex;\n          }\n        }\n\n        // this shouldn't happen\n        if (metadata == null) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"missing file metadata for: \" + srcFilePath);\n        }\n\n        String destFileName = metadata.destFileName;\n\n        long uploadSize;\n\n        String digest = null;\n\n        logger.debug(\"Dest file name: {}\", false);\n\n        // Temp file that needs to be cleaned up when upload was successful\n        FileBackedOutputStream fileBackedOutputStream = null;\n\n        // SNOW-16082: we should capture exception if we fail to compress or\n        // calculate digest.\n        try {\n          if (metadata.requireCompress) {\n            InputStreamWithMetadata compressedSizeAndStream =\n                (encMat == null\n                    ? compressStreamWithGZIPNoDigest(uploadStream, session, queryId)\n                    : compressStreamWithGZIP(uploadStream, session, queryId));\n\n            fileBackedOutputStream = compressedSizeAndStream.fileBackedOutputStream;\n\n            // update the size\n            uploadSize = compressedSizeAndStream.size;\n            digest = compressedSizeAndStream.digest;\n\n            if (compressedSizeAndStream.fileBackedOutputStream.getFile() != null) {\n              fileToUpload = compressedSizeAndStream.fileBackedOutputStream.getFile();\n            }\n\n            logger.debug(\"New size after compression: {}\", uploadSize);\n          } else if (stage.getStageType() != StageInfo.StageType.LOCAL_FS) {\n            // If it's not local_fs, we store our digest in the metadata\n            // In local_fs, we don't need digest, and if we turn it on, we will consume whole\n            // uploadStream, which local_fs uses.\n            InputStreamWithMetadata result = computeDigest(uploadStream, sourceFromStream);\n            digest = result.digest;\n            fileBackedOutputStream = result.fileBackedOutputStream;\n            uploadSize = result.size;\n\n            if (!sourceFromStream) {\n              fileToUpload = srcFile;\n            } else if (result.fileBackedOutputStream.getFile() != null) {\n              fileToUpload = result.fileBackedOutputStream.getFile();\n            }\n          } else {\n            if (!sourceFromStream && (srcFile != null)) {\n              fileToUpload = srcFile;\n            }\n\n            // if stage is local_fs and upload source is stream, upload size\n            // does not matter since 1) transfer did not require size 2) no\n            // output from uploadStream api is required\n            uploadSize = sourceFromStream ? 0 : srcFile.length();\n          }\n\n          logger.debug(\n              \"Started copying file from: {} to {}:{} destName: {} \"\n                  + \"auto compressed? {} size: {}\",\n              srcFilePath,\n              stage.getStageType().name(),\n              stage.getLocation(),\n              destFileName,\n              (metadata.requireCompress ? \"yes\" : \"no\"),\n              uploadSize);\n\n          // Simulated failure code.\n          if (session.getInjectFileUploadFailure() != null\n              && srcFilePath.endsWith((session).getInjectFileUploadFailure())) {\n            throw new SnowflakeSimulatedUploadFailure(\n                srcFile != null ? srcFile.getName() : \"Unknown\");\n          }\n\n          // upload it\n          switch (stage.getStageType()) {\n            case LOCAL_FS:\n              pushFileToLocal(\n                  stage.getLocation(),\n                  srcFilePath,\n                  destFileName,\n                  uploadStream,\n                  fileBackedOutputStream,\n                  session,\n                  queryId);\n              break;\n\n            case S3:\n            case AZURE:\n            case GCS:\n              pushFileToRemoteStore(\n                  stage,\n                  destFileName,\n                  uploadStream,\n                  fileBackedOutputStream,\n                  uploadSize,\n                  digest,\n                  metadata.destCompressionType,\n                  client,\n                  session,\n                  command,\n                  parallel,\n                  fileToUpload,\n                  (fileToUpload == null),\n                  encMat,\n                  null,\n                  null,\n                  queryId);\n              metadata.isEncrypted = encMat != null;\n              break;\n          }\n        } catch (SnowflakeSimulatedUploadFailure ex) {\n          // This code path is used for Simulated failure code in tests.\n          // Never happen in production\n          metadata.resultStatus = ResultStatus.ERROR;\n          metadata.errorDetails = ex.getMessage();\n          throw ex;\n        } catch (Throwable ex) {\n          logger.error(\"Exception encountered during file upload\", ex);\n          metadata.resultStatus = ResultStatus.ERROR;\n          metadata.errorDetails = ex.getMessage();\n          throw ex;\n        } finally {\n          if (fileBackedOutputStream != null) {\n            try {\n              fileBackedOutputStream.reset();\n            } catch (IOException ex) {\n              logger.debug(\"Failed to clean up temp file: {}\", ex);\n            }\n          }\n          if (inputStream == null) {\n            IOUtils.closeQuietly(uploadStream);\n          }\n          if (client != null) {\n            client.shutdown();\n          }\n        }\n\n        logger.debug(\"FilePath: {}\", srcFilePath);\n\n        // set dest size\n        metadata.destFileSize = uploadSize;\n\n        // mark the file as being uploaded\n        metadata.resultStatus = ResultStatus.UPLOADED;\n\n        return null;\n      }\n    };\n  }\n\n  /**\n   * A callable that can be executed in a separate thread using executor service.\n   *\n   * <p>The callable download files from a stage location to a local location\n   *\n   * @deprecated use {@link #getDownloadFileCallable(StageInfo, String, String, Map,\n   *     SnowflakeStorageClient, SFSession, String, int, RemoteStoreFileEncryptionMaterial, String,\n   *     String)}\n   * @param stage stage information\n   * @param srcFilePath path that stores the downloaded file\n   * @param localLocation local location\n   * @param fileMetadataMap file metadata map\n   * @param client remote store client\n   * @param session session object\n   * @param command command string\n   * @param encMat remote store encryption material\n   * @param parallel number of parallel threads for downloading\n   * @param presignedUrl Presigned URL for file download\n   * @return a callable responsible for downloading files\n   */\n  @Deprecated\n  public static Callable<Void> getDownloadFileCallable(\n      final StageInfo stage,\n      final String srcFilePath,\n      final String localLocation,\n      final Map<String, FileMetadata> fileMetadataMap,\n      final SnowflakeStorageClient client,\n      final SFSession session,\n      final String command,\n      final int parallel,\n      final RemoteStoreFileEncryptionMaterial encMat,\n      final String presignedUrl) {\n    return getDownloadFileCallable(\n        stage,\n        srcFilePath,\n        localLocation,\n        fileMetadataMap,\n        client,\n        session,\n        command,\n        parallel,\n        encMat,\n        presignedUrl,\n        null);\n  }\n\n  /**\n   * A callable that can be executed in a separate thread using executor service.\n   *\n   * <p>The callable download files from a stage location to a local location\n   *\n   * @param stage stage information\n   * @param srcFilePath path that stores the downloaded file\n   * @param localLocation local location\n   * @param fileMetadataMap file metadata map\n   * @param client remote store client\n   * @param session session object\n   * @param command command string\n   * @param encMat remote store encryption material\n   * @param parallel number of parallel threads for downloading\n   * @param presignedUrl Presigned URL for file download\n   * @param queryId the query ID\n   * @return a callable responsible for downloading files\n   */\n  public static Callable<Void> getDownloadFileCallable(\n      final StageInfo stage,\n      final String srcFilePath,\n      final String localLocation,\n      final Map<String, FileMetadata> fileMetadataMap,\n      final SnowflakeStorageClient client,\n      final SFSession session,\n      final String command,\n      final int parallel,\n      final RemoteStoreFileEncryptionMaterial encMat,\n      final String presignedUrl,\n      final String queryId) {\n    return new Callable<Void>() {\n      public Void call() throws Exception {\n\n        logger.debug(\"Entering getDownloadFileCallable...\", false);\n\n        // make sure initialize context for the telemetry service for this thread\n        TelemetryService.getInstance().updateContext(session.getSnowflakeConnectionString());\n\n        FileMetadata metadata = fileMetadataMap.get(srcFilePath);\n\n        // this shouldn't happen\n        if (metadata == null) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"missing file metadata for: \" + srcFilePath);\n        }\n\n        String destFileName = metadata.destFileName;\n        logger.debug(\n            \"Started copying file from: {}:{} file path:{} to {} destName:{}\",\n            stage.getStageType().name(),\n            stage.getLocation(),\n            srcFilePath,\n            localLocation,\n            destFileName);\n\n        try {\n          switch (stage.getStageType()) {\n            case LOCAL_FS:\n              pullFileFromLocal(\n                  stage.getLocation(), srcFilePath, localLocation, destFileName, session, queryId);\n              break;\n\n            case AZURE:\n            case S3:\n            case GCS:\n              pullFileFromRemoteStore(\n                  stage,\n                  srcFilePath,\n                  destFileName,\n                  localLocation,\n                  client,\n                  session,\n                  command,\n                  parallel,\n                  encMat,\n                  presignedUrl,\n                  queryId);\n              metadata.isEncrypted = encMat != null;\n              break;\n          }\n        } catch (Throwable ex) {\n          logger.error(\"Exception encountered during file download\", ex);\n\n          metadata.resultStatus = ResultStatus.ERROR;\n          metadata.errorDetails = ex.getMessage();\n          throw ex;\n        } finally {\n          if (client != null) {\n            client.shutdown();\n          }\n        }\n\n        logger.debug(\"FilePath: {}\", srcFilePath);\n\n        File destFile = new File(localLocation + localFSFileSep + destFileName);\n        long downloadSize = destFile.length();\n\n        // set dest size\n        metadata.destFileSize = downloadSize;\n\n        // mark the file as being uploaded\n        metadata.resultStatus = ResultStatus.DOWNLOADED;\n\n        return null;\n      }\n    };\n  }\n\n  public SnowflakeFileTransferAgent(String command, SFSession session, SFStatement statement)\n      throws SnowflakeSQLException {\n    this(command, session, statement, null);\n  }\n\n  public SnowflakeFileTransferAgent(\n      String command,\n      SFSession session,\n      SFStatement statement,\n      InternalCallMarker internalCallMarker)\n      throws SnowflakeSQLException {\n    recordIfExternal(\"SnowflakeFileTransferAgent\", \"<init>\", internalCallMarker);\n    this.command = command;\n    this.session = session;\n    this.statement = statement;\n\n    // parse the command\n    logger.debug(\"Start parsing\", false);\n\n    parseCommand();\n\n    if (stageInfo.getStageType() != StageInfo.StageType.LOCAL_FS) {\n      storageClient =\n          StorageClientFactory.getFactory().createClient(stageInfo, parallel, null, session);\n    }\n  }\n\n  /**\n   * Parse the put/get command.\n   *\n   * <p>We send the command to the GS to do the parsing. In the future, we will delegate more work\n   * to GS such as copying files from HTTP to the remote store.\n   *\n   * @throws SnowflakeSQLException failure to parse the PUT/GET command\n   */\n  private void parseCommand() throws SnowflakeSQLException {\n    // For AWS and Azure, this command returns enough info for us to get creds\n    // we can use for each of the GETs/PUTs. For GCS, we need to issue a separate\n    // call to GS to get creds (in the form of a presigned URL) for each file\n    // we're uploading or downloading. This call gets our src_location and\n    // encryption material, which we'll use for all the subsequent calls to GS\n    // for creds for each file. Those calls are made from pushFileToRemoteStore\n    // and pullFileFromRemoteStore if the storage client requires a presigned\n    // URL.\n    JsonNode jsonNode = parseCommandInGS(statement, command);\n\n    // get command type\n    if (!jsonNode.path(\"data\").path(\"command\").isMissingNode()) {\n      commandType = CommandType.valueOf(jsonNode.path(\"data\").path(\"command\").asText());\n    }\n\n    // get source file locations as array (apply to both upload and download)\n    JsonNode locationsNode = jsonNode.path(\"data\").path(\"src_locations\");\n    if (!locationsNode.isArray()) {\n      throw new SnowflakeSQLException(\n          queryID, ErrorCode.INTERNAL_ERROR, \"src_locations must be an array\");\n    }\n\n    queryID = jsonNode.path(\"data\").path(\"queryId\").asText();\n\n    String[] src_locations;\n\n    try {\n      // Normal flow will never hit here. This is only for testing purposes\n      if (isInjectedFileTransferExceptionEnabled()\n          && injectedFileTransferException instanceof SnowflakeSQLException) {\n        throw (SnowflakeSQLException) SnowflakeFileTransferAgent.injectedFileTransferException;\n      }\n\n      src_locations = mapper.readValue(locationsNode.toString(), String[].class);\n      initEncryptionMaterial(commandType, jsonNode);\n      initPresignedUrls(commandType, jsonNode);\n    } catch (Exception ex) {\n      throw new SnowflakeSQLException(\n          queryID,\n          ex,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          \"Failed to parse the locations due to: \" + ex.getMessage());\n    }\n\n    showEncryptionParameter =\n        jsonNode.path(\"data\").path(\"clientShowEncryptionParameter\").asBoolean();\n\n    JsonNode thresholdNode = jsonNode.path(\"data\").path(\"threshold\");\n    int threshold = thresholdNode.asInt();\n    // if value is <= 0, this means an error was made in parsing the threshold or the threshold is\n    // invalid.\n    // Only use the threshold value if it is valid.\n    if (threshold > 0) {\n      bigFileThreshold = threshold;\n    }\n\n    String localFilePathFromGS = null;\n\n    // do upload command specific parsing\n    if (commandType == CommandType.UPLOAD) {\n      if (src_locations.length > 0) {\n        localFilePathFromGS = src_locations[0];\n      }\n\n      sourceFiles = expandFileNames(src_locations, queryID);\n\n      autoCompress = jsonNode.path(\"data\").path(\"autoCompress\").asBoolean(true);\n\n      if (!jsonNode.path(\"data\").path(\"sourceCompression\").isMissingNode()) {\n        sourceCompression = jsonNode.path(\"data\").path(\"sourceCompression\").asText();\n      }\n\n    } else {\n      // do download command specific parsing\n      srcFileToEncMat = new HashMap<>();\n\n      // create mapping from source file to encryption materials\n      if (src_locations.length == encryptionMaterial.size()) {\n        for (int srcFileIdx = 0; srcFileIdx < src_locations.length; srcFileIdx++) {\n          srcFileToEncMat.put(src_locations[srcFileIdx], encryptionMaterial.get(srcFileIdx));\n        }\n      }\n\n      // create mapping from source file to presigned URLs\n      srcFileToPresignedUrl = new HashMap<>();\n      if (src_locations.length == presignedUrls.size()) {\n        for (int srcFileIdx = 0; srcFileIdx < src_locations.length; srcFileIdx++) {\n          srcFileToPresignedUrl.put(src_locations[srcFileIdx], presignedUrls.get(srcFileIdx));\n        }\n      }\n\n      sourceFiles = new HashSet<String>(Arrays.asList(src_locations));\n\n      localLocation = jsonNode.path(\"data\").path(\"localLocation\").asText();\n\n      localFilePathFromGS = localLocation;\n\n      if (localLocation.startsWith(\"~\")) {\n        // replace ~ with user home\n        localLocation = systemGetProperty(\"user.home\") + localLocation.substring(1);\n      }\n\n      // it should not start with any ~ after the above replacement\n      if (localLocation.startsWith(\"~\")) {\n        throw new SnowflakeSQLLoggedException(\n            queryID,\n            session,\n            ErrorCode.PATH_NOT_DIRECTORY.getMessageCode(),\n            SqlState.IO_ERROR,\n            localLocation);\n      }\n\n      // todo: replace ~userid with the home directory of a given userid\n      // one idea is to get the home directory for current user and replace\n      // the last user id with the given user id.\n\n      // user may also specify files relative to current directory\n      // add the current path if that is the case\n      if (!(new File(localLocation)).isAbsolute()) {\n        String cwd = systemGetProperty(\"user.dir\");\n\n        logger.debug(\"Adding current working dir to relative file path.\", false);\n\n        localLocation = cwd + localFSFileSep + localLocation;\n      }\n\n      // local location should be a directory\n      if ((new File(localLocation)).isFile()) {\n        throw new SnowflakeSQLLoggedException(\n            queryID,\n            session,\n            ErrorCode.PATH_NOT_DIRECTORY.getMessageCode(),\n            SqlState.IO_ERROR,\n            localLocation);\n      }\n    }\n\n    // SNOW-15153: verify that the value after file:// is not changed by GS\n    verifyLocalFilePath(localFilePathFromGS);\n\n    parallel = jsonNode.path(\"data\").path(\"parallel\").asInt();\n\n    overwrite = jsonNode.path(\"data\").path(\"overwrite\").asBoolean(false);\n\n    stageInfo = getStageInfo(jsonNode, this.session);\n\n    if (logger.isDebugEnabled()) {\n      logger.debug(\"Command type: {}\", commandType);\n\n      if (commandType == CommandType.UPLOAD) {\n        logger.debug(\"Auto compress: {}, source compression: {}\", autoCompress, sourceCompression);\n      } else {\n        logger.debug(\"Local download location: {}\", localLocation);\n      }\n\n      logger.debug(\"Source files: {}\", String.join(\",\", sourceFiles));\n      logger.debug(\n          \"stageLocation: {}, parallel: {}, overwrite: {}, destLocationType: {}, stageRegion: {},\"\n              + \" endPoint: {}, storageAccount: {}\",\n          stageInfo.getLocation(),\n          parallel,\n          overwrite,\n          stageInfo.getStageType(),\n          stageInfo.getRegion(),\n          stageInfo.getEndPoint(),\n          stageInfo.getStorageAccount());\n    }\n  }\n\n  /**\n   * Construct Stage Info object from JsonNode.\n   *\n   * @param jsonNode JsonNode to use serialize into StageInfo Object\n   * @param session can be null.\n   * @return StageInfo constructed from JsonNode and session params.\n   * @throws SnowflakeSQLException\n   */\n  static StageInfo getStageInfo(JsonNode jsonNode, SFSession session) throws SnowflakeSQLException {\n    String queryId = jsonNode.path(\"data\").path(\"queryId\").asText();\n\n    // more parameters common to upload/download\n    String stageLocation = jsonNode.path(\"data\").path(\"stageInfo\").path(\"location\").asText();\n\n    String stageLocationType =\n        jsonNode.path(\"data\").path(\"stageInfo\").path(\"locationType\").asText();\n\n    String stageRegion = null;\n    if (!jsonNode.path(\"data\").path(\"stageInfo\").path(\"region\").isMissingNode()) {\n      stageRegion = jsonNode.path(\"data\").path(\"stageInfo\").path(\"region\").asText();\n    }\n\n    boolean isClientSideEncrypted = true;\n    if (!jsonNode.path(\"data\").path(\"stageInfo\").path(\"isClientSideEncrypted\").isMissingNode()) {\n      isClientSideEncrypted =\n          jsonNode.path(\"data\").path(\"stageInfo\").path(\"isClientSideEncrypted\").asBoolean(true);\n    }\n\n    // endPoint is currently known to be set for Azure stages or S3. For S3 it will be set\n    // specifically\n    // for FIPS or VPCE S3 endpoint. SNOW-652696\n    String endPoint = null;\n    if (\"AZURE\".equalsIgnoreCase(stageLocationType)\n        || \"S3\".equalsIgnoreCase(stageLocationType)\n        || \"GCS\".equalsIgnoreCase(stageLocationType)) {\n      endPoint = jsonNode.path(\"data\").path(\"stageInfo\").findValue(\"endPoint\").asText();\n      if (\"GCS\".equalsIgnoreCase(stageLocationType)\n          && endPoint != null\n          && (endPoint.trim().isEmpty() || \"null\".equals(endPoint))) {\n        // setting to null to preserve previous behaviour for GCS\n        endPoint = null;\n      }\n    }\n\n    String stgAcct = null;\n    // storageAccount are only available in Azure stages. Value\n    // will be present but null in other platforms.\n    if (\"AZURE\".equalsIgnoreCase(stageLocationType)) {\n      // Jackson is doing some very strange things trying to pull the value of\n      // the storageAccount node after adding the GCP library dependencies.\n      // If we try to pull the value by name, we get back null, but clearly the\n      // node is there. This code works around the issue by enumerating through\n      // all the nodes and getting the one that starts with \"sto\". The value\n      // then comes back with double quotes around it, so we're stripping them\n      // off. As long as our JSON doc doesn't add another node that starts with\n      // \"sto\", this should work fine.\n      Iterator<Entry<String, JsonNode>> fields = jsonNode.path(\"data\").path(\"stageInfo\").fields();\n      while (fields.hasNext()) {\n        Entry<String, JsonNode> jsonField = fields.next();\n        if (jsonField.getKey().startsWith(\"sto\")) {\n          stgAcct =\n              jsonField\n                  .getValue()\n                  .toString()\n                  .trim()\n                  .substring(1, jsonField.getValue().toString().trim().lastIndexOf(\"\\\"\"));\n        }\n      }\n    }\n\n    if (\"LOCAL_FS\".equalsIgnoreCase(stageLocationType)) {\n      if (stageLocation.startsWith(\"~\")) {\n        // replace ~ with user home\n        stageLocation = systemGetProperty(\"user.home\") + stageLocation.substring(1);\n      }\n\n      if (!(new File(stageLocation)).isAbsolute()) {\n        String cwd = systemGetProperty(\"user.dir\");\n\n        logger.debug(\"Adding current working dir to stage file path.\");\n\n        stageLocation = cwd + localFSFileSep + stageLocation;\n      }\n    }\n\n    Map<?, ?> stageCredentials = extractStageCreds(jsonNode, queryId);\n\n    StageInfo stageInfo =\n        StageInfo.createStageInfo(\n            stageLocationType,\n            stageLocation,\n            stageCredentials,\n            stageRegion,\n            endPoint,\n            stgAcct,\n            isClientSideEncrypted);\n\n    // Setup pre-signed URL into stage info if pre-signed URL is returned.\n    if (stageInfo.getStageType() == StageInfo.StageType.GCS) {\n      JsonNode presignedUrlNode = jsonNode.path(\"data\").path(\"stageInfo\").path(\"presignedUrl\");\n      if (!presignedUrlNode.isMissingNode()) {\n        String presignedUrl = presignedUrlNode.asText();\n        if (!isNullOrEmpty(presignedUrl)) {\n          stageInfo.setPresignedUrl(presignedUrl);\n        }\n      }\n    }\n\n    setupUseRegionalUrl(jsonNode, stageInfo);\n    setupUseVirtualUrl(jsonNode, stageInfo);\n\n    if (stageInfo.getStageType() == StageInfo.StageType.S3) {\n      if (session == null) {\n        // This node's value is set if PUT is used without Session. (For Snowpipe Streaming, we rely\n        // on a response from a server to have this field set to use S3RegionalURL)\n        JsonNode useS3RegionalURLNode =\n            jsonNode.path(\"data\").path(\"stageInfo\").path(\"useS3RegionalUrl\");\n        if (!useS3RegionalURLNode.isMissingNode()) {\n          boolean useS3RegionalUrl = useS3RegionalURLNode.asBoolean(false);\n          stageInfo.setUseS3RegionalUrl(useS3RegionalUrl);\n        }\n      } else {\n        // Update StageInfo to reflect use of S3 regional URL.\n        // This is required for connecting to S3 over privatelink when the\n        // target stage is in us-east-1\n        stageInfo.setUseS3RegionalUrl(session.getUseRegionalS3EndpointsForPresignedURL());\n      }\n    }\n\n    return stageInfo;\n  }\n\n  private static void setupUseRegionalUrl(JsonNode jsonNode, StageInfo stageInfo) {\n    if (stageInfo.getStageType() != StageInfo.StageType.GCS\n        && stageInfo.getStageType() != StageInfo.StageType.S3) {\n      return;\n    }\n    JsonNode useRegionalURLNode = jsonNode.path(\"data\").path(\"stageInfo\").path(\"useRegionalUrl\");\n    if (!useRegionalURLNode.isMissingNode()) {\n      boolean useRegionalURL = useRegionalURLNode.asBoolean(false);\n      stageInfo.setUseRegionalUrl(useRegionalURL);\n    }\n  }\n\n  private static void setupUseVirtualUrl(JsonNode jsonNode, StageInfo stageInfo) {\n    if (stageInfo.getStageType() != StageInfo.StageType.GCS) {\n      return;\n    }\n    JsonNode useVirtualURLNode = jsonNode.path(\"data\").path(\"stageInfo\").path(\"useVirtualUrl\");\n    if (!useVirtualURLNode.isMissingNode()) {\n      boolean useVirtualURL = useVirtualURLNode.asBoolean(false);\n      stageInfo.setUseVirtualUrl(useVirtualURL);\n    } else {\n      logger.debug(\"useVirtualUrl property missing from stage info\");\n    }\n  }\n\n  /**\n   * A helper method to verify if the local file path from GS matches what's parsed locally. This is\n   * for security purpose as documented in SNOW-15153.\n   *\n   * @param localFilePathFromGS the local file path to verify\n   * @throws SnowflakeSQLException Will be thrown if the log path if empty or if it doesn't match\n   *     what comes back from GS\n   */\n  private void verifyLocalFilePath(String localFilePathFromGS) throws SnowflakeSQLException {\n    String localFilePath = getLocalFilePathFromCommand(command, true);\n\n    if (!localFilePath.isEmpty() && !localFilePath.equals(localFilePathFromGS)) {\n      throw new SnowflakeSQLLoggedException(\n          queryID,\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Unexpected local file path from GS. From GS: \"\n              + localFilePathFromGS\n              + \", expected: \"\n              + localFilePath);\n    } else if (localFilePath.isEmpty()) {\n      logger.debug(\"Fail to parse local file path from command: {}\", command);\n    } else {\n      logger.trace(\"local file path from GS matches local parsing: {}\", localFilePath);\n    }\n  }\n\n  /**\n   * Parses out the local file path from the command. We need this to get the file paths to expand\n   * wildcards and make sure the paths GS returns are correct\n   *\n   * @param command The GET/PUT command we send to GS\n   * @param unescape True to unescape backslashes coming from GS\n   * @return Path to the local file\n   */\n  private static String getLocalFilePathFromCommand(String command, boolean unescape) {\n    if (command == null) {\n      logger.error(\"null command\", false);\n      return null;\n    }\n\n    if (command.indexOf(FILE_PROTOCOL) < 0) {\n      logger.error(\"file:// prefix not found in command: {}\", command);\n      return null;\n    }\n\n    int localFilePathBeginIdx = command.indexOf(FILE_PROTOCOL) + FILE_PROTOCOL.length();\n    boolean isLocalFilePathQuoted =\n        (localFilePathBeginIdx > FILE_PROTOCOL.length())\n            && (command.charAt(localFilePathBeginIdx - 1 - FILE_PROTOCOL.length()) == '\\'');\n\n    // the ending index is exclusive\n    int localFilePathEndIdx = 0;\n    String localFilePath = \"\";\n\n    if (isLocalFilePathQuoted) {\n      // look for the matching quote\n      localFilePathEndIdx = command.indexOf(\"'\", localFilePathBeginIdx);\n      if (localFilePathEndIdx > localFilePathBeginIdx) {\n        localFilePath = command.substring(localFilePathBeginIdx, localFilePathEndIdx);\n      }\n      // unescape backslashes to match the file name from GS\n      if (unescape) {\n        localFilePath = localFilePath.replaceAll(\"\\\\\\\\\\\\\\\\\", \"\\\\\\\\\");\n      }\n    } else {\n      // look for the first space or new line or semi colon\n      List<Integer> indexList = new ArrayList<>();\n      char[] delimiterChars = {' ', '\\n', ';'};\n      for (int i = 0; i < delimiterChars.length; i++) {\n        int charIndex = command.indexOf(delimiterChars[i], localFilePathBeginIdx);\n        if (charIndex != -1) {\n          indexList.add(charIndex);\n        }\n      }\n\n      localFilePathEndIdx = indexList.isEmpty() ? -1 : Collections.min(indexList);\n\n      if (localFilePathEndIdx > localFilePathBeginIdx) {\n        localFilePath = command.substring(localFilePathBeginIdx, localFilePathEndIdx);\n      } else if (localFilePathEndIdx == -1) {\n        localFilePath = command.substring(localFilePathBeginIdx);\n      }\n    }\n\n    return localFilePath;\n  }\n\n  /**\n   * @return JSON doc containing the command options returned by GS\n   * @throws SnowflakeSQLException Will be thrown if parsing the command by GS fails\n   */\n  private static JsonNode parseCommandInGS(SFStatement statement, String command)\n      throws SnowflakeSQLException {\n    Object result = null;\n    // send the command to GS\n    try {\n      result =\n          statement.executeHelper(\n              command,\n              \"application/json\",\n              null, // bindValues\n              false, // describeOnly\n              false, // internal\n              false, // async\n              new ExecTimeTelemetryData()); // OOB telemetry timing queries\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getQueryId(), ex, ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n\n    JsonNode jsonNode = (JsonNode) result;\n\n    logger.debug(\"Response: {}\", SecretDetector.maskSecrets(jsonNode.toString()));\n\n    SnowflakeUtil.checkErrorAndThrowException(jsonNode);\n    return jsonNode;\n  }\n\n  /**\n   * @param rootNode JSON doc returned by GS\n   * @throws SnowflakeSQLException Will be thrown if we fail to parse the stage credentials\n   */\n  private static Map<?, ?> extractStageCreds(JsonNode rootNode, String queryId)\n      throws SnowflakeSQLException {\n    JsonNode credsNode = rootNode.path(\"data\").path(\"stageInfo\").path(\"creds\");\n    Map<?, ?> stageCredentials = null;\n\n    try {\n      TypeReference<HashMap<String, String>> typeRef =\n          new TypeReference<HashMap<String, String>>() {};\n      stageCredentials = mapper.readValue(credsNode.toString(), typeRef);\n\n    } catch (Exception ex) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ex,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          \"Failed to parse the credentials (\"\n              + (credsNode != null ? credsNode.toString() : \"null\")\n              + \") due to exception: \"\n              + ex.getMessage());\n    }\n\n    return stageCredentials;\n  }\n\n  /**\n   * This is API function to retrieve the File Transfer Metadatas.\n   *\n   * <p>NOTE: It only supports PUT on S3/AZURE/GCS\n   *\n   * @return The file transfer metadatas for to-be-transferred files.\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  public List<SnowflakeFileTransferMetadata> getFileTransferMetadatas()\n      throws SnowflakeSQLException {\n    return getFileTransferMetadatas((InternalCallMarker) null);\n  }\n\n  public List<SnowflakeFileTransferMetadata> getFileTransferMetadatas(\n      InternalCallMarker internalCallMarker) throws SnowflakeSQLException {\n    recordIfExternal(\"SnowflakeFileTransferAgent\", \"getFileTransferMetadatas\", internalCallMarker);\n    List<SnowflakeFileTransferMetadata> result = new ArrayList<>();\n    if (stageInfo.getStageType() != StageInfo.StageType.GCS\n        && stageInfo.getStageType() != StageInfo.StageType.AZURE\n        && stageInfo.getStageType() != StageInfo.StageType.S3) {\n      throw new SnowflakeSQLLoggedException(\n          queryID,\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"This API only supports S3/AZURE/GCS\");\n    }\n\n    if (commandType != CommandType.UPLOAD) {\n      throw new SnowflakeSQLLoggedException(\n          queryID,\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"This API only supports PUT command\");\n    }\n\n    for (String sourceFilePath : sourceFiles) {\n      String sourceFileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(\"/\") + 1);\n      result.add(\n          new SnowflakeFileTransferMetadataV1(\n              stageInfo.getPresignedUrl(),\n              sourceFileName,\n              encryptionMaterial.get(0).getQueryStageMasterKey(),\n              encryptionMaterial.get(0).getQueryId(),\n              encryptionMaterial.get(0).getSmkId(),\n              commandType,\n              stageInfo));\n    }\n\n    return result;\n  }\n\n  /**\n   * This is API function to parse the File Transfer Metadatas from a supplied PUT call response.\n   *\n   * <p>NOTE: It only supports PUT on S3/AZURE/GCS (i.e. NOT LOCAL_FS)\n   *\n   * <p>It also assumes there is no active SFSession\n   *\n   * @param jsonNode JSON doc returned by GS from PUT call\n   * @return The file transfer metadatas for to-be-transferred files.\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  public static List<SnowflakeFileTransferMetadata> getFileTransferMetadatas(JsonNode jsonNode)\n      throws SnowflakeSQLException {\n    return getFileTransferMetadatas(jsonNode, null, null);\n  }\n\n  /**\n   * This is API function to parse the File Transfer Metadatas from a supplied PUT call response.\n   *\n   * <p>NOTE: It only supports PUT on S3/AZURE/GCS (i.e. NOT LOCAL_FS)\n   *\n   * <p>It also assumes there is no active SFSession\n   *\n   * @param jsonNode JSON doc returned by GS from PUT call\n   * @param queryId String last executed query id if available\n   * @return The file transfer metadatas for to-be-transferred files.\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  public static List<SnowflakeFileTransferMetadata> getFileTransferMetadatas(\n      JsonNode jsonNode, String queryId) throws SnowflakeSQLException {\n    return getFileTransferMetadatas(jsonNode, queryId, null);\n  }\n\n  public static List<SnowflakeFileTransferMetadata> getFileTransferMetadatas(\n      JsonNode jsonNode, String queryId, InternalCallMarker internalCallMarker)\n      throws SnowflakeSQLException {\n    recordIfExternal(\"SnowflakeFileTransferAgent\", \"getFileTransferMetadatas\", internalCallMarker);\n    CommandType commandType =\n        !jsonNode.path(\"data\").path(\"command\").isMissingNode()\n            ? CommandType.valueOf(jsonNode.path(\"data\").path(\"command\").asText())\n            : CommandType.UPLOAD;\n    if (commandType != CommandType.UPLOAD) {\n      throw new SnowflakeSQLException(\n          queryId, ErrorCode.INTERNAL_ERROR, \"This API only supports PUT commands\");\n    }\n\n    JsonNode locationsNode = jsonNode.path(\"data\").path(\"src_locations\");\n\n    if (!locationsNode.isArray()) {\n      throw new SnowflakeSQLException(\n          queryId, ErrorCode.INTERNAL_ERROR, \"src_locations must be an array\");\n    }\n\n    final String[] srcLocations;\n    final List<RemoteStoreFileEncryptionMaterial> encryptionMaterial;\n    try {\n      srcLocations = mapper.readValue(locationsNode.toString(), String[].class);\n    } catch (Exception ex) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ErrorCode.INTERNAL_ERROR,\n          \"Failed to parse the locations due to: \" + ex.getMessage());\n    }\n\n    try {\n      encryptionMaterial = getEncryptionMaterial(commandType, jsonNode);\n    } catch (Exception ex) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ErrorCode.INTERNAL_ERROR,\n          \"Failed to parse encryptionMaterial due to: \" + ex.getMessage());\n    }\n\n    // For UPLOAD we expect encryptionMaterial to have length 1\n    if (encryptionMaterial.size() != 1) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ErrorCode.INTERNAL_ERROR,\n          \"Encryption material for UPLOAD should have size 1 but have \"\n              + encryptionMaterial.size());\n    }\n\n    final Set<String> sourceFiles = expandFileNames(srcLocations, queryId);\n\n    StageInfo stageInfo = getStageInfo(jsonNode, null /*SFSession*/);\n\n    List<SnowflakeFileTransferMetadata> result = new ArrayList<>();\n    if (stageInfo.getStageType() != StageInfo.StageType.GCS\n        && stageInfo.getStageType() != StageInfo.StageType.AZURE\n        && stageInfo.getStageType() != StageInfo.StageType.S3) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ErrorCode.INTERNAL_ERROR,\n          \"This API only supports S3/AZURE/GCS, received=\" + stageInfo.getStageType());\n    }\n\n    for (String sourceFilePath : sourceFiles) {\n      String sourceFileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(\"/\") + 1);\n      result.add(\n          new SnowflakeFileTransferMetadataV1(\n              stageInfo.getPresignedUrl(),\n              sourceFileName,\n              encryptionMaterial.get(0) != null\n                  ? encryptionMaterial.get(0).getQueryStageMasterKey()\n                  : null,\n              encryptionMaterial.get(0) != null ? encryptionMaterial.get(0).getQueryId() : null,\n              encryptionMaterial.get(0) != null ? encryptionMaterial.get(0).getSmkId() : null,\n              commandType,\n              stageInfo));\n    }\n\n    return result;\n  }\n\n  @Override\n  public boolean execute() throws SQLException {\n    try {\n      logger.debug(\"Start init metadata\");\n\n      // initialize file metadata map\n      initFileMetadata();\n\n      logger.debug(\"Start checking file types\");\n\n      // check file compression type\n      if (commandType == CommandType.UPLOAD) {\n        processFileCompressionTypes();\n      }\n\n      // Filter out files that are already existing in the destination.\n      // GCS may or may not use presigned URL\n      if (!overwrite\n          && (stageInfo.getStageType() != StageInfo.StageType.GCS\n              || !storageClient.requirePresignedUrl())) {\n        logger.debug(\"Start filtering\");\n\n        filterExistingFiles();\n\n        logger.debug(\"Filtering done\");\n      }\n\n      synchronized (canceled) {\n        if (canceled) {\n          logger.debug(\"File transfer canceled by user\");\n          threadExecutor = null;\n          return false;\n        }\n      }\n\n      // create target directory for download command\n      if (commandType == CommandType.DOWNLOAD) {\n        File dir = new File(localLocation);\n        if (!dir.exists()) {\n          boolean created;\n          if (session.isOwnerOnlyStageFilePermissionsEnabled()) {\n            created = createOwnerOnlyPermissionDir(localLocation);\n          } else {\n            created = dir.mkdirs();\n          }\n          if (created) {\n            logger.debug(\"Directory created: {}\", localLocation);\n          } else {\n            logger.debug(\"Directory not created {}\", localLocation);\n          }\n        }\n\n        downloadFiles();\n      } else if (sourceFromStream) {\n        uploadStream();\n      } else {\n        // separate files to big files list and small files list\n        // big files will be uploaded in serial, while small files will be\n        // uploaded concurrently.\n        logger.debug(\"Start segregate files by size\");\n        segregateFilesBySize();\n\n        if (bigSourceFiles != null) {\n          logger.debug(\"Start uploading big files\");\n          uploadFiles(bigSourceFiles, 1);\n          logger.debug(\"End uploading big files\");\n        }\n\n        if (smallSourceFiles != null) {\n          logger.debug(\"Start uploading small files\");\n          uploadFiles(smallSourceFiles, parallel);\n          logger.debug(\"End uploading small files\");\n        }\n      }\n\n      // populate status rows to be returned to the client\n      populateStatusRows();\n\n      return true;\n    } finally {\n      if (storageClient != null) {\n        storageClient.shutdown();\n      }\n    }\n  }\n\n  /** Helper to upload data from a stream */\n  private void uploadStream() throws SnowflakeSQLException {\n    try {\n      FileMetadata fileMetadata = fileMetadataMap.get(SRC_FILE_NAME_FOR_STREAM);\n\n      if (fileMetadata.resultStatus == ResultStatus.SKIPPED) {\n        logger.debug(\n            \"Skipping {}, status: {}, details: {}\",\n            SRC_FILE_NAME_FOR_STREAM,\n            fileMetadata.resultStatus,\n            fileMetadata.errorDetails);\n        return;\n      }\n      threadExecutor = SnowflakeUtil.createDefaultExecutorService(\"sf-stream-upload-worker-\", 1);\n\n      RemoteStoreFileEncryptionMaterial encMat = encryptionMaterial.get(0);\n      Future<Void> uploadTask = null;\n      if (commandType == CommandType.UPLOAD) {\n        uploadTask =\n            threadExecutor.submit(\n                getUploadFileCallable(\n                    stageInfo,\n                    SRC_FILE_NAME_FOR_STREAM,\n                    fileMetadata,\n                    (stageInfo.getStageType() == StageInfo.StageType.LOCAL_FS)\n                        ? null\n                        : StorageClientFactory.getFactory()\n                            .createClient(stageInfo, parallel, encMat, session),\n                    session,\n                    command,\n                    sourceStream,\n                    true,\n                    parallel,\n                    null,\n                    encMat,\n                    queryID));\n      } else if (commandType == CommandType.DOWNLOAD) {\n        throw new SnowflakeSQLLoggedException(\n            queryID, session, ErrorCode.INTERNAL_ERROR.getMessageCode(), SqlState.INTERNAL_ERROR);\n      }\n\n      threadExecutor.shutdown();\n\n      try {\n        // Normal flow will never hit here. This is only for testing purposes\n        if (isInjectedFileTransferExceptionEnabled()\n            && SnowflakeFileTransferAgent.injectedFileTransferException\n                instanceof InterruptedException) {\n          throw (InterruptedException) SnowflakeFileTransferAgent.injectedFileTransferException;\n        }\n\n        // wait for the task to complete\n        uploadTask.get();\n      } catch (InterruptedException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryID, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n      } catch (ExecutionException ex) {\n        throw new SnowflakeSQLException(\n            queryID,\n            ex.getCause(),\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.FILE_OPERATION_UPLOAD_ERROR.getMessageCode());\n      }\n      logger.debug(\"Done with uploading from a stream\");\n    } finally {\n      if (threadExecutor != null) {\n        threadExecutor.shutdownNow();\n        threadExecutor = null;\n      }\n    }\n  }\n\n  /** Download a file from remote, and return an input stream */\n  @Override\n  public InputStream downloadStream(String fileName) throws SnowflakeSQLException {\n    logger.debug(\"Downloading file as stream: {}\", fileName);\n    if (stageInfo.getStageType() == StageInfo.StageType.LOCAL_FS) {\n      logger.error(\"downloadStream function doesn't support local file system\", false);\n\n      throw new SnowflakeSQLException(\n          queryID,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          session,\n          \"downloadStream function only supported in remote stages\");\n    }\n\n    remoteLocation remoteLocation = extractLocationAndPath(stageInfo.getLocation());\n\n    // when downloading files as stream there should be only one file in source files\n    // let's fail fast when more than one file matches instead of fetching random one\n    Set<String> matchedFiles =\n        sourceFiles.stream()\n            .filter(\n                sourceFileName -> {\n                  // We cannot match whole sourceFileName since it may be different e.g. for git\n                  // repositories so we match only raw filename\n                  String[] split = sourceFileName.split(\"/\");\n                  String fileNamePattern = \".*\" + Pattern.quote(split[split.length - 1]) + \"$\";\n                  return fileName.matches(fileNamePattern);\n                })\n            .collect(Collectors.toSet());\n    if (matchedFiles.size() > 1) {\n      throw new SnowflakeSQLException(\n          queryID,\n          SqlState.NO_DATA,\n          ErrorCode.TOO_MANY_FILES_TO_DOWNLOAD_AS_STREAM.getMessageCode(),\n          session,\n          \"There are more than one file matching \"\n              + fileName\n              + \": \"\n              + String.join(\",\", matchedFiles));\n    }\n    String sourceLocation =\n        matchedFiles.stream()\n            .findFirst()\n            .orElseThrow(\n                () ->\n                    new SnowflakeSQLException(\n                        queryID,\n                        SqlState.NO_DATA,\n                        ErrorCode.FILE_NOT_FOUND.getMessageCode(),\n                        session,\n                        \"File not found: \" + fileName));\n\n    if (!fileName.equals(sourceLocation)) {\n      // filename may be different from source location e.g. in git repositories\n      logger.debug(\"Changing file to download location from {} to {}\", fileName, sourceLocation);\n    }\n    String stageFilePath = sourceLocation;\n\n    if (!remoteLocation.path.isEmpty()) {\n      stageFilePath = SnowflakeUtil.concatFilePathNames(remoteLocation.path, sourceLocation, \"/\");\n    }\n    logger.debug(\"Stage file path for {} is {}\", sourceLocation, stageFilePath);\n\n    RemoteStoreFileEncryptionMaterial encMat = srcFileToEncMat.get(sourceLocation);\n    String presignedUrl = srcFileToPresignedUrl.get(sourceLocation);\n\n    return StorageClientFactory.getFactory()\n        .createClient(stageInfo, parallel, encMat, session)\n        .downloadToStream(\n            session,\n            command,\n            parallel,\n            remoteLocation.location,\n            stageFilePath,\n            stageInfo.getRegion(),\n            presignedUrl,\n            queryID);\n  }\n\n  /** Helper to download files from remote */\n  private void downloadFiles() throws SnowflakeSQLException {\n    try {\n      threadExecutor = SnowflakeUtil.createDefaultExecutorService(\"sf-file-download-worker-\", 1);\n\n      List<Future<Void>> downloadFileFutures = new LinkedList<>();\n      for (String srcFile : sourceFiles) {\n        FileMetadata fileMetadata = fileMetadataMap.get(srcFile);\n\n        // Check if the result status is already set so that we don't need to\n        // upload it\n        if (fileMetadata.resultStatus != ResultStatus.UNKNOWN) {\n          logger.debug(\n              \"Skipping {}, status: {}, details: {}\",\n              srcFile,\n              fileMetadata.resultStatus,\n              fileMetadata.errorDetails);\n          continue;\n        }\n\n        RemoteStoreFileEncryptionMaterial encMat = srcFileToEncMat.get(srcFile);\n        String presignedUrl = srcFileToPresignedUrl.get(srcFile);\n        downloadFileFutures.add(\n            threadExecutor.submit(\n                getDownloadFileCallable(\n                    stageInfo,\n                    srcFile,\n                    localLocation,\n                    fileMetadataMap,\n                    (stageInfo.getStageType() == StageInfo.StageType.LOCAL_FS)\n                        ? null\n                        : StorageClientFactory.getFactory()\n                            .createClient(stageInfo, parallel, encMat, session),\n                    session,\n                    command,\n                    parallel,\n                    encMat,\n                    presignedUrl,\n                    queryID)));\n\n        logger.debug(\"Submitted download job for: {}\", srcFile);\n      }\n\n      threadExecutor.shutdown();\n\n      try {\n        // wait for all threads to complete without timeout\n        threadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);\n        for (Future<Void> downloadFileFuture : downloadFileFutures) {\n          if (downloadFileFuture.isDone()) {\n            downloadFileFuture.get();\n          }\n        }\n      } catch (InterruptedException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryID, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n      } catch (ExecutionException ex) {\n        throw new SnowflakeSQLException(\n            queryID,\n            ex.getCause(),\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.FILE_OPERATION_DOWNLOAD_ERROR.getMessageCode());\n      }\n      logger.debug(\"Done with downloading\");\n    } finally {\n      if (threadExecutor != null) {\n        threadExecutor.shutdownNow();\n        threadExecutor = null;\n      }\n    }\n  }\n\n  /**\n   * This method is used in uploadFiles to delay the file upload for the given time, which is set as\n   * a session parameter called \"inject_wait_in_put.\" Normally this value is 0, but it is used in\n   * testing.\n   *\n   * @param delayTime the number of seconds to sleep before uploading the file\n   */\n  private void setUploadDelay(int delayTime) {\n    if (delayTime > 0) {\n      try {\n        TimeUnit.SECONDS.sleep(delayTime);\n      } catch (InterruptedException ie) {\n        Thread.currentThread().interrupt();\n      }\n    }\n  }\n\n  /**\n   * This method create a thread pool based on requested number of threads and upload the files\n   * using the thread pool.\n   *\n   * @param fileList The set of files to upload\n   * @param parallel degree of parallelism for the upload\n   * @throws SnowflakeSQLException Will be thrown if uploading the files fails\n   */\n  private void uploadFiles(Set<String> fileList, int parallel) throws SnowflakeSQLException {\n    try {\n      threadExecutor =\n          SnowflakeUtil.createDefaultExecutorService(\"sf-file-upload-worker-\", parallel);\n\n      List<Future<Void>> uploadFileFutures = new LinkedList<>();\n      for (String srcFile : fileList) {\n        FileMetadata fileMetadata = fileMetadataMap.get(srcFile);\n\n        // Check if the result status is already set so that we don't need to\n        // upload it\n        if (fileMetadata.resultStatus != ResultStatus.UNKNOWN) {\n          logger.debug(\n              \"Skipping {}, status: {}, details: {}\",\n              srcFile,\n              fileMetadata.resultStatus,\n              fileMetadata.errorDetails);\n\n          continue;\n        }\n\n        /*\n         * For small files, we upload files in parallel, so we don't\n         * want the remote store uploader to upload parts in parallel for each file.\n         * For large files, we upload them in serial, and we want remote store uploader\n         * to upload parts in parallel for each file. This is the reason\n         * for the parallel value.\n         */\n        File srcFileObj = new File(srcFile);\n        // PUT delay goes here!!\n        int delay = session.getInjectWaitInPut();\n        setUploadDelay(delay);\n\n        uploadFileFutures.add(\n            threadExecutor.submit(\n                getUploadFileCallable(\n                    stageInfo,\n                    srcFileObj.getPath(),\n                    fileMetadata,\n                    (stageInfo.getStageType() == StageInfo.StageType.LOCAL_FS)\n                        ? null\n                        : StorageClientFactory.getFactory()\n                            .createClient(stageInfo, parallel, encryptionMaterial.get(0), session),\n                    session,\n                    command,\n                    null,\n                    false,\n                    (parallel > 1 ? 1 : this.parallel),\n                    srcFileObj,\n                    encryptionMaterial.get(0),\n                    queryID)));\n\n        logger.debug(\"Submitted copy job for: {}\", srcFile);\n      }\n\n      // shut down the thread executor\n      threadExecutor.shutdown();\n\n      try {\n        // wait for all threads to complete without timeout\n        threadExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);\n        for (Future<Void> uploadFileFuture : uploadFileFutures) {\n          if (uploadFileFuture.isDone()) {\n            uploadFileFuture.get();\n          }\n        }\n      } catch (InterruptedException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryID, session, ErrorCode.INTERRUPTED.getMessageCode(), SqlState.QUERY_CANCELED);\n      } catch (ExecutionException ex) {\n        throw new SnowflakeSQLException(\n            queryID,\n            ex.getCause(),\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.FILE_OPERATION_UPLOAD_ERROR.getMessageCode());\n      }\n      logger.debug(\"Done with uploading\");\n\n    } finally {\n      // shut down the thread pool in any case\n      if (threadExecutor != null) {\n        threadExecutor.shutdownNow();\n        threadExecutor = null;\n      }\n    }\n  }\n\n  private void segregateFilesBySize() {\n    for (String srcFile : sourceFiles) {\n      if ((new File(srcFile)).length() > bigFileThreshold) {\n        if (bigSourceFiles == null) {\n          bigSourceFiles = new HashSet<String>(sourceFiles.size());\n        }\n\n        bigSourceFiles.add(srcFile);\n      } else {\n        if (smallSourceFiles == null) {\n          smallSourceFiles = new HashSet<String>(sourceFiles.size());\n        }\n\n        smallSourceFiles.add(srcFile);\n      }\n    }\n  }\n\n  public void cancel() {\n    synchronized (canceled) {\n      if (threadExecutor != null) {\n        threadExecutor.shutdownNow();\n        threadExecutor = null;\n      }\n      canceled = true;\n    }\n  }\n\n  /**\n   * process a list of file paths separated by \",\" and expand the wildcards if any to generate the\n   * list of paths for all files matched by the wildcards\n   *\n   * @param filePathList file path list\n   * @return a set of file names that is matched\n   * @throws SnowflakeSQLException if cannot find the file\n   */\n  static Set<String> expandFileNames(String[] filePathList, String queryId)\n      throws SnowflakeSQLException {\n    Set<String> result = new HashSet<String>();\n\n    // a location to file pattern map so that we only need to list the\n    // same directory once when they appear in multiple times.\n    Map<String, List<String>> locationToFilePatterns;\n\n    locationToFilePatterns = new HashMap<String, List<String>>();\n\n    String cwd = systemGetProperty(\"user.dir\");\n\n    for (String path : filePathList) {\n      // replace ~ with user home\n      if (path.startsWith(\"~\")) {\n        path = systemGetProperty(\"user.home\") + path.substring(1);\n      }\n\n      // user may also specify files relative to current directory\n      // add the current path if that is the case\n      if (!(new File(path)).isAbsolute()) {\n        logger.debug(\"Adding current working dir to relative file path.\");\n\n        path = cwd + localFSFileSep + path;\n      }\n\n      // check if the path contains any wildcards\n      if (!path.contains(\"*\")\n          && !path.contains(\"?\")\n          && !(path.contains(\"[\") && path.contains(\"]\"))) {\n        /* this file path doesn't have any wildcard, so we don't need to\n         * expand it\n         */\n        result.add(path);\n      } else {\n        // get the directory path\n        int lastFileSepIndex = path.lastIndexOf(localFSFileSep);\n\n        // SNOW-15203: if we don't find a default file sep, try \"/\" if it is not\n        // the default file sep.\n        if (lastFileSepIndex < 0 && !\"/\".equals(localFSFileSep)) {\n          lastFileSepIndex = path.lastIndexOf(\"/\");\n        }\n\n        String loc = path.substring(0, lastFileSepIndex + 1);\n\n        String filePattern = path.substring(lastFileSepIndex + 1);\n\n        List<String> filePatterns = locationToFilePatterns.get(loc);\n\n        if (filePatterns == null) {\n          filePatterns = new ArrayList<String>();\n          locationToFilePatterns.put(loc, filePatterns);\n        }\n\n        filePatterns.add(filePattern);\n      }\n    }\n\n    // For each location, list files and match against the patterns\n    for (Map.Entry<String, List<String>> entry : locationToFilePatterns.entrySet()) {\n      try {\n        File dir = new File(entry.getKey());\n\n        logger.debug(\n            \"Listing files under: {} with patterns: {}\",\n            entry.getKey(),\n            entry.getValue().toString());\n\n        // Normal flow will never hit here. This is only for testing purposes\n        if (isInjectedFileTransferExceptionEnabled()\n            && injectedFileTransferException instanceof Exception) {\n          throw (Exception) SnowflakeFileTransferAgent.injectedFileTransferException;\n        }\n        // The following currently ignore sub directories\n        File[] filesMatchingPattern =\n            dir.listFiles((FileFilter) new WildcardFileFilter(entry.getValue()));\n        if (filesMatchingPattern != null) {\n          for (File file : filesMatchingPattern) {\n            result.add(file.getCanonicalPath());\n          }\n        } else {\n          logger.debug(\"No files under {} matching pattern {}\", entry.getKey(), entry.getValue());\n        }\n      } catch (Exception ex) {\n        throw new SnowflakeSQLException(\n            queryId,\n            ex,\n            SqlState.DATA_EXCEPTION,\n            ErrorCode.FAIL_LIST_FILES.getMessageCode(),\n            \"Exception: \"\n                + ex.getMessage()\n                + \", Dir=\"\n                + entry.getKey()\n                + \", Patterns=\"\n                + entry.getValue().toString());\n      }\n    }\n\n    logger.debug(\"Expanded file paths: \");\n\n    for (String filePath : result) {\n      logger.debug(\"File: {}\", filePath);\n    }\n\n    return result;\n  }\n\n  private static boolean pushFileToLocal(\n      String stageLocation,\n      String filePath,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutStr,\n      SFBaseSession session,\n      String queryId)\n      throws SQLException {\n\n    // replace ~ with user home\n    stageLocation = stageLocation.replace(\"~\", systemGetProperty(\"user.home\"));\n    try {\n      logger.debug(\n          \"Copy file. srcFile: {}, destination: {}, destFileName: {}\",\n          filePath,\n          stageLocation,\n          destFileName);\n\n      File destFile =\n          new File(SnowflakeUtil.concatFilePathNames(stageLocation, destFileName, localFSFileSep));\n\n      if (fileBackedOutStr != null) {\n        inputStream = fileBackedOutStr.asByteSource().openStream();\n      }\n      FileUtils.copyInputStreamToFile(inputStream, destFile);\n    } catch (Exception ex) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          ex.getMessage());\n    }\n\n    return true;\n  }\n\n  private static boolean pullFileFromLocal(\n      String sourceLocation,\n      String filePath,\n      String destLocation,\n      String destFileName,\n      SFBaseSession session,\n      String queryId)\n      throws SQLException {\n    try {\n      logger.debug(\n          \"Copy file. srcFile: {}, destination: {}, destFileName: {}\",\n          sourceLocation + localFSFileSep + filePath,\n          destLocation,\n          destFileName);\n\n      File srcFile =\n          new File(SnowflakeUtil.concatFilePathNames(sourceLocation, filePath, localFSFileSep));\n\n      FileUtils.copyFileToDirectory(srcFile, new File(destLocation));\n    } catch (Exception ex) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          ex.getMessage());\n    }\n\n    return true;\n  }\n\n  private static void pushFileToRemoteStore(\n      StageInfo stage,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutStr,\n      long uploadSize,\n      String digest,\n      FileCompressionType compressionType,\n      SnowflakeStorageClient initialClient,\n      SFSession session,\n      String command,\n      int parallel,\n      File srcFile,\n      boolean uploadFromStream,\n      RemoteStoreFileEncryptionMaterial encMat,\n      String streamingIngestClientName,\n      String streamingIngestClientKey,\n      String queryId)\n      throws SQLException, IOException {\n    remoteLocation remoteLocation = extractLocationAndPath(stage.getLocation());\n\n    String origDestFileName = destFileName;\n    if (remoteLocation.path != null && !remoteLocation.path.isEmpty()) {\n      destFileName =\n          remoteLocation.path + (!remoteLocation.path.endsWith(\"/\") ? \"/\" : \"\") + destFileName;\n    }\n\n    logger.debug(\n        \"Upload object. Location: {}, key: {}, srcFile: {}, encryption: {}\",\n        remoteLocation.location,\n        destFileName,\n        srcFile,\n        (ArgSupplier)\n            () -> (encMat == null ? \"NULL\" : encMat.getSmkId() + \"|\" + encMat.getQueryId()));\n\n    StorageObjectMetadata meta =\n        StorageClientFactory.getFactory().createStorageMetadataObj(stage.getStageType());\n    meta.setContentLength(uploadSize);\n    if (digest != null) {\n      initialClient.addDigestMetadata(meta, digest);\n    }\n\n    if (compressionType != null && compressionType.isSupported()) {\n      meta.setContentEncoding(compressionType.name().toLowerCase());\n    }\n\n    if (streamingIngestClientName != null && streamingIngestClientKey != null) {\n      initialClient.addStreamingIngestMetadata(\n          meta, streamingIngestClientName, streamingIngestClientKey);\n    }\n\n    try {\n      String presignedUrl = \"\";\n      if (initialClient.requirePresignedUrl()) {\n        // need to replace file://mypath/myfile?.csv with file://mypath/myfile1.csv.gz\n        String localFilePath = getLocalFilePathFromCommand(command, false);\n        String commandWithExactPath = command.replace(localFilePath, origDestFileName);\n        // then hand that to GS to get the actual presigned URL we'll use\n        SFStatement statement = new SFStatement(session);\n        JsonNode jsonNode = parseCommandInGS(statement, commandWithExactPath);\n\n        if (!jsonNode.path(\"data\").path(\"stageInfo\").path(\"presignedUrl\").isMissingNode()) {\n          presignedUrl = jsonNode.path(\"data\").path(\"stageInfo\").path(\"presignedUrl\").asText();\n        }\n      }\n      initialClient.upload(\n          session,\n          command,\n          parallel,\n          uploadFromStream,\n          remoteLocation.location,\n          srcFile,\n          destFileName,\n          inputStream,\n          fileBackedOutStr,\n          meta,\n          stage.getRegion(),\n          presignedUrl,\n          queryId);\n    } finally {\n      if (uploadFromStream && inputStream != null) {\n        inputStream.close();\n      }\n    }\n  }\n\n  /**\n   * Static API function to upload data without JDBC session.\n   *\n   * <p>NOTE: This function is developed based on getUploadFileCallable().\n   *\n   * @param config Configuration to upload a file to cloud storage\n   * @throws Exception if error occurs while data upload.\n   */\n  public static void uploadWithoutConnection(SnowflakeFileTransferConfig config) throws Exception {\n    logger.trace(\"Entering uploadWithoutConnection...\");\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) config.getSnowflakeFileTransferMetadata();\n    InputStream uploadStream = config.getUploadStream();\n    boolean requireCompress = config.getRequireCompress();\n    int networkTimeoutInMilli = config.getNetworkTimeoutInMilli();\n    OCSPMode ocspMode = config.getOcspMode();\n    Properties proxyProperties = config.getProxyProperties();\n    String streamingIngestClientName = config.getStreamingIngestClientName();\n    String streamingIngestClientKey = config.getStreamingIngestClientKey();\n\n    // Create HttpClient key\n    HttpClientSettingsKey key =\n        SnowflakeUtil.convertProxyPropertiesToHttpClientKey(ocspMode, proxyProperties);\n\n    StageInfo stageInfo = metadata.getStageInfo();\n    stageInfo.setProxyProperties(proxyProperties);\n    String destFileName = metadata.getPresignedUrlFileName();\n\n    logger.debug(\"Begin upload data for \" + destFileName);\n\n    long uploadSize;\n    File fileToUpload = null;\n    String digest = null;\n\n    // Temp file that needs to be cleaned up when upload was successful\n    FileBackedOutputStream fileBackedOutputStream = null;\n\n    RemoteStoreFileEncryptionMaterial encMat = metadata.getEncryptionMaterial();\n    if (encMat.getQueryId() == null\n        && encMat.getQueryStageMasterKey() == null\n        && encMat.getSmkId() == null) {\n      encMat = null;\n    }\n    // SNOW-16082: we should capture exception if we fail to compress or\n    // calculate digest.\n    try {\n      if (requireCompress) {\n        InputStreamWithMetadata compressedSizeAndStream =\n            (encMat == null\n                ? compressStreamWithGZIPNoDigest(uploadStream, /* session= */ null, null)\n                : compressStreamWithGZIP(uploadStream, /* session= */ null, encMat.getQueryId()));\n\n        fileBackedOutputStream = compressedSizeAndStream.fileBackedOutputStream;\n\n        // update the size\n        uploadSize = compressedSizeAndStream.size;\n        digest = compressedSizeAndStream.digest;\n\n        if (compressedSizeAndStream.fileBackedOutputStream.getFile() != null) {\n          fileToUpload = compressedSizeAndStream.fileBackedOutputStream.getFile();\n        }\n\n        logger.debug(\"New size after compression: {}\", uploadSize);\n      } else {\n        // If it's not local_fs, we store our digest in the metadata\n        // In local_fs, we don't need digest, and if we turn it on,\n        // we will consume whole uploadStream, which local_fs uses.\n        InputStreamWithMetadata result = computeDigest(uploadStream, true);\n        digest = result.digest;\n        fileBackedOutputStream = result.fileBackedOutputStream;\n        uploadSize = result.size;\n\n        if (result.fileBackedOutputStream.getFile() != null) {\n          fileToUpload = result.fileBackedOutputStream.getFile();\n        }\n      }\n\n      logger.debug(\n          \"Started copying file to {}:{} destName: {} compressed ? {} size={}\",\n          stageInfo.getStageType().name(),\n          stageInfo.getLocation(),\n          destFileName,\n          (requireCompress ? \"yes\" : \"no\"),\n          uploadSize);\n\n      SnowflakeStorageClient initialClient =\n          StorageClientFactory.getFactory().createClient(stageInfo, 1, encMat, /* session= */ null);\n\n      // Normal flow will never hit here. This is only for testing purposes\n      if (isInjectedFileTransferExceptionEnabled()) {\n        throw (Exception) SnowflakeFileTransferAgent.injectedFileTransferException;\n      }\n\n      String queryId = encMat != null && encMat.getQueryId() != null ? encMat.getQueryId() : null;\n      switch (stageInfo.getStageType()) {\n        case S3:\n        case AZURE:\n          pushFileToRemoteStore(\n              metadata.getStageInfo(),\n              destFileName,\n              uploadStream,\n              fileBackedOutputStream,\n              uploadSize,\n              digest,\n              (requireCompress ? FileCompressionType.GZIP : null),\n              initialClient,\n              config.getSession(),\n              config.getCommand(),\n              1,\n              fileToUpload,\n              (fileToUpload == null),\n              encMat,\n              streamingIngestClientName,\n              streamingIngestClientKey,\n              queryId);\n          break;\n        case GCS:\n          // If the down-scoped token is used to upload file, one metadata may be used to upload\n          // multiple files, so use the dest file name in config.\n          destFileName =\n              metadata.isForOneFile()\n                  ? metadata.getPresignedUrlFileName()\n                  : config.getDestFileName();\n\n          pushFileToRemoteStoreWithPresignedUrl(\n              metadata.getStageInfo(),\n              destFileName,\n              uploadStream,\n              fileBackedOutputStream,\n              uploadSize,\n              digest,\n              (requireCompress ? FileCompressionType.GZIP : null),\n              initialClient,\n              networkTimeoutInMilli,\n              key,\n              1,\n              null,\n              true,\n              encMat,\n              metadata.getPresignedUrl(),\n              streamingIngestClientName,\n              streamingIngestClientKey,\n              queryId);\n          break;\n      }\n    } catch (Exception ex) {\n      if (!config.isSilentException()) {\n        logger.error(\"Exception encountered during file upload in uploadWithoutConnection\", ex);\n      }\n      throw ex;\n    } finally {\n      if (fileBackedOutputStream != null) {\n        try {\n          fileBackedOutputStream.reset();\n        } catch (IOException ex) {\n          logger.debug(\"Failed to clean up temp file: {}\", ex);\n        }\n      }\n    }\n  }\n\n  /**\n   * Push a file (or stream) to remote store with pre-signed URL without JDBC session.\n   *\n   * <p>NOTE: This function is developed based on pushFileToRemoteStore(). The main difference is\n   * that the caller needs to provide pre-signed URL and the upload doesn't need JDBC session.\n   */\n  private static void pushFileToRemoteStoreWithPresignedUrl(\n      StageInfo stage,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutStr,\n      long uploadSize,\n      String digest,\n      FileCompressionType compressionType,\n      SnowflakeStorageClient initialClient,\n      int networkTimeoutInMilli,\n      HttpClientSettingsKey ocspModeAndProxyKey,\n      int parallel,\n      File srcFile,\n      boolean uploadFromStream,\n      RemoteStoreFileEncryptionMaterial encMat,\n      String presignedUrl,\n      String streamingIngestClientName,\n      String streamingIngestClientKey,\n      String queryId)\n      throws SQLException, IOException {\n    remoteLocation remoteLocation = extractLocationAndPath(stage.getLocation());\n\n    if (remoteLocation.path != null && !remoteLocation.path.isEmpty()) {\n      destFileName =\n          remoteLocation.path + (!remoteLocation.path.endsWith(\"/\") ? \"/\" : \"\") + destFileName;\n    }\n\n    logger.debug(\n        \"Upload object. Location: {}, key: {}, srcFile: {}, encryption: {}\",\n        remoteLocation.location,\n        destFileName,\n        srcFile,\n        (ArgSupplier)\n            () -> (encMat == null ? \"NULL\" : encMat.getSmkId() + \"|\" + encMat.getQueryId()));\n\n    StorageObjectMetadata meta =\n        StorageClientFactory.getFactory().createStorageMetadataObj(stage.getStageType());\n    meta.setContentLength(uploadSize);\n    if (digest != null) {\n      initialClient.addDigestMetadata(meta, digest);\n    }\n\n    if (compressionType != null && compressionType.isSupported()) {\n      meta.setContentEncoding(compressionType.name().toLowerCase());\n    }\n\n    if (streamingIngestClientName != null && streamingIngestClientKey != null) {\n      initialClient.addStreamingIngestMetadata(\n          meta, streamingIngestClientName, streamingIngestClientKey);\n    }\n\n    try {\n      initialClient.uploadWithPresignedUrlWithoutConnection(\n          networkTimeoutInMilli,\n          ocspModeAndProxyKey,\n          parallel,\n          uploadFromStream,\n          remoteLocation.location,\n          srcFile,\n          destFileName,\n          inputStream,\n          fileBackedOutStr,\n          meta,\n          stage.getRegion(),\n          presignedUrl,\n          queryId);\n    } finally {\n      if (uploadFromStream && inputStream != null) {\n        inputStream.close();\n      }\n    }\n  }\n\n  /**\n   * This static method is called when we are handling an expired token exception It retrieves a\n   * fresh token from GS and then calls .renew() on the storage client to refresh itself with the\n   * new token\n   *\n   * @param session a session object\n   * @param command a command to be retried\n   * @param client a Snowflake Storage client object\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  public static void renewExpiredToken(\n      SFSession session, String command, SnowflakeStorageClient client)\n      throws SnowflakeSQLException {\n    SFStatement statement = new SFStatement(session);\n    JsonNode jsonNode = parseCommandInGS(statement, command);\n    String queryId = jsonNode.path(\"data\").path(\"queryId\").asText();\n    Map<?, ?> stageCredentials = extractStageCreds(jsonNode, queryId);\n\n    // renew client with the fresh token\n    logger.debug(\"Renewing expired access token\");\n    client.renew(stageCredentials);\n  }\n\n  private static void pullFileFromRemoteStore(\n      StageInfo stage,\n      String filePath,\n      String destFileName,\n      String localLocation,\n      SnowflakeStorageClient initialClient,\n      SFSession session,\n      String command,\n      int parallel,\n      RemoteStoreFileEncryptionMaterial encMat,\n      String presignedUrl,\n      String queryId)\n      throws SQLException, IOException {\n    remoteLocation remoteLocation = extractLocationAndPath(stage.getLocation());\n\n    String stageFilePath = filePath;\n\n    if (!remoteLocation.path.isEmpty()) {\n      stageFilePath = SnowflakeUtil.concatFilePathNames(remoteLocation.path, filePath, \"/\");\n    }\n\n    logger.debug(\n        \"Download object. Location: {}, key: {}, srcFile: {}, encryption: {}\",\n        remoteLocation.location,\n        stageFilePath,\n        filePath,\n        (ArgSupplier)\n            () -> (encMat == null ? \"NULL\" : encMat.getSmkId() + \"|\" + encMat.getQueryId()));\n\n    initialClient.download(\n        session,\n        command,\n        localLocation,\n        destFileName,\n        parallel,\n        remoteLocation.location,\n        stageFilePath,\n        stage.getRegion(),\n        presignedUrl,\n        queryId);\n  }\n\n  /**\n   * From the set of files intended to be uploaded/downloaded, derive a common prefix and use the\n   * listObjects API to get the object summary for each object that has the common prefix.\n   *\n   * <p>For each returned object, we compare the size and digest with the local file and if they are\n   * the same, we will not upload/download the file.\n   *\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  private void filterExistingFiles() throws SnowflakeSQLException {\n    /*\n     * Build a reverse map from destination file name to source file path\n     * The map will be used for looking up the source file for destination\n     * files that already exist in destination location and mark them to be\n     * skipped for uploading/downloading\n     */\n    Map<String, String> destFileNameToSrcFileMap =\n        new HashMap<String, String>(fileMetadataMap.size());\n\n    logger.debug(\"Build reverse map from destination file name to source file\");\n\n    for (Map.Entry<String, FileMetadata> entry : fileMetadataMap.entrySet()) {\n      if (entry.getValue().destFileName != null) {\n        String prevSrcFile =\n            destFileNameToSrcFileMap.put(entry.getValue().destFileName, entry.getKey());\n\n        if (prevSrcFile != null) {\n          FileMetadata prevFileMetadata = fileMetadataMap.get(prevSrcFile);\n\n          prevFileMetadata.resultStatus = ResultStatus.COLLISION;\n          prevFileMetadata.errorDetails = prevSrcFile + \" has same name as \" + entry.getKey();\n        }\n      } else {\n        logger.debug(\"No dest file name found for: {}\", entry.getKey());\n        logger.debug(\"Status: {}\", entry.getValue().resultStatus);\n      }\n    }\n\n    // no files to be processed\n    if (destFileNameToSrcFileMap.size() == 0) {\n      return;\n    }\n\n    // determine greatest common prefix for all stage file names so that\n    // we can call remote store API to list the objects and get their digest to compare\n    // with local files\n    String[] stageFileNames;\n\n    if (commandType == CommandType.UPLOAD) {\n      stageFileNames = destFileNameToSrcFileMap.keySet().toArray(new String[0]);\n    } else {\n      stageFileNames = destFileNameToSrcFileMap.values().toArray(new String[0]);\n    }\n\n    // find greatest common prefix for all stage file names\n    Arrays.sort(stageFileNames);\n\n    String greatestCommonPrefix =\n        SnowflakeUtil.greatestCommonPrefix(\n            stageFileNames[0], stageFileNames[stageFileNames.length - 1]);\n\n    logger.debug(\"Greatest common prefix: {}\", greatestCommonPrefix);\n\n    // use the greatest common prefix to list objects under stage location\n    if (stageInfo.getStageType() == StageInfo.StageType.S3\n        || stageInfo.getStageType() == StageInfo.StageType.AZURE\n        || stageInfo.getStageType() == StageInfo.StageType.GCS) {\n      logger.debug(\"Check existing files on remote storage for the common prefix\");\n\n      remoteLocation storeLocation = extractLocationAndPath(stageInfo.getLocation());\n\n      StorageObjectSummaryCollection objectSummaries = null;\n\n      int retryCount = 0;\n\n      logger.debug(\"Start dragging object summaries from remote storage\");\n      do {\n        try {\n          // Normal flow will never hit here. This is only for testing purposes\n          if (isInjectedFileTransferExceptionEnabled()\n              && SnowflakeFileTransferAgent.injectedFileTransferException\n                  instanceof StorageProviderException) {\n            throw (StorageProviderException)\n                SnowflakeFileTransferAgent.injectedFileTransferException;\n          }\n          objectSummaries =\n              storageClient.listObjects(\n                  storeLocation.location,\n                  SnowflakeUtil.concatFilePathNames(storeLocation.path, greatestCommonPrefix, \"/\"));\n          logger.debug(\"Received object summaries from remote storage\");\n        } catch (Exception ex) {\n          logger.debug(\"Listing objects for filtering encountered exception: {}\", ex.getMessage());\n\n          // Need to unwrap StorageProviderException since handleStorageException only handle base\n          // cause.\n          if (ex instanceof StorageProviderException) {\n            ex =\n                (Exception)\n                    ex.getCause(); // Cause of StorageProviderException is always an Exception\n          }\n          storageClient.handleStorageException(\n              ex, ++retryCount, \"listObjects\", session, command, queryID);\n          continue;\n        }\n\n        try {\n          compareAndSkipRemoteFiles(objectSummaries, destFileNameToSrcFileMap);\n          break; // exit retry loop\n        } catch (Exception ex) {\n          // This exception retry logic is mainly for Azure iterator. Since Azure iterator is a lazy\n          // iterator,\n          // it can throw exceptions during the for-each calls. To be more specific, iterator apis,\n          // e.g. hasNext(), may throw Storage service error.\n          logger.debug(\n              \"Comparison with existing files in remote storage encountered exception.\", ex);\n          if (ex instanceof StorageProviderException) {\n            ex =\n                (Exception)\n                    ex.getCause(); // Cause of StorageProviderException is always an Exception\n          }\n          storageClient.handleStorageException(\n              ex, ++retryCount, \"compareRemoteFiles\", session, command, queryID);\n        }\n      } while (retryCount <= storageClient.getMaxRetries());\n    } else if (stageInfo.getStageType() == StageInfo.StageType.LOCAL_FS) {\n      for (String stageFileName : stageFileNames) {\n        String stageFilePath =\n            SnowflakeUtil.concatFilePathNames(\n                stageInfo.getLocation(), stageFileName, localFSFileSep);\n\n        File stageFile = new File(stageFilePath);\n\n        // if stage file doesn't exist, no need to skip whether for\n        // upload/download\n        if (!stageFile.exists()) {\n          continue;\n        }\n\n        String mappedSrcFile =\n            (commandType == CommandType.UPLOAD)\n                ? destFileNameToSrcFileMap.get(stageFileName)\n                : stageFileName;\n\n        String localFile =\n            (commandType == CommandType.UPLOAD)\n                ? mappedSrcFile\n                : (localLocation + fileMetadataMap.get(mappedSrcFile).destFileName);\n\n        if (commandType == CommandType.UPLOAD\n            && stageFileName.equals(fileMetadataMap.get(mappedSrcFile).destFileName)) {\n          skipFile(mappedSrcFile, stageFileName);\n          continue;\n        }\n\n        // Check file size first, if they are different, we don't need\n        // to check digest\n        if (!fileMetadataMap.get(mappedSrcFile).requireCompress\n            && stageFile.length() != (new File(localFile)).length()) {\n          logger.debug(\n              \"Size diff between stage and local, will {} {}\",\n              commandType.name().toLowerCase(),\n              mappedSrcFile);\n          continue;\n        }\n\n        // stage file exist and either we will be compressing or\n        // the dest file has same size as the source file size we will\n        // compare digest values below\n        String localFileHashText = null;\n        String stageFileHashText = null;\n\n        List<FileBackedOutputStream> fileBackedOutputStreams = new ArrayList<>();\n        InputStream localFileStream = null;\n        try {\n          // calculate the digest hash of the local file\n          localFileStream = new FileInputStream(localFile);\n\n          if (fileMetadataMap.get(mappedSrcFile).requireCompress) {\n            logger.debug(\"Compressing stream for digest check\");\n\n            InputStreamWithMetadata res = compressStreamWithGZIP(localFileStream, session, queryID);\n            fileBackedOutputStreams.add(res.fileBackedOutputStream);\n\n            localFileStream = res.fileBackedOutputStream.asByteSource().openStream();\n          }\n\n          InputStreamWithMetadata res = computeDigest(localFileStream, false);\n          localFileHashText = res.digest;\n          fileBackedOutputStreams.add(res.fileBackedOutputStream);\n        } catch (IOException | NoSuchAlgorithmException ex) {\n          throw new SnowflakeSQLLoggedException(\n              queryID,\n              session,\n              SqlState.INTERNAL_ERROR,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              ex,\n              \"Error reading local file: \" + localFile);\n        } finally {\n          for (FileBackedOutputStream stream : fileBackedOutputStreams) {\n            if (stream != null) {\n              try {\n                stream.reset();\n              } catch (IOException ex) {\n                logger.debug(\"Failed to clean up temp file: {}\", ex);\n              }\n            }\n          }\n          IOUtils.closeQuietly(localFileStream);\n        }\n\n        FileBackedOutputStream fileBackedOutputStream = null;\n        InputStream stageFileStream = null;\n        try {\n          // calculate digest for stage file\n          stageFileStream = new FileInputStream(stageFilePath);\n\n          InputStreamWithMetadata res = computeDigest(stageFileStream, false);\n          stageFileHashText = res.digest;\n          fileBackedOutputStream = res.fileBackedOutputStream;\n\n        } catch (IOException | NoSuchAlgorithmException ex) {\n          throw new SnowflakeSQLLoggedException(\n              queryID,\n              session,\n              SqlState.INTERNAL_ERROR,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              ex,\n              \"Error reading stage file: \" + stageFilePath);\n        } finally {\n          try {\n            if (fileBackedOutputStream != null) {\n              fileBackedOutputStream.reset();\n            }\n          } catch (IOException ex) {\n            logger.debug(\"Failed to clean up temp file: {}\", ex);\n          }\n          IOUtils.closeQuietly(stageFileStream);\n        }\n\n        // continue if digest is different so that we will process the file\n        if (!stageFileHashText.equals(localFileHashText)) {\n          logger.debug(\n              \"Digest diff between local and stage, will {} {}\",\n              commandType.name().toLowerCase(),\n              mappedSrcFile);\n          continue;\n        } else {\n          logger.debug(\"Digest matches between local and stage, will skip {}\", mappedSrcFile);\n\n          // skip the file given that the check sum is the same b/w source\n          // and destination\n          skipFile(mappedSrcFile, stageFileName);\n        }\n      }\n    }\n  }\n\n  /**\n   * For input objects, we compare the size and digest with the local files and if they are the\n   * same, we will not upload/download the file.\n   *\n   * @param objectSummaries input objects collection\n   * @param destFileNameToSrcFileMap map between dest file name and src file\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  private void compareAndSkipRemoteFiles(\n      StorageObjectSummaryCollection objectSummaries, Map<String, String> destFileNameToSrcFileMap)\n      throws SnowflakeSQLException {\n    for (StorageObjectSummary obj : objectSummaries) {\n      logger.debug(\n          \"Existing object: key: {} size: {} md5: {}\", obj.getKey(), obj.getSize(), obj.getMD5());\n\n      int idxOfLastFileSep = obj.getKey().lastIndexOf(\"/\");\n      String objFileName = obj.getKey().substring(idxOfLastFileSep + 1);\n\n      // get the path to the local file so that we can calculate digest\n      String mappedSrcFile = destFileNameToSrcFileMap.get(objFileName);\n\n      // skip objects that don't have a corresponding file to be uploaded\n      if (mappedSrcFile == null) {\n        continue;\n      }\n\n      logger.debug(\n          \"Next compare digest for {} against {} on the remote store\", mappedSrcFile, objFileName);\n\n      String localFile = null;\n      final boolean remoteEncrypted;\n\n      try {\n        // Normal flow will never hit here. This is only for testing purposes\n        if (isInjectedFileTransferExceptionEnabled()) {\n          throw (NoSuchAlgorithmException) SnowflakeFileTransferAgent.injectedFileTransferException;\n        }\n\n        localFile =\n            (commandType == CommandType.UPLOAD) ? mappedSrcFile : (localLocation + objFileName);\n\n        if (commandType == CommandType.DOWNLOAD && !(new File(localFile)).exists()) {\n          logger.debug(\"File does not exist locally, will download {}\", mappedSrcFile);\n          continue;\n        }\n\n        // if it's an upload and there's already a file existing remotely with the same name, skip\n        // uploading it\n        if (commandType == CommandType.UPLOAD\n            && objFileName.equals(fileMetadataMap.get(mappedSrcFile).destFileName)) {\n          skipFile(mappedSrcFile, objFileName);\n          continue;\n        }\n\n        // Check file size first, if their difference is bigger than the block\n        // size, we don't need to compare digests\n        if (!fileMetadataMap.get(mappedSrcFile).requireCompress\n            && Math.abs(obj.getSize() - (new File(localFile)).length()) > 16) {\n          logger.debug(\n              \"Size diff between remote and local, will {} {}\",\n              commandType.name().toLowerCase(),\n              mappedSrcFile);\n          continue;\n        }\n\n        // Get object metadata from remote storage\n        //\n        StorageObjectMetadata meta;\n\n        try {\n          meta = storageClient.getObjectMetadata(obj.getLocation(), obj.getKey());\n        } catch (StorageProviderException spEx) {\n          // SNOW-14521: when file is not found, ok to upload\n          if (spEx.isServiceException404()) {\n            // log it\n            logger.debug(\n                \"File returned from listing but found missing {} when getting its\"\n                    + \" metadata. Location: {}, key: {}\",\n                obj.getLocation(),\n                obj.getKey());\n\n            // the file is not found, ok to upload\n            continue;\n          }\n\n          // for any other exception, log an error\n          logger.error(\"Fetching object metadata encountered exception: {}\", spEx.getMessage());\n\n          throw spEx;\n        }\n\n        String objDigest = storageClient.getDigestMetadata(meta);\n\n        remoteEncrypted =\n            MatDesc.parse(meta.getUserMetadata().get(storageClient.getMatdescKey())) != null;\n\n        // calculate the digest hash of the local file\n        InputStream fileStream = null;\n        String hashText = null;\n\n        // Streams (potentially with temp files) to clean up\n        final List<FileBackedOutputStream> fileBackedOutputStreams = new ArrayList<>();\n        try {\n          fileStream = new FileInputStream(localFile);\n          if (fileMetadataMap.get(mappedSrcFile).requireCompress) {\n            logger.debug(\"Compressing stream for digest check\");\n\n            InputStreamWithMetadata res = compressStreamWithGZIP(fileStream, session, queryID);\n\n            fileStream = res.fileBackedOutputStream.asByteSource().openStream();\n            fileBackedOutputStreams.add(res.fileBackedOutputStream);\n          }\n\n          // If the remote file has our digest, compute the SHA-256\n          // for the local file\n          // If the remote file does not have our digest but is unencrypted,\n          // we compare the MD5 of the unencrypted local file to the ETag\n          // of the S3 file.\n          // Otherwise (remote file is encrypted, but has no sfc-digest),\n          // no comparison is performed\n          if (objDigest != null) {\n            InputStreamWithMetadata res = computeDigest(fileStream, false);\n            hashText = res.digest;\n            fileBackedOutputStreams.add(res.fileBackedOutputStream);\n\n          } else if (!remoteEncrypted) {\n            hashText = DigestUtils.md5Hex(fileStream);\n          }\n        } finally {\n          if (fileStream != null) {\n            fileStream.close();\n          }\n\n          for (FileBackedOutputStream stream : fileBackedOutputStreams) {\n            if (stream != null) {\n              try {\n                stream.reset();\n              } catch (IOException ex) {\n                logger.debug(\"Failed to clean up temp file: {}\", ex);\n              }\n            }\n          }\n        }\n\n        // continue so that we will upload the file\n        if (hashText == null\n            || // remote is encrypted & has no digest\n            (objDigest != null && !hashText.equals(objDigest))\n            || // digest mismatch\n            (objDigest == null && !hashText.equals(obj.getMD5()))) // ETag/MD5 mismatch\n        {\n          logger.debug(\n              \"Digest diff between remote store and local, will {} {}, \"\n                  + \"local digest: {}, remote store md5: {}\",\n              commandType.name().toLowerCase(),\n              mappedSrcFile,\n              hashText,\n              obj.getMD5());\n          continue;\n        }\n      } catch (IOException | NoSuchAlgorithmException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryID,\n            session,\n            SqlState.INTERNAL_ERROR,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            ex,\n            \"Error reading: \" + localFile);\n      }\n\n      logger.debug(\n          \"Digest same between remote store and local, will not upload {} {}\",\n          commandType.name().toLowerCase(),\n          mappedSrcFile);\n\n      skipFile(mappedSrcFile, objFileName);\n    }\n  }\n\n  private void skipFile(String srcFilePath, String destFileName) {\n    FileMetadata fileMetadata = fileMetadataMap.get(srcFilePath);\n\n    if (fileMetadata != null) {\n      if (fileMetadata.resultStatus == null || fileMetadata.resultStatus == ResultStatus.UNKNOWN) {\n        logger.debug(\"Mark {} as skipped\", srcFilePath);\n\n        fileMetadata.resultStatus = ResultStatus.SKIPPED;\n        fileMetadata.errorDetails =\n            \"File with same destination name and checksum already exists: \" + destFileName;\n      } else {\n        logger.debug(\n            \"No need to mark as skipped for: {} status was already marked as: {}\",\n            srcFilePath,\n            fileMetadata.resultStatus);\n      }\n    }\n  }\n\n  private void initFileMetadata() throws SnowflakeSQLException {\n    // file metadata is keyed on source file names (which are local file names\n    // for upload command and stage file names for download command)\n    fileMetadataMap = new HashMap<String, FileMetadata>(sourceFiles.size());\n\n    if (commandType == CommandType.UPLOAD) {\n      if (sourceFromStream) {\n        FileMetadata fileMetadata = new FileMetadata();\n        fileMetadataMap.put(SRC_FILE_NAME_FOR_STREAM, fileMetadata);\n        fileMetadata.srcFileName = SRC_FILE_NAME_FOR_STREAM;\n      } else {\n        for (String sourceFile : sourceFiles) {\n          FileMetadata fileMetadata = new FileMetadata();\n          fileMetadataMap.put(sourceFile, fileMetadata);\n          File file = new File(sourceFile);\n\n          fileMetadata.srcFileName = file.getName();\n          fileMetadata.srcFileSize = file.length();\n\n          if (!file.exists()) {\n            logger.debug(\"File doesn't exist: {}\", sourceFile);\n\n            throw new SnowflakeSQLLoggedException(\n                queryID,\n                session,\n                ErrorCode.FILE_NOT_FOUND.getMessageCode(),\n                SqlState.DATA_EXCEPTION,\n                sourceFile);\n          } else if (file.isDirectory()) {\n            logger.debug(\"Not a file, but directory: {}\", sourceFile);\n\n            throw new SnowflakeSQLLoggedException(\n                queryID,\n                session,\n                ErrorCode.FILE_IS_DIRECTORY.getMessageCode(),\n                SqlState.DATA_EXCEPTION,\n                sourceFile);\n          }\n        }\n      }\n    } else if (commandType == CommandType.DOWNLOAD) {\n      for (String sourceFile : sourceFiles) {\n        FileMetadata fileMetadata = new FileMetadata();\n        fileMetadataMap.put(sourceFile, fileMetadata);\n        fileMetadata.srcFileName = sourceFile;\n        fileMetadata.destFileName = extractSafeDestFileName(sourceFile, queryID);\n      }\n    }\n  }\n\n  static String extractSafeDestFileName(String sourceFile, String queryId)\n      throws SnowflakeSQLException {\n    if (sourceFile == null) {\n      throw new SnowflakeSQLException(\n          queryId, ErrorCode.INTERNAL_ERROR, \"Source file path from server is null\");\n    }\n    int lastSeparator = Math.max(sourceFile.lastIndexOf('/'), sourceFile.lastIndexOf('\\\\'));\n    String name = sourceFile.substring(lastSeparator + 1);\n\n    if (name.isEmpty()\n        || \".\".equals(name)\n        || \"..\".equals(name)\n        || name.indexOf('\\0') >= 0\n        || name.indexOf('/') >= 0\n        || name.indexOf('\\\\') >= 0\n        || name.indexOf(':') >= 0) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ErrorCode.INTERNAL_ERROR,\n          \"Invalid destination file name received from server: \" + sourceFile);\n    }\n    return name;\n  }\n\n  /**\n   * Derive compression type from mime type\n   *\n   * @param mimeTypeStr The mime type passed to us\n   * @return the Optional for the compression type or Optional.empty()\n   */\n  static Optional<FileCompressionType> mimeTypeToCompressionType(String mimeTypeStr) {\n    if (mimeTypeStr == null) {\n      return Optional.empty();\n    }\n    int slashIndex = mimeTypeStr.indexOf('/');\n    if (slashIndex < 0) {\n      return Optional.empty(); // unable to find sub type\n    }\n    int semiColonIndex = mimeTypeStr.indexOf(';');\n    String subType;\n    if (semiColonIndex < 0) {\n      subType = mimeTypeStr.substring(slashIndex + 1).trim().toLowerCase(Locale.ENGLISH);\n    } else {\n      subType = mimeTypeStr.substring(slashIndex + 1, semiColonIndex);\n    }\n    if (isNullOrEmpty(subType)) {\n      return Optional.empty();\n    }\n    return FileCompressionType.lookupByMimeSubType(subType);\n  }\n\n  /**\n   * Detect file compression type for all files to be uploaded\n   *\n   * @throws SnowflakeSQLException Will be thrown if the compression type is unknown or unsupported\n   */\n  private void processFileCompressionTypes() throws SnowflakeSQLException {\n    // see what user has told us about the source file compression types\n    boolean autoDetect = true;\n    FileCompressionType userSpecifiedSourceCompression = null;\n\n    if (SOURCE_COMPRESSION_AUTO_DETECT.equalsIgnoreCase(sourceCompression)) {\n      autoDetect = true;\n    } else if (SOURCE_COMPRESSION_NONE.equalsIgnoreCase(sourceCompression)) {\n      autoDetect = false;\n    } else {\n      Optional<FileCompressionType> foundCompType =\n          FileCompressionType.lookupByMimeSubType(sourceCompression.toLowerCase());\n      if (!foundCompType.isPresent()) {\n        throw new SnowflakeSQLLoggedException(\n            queryID,\n            session,\n            ErrorCode.COMPRESSION_TYPE_NOT_KNOWN.getMessageCode(),\n            SqlState.FEATURE_NOT_SUPPORTED,\n            sourceCompression);\n      }\n      userSpecifiedSourceCompression = foundCompType.get();\n\n      if (!userSpecifiedSourceCompression.isSupported()) {\n        throw new SnowflakeSQLLoggedException(\n            queryID,\n            session,\n            ErrorCode.COMPRESSION_TYPE_NOT_SUPPORTED.getMessageCode(),\n            SqlState.FEATURE_NOT_SUPPORTED,\n            sourceCompression);\n      }\n\n      autoDetect = false;\n    }\n\n    if (!sourceFromStream) {\n      for (String srcFile : sourceFiles) {\n        FileMetadata fileMetadata = fileMetadataMap.get(srcFile);\n\n        if (fileMetadata.resultStatus == ResultStatus.NONEXIST\n            || fileMetadata.resultStatus == ResultStatus.DIRECTORY) {\n          continue;\n        }\n\n        File file = new File(srcFile);\n        String srcFileName = file.getName();\n\n        String mimeTypeStr = null;\n        FileCompressionType currentFileCompressionType = null;\n\n        try {\n          if (autoDetect) {\n            // probe the file for compression type using tika file type detector\n            mimeTypeStr = Files.probeContentType(file.toPath());\n\n            if (mimeTypeStr == null) {\n              try (FileInputStream f = new FileInputStream(file)) {\n                byte[] magic = new byte[4];\n                if (f.read(magic, 0, 4) == 4) {\n                  if (Arrays.equals(magic, new byte[] {'P', 'A', 'R', '1'})) {\n                    mimeTypeStr = \"snowflake/parquet\";\n                  } else if (Arrays.equals(\n                      Arrays.copyOfRange(magic, 0, 3), new byte[] {'O', 'R', 'C'})) {\n                    mimeTypeStr = \"snowflake/orc\";\n                  }\n                }\n              }\n            }\n\n            if (mimeTypeStr != null) {\n              logger.debug(\"Mime type for {} is: {}\", srcFile, mimeTypeStr);\n\n              Optional<FileCompressionType> foundCompType = mimeTypeToCompressionType(mimeTypeStr);\n              if (foundCompType.isPresent()) {\n                currentFileCompressionType = foundCompType.get();\n              }\n            }\n\n            // fallback: use file extension\n            if (currentFileCompressionType == null) {\n              mimeTypeStr = getMimeTypeFromFileExtension(srcFile);\n\n              if (mimeTypeStr != null) {\n                logger.debug(\"Mime type for {} is: {}\", srcFile, mimeTypeStr);\n                Optional<FileCompressionType> foundCompType =\n                    mimeTypeToCompressionType(mimeTypeStr);\n                if (foundCompType.isPresent()) {\n                  currentFileCompressionType = foundCompType.get();\n                }\n              }\n            }\n          } else {\n            currentFileCompressionType = userSpecifiedSourceCompression;\n          }\n\n          // check if the compression type is supported by us\n          if (currentFileCompressionType != null) {\n            fileMetadata.srcCompressionType = currentFileCompressionType;\n\n            if (currentFileCompressionType.isSupported()) {\n              // remember the compression type if supported\n              fileMetadata.destCompressionType = currentFileCompressionType;\n              fileMetadata.requireCompress = false;\n              fileMetadata.destFileName = srcFileName;\n              logger.debug(\n                  \"File compression detected as {} for: {}\",\n                  currentFileCompressionType.name(),\n                  srcFile);\n            } else {\n              // error if not supported\n              throw new SnowflakeSQLLoggedException(\n                  queryID,\n                  session,\n                  ErrorCode.COMPRESSION_TYPE_NOT_SUPPORTED.getMessageCode(),\n                  SqlState.FEATURE_NOT_SUPPORTED,\n                  currentFileCompressionType.name());\n            }\n          } else {\n            // we want to auto compress the files unless the user has disabled it\n            logger.debug(\"Compression not found for file: {}\", srcFile);\n\n            // Set compress flag\n            fileMetadata.requireCompress = autoCompress;\n            fileMetadata.srcCompressionType = null;\n\n            if (autoCompress) {\n              // We only support gzip auto compression\n              fileMetadata.destFileName = srcFileName + FileCompressionType.GZIP.getFileExtension();\n              fileMetadata.destCompressionType = FileCompressionType.GZIP;\n            } else {\n              fileMetadata.destFileName = srcFileName;\n              fileMetadata.destCompressionType = null;\n            }\n          }\n        } catch (Exception ex) {\n\n          // SNOW-13146: don't log severe message for user error\n          if (ex instanceof SnowflakeSQLException) {\n            logger.debug(\"Exception encountered when processing file compression types\", ex);\n          } else {\n            logger.debug(\"Exception encountered when processing file compression types\", ex);\n          }\n\n          fileMetadata.resultStatus = ResultStatus.ERROR;\n          fileMetadata.errorDetails = ex.getMessage();\n        }\n      }\n    } else {\n      // source from stream case\n      FileMetadata fileMetadata = fileMetadataMap.get(SRC_FILE_NAME_FOR_STREAM);\n      fileMetadata.srcCompressionType = userSpecifiedSourceCompression;\n\n      if (compressSourceFromStream) {\n        fileMetadata.destCompressionType = FileCompressionType.GZIP;\n        fileMetadata.requireCompress = true;\n      } else {\n        fileMetadata.destCompressionType = userSpecifiedSourceCompression;\n        fileMetadata.requireCompress = false;\n      }\n\n      // add gz extension if file name doesn't have it\n      if (compressSourceFromStream\n          && !destFileNameForStreamSource.endsWith(FileCompressionType.GZIP.getFileExtension())) {\n        fileMetadata.destFileName =\n            destFileNameForStreamSource + FileCompressionType.GZIP.getFileExtension();\n      } else {\n        fileMetadata.destFileName = destFileNameForStreamSource;\n      }\n    }\n  }\n\n  /**\n   * Derive mime type from file extension\n   *\n   * @param srcFile The source file name\n   * @return the mime type derived from the file extension\n   */\n  private String getMimeTypeFromFileExtension(String srcFile) {\n    String srcFileLowCase = srcFile.toLowerCase();\n\n    for (FileCompressionType compressionType : FileCompressionType.values()) {\n      if (srcFileLowCase.endsWith(compressionType.getFileExtension())) {\n        return compressionType.getMimeType() + \"/\" + compressionType.getMimeSubTypes().get(0);\n      }\n    }\n\n    return null;\n  }\n\n  /**\n   * A small helper for extracting location name and path from full location path\n   *\n   * @param stageLocationPath stage location\n   * @return remoteLocation object\n   */\n  public static remoteLocation extractLocationAndPath(String stageLocationPath) {\n    String location = stageLocationPath;\n    String path = \"\";\n\n    // split stage location as location name and path\n    if (stageLocationPath.contains(\"/\")) {\n      location = stageLocationPath.substring(0, stageLocationPath.indexOf(\"/\"));\n      path = stageLocationPath.substring(stageLocationPath.indexOf(\"/\") + 1);\n    }\n\n    return new remoteLocation(location, path);\n  }\n\n  /** Generate status rows for each file */\n  private void populateStatusRows() {\n    for (Map.Entry<String, FileMetadata> entry : fileMetadataMap.entrySet()) {\n      FileMetadata fileMetadata = entry.getValue();\n\n      if (commandType == CommandType.UPLOAD) {\n        statusRows.add(\n            showEncryptionParameter\n                ? new UploadCommandEncryptionFacade(\n                    fileMetadata.srcFileName,\n                    fileMetadata.destFileName,\n                    fileMetadata.resultStatus.name(),\n                    fileMetadata.errorDetails,\n                    fileMetadata.srcFileSize,\n                    fileMetadata.destFileSize,\n                    (fileMetadata.srcCompressionType == null)\n                        ? \"NONE\"\n                        : fileMetadata.srcCompressionType.name(),\n                    (fileMetadata.destCompressionType == null)\n                        ? \"NONE\"\n                        : fileMetadata.destCompressionType.name(),\n                    fileMetadata.isEncrypted)\n                : new UploadCommandFacade(\n                    fileMetadata.srcFileName,\n                    fileMetadata.destFileName,\n                    fileMetadata.resultStatus.name(),\n                    fileMetadata.errorDetails,\n                    fileMetadata.srcFileSize,\n                    fileMetadata.destFileSize,\n                    (fileMetadata.srcCompressionType == null)\n                        ? \"NONE\"\n                        : fileMetadata.srcCompressionType.name(),\n                    (fileMetadata.destCompressionType == null)\n                        ? \"NONE\"\n                        : fileMetadata.destCompressionType.name()));\n      } else if (commandType == CommandType.DOWNLOAD) {\n        statusRows.add(\n            showEncryptionParameter\n                ? new DownloadCommandEncryptionFacade(\n                    fileMetadata.srcFileName.startsWith(\"/\")\n                        ? fileMetadata.srcFileName.substring(1)\n                        : fileMetadata.srcFileName,\n                    fileMetadata.resultStatus.name(),\n                    fileMetadata.errorDetails,\n                    fileMetadata.destFileSize,\n                    fileMetadata.isEncrypted)\n                : new DownloadCommandFacade(\n                    fileMetadata.srcFileName.startsWith(\"/\")\n                        ? fileMetadata.srcFileName.substring(1)\n                        : fileMetadata.srcFileName,\n                    fileMetadata.resultStatus.name(),\n                    fileMetadata.errorDetails,\n                    fileMetadata.destFileSize));\n      }\n    }\n\n    /* we sort the result if the connection is in sorting mode\n     */\n    Object sortProperty = null;\n\n    sortProperty = session.getSessionPropertyByKey(\"sort\");\n\n    boolean sortResult = sortProperty != null && (Boolean) sortProperty;\n\n    if (sortResult) {\n      Comparator<Object> comparator =\n          (commandType == CommandType.UPLOAD)\n              ? new Comparator<Object>() {\n                public int compare(Object a, Object b) {\n                  String srcFileNameA = ((UploadCommandFacade) a).getSrcFile();\n                  String srcFileNameB = ((UploadCommandFacade) b).getSrcFile();\n\n                  return srcFileNameA.compareTo(srcFileNameB);\n                }\n              }\n              : new Comparator<Object>() {\n                public int compare(Object a, Object b) {\n                  String srcFileNameA = ((DownloadCommandFacade) a).getFile();\n                  String srcFileNameB = ((DownloadCommandFacade) b).getFile();\n\n                  return srcFileNameA.compareTo(srcFileNameB);\n                }\n              };\n\n      // sort the rows by source file names\n      Collections.sort(statusRows, comparator);\n    }\n  }\n\n  public Object getResultSet() throws SnowflakeSQLException {\n    return new SFFixedViewResultSet(this, this.commandType, this.queryID);\n  }\n\n  public CommandType getCommandType() {\n    return commandType;\n  }\n\n  /**\n   * Handles an InvalidKeyException which indicates that the JCE component is not installed properly\n   *\n   * @deprecated use {@link #throwJCEMissingError(String, Exception, String)}\n   * @param operation a string indicating the operation type, e.g. upload/download\n   * @param ex The exception to be handled\n   * @throws SnowflakeSQLException throws the error as a SnowflakeSQLException\n   */\n  @Deprecated\n  public static void throwJCEMissingError(String operation, Exception ex)\n      throws SnowflakeSQLException {\n    throwJCEMissingError(operation, ex, null);\n  }\n\n  /**\n   * Handles an InvalidKeyException which indicates that the JCE component is not installed properly\n   *\n   * @param operation a string indicating the operation type, e.g. upload/download\n   * @param ex The exception to be handled\n   * @param queryId last query id if available\n   * @throws SnowflakeSQLException throws the error as a SnowflakeSQLException\n   */\n  public static void throwJCEMissingError(String operation, Exception ex, String queryId)\n      throws SnowflakeSQLException {\n    // Most likely cause: Unlimited strength policy files not installed\n    String msg =\n        \"Strong encryption with Java JRE requires JCE \"\n            + \"Unlimited Strength Jurisdiction Policy files. \"\n            + \"Follow JDBC client installation instructions \"\n            + \"provided by Snowflake or contact Snowflake Support.\";\n\n    logger.error(\n        \"JCE Unlimited Strength policy files missing: {}. {}.\",\n        ex.getMessage(),\n        ex.getCause().getMessage());\n\n    String bootLib = systemGetProperty(\"sun.boot.library.path\");\n    if (bootLib != null) {\n      msg +=\n          \" The target directory on your system is: \" + Paths.get(bootLib, \"security\").toString();\n      logger.error(msg);\n    }\n    throw new SnowflakeSQLException(\n        queryId,\n        ex,\n        SqlState.SYSTEM_ERROR,\n        ErrorCode.AWS_CLIENT_ERROR.getMessageCode(),\n        operation,\n        msg);\n  }\n\n  /**\n   * For handling IOException: No space left on device when attempting to download a file to a\n   * location where there is not enough space. We don't want to retry on this exception.\n   *\n   * @deprecated use {@link #throwNoSpaceLeftError(SFSession, String, Exception, String)}\n   * @param session the current session\n   * @param operation the operation i.e. GET\n   * @param ex the exception caught\n   * @throws SnowflakeSQLLoggedException if not enough space left on device to download file.\n   */\n  @Deprecated\n  public static void throwNoSpaceLeftError(SFSession session, String operation, Exception ex)\n      throws SnowflakeSQLLoggedException {\n    throwNoSpaceLeftError(session, operation, ex, null);\n  }\n\n  /**\n   * For handling IOException: No space left on device when attempting to download a file to a\n   * location where there is not enough space. We don't want to retry on this exception.\n   *\n   * @param session the current session\n   * @param operation the operation i.e. GET\n   * @param ex the exception caught\n   * @param queryId the query ID\n   * @throws SnowflakeSQLLoggedException if not enough space left on device to download file.\n   */\n  public static void throwNoSpaceLeftError(\n      SFSession session, String operation, Exception ex, String queryId)\n      throws SnowflakeSQLLoggedException {\n    String exMessage = SnowflakeUtil.getRootCause(ex).getMessage();\n    if (exMessage != null && exMessage.equals(NO_SPACE_LEFT_ON_DEVICE_ERR)) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.SYSTEM_ERROR,\n          ErrorCode.IO_ERROR.getMessageCode(),\n          ex,\n          \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeFileTransferConfig.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.InputStream;\nimport java.util.Properties;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFSession;\n\n/**\n * This class manages the parameters to call SnowflakeFileTransferAgent.uploadWithoutConnection()\n */\npublic class SnowflakeFileTransferConfig {\n  private SnowflakeFileTransferMetadata metadata;\n  private InputStream uploadStream;\n  private boolean requireCompress;\n  private int networkTimeoutInMilli;\n  private OCSPMode ocspMode;\n  private Properties proxyProperties;\n  private String prefix;\n  private String destFileName;\n  private SFSession session; // Optional, added for S3 and Azure\n  private String command; // Optional, added for S3 and Azure\n  private boolean useS3RegionalUrl; // only for S3 us-east-1 private link deployments\n  private String streamingIngestClientName;\n  private String streamingIngestClientKey;\n  private boolean silentException;\n\n  public SnowflakeFileTransferConfig(Builder builder) {\n    this.metadata = builder.metadata;\n    this.uploadStream = builder.uploadStream;\n    this.requireCompress = builder.requireCompress;\n    this.networkTimeoutInMilli = builder.networkTimeoutInMilli;\n    this.ocspMode = builder.ocspMode;\n    this.proxyProperties = builder.proxyProperties;\n    this.prefix = builder.prefix;\n    this.destFileName = builder.destFileName;\n    this.session = builder.session;\n    this.command = builder.command;\n    this.useS3RegionalUrl = builder.useS3RegionalUrl;\n    this.streamingIngestClientKey = builder.streamingIngestClientKey;\n    this.streamingIngestClientName = builder.streamingIngestClientName;\n    this.silentException = builder.silentException;\n  }\n\n  public SnowflakeFileTransferMetadata getSnowflakeFileTransferMetadata() {\n    return metadata;\n  }\n\n  public InputStream getUploadStream() {\n    return uploadStream;\n  }\n\n  public boolean getRequireCompress() {\n    return requireCompress;\n  }\n\n  public int getNetworkTimeoutInMilli() {\n    return networkTimeoutInMilli;\n  }\n\n  public OCSPMode getOcspMode() {\n    return ocspMode;\n  }\n\n  public Properties getProxyProperties() {\n    return proxyProperties;\n  }\n\n  public String getPrefix() {\n    return prefix;\n  }\n\n  public String getDestFileName() {\n    return destFileName;\n  }\n\n  public SFSession getSession() {\n    return session;\n  }\n\n  public String getCommand() {\n    return command;\n  }\n\n  public boolean getUseS3RegionalUrl() {\n    return useS3RegionalUrl;\n  }\n\n  public String getStreamingIngestClientName() {\n    return this.streamingIngestClientName;\n  }\n\n  public String getStreamingIngestClientKey() {\n    return this.streamingIngestClientKey;\n  }\n\n  public boolean isSilentException() {\n    return silentException;\n  }\n\n  // Builder class\n  public static class Builder {\n    private SnowflakeFileTransferMetadata metadata = null;\n    private InputStream uploadStream = null;\n    private boolean requireCompress = true;\n    private int networkTimeoutInMilli = 0;\n    private OCSPMode ocspMode = null;\n    private Properties proxyProperties = null;\n    private String prefix = null;\n    private String destFileName = null;\n    private SFSession session = null;\n    private String command = null;\n    private boolean useS3RegionalUrl = false; // only for S3 us-east-1 private link deployments\n    private String streamingIngestClientName;\n    private String streamingIngestClientKey;\n    private boolean silentException = false;\n\n    public static Builder newInstance() {\n      return new Builder();\n    }\n\n    private Builder() {}\n\n    // Build method to deal with outer class\n    // to return outer instance\n    public SnowflakeFileTransferConfig build() throws IllegalArgumentException {\n      // Validate required parameters\n      if (metadata == null) {\n        throw new IllegalArgumentException(\"Snowflake File Transfer metadata is needed.\");\n      }\n      if (uploadStream == null) {\n        throw new IllegalArgumentException(\"Upload data stream is needed.\");\n      }\n      if (ocspMode == null) {\n        throw new IllegalArgumentException(\"Upload OCSP mode is needed.\");\n      }\n\n      // Create the object\n      return new SnowflakeFileTransferConfig(this);\n    }\n\n    public Builder setSnowflakeFileTransferMetadata(SnowflakeFileTransferMetadata metadata) {\n      this.metadata = metadata;\n      return this;\n    }\n\n    public Builder setUploadStream(InputStream uploadStream) {\n      this.uploadStream = uploadStream;\n      return this;\n    }\n\n    public Builder setRequireCompress(boolean requireCompress) {\n      this.requireCompress = requireCompress;\n      return this;\n    }\n\n    public Builder setNetworkTimeoutInMilli(int networkTimeoutInMilli) {\n      this.networkTimeoutInMilli = networkTimeoutInMilli;\n      return this;\n    }\n\n    public Builder setOcspMode(OCSPMode ocspMode) {\n      this.ocspMode = ocspMode;\n      return this;\n    }\n\n    public Builder setProxyProperties(Properties proxyProperties) {\n      this.proxyProperties = proxyProperties;\n      return this;\n    }\n\n    public Builder setPrefix(String prefix) {\n      this.prefix = prefix;\n      return this;\n    }\n\n    public Builder setDestFileName(String destFileName) {\n      this.destFileName = destFileName;\n      return this;\n    }\n\n    public Builder setSFSession(SFSession session) {\n      this.session = session;\n      return this;\n    }\n\n    public Builder setCommand(String command) {\n      this.command = command;\n      return this;\n    }\n\n    public Builder setUseS3RegionalUrl(boolean useS3RegUrl) {\n      this.useS3RegionalUrl = useS3RegUrl;\n      return this;\n    }\n\n    /**\n     * Streaming ingest client name, used to calculate streaming ingest billing per client\n     *\n     * @param streamingIngestClientName streaming ingest client name\n     * @return Builder\n     */\n    public Builder setStreamingIngestClientName(String streamingIngestClientName) {\n      this.streamingIngestClientName = streamingIngestClientName;\n      return this;\n    }\n\n    /**\n     * Streaming ingest client key provided by Snowflake, used to calculate streaming ingest billing\n     * per client\n     *\n     * @param streamingIngestClientKey streaming ingest client key\n     * @return Builder\n     */\n    public Builder setStreamingIngestClientKey(String streamingIngestClientKey) {\n      this.streamingIngestClientKey = streamingIngestClientKey;\n      return this;\n    }\n\n    /**\n     * Do not log exception when occurred, default: false\n     *\n     * @param silentException should not log exception when occurred\n     * @return Builder\n     */\n    public Builder setSilentException(boolean silentException) {\n      this.silentException = silentException;\n      return this;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeFileTransferMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\npublic interface SnowflakeFileTransferMetadata {\n  /**\n   * Determine this metadata is for transferring one or multiple files.\n   *\n   * @return return true if it is for transferring one file.\n   */\n  boolean isForOneFile();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeFileTransferMetadataV1.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport java.io.Serializable;\nimport net.snowflake.client.internal.jdbc.SFBaseFileTransferAgent.CommandType;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StageInfo;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\n\n/**\n * A class to manage metadata for upload or download files. It is introduced for distributed data\n * processing. The typical use case is: 1. The cluster master has JDBC connection to the Snowflake\n * and it can generate this object for the file transfer. 2. The cluster master node can transfer\n * the object to cluster worker. 3. The cluster worker can upload or download data with the object\n * without JDBC Connection.\n *\n * <p>NOTE: When this class is created, it only supports UPLOAD for GCS. It is created for Snowflake\n * Spark Connector.\n */\npublic class SnowflakeFileTransferMetadataV1\n    implements SnowflakeFileTransferMetadata, Serializable {\n  private static final long serialVersionUID = 1L;\n  private String presignedUrl;\n  private String presignedUrlFileName;\n  private String encryptionMaterial_queryStageMasterKey;\n  private String encryptionMaterial_queryId;\n  private Long encryptionMaterial_smkId;\n  private CommandType commandType;\n  private StageInfo stageInfo;\n\n  public SnowflakeFileTransferMetadataV1(\n      String presignedUrl,\n      String presignedUrlFileName,\n      String encryptionMaterial_queryStageMasterKey,\n      String encryptionMaterial_queryId,\n      Long encryptionMaterial_smkId,\n      CommandType commandType,\n      StageInfo stageInfo) {\n    this.presignedUrl = presignedUrl;\n    this.presignedUrlFileName = presignedUrlFileName;\n    this.encryptionMaterial_queryStageMasterKey = encryptionMaterial_queryStageMasterKey;\n    this.encryptionMaterial_queryId = encryptionMaterial_queryId;\n    this.encryptionMaterial_smkId = encryptionMaterial_smkId;\n    this.commandType = commandType;\n    this.stageInfo = stageInfo;\n  }\n\n  @Override\n  public boolean isForOneFile() {\n    // The presigned url is for one file and the down-scoped token can be used for multiple files.\n    return stageInfo.getStageType() == StageInfo.StageType.GCS\n        && !isNullOrEmpty(presignedUrl)\n        && !\"null\".equalsIgnoreCase(presignedUrl);\n  }\n\n  @JsonProperty(\"presignedUrl\")\n  public String getPresignedUrl() {\n    return presignedUrl;\n  }\n\n  public void setPresignedUrl(String presignedUrl) {\n    this.presignedUrl = presignedUrl;\n  }\n\n  @JsonProperty(\"presignedUrlFileName\")\n  public String getPresignedUrlFileName() {\n    return presignedUrlFileName;\n  }\n\n  public void setPresignedUrlFileName(String presignedUrlFileName) {\n    this.presignedUrlFileName = presignedUrlFileName;\n  }\n\n  @JsonProperty(\"encryptionMaterial\")\n  public RemoteStoreFileEncryptionMaterial getEncryptionMaterial() {\n    return new RemoteStoreFileEncryptionMaterial(\n        encryptionMaterial_queryStageMasterKey,\n        encryptionMaterial_queryId,\n        encryptionMaterial_smkId);\n  }\n\n  public void setEncryptionMaterial(\n      String encryptionMaterial_queryStageMasterKey,\n      String encryptionMaterial_queryId,\n      Long encryptionMaterial_smkId) {\n    this.encryptionMaterial_queryStageMasterKey = encryptionMaterial_queryStageMasterKey;\n    this.encryptionMaterial_queryId = encryptionMaterial_queryId;\n    this.encryptionMaterial_smkId = encryptionMaterial_smkId;\n  }\n\n  @JsonProperty(\"commandType\")\n  public CommandType getCommandType() {\n    return commandType;\n  }\n\n  public void setCommandType(CommandType commandType) {\n    this.commandType = commandType;\n  }\n\n  @JsonProperty(\"stageInfo\")\n  public StageInfo getStageInfo() {\n    return this.stageInfo;\n  }\n\n  public void setStageInfo(StageInfo stageInfo) {\n    this.stageInfo = stageInfo;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeFixedView.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.util.List;\nimport net.snowflake.client.internal.core.SFBaseSession;\n\n/** An interface to use for returning query results from any java class */\npublic interface SnowflakeFixedView {\n  List<SnowflakeColumnMetadata> describeColumns(SFBaseSession session) throws Exception;\n\n  List<Object> getNextRow() throws Exception;\n\n  int getTotalRows();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeLoggedFeatureNotSupportedException.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.SQLFeatureNotSupportedException;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.jdbc.telemetry.SqlExceptionTelemetryHandler;\nimport net.snowflake.common.core.SqlState;\n\npublic class SnowflakeLoggedFeatureNotSupportedException extends SQLFeatureNotSupportedException {\n\n  public SnowflakeLoggedFeatureNotSupportedException(SFBaseSession session) {\n    super();\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        null, SqlState.FEATURE_NOT_SUPPORTED, -1, session, this);\n  }\n\n  public SnowflakeLoggedFeatureNotSupportedException(SFBaseSession session, String message) {\n    super(message);\n    SqlExceptionTelemetryHandler.sendTelemetry(\n        null, SqlState.FEATURE_NOT_SUPPORTED, -1, session, this);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeParameterMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper.convertStringToType;\n\nimport java.sql.ParameterMetaData;\nimport java.sql.SQLException;\nimport net.snowflake.client.internal.core.MetaDataOfBinds;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFPreparedStatementMetaData;\n\n/**\n * Naive implementation of ParameterMetadata class.\n *\n * <p>This class is backed by SFStatementMetadata class, where metadata information is stored as\n * describe sql response.\n */\npublic class SnowflakeParameterMetadata implements ParameterMetaData {\n  private SFPreparedStatementMetaData sfPreparedStatementMetaData;\n  private SFBaseSession session;\n\n  public SnowflakeParameterMetadata(\n      SFPreparedStatementMetaData sfStatementMetaData, SFBaseSession session) {\n    this.sfPreparedStatementMetaData = sfStatementMetaData;\n    this.session = session;\n  }\n\n  @Override\n  public int getParameterCount() throws SQLException {\n    return sfPreparedStatementMetaData.getNumberOfBinds();\n  }\n\n  @Override\n  public int isNullable(int param) throws SQLException {\n    MetaDataOfBinds paramInfo = sfPreparedStatementMetaData.getMetaDataForBindParam(param);\n    if (paramInfo.isNullable()) {\n      return ParameterMetaData.parameterNullable;\n    }\n    return ParameterMetaData.parameterNoNulls;\n  }\n\n  @Override\n  public boolean isSigned(int param) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public int getPrecision(int param) throws SQLException {\n    MetaDataOfBinds paramInfo = sfPreparedStatementMetaData.getMetaDataForBindParam(param);\n    return paramInfo.getPrecision();\n  }\n\n  @Override\n  public int getScale(int param) throws SQLException {\n    MetaDataOfBinds paramInfo = sfPreparedStatementMetaData.getMetaDataForBindParam(param);\n    return paramInfo.getScale();\n  }\n\n  @Override\n  public int getParameterType(int param) throws SQLException {\n    MetaDataOfBinds paramInfo = sfPreparedStatementMetaData.getMetaDataForBindParam(param);\n    return convertStringToType(paramInfo.getTypeName());\n  }\n\n  @Override\n  public String getParameterTypeName(int param) throws SQLException {\n    MetaDataOfBinds paramInfo = sfPreparedStatementMetaData.getMetaDataForBindParam(param);\n    return paramInfo.getTypeName();\n  }\n\n  @Override\n  public String getParameterClassName(int param) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @Override\n  public int getParameterMode(int param) throws SQLException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(session);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public <T> T unwrap(java.lang.Class<T> iface) throws SQLException {\n    if (!isWrapperFor(iface)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  @Override\n  public boolean isWrapperFor(java.lang.Class<?> iface) throws SQLException {\n    return iface.isInstance(this);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeReauthenticationRequest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\n\n/** SnowflakeReauthenticationRequest signals the reauthentication used for SSO */\npublic class SnowflakeReauthenticationRequest extends SnowflakeSQLException {\n  private static final long serialVersionUID = 1L;\n\n  public SnowflakeReauthenticationRequest(\n      String queryId, String reason, String sqlState, int vendorCode) {\n    super(queryId, reason, sqlState, vendorCode);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeResultChunk.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.util.concurrent.locks.Condition;\nimport java.util.concurrent.locks.ReentrantLock;\nimport net.snowflake.client.internal.util.SecretDetector;\n\n/** Class for result chunk */\npublic abstract class SnowflakeResultChunk {\n  public boolean isReleased() {\n    return released;\n  }\n\n  public void setReleased() {\n    released = true;\n  }\n\n  public enum DownloadState {\n    NOT_STARTED,\n    IN_PROGRESS,\n    SUCCESS,\n    FAILURE\n  }\n\n  // url for result chunk\n  private final String url;\n\n  // url for result chunk, with any credentials present (e.g. SAS tokens)\n  // masked\n  private final String scrubbedUrl;\n\n  // number of columns to expect\n  final int colCount;\n\n  // uncompressed size in bytes of this chunk\n  int uncompressedSize;\n\n  // row count\n  final int rowCount;\n\n  // download time for the chunk\n  private long downloadTime;\n\n  // parse time for the chunk\n  private long parseTime;\n\n  private DownloadState downloadState = DownloadState.NOT_STARTED;\n\n  // lock for guarding shared chunk state between consumer and downloader\n  private ReentrantLock lock = new ReentrantLock();\n\n  // a condition to signal from downloader to consumer\n  private Condition downloadCondition = lock.newCondition();\n\n  // download error if any for the chunk\n  private String downloadError;\n\n  private boolean released = false;\n\n  /**\n   * Compute the memory necessary to store the data of this chunk\n   *\n   * @return necessary memory in bytes\n   */\n  abstract long computeNeededChunkMemory();\n\n  /** Free the data stored in this chunk. Called when finish consuming the chunk */\n  abstract void freeData();\n\n  /** Reset all data structure used in this result chunk */\n  abstract void reset();\n\n  public SnowflakeResultChunk(String url, int rowCount, int colCount, int uncompressedSize) {\n    this.url = url;\n    this.scrubbedUrl = SecretDetector.maskSASToken(this.url);\n    this.rowCount = rowCount;\n    this.colCount = colCount;\n    this.uncompressedSize = uncompressedSize;\n  }\n\n  public final String getUrl() {\n    return url;\n  }\n\n  public final String getScrubbedUrl() {\n    return this.scrubbedUrl;\n  }\n\n  public final int getRowCount() {\n    return rowCount;\n  }\n\n  public final int getUncompressedSize() {\n    return uncompressedSize;\n  }\n\n  public final int getColCount() {\n    return this.colCount;\n  }\n\n  public long getDownloadTime() {\n    return downloadTime;\n  }\n\n  public void setDownloadTime(long downloadTime) {\n    this.downloadTime = downloadTime;\n  }\n\n  public long getParseTime() {\n    return parseTime;\n  }\n\n  public void setParseTime(long parseTime) {\n    this.parseTime = parseTime;\n  }\n\n  public ReentrantLock getLock() {\n    return lock;\n  }\n\n  public Condition getDownloadCondition() {\n    return downloadCondition;\n  }\n\n  public String getDownloadError() {\n    return downloadError;\n  }\n\n  public void setDownloadError(String downloadError) {\n    this.downloadError = downloadError;\n  }\n\n  public DownloadState getDownloadState() {\n    return downloadState;\n  }\n\n  public void setDownloadState(DownloadState downloadState) {\n    this.downloadState = downloadState;\n  }\n\n  long getTotalTime() {\n    return downloadTime + parseTime;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeResultSetMetaDataV1.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Types;\nimport java.util.List;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFResultSetMetaData;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Snowflake ResultSetMetaData */\npublic class SnowflakeResultSetMetaDataV1 implements ResultSetMetaData, SnowflakeResultSetMetaData {\n\n  public enum QueryType {\n    ASYNC,\n    SYNC\n  };\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeResultSetMetaDataV1.class);\n\n  private SFResultSetMetaData resultSetMetaData;\n  private String queryId;\n  private QueryType queryType = QueryType.SYNC;\n  private SFBaseSession session;\n\n  public SnowflakeResultSetMetaDataV1(SFResultSetMetaData resultSetMetaData)\n      throws SnowflakeSQLException {\n    this.resultSetMetaData = resultSetMetaData;\n    this.queryId = resultSetMetaData.getQueryId();\n    this.session = resultSetMetaData.getSession();\n  }\n\n  public void setQueryType(QueryType type) {\n    this.queryType = type;\n  }\n\n  /**\n   * @return query id\n   */\n  public String getQueryID() throws SQLException {\n    return this.queryId;\n  }\n\n  /**\n   * Override the original query ID to provide the accurate query ID for metadata produced from an\n   * SFAsyncResultSet. The original query ID is from the result_scan query. The user expects to\n   * retrieve the query ID from the original query instead.\n   */\n  public void setQueryIdForAsyncResults(String queryId) {\n    this.queryId = queryId;\n  }\n\n  /**\n   * @return list of column names\n   */\n  public List<String> getColumnNames() throws SQLException {\n    return resultSetMetaData.getColumnNames();\n  }\n\n  /**\n   * @return index of the column by name, index starts from zero\n   */\n  public int getColumnIndex(String columnName) throws SQLException {\n    return resultSetMetaData.getColumnIndex(columnName);\n  }\n\n  public int getInternalColumnType(int column) throws SQLException {\n    try {\n      return resultSetMetaData.getInternalColumnType(column);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLLoggedException(\n          session, ex.getSqlState(), ex.getVendorCode(), ex.getCause(), ex.getParams());\n    }\n  }\n\n  @Override\n  public List<FieldMetadata> getColumnFields(int column) throws SQLException {\n    return SnowflakeUtil.mapSFExceptionToSQLException(\n        () -> resultSetMetaData.getColumnFields(column));\n  }\n\n  @Override\n  public int getVectorDimension(int column) throws SQLException {\n    return resultSetMetaData.getDimension(column);\n  }\n\n  @Override\n  public int getVectorDimension(String columnName) throws SQLException {\n    return resultSetMetaData.getDimension(getColumnIndex(columnName) + 1);\n  }\n\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"<T> T unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"boolean isWrapperFor(Class<?> iface)\", false);\n\n    return iface.isInstance(this);\n  }\n\n  @Override\n  public boolean isAutoIncrement(int column) throws SQLException {\n    return resultSetMetaData.getIsAutoIncrement(column);\n  }\n\n  @Override\n  public boolean isCaseSensitive(int column) throws SQLException {\n    int colType = getColumnType(column);\n\n    switch (colType) {\n        // Note: SF types GEOGRAPHY, GEOMETRY are also represented as VARCHAR.\n      case Types.VARCHAR:\n      case Types.CHAR:\n      case Types.STRUCT:\n      case Types.ARRAY:\n        return true;\n\n      case Types.INTEGER:\n      case Types.BIGINT:\n      case Types.DECIMAL:\n      case Types.DOUBLE:\n      case Types.BOOLEAN:\n      case Types.TIMESTAMP:\n      case Types.TIMESTAMP_WITH_TIMEZONE:\n      case Types.DATE:\n      case Types.TIME:\n      case Types.BINARY:\n      default:\n        return false;\n    }\n  }\n\n  @Override\n  public boolean isSearchable(int column) throws SQLException {\n    return true;\n  }\n\n  @Override\n  public boolean isCurrency(int column) throws SQLException {\n    return false;\n  }\n\n  @Override\n  public boolean isReadOnly(int column) throws SQLException {\n    return true; // metadata column is always readonly\n  }\n\n  @Override\n  public boolean isWritable(int column) throws SQLException {\n    return false; // never writable\n  }\n\n  @Override\n  public boolean isDefinitelyWritable(int column) throws SQLException {\n    return false; // never writable\n  }\n\n  @Override\n  public String getColumnClassName(int column) throws SQLException {\n    logger.trace(\"String getColumnClassName(int column)\", false);\n\n    int type = this.getColumnType(column);\n\n    return SnowflakeTypeUtil.javaTypeToClassName(type);\n  }\n\n  /**\n   * @return column count\n   * @throws java.sql.SQLException if failed to get column count\n   */\n  @Override\n  public int getColumnCount() throws SQLException {\n    return resultSetMetaData.getColumnCount();\n  }\n\n  @Override\n  public boolean isSigned(int column) throws SQLException {\n    return resultSetMetaData.isSigned(column);\n  }\n\n  @Override\n  public String getColumnLabel(int column) throws SQLException {\n    return resultSetMetaData.getColumnLabel(column);\n  }\n\n  @Override\n  public String getColumnName(int column) throws SQLException {\n    return resultSetMetaData.getColumnName(column);\n  }\n\n  @Override\n  public int getPrecision(int column) throws SQLException {\n    return resultSetMetaData.getPrecision(column);\n  }\n\n  @Override\n  public int getScale(int column) throws SQLException {\n    return resultSetMetaData.getScale(column);\n  }\n\n  @Override\n  public int getColumnType(int column) throws SQLException {\n    try {\n      return resultSetMetaData.getColumnType(column);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLLoggedException(\n          session, ex.getSqlState(), ex.getVendorCode(), ex.getCause(), ex.getParams());\n    }\n  }\n\n  @Override\n  public String getColumnTypeName(int column) throws SQLException {\n    try {\n      return resultSetMetaData.getColumnTypeName(column);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLLoggedException(\n          session, ex.getSqlState(), ex.getVendorCode(), ex.getCause(), ex.getParams());\n    }\n  }\n\n  @Override\n  public int isNullable(int column) throws SQLException {\n    return resultSetMetaData.isNullable(column);\n  }\n\n  @Override\n  public String getCatalogName(int column) throws SQLException {\n    if (this.queryType == QueryType.SYNC) {\n      return resultSetMetaData.getCatalogName(column);\n    }\n    return \"\";\n  }\n\n  @Override\n  public String getSchemaName(int column) throws SQLException {\n    if (this.queryType == QueryType.SYNC) {\n      return resultSetMetaData.getSchemaName(column);\n    }\n    return \"\";\n  }\n\n  @Override\n  public String getTableName(int column) throws SQLException {\n    if (this.queryType == QueryType.SYNC) {\n      return resultSetMetaData.getTableName(column);\n    }\n    return \"\";\n  }\n\n  @Override\n  public int getColumnDisplaySize(int column) throws SQLException {\n    return resultSetMetaData.getColumnDisplaySize(column);\n  }\n\n  public boolean isStructuredTypeColumn(int column) {\n    return resultSetMetaData.isStructuredTypeColumn(column);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeResultSetSerializableV1.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.core.Constants.GB;\nimport static net.snowflake.client.internal.core.Constants.MB;\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE;\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_MEMORY_LIMIT;\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_PREFETCH_THREADS;\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_RESULT_CHUNK_SIZE;\nimport static net.snowflake.client.internal.core.SessionUtil.DEFAULT_CLIENT_MEMORY_LIMIT;\nimport static net.snowflake.client.internal.core.SessionUtil.DEFAULT_CLIENT_PREFETCH_THREADS;\nimport static net.snowflake.client.internal.jdbc.SnowflakeChunkDownloader.NoOpChunkDownloader;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.recordIfExternal;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.Serializable;\nimport java.nio.channels.ClosedByInterruptException;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.ChunkDownloader;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.MetaDataOfBinds;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.QueryResultFormat;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFArrowResultSet;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFResultSet;\nimport net.snowflake.client.internal.core.SFResultSetMetaData;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatementType;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetry.NoOpTelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.VectorSchemaRoot;\nimport org.apache.arrow.vector.ipc.ArrowStreamReader;\n\n/**\n * This object is an intermediate object between result JSON from GS and ResultSet. Originally, it\n * is created from result JSON. And it can also be serializable. Logically, it stands for a part of\n * ResultSet.\n *\n * <p>A typical result JSON data section consists of the content of the first chunk file and file\n * metadata for the rest of chunk files e.g. URL, chunk size, etc. So this object consists of one\n * chunk data and a list of chunk file entries. In actual cases, it may only include chunk data or\n * chunk files entries.\n *\n * <p>This object is serializable, so it can be distributed to other threads or worker nodes for\n * distributed processing.\n */\npublic class SnowflakeResultSetSerializableV1\n    implements SnowflakeResultSetSerializable, Serializable {\n  private static final long serialVersionUID = 1L;\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeResultSetSerializableV1.class);\n\n  static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n  private static final long LOW_MAX_MEMORY = GB;\n\n  /** An Entity class to represent a chunk file metadata. */\n  public static class ChunkFileMetadata implements Serializable {\n    private static final long serialVersionUID = 1L;\n    String fileURL;\n    int rowCount;\n    int compressedByteSize;\n    int uncompressedByteSize;\n\n    public ChunkFileMetadata(\n        String fileURL, int rowCount, int compressedByteSize, int uncompressedByteSize) {\n      this.fileURL = fileURL;\n      this.rowCount = rowCount;\n      this.compressedByteSize = compressedByteSize;\n      this.uncompressedByteSize = uncompressedByteSize;\n    }\n\n    public void setFileURL(String fileURL) {\n      this.fileURL = fileURL;\n    }\n\n    public String getFileURL() {\n      return fileURL;\n    }\n\n    public int getRowCount() {\n      return rowCount;\n    }\n\n    public int getCompressedByteSize() {\n      return compressedByteSize;\n    }\n\n    public int getUncompressedByteSize() {\n      return uncompressedByteSize;\n    }\n\n    public String toString() {\n      StringBuilder builder = new StringBuilder(1024);\n\n      builder.append(\"RowCount: \").append(rowCount).append(\", \");\n      builder.append(\"CompressedSize: \").append(compressedByteSize).append(\", \");\n      builder.append(\"UnCompressedSize: \").append(uncompressedByteSize);\n\n      return builder.toString();\n    }\n  }\n\n  // Below fields are for the data fields that this object wraps\n  // For ARROW, firstChunkStringData is BASE64-encoded arrow file.\n  // For JSON,  it's string data for the json.\n  String firstChunkStringData;\n  int firstChunkRowCount;\n  int chunkFileCount;\n  List<ChunkFileMetadata> chunkFileMetadatas = new ArrayList<>();\n  byte[] firstChunkByteData;\n\n  // below fields are used for building a ChunkDownloader which\n  // uses http client to download chunk files\n  int resultPrefetchThreads;\n  String qrmk;\n  Map<String, String> chunkHeadersMap = new HashMap<>();\n  // Below fields are from session or statement\n  SnowflakeConnectString snowflakeConnectionString;\n  OCSPMode ocspMode;\n  String serverUrl;\n  HttpClientSettingsKey httpClientKey;\n  int networkTimeoutInMilli;\n  int authTimeout;\n  int socketTimeout;\n  int maxHttpRetries;\n  boolean isResultColumnCaseInsensitive;\n  int resultSetType;\n  int resultSetConcurrency;\n  int resultSetHoldability;\n  boolean treatNTZAsUTC;\n  boolean formatDateWithTimezone;\n  boolean useSessionTimezone;\n  boolean getDateUseNullTimezone;\n\n  // Below are some metadata fields parsed from the result JSON node\n  String queryId;\n  String finalDatabaseName;\n  String finalSchemaName;\n  String finalRoleName;\n  String finalWarehouseName;\n  SFStatementType statementType;\n  boolean totalRowCountTruncated;\n  Map<String, Object> parameters = new HashMap<>();\n  int columnCount;\n  private List<SnowflakeColumnMetadata> resultColumnMetadata = new ArrayList<>();\n  long resultVersion;\n  int numberOfBinds;\n  boolean arrayBindSupported;\n  long sendResultTime;\n  List<MetaDataOfBinds> metaDataOfBinds = new ArrayList<>();\n  QueryResultFormat queryResultFormat;\n  int sessionClientMemoryLimit;\n\n  // Below fields are transient, they are generated from parameters\n  transient TimeZone timeZone;\n  transient Optional<SFBaseSession> possibleSession = Optional.empty();\n  transient boolean honorClientTZForTimestampNTZ;\n  transient SnowflakeDateTimeFormat timestampNTZFormatter;\n  transient SnowflakeDateTimeFormat timestampLTZFormatter;\n  transient SnowflakeDateTimeFormat timestampTZFormatter;\n  transient SnowflakeDateTimeFormat dateFormatter;\n  transient SnowflakeDateTimeFormat timeFormatter;\n  transient SFBinaryFormat binaryFormatter;\n  transient long memoryLimit;\n\n  // Below fields are transient, they are generated on the fly.\n  transient JsonNode firstChunkRowset = null; // only used for JSON result\n  transient ChunkDownloader chunkDownloader = null;\n  transient RootAllocator rootAllocator = null; // only used for ARROW result\n  transient SFResultSetMetaData resultSetMetaData = null;\n  transient ResultStreamProvider resultStreamProvider = new DefaultResultStreamProvider();\n\n  /** Default constructor. */\n  public SnowflakeResultSetSerializableV1() {}\n\n  /**\n   * This is copy constructor.\n   *\n   * <p>NOTE: The copy is NOT deep copy.\n   *\n   * @param toCopy the source object to be copied.\n   */\n  private SnowflakeResultSetSerializableV1(SnowflakeResultSetSerializableV1 toCopy) {\n    // Below fields are for the data fields that this object wraps\n    this.firstChunkStringData = toCopy.firstChunkStringData;\n    this.firstChunkRowCount = toCopy.firstChunkRowCount;\n    this.chunkFileCount = toCopy.chunkFileCount;\n    this.chunkFileMetadatas = toCopy.chunkFileMetadatas;\n    this.firstChunkByteData = toCopy.firstChunkByteData;\n\n    // below fields are used for building a ChunkDownloader\n    this.resultPrefetchThreads = toCopy.resultPrefetchThreads;\n    this.qrmk = toCopy.qrmk;\n    this.chunkHeadersMap = toCopy.chunkHeadersMap;\n\n    // Below fields are from session or statement\n    this.snowflakeConnectionString = toCopy.snowflakeConnectionString;\n    this.ocspMode = toCopy.ocspMode;\n    this.serverUrl = toCopy.serverUrl;\n    this.httpClientKey = toCopy.httpClientKey;\n    this.networkTimeoutInMilli = toCopy.networkTimeoutInMilli;\n    this.authTimeout = toCopy.authTimeout;\n    this.socketTimeout = toCopy.socketTimeout;\n    this.maxHttpRetries = toCopy.maxHttpRetries;\n    this.isResultColumnCaseInsensitive = toCopy.isResultColumnCaseInsensitive;\n    this.resultSetType = toCopy.resultSetType;\n    this.resultSetConcurrency = toCopy.resultSetConcurrency;\n    this.resultSetHoldability = toCopy.resultSetHoldability;\n    this.treatNTZAsUTC = toCopy.treatNTZAsUTC;\n    this.formatDateWithTimezone = toCopy.formatDateWithTimezone;\n    this.useSessionTimezone = toCopy.useSessionTimezone;\n    this.getDateUseNullTimezone = toCopy.getDateUseNullTimezone;\n\n    // Below are some metadata fields parsed from the result JSON node\n    this.queryId = toCopy.queryId;\n    this.finalDatabaseName = toCopy.finalDatabaseName;\n    this.finalSchemaName = toCopy.finalSchemaName;\n    this.finalRoleName = toCopy.finalRoleName;\n    this.finalWarehouseName = toCopy.finalWarehouseName;\n    this.statementType = toCopy.statementType;\n    this.totalRowCountTruncated = toCopy.totalRowCountTruncated;\n    this.parameters = toCopy.parameters;\n    this.columnCount = toCopy.columnCount;\n    this.resultColumnMetadata = toCopy.resultColumnMetadata;\n    this.resultVersion = toCopy.resultVersion;\n    this.numberOfBinds = toCopy.numberOfBinds;\n    this.arrayBindSupported = toCopy.arrayBindSupported;\n    this.sendResultTime = toCopy.sendResultTime;\n    this.metaDataOfBinds = toCopy.metaDataOfBinds;\n    this.queryResultFormat = toCopy.queryResultFormat;\n    this.possibleSession = toCopy.possibleSession;\n\n    // Below fields are transient, they are generated from parameters\n    this.timeZone = toCopy.timeZone;\n    this.honorClientTZForTimestampNTZ = toCopy.honorClientTZForTimestampNTZ;\n    this.timestampNTZFormatter = toCopy.timestampNTZFormatter;\n    this.timestampLTZFormatter = toCopy.timestampLTZFormatter;\n    this.timestampTZFormatter = toCopy.timestampTZFormatter;\n    this.dateFormatter = toCopy.dateFormatter;\n    this.timeFormatter = toCopy.timeFormatter;\n    this.binaryFormatter = toCopy.binaryFormatter;\n    this.memoryLimit = toCopy.memoryLimit;\n\n    // Below fields are transient, they are generated on the fly.\n    this.firstChunkRowset = toCopy.firstChunkRowset;\n    this.chunkDownloader = toCopy.chunkDownloader;\n    this.rootAllocator = toCopy.rootAllocator;\n    this.resultSetMetaData = toCopy.resultSetMetaData;\n    this.resultStreamProvider = toCopy.resultStreamProvider;\n  }\n\n  /**\n   * @param rootNode result JSON node received from GS\n   * @param sfSession the Snowflake session\n   * @param sfStatement the Snowflake statement\n   * @param resultStreamProvider a ResultStreamProvider for computing a custom data source for\n   *     result-file streams\n   * @param disableChunksPrefetch is prefetch disabled\n   * @throws SnowflakeSQLException if failed to parse the result JSON node\n   */\n  protected SnowflakeResultSetSerializableV1(\n      JsonNode rootNode,\n      SFBaseSession sfSession,\n      SFBaseStatement sfStatement,\n      ResultStreamProvider resultStreamProvider,\n      boolean disableChunksPrefetch)\n      throws SnowflakeSQLException {\n    SnowflakeUtil.checkErrorAndThrowException(rootNode);\n\n    // get the query id\n    this.queryId = rootNode.path(\"data\").path(\"queryId\").asText();\n\n    JsonNode databaseNode = rootNode.path(\"data\").path(\"finalDatabaseName\");\n    this.finalDatabaseName =\n        databaseNode.isNull()\n            ? (sfSession != null ? sfSession.getDatabase() : null)\n            : databaseNode.asText();\n\n    JsonNode schemaNode = rootNode.path(\"data\").path(\"finalSchemaName\");\n    this.finalSchemaName =\n        schemaNode.isNull()\n            ? (sfSession != null ? sfSession.getSchema() : null)\n            : schemaNode.asText();\n\n    JsonNode roleNode = rootNode.path(\"data\").path(\"finalRoleName\");\n    this.finalRoleName =\n        roleNode.isNull() ? (sfSession != null ? sfSession.getRole() : null) : roleNode.asText();\n\n    JsonNode warehouseNode = rootNode.path(\"data\").path(\"finalWarehouseName\");\n    this.finalWarehouseName =\n        warehouseNode.isNull()\n            ? (sfSession != null ? sfSession.getWarehouse() : null)\n            : warehouseNode.asText();\n\n    this.statementType =\n        SFStatementType.lookUpTypeById(rootNode.path(\"data\").path(\"statementTypeId\").asLong());\n\n    this.totalRowCountTruncated = rootNode.path(\"data\").path(\"totalTruncated\").asBoolean();\n\n    this.possibleSession = Optional.ofNullable(sfSession);\n\n    logger.debug(\"Query id: {}\", this.queryId);\n\n    Optional<QueryResultFormat> queryResultFormat =\n        QueryResultFormat.lookupByName(rootNode.path(\"data\").path(\"queryResultFormat\").asText());\n    this.queryResultFormat = queryResultFormat.orElse(QueryResultFormat.JSON);\n\n    // extract query context and save it in current session\n    JsonNode queryContextNode = rootNode.path(\"data\").path(\"queryContext\");\n    String queryContext = queryContextNode.isNull() ? null : queryContextNode.toString();\n\n    if (!sfSession.isAsyncSession()) {\n      sfSession.setQueryContext(queryContext);\n    }\n\n    // extract parameters\n    this.parameters = SessionUtil.getCommonParams(rootNode.path(\"data\").path(\"parameters\"));\n    if (this.parameters.isEmpty()) {\n      this.parameters = new HashMap<>(sfSession.getCommonParameters());\n      this.setStatemementLevelParameters(sfStatement.getStatementParameters());\n    }\n\n    // initialize column metadata\n    this.columnCount = rootNode.path(\"data\").path(\"rowtype\").size();\n\n    for (int i = 0; i < this.columnCount; i++) {\n      JsonNode colNode = rootNode.path(\"data\").path(\"rowtype\").path(i);\n\n      SnowflakeColumnMetadata columnMetadata =\n          new SnowflakeColumnMetadata(colNode, sfSession.isJdbcTreatDecimalAsInt(), sfSession);\n\n      this.resultColumnMetadata.add(columnMetadata);\n\n      logger.debug(\"Get column metadata: {}\", (ArgSupplier) columnMetadata::toString);\n    }\n\n    this.resultStreamProvider = resultStreamProvider;\n\n    // process the content of first chunk.\n    if (this.queryResultFormat == QueryResultFormat.ARROW) {\n      this.firstChunkStringData = rootNode.path(\"data\").path(\"rowsetBase64\").asText();\n      this.rootAllocator = new RootAllocator(Long.MAX_VALUE);\n      // Set first chunk row count from firstChunkStringData\n      this.setFirstChunkRowCountForArrow();\n    } else {\n      this.firstChunkRowset = rootNode.path(\"data\").path(\"rowset\");\n\n      if (this.firstChunkRowset == null || this.firstChunkRowset.isMissingNode()) {\n        this.firstChunkRowCount = 0;\n        this.firstChunkStringData = null;\n        this.firstChunkByteData = new byte[0];\n      } else {\n        this.firstChunkRowCount = this.firstChunkRowset.size();\n        this.firstChunkStringData = this.firstChunkRowset.toString();\n      }\n    }\n    logger.debug(\"First chunk row count: {}\", this.firstChunkRowCount);\n\n    // parse file chunks\n    this.parseChunkFiles(rootNode, sfStatement);\n\n    // result version\n    JsonNode versionNode = rootNode.path(\"data\").path(\"version\");\n\n    if (!versionNode.isMissingNode()) {\n      this.resultVersion = versionNode.longValue();\n    }\n\n    // number of binds\n    JsonNode numberOfBindsNode = rootNode.path(\"data\").path(\"numberOfBinds\");\n\n    if (!numberOfBindsNode.isMissingNode()) {\n      this.numberOfBinds = numberOfBindsNode.intValue();\n    }\n\n    JsonNode arrayBindSupported = rootNode.path(\"data\").path(\"arrayBindSupported\");\n    this.arrayBindSupported = !arrayBindSupported.isMissingNode() && arrayBindSupported.asBoolean();\n\n    // time result sent by GS (epoch time in millis)\n    JsonNode sendResultTimeNode = rootNode.path(\"data\").path(\"sendResultTime\");\n    if (!sendResultTimeNode.isMissingNode()) {\n      this.sendResultTime = sendResultTimeNode.longValue();\n    }\n\n    logger.debug(\"Result version: {}\", this.resultVersion);\n\n    // Bind parameter metadata\n    JsonNode bindData = rootNode.path(\"data\").path(\"metaDataOfBinds\");\n    if (!bindData.isMissingNode()) {\n      List<MetaDataOfBinds> returnVal = new ArrayList<>();\n      for (JsonNode child : bindData) {\n        int precision = child.path(\"precision\").asInt();\n        boolean nullable = child.path(\"nullable\").asBoolean();\n        int scale = child.path(\"scale\").asInt();\n        int byteLength = child.path(\"byteLength\").asInt();\n        int length = child.path(\"length\").asInt();\n        String name = child.path(\"name\").asText();\n        String type = child.path(\"type\").asText();\n        MetaDataOfBinds param =\n            new MetaDataOfBinds(precision, nullable, scale, byteLength, length, name, type);\n        returnVal.add(param);\n      }\n      this.metaDataOfBinds = returnVal;\n    }\n\n    // setup fields from sessions.\n    this.ocspMode = sfSession.getOCSPMode();\n    this.serverUrl = sfSession.getServerUrl();\n    this.httpClientKey = sfSession.getHttpClientKey();\n    this.snowflakeConnectionString = sfSession.getSnowflakeConnectionString();\n    this.networkTimeoutInMilli = sfSession.getNetworkTimeoutInMilli();\n    this.authTimeout = 0;\n    this.maxHttpRetries = sfSession.getMaxHttpRetries();\n    this.isResultColumnCaseInsensitive = sfSession.isResultColumnCaseInsensitive();\n    this.treatNTZAsUTC = sfSession.getTreatNTZAsUTC();\n    this.formatDateWithTimezone = sfSession.getFormatDateWithTimezone();\n    this.useSessionTimezone = sfSession.getUseSessionTimezone();\n    this.getDateUseNullTimezone = sfSession.getGetDateUseNullTimezone();\n\n    // setup transient fields from parameter\n    this.setupFieldsFromParameters();\n\n    if (disableChunksPrefetch) {\n      this.chunkDownloader = new NoOpChunkDownloader();\n    } else {\n      this.chunkDownloader =\n          (this.chunkFileCount > 0)\n              // The chunk downloader will start prefetching\n              // first few chunk files in background thread(s)\n              ? new SnowflakeChunkDownloader(this)\n              : new NoOpChunkDownloader();\n    }\n\n    // Setup ResultSet metadata\n    this.resultSetMetaData =\n        new SFResultSetMetaData(\n            this.getResultColumnMetadata(),\n            this.queryId,\n            sfSession,\n            this.isResultColumnCaseInsensitive,\n            this.timestampNTZFormatter,\n            this.timestampLTZFormatter,\n            this.timestampTZFormatter,\n            this.dateFormatter,\n            this.timeFormatter);\n  }\n\n  public void setRootAllocator(RootAllocator rootAllocator) {\n    this.rootAllocator = rootAllocator;\n  }\n\n  public void setQueryResultFormat(QueryResultFormat queryResultFormat) {\n    this.queryResultFormat = queryResultFormat;\n  }\n\n  public void setChunkFileCount(int chunkFileCount) {\n    this.chunkFileCount = chunkFileCount;\n  }\n\n  public void setFirstChunkStringData(String firstChunkStringData) {\n    this.firstChunkStringData = firstChunkStringData;\n  }\n\n  public void setFirstChunkByteData(byte[] firstChunkByteData) {\n    this.firstChunkByteData = firstChunkByteData;\n  }\n\n  public void setChunkDownloader(ChunkDownloader chunkDownloader) {\n    this.chunkDownloader = chunkDownloader;\n  }\n\n  public void setResultStreamProvider(ResultStreamProvider resultStreamProvider) {\n    this.resultStreamProvider = resultStreamProvider;\n  }\n\n  public ResultStreamProvider getResultStreamProvider() {\n    return getResultStreamProvider(null);\n  }\n\n  public ResultStreamProvider getResultStreamProvider(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\n        \"SnowflakeResultSetSerializableV1\", \"getResultStreamProvider\", internalCallMarker);\n    return resultStreamProvider;\n  }\n\n  public SFResultSetMetaData getSFResultSetMetaData() {\n    return getSFResultSetMetaData(null);\n  }\n\n  public SFResultSetMetaData getSFResultSetMetaData(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\n        \"SnowflakeResultSetSerializableV1\", \"getSFResultSetMetaData\", internalCallMarker);\n    return resultSetMetaData;\n  }\n\n  public int getResultSetType() {\n    return resultSetType;\n  }\n\n  public int getResultSetConcurrency() {\n    return resultSetConcurrency;\n  }\n\n  public int getResultSetHoldability() {\n    return resultSetHoldability;\n  }\n\n  public SnowflakeConnectString getSnowflakeConnectString() {\n    return snowflakeConnectionString;\n  }\n\n  public OCSPMode getOCSPMode() {\n    return ocspMode;\n  }\n\n  public String getServerURL() {\n    return serverUrl;\n  }\n\n  public HttpClientSettingsKey getHttpClientKey() {\n    return httpClientKey;\n  }\n\n  public String getQrmk() {\n    return qrmk;\n  }\n\n  public int getNetworkTimeoutInMilli() {\n    return networkTimeoutInMilli;\n  }\n\n  public int getAuthTimeout() {\n    return authTimeout;\n  }\n\n  public int getSocketTimeout() {\n    return socketTimeout;\n  }\n\n  public int getMaxHttpRetries() {\n    return maxHttpRetries;\n  }\n\n  public int getResultPrefetchThreads() {\n    return resultPrefetchThreads;\n  }\n\n  public long getMemoryLimit() {\n    return memoryLimit;\n  }\n\n  public Map<String, String> getChunkHeadersMap() {\n    return chunkHeadersMap;\n  }\n\n  public List<ChunkFileMetadata> getChunkFileMetadatas() {\n    return chunkFileMetadatas;\n  }\n\n  public RootAllocator getRootAllocator() {\n    return rootAllocator;\n  }\n\n  public QueryResultFormat getQueryResultFormat() {\n    return queryResultFormat;\n  }\n\n  public int getChunkFileCount() {\n    return chunkFileCount;\n  }\n\n  public boolean isArrayBindSupported() {\n    return arrayBindSupported;\n  }\n\n  public String getQueryId() {\n    return queryId;\n  }\n\n  public String getFinalDatabaseName() {\n    return finalDatabaseName;\n  }\n\n  public String getFinalSchemaName() {\n    return finalSchemaName;\n  }\n\n  public String getFinalRoleName() {\n    return finalRoleName;\n  }\n\n  public String getFinalWarehouseName() {\n    return finalWarehouseName;\n  }\n\n  public SFStatementType getStatementType() {\n    return statementType;\n  }\n\n  public boolean isTotalRowCountTruncated() {\n    return totalRowCountTruncated;\n  }\n\n  public Map<String, Object> getParameters() {\n    return parameters;\n  }\n\n  public int getColumnCount() {\n    return columnCount;\n  }\n\n  public List<SnowflakeColumnMetadata> getResultColumnMetadata() {\n    return resultColumnMetadata;\n  }\n\n  public JsonNode getAndClearFirstChunkRowset() {\n    JsonNode firstChunkRowset = this.firstChunkRowset;\n    this.firstChunkRowset = null;\n    return firstChunkRowset;\n  }\n\n  public int getFirstChunkRowCount() {\n    return firstChunkRowCount;\n  }\n\n  public long getResultVersion() {\n    return resultVersion;\n  }\n\n  public int getNumberOfBinds() {\n    return numberOfBinds;\n  }\n\n  public ChunkDownloader getChunkDownloader() {\n    return chunkDownloader;\n  }\n\n  public SnowflakeDateTimeFormat getTimestampNTZFormatter() {\n    return timestampNTZFormatter;\n  }\n\n  public SnowflakeDateTimeFormat getTimestampLTZFormatter() {\n    return timestampLTZFormatter;\n  }\n\n  public SnowflakeDateTimeFormat getTimestampTZFormatter() {\n    return timestampTZFormatter;\n  }\n\n  public SnowflakeDateTimeFormat getDateFormatter() {\n    return dateFormatter;\n  }\n\n  public SnowflakeDateTimeFormat getTimeFormatter() {\n    return timeFormatter;\n  }\n\n  public TimeZone getTimeZone() {\n    return timeZone;\n  }\n\n  public boolean isHonorClientTZForTimestampNTZ() {\n    return honorClientTZForTimestampNTZ;\n  }\n\n  public SFBinaryFormat getBinaryFormatter() {\n    return binaryFormatter;\n  }\n\n  public long getSendResultTime() {\n    return sendResultTime;\n  }\n\n  public List<MetaDataOfBinds> getMetaDataOfBinds() {\n    return metaDataOfBinds;\n  }\n\n  public String getFirstChunkStringData() {\n    return firstChunkStringData;\n  }\n\n  public byte[] getFirstChunkByteData() {\n    return firstChunkByteData;\n  }\n\n  public boolean getTreatNTZAsUTC() {\n    return treatNTZAsUTC;\n  }\n\n  public boolean getFormatDateWithTimeZone() {\n    return formatDateWithTimezone;\n  }\n\n  public boolean getUseSessionTimezone() {\n    return useSessionTimezone;\n  }\n\n  public boolean getGetDateUseNullTimezone() {\n    return getDateUseNullTimezone;\n  }\n\n  public Optional<SFBaseSession> getSession() {\n    return getSession(null);\n  }\n\n  public Optional<SFBaseSession> getSession(InternalCallMarker internalCallMarker) {\n    recordIfExternal(\"SnowflakeResultSetSerializableV1\", \"getSession\", internalCallMarker);\n    return possibleSession;\n  }\n\n  /**\n   * A factory function to create SnowflakeResultSetSerializable object from result JSON node, using\n   * the DefaultResultStreamProvider.\n   *\n   * @param rootNode result JSON node received from GS\n   * @param sfSession the Snowflake session\n   * @param sfStatement the Snowflake statement\n   * @return processed ResultSetSerializable object\n   * @throws SnowflakeSQLException if failed to parse the result JSON node\n   */\n  public static SnowflakeResultSetSerializableV1 create(\n      JsonNode rootNode, SFBaseSession sfSession, SFBaseStatement sfStatement)\n      throws SnowflakeSQLException {\n    return create(rootNode, sfSession, sfStatement, (InternalCallMarker) null);\n  }\n\n  public static SnowflakeResultSetSerializableV1 create(\n      JsonNode rootNode,\n      SFBaseSession sfSession,\n      SFBaseStatement sfStatement,\n      InternalCallMarker internalCallMarker)\n      throws SnowflakeSQLException {\n    recordIfExternal(\"SnowflakeResultSetSerializableV1\", \"create\", internalCallMarker);\n    return create(\n        rootNode, sfSession, sfStatement, new DefaultResultStreamProvider(), internalCallMarker);\n  }\n\n  /**\n   * A factory function to create SnowflakeResultSetSerializable object from result JSON node, with\n   * an overridable ResultStreamProvider.\n   *\n   * @param rootNode result JSON node received from GS\n   * @param sfSession the Snowflake session\n   * @param sfStatement the Snowflake statement\n   * @param resultStreamProvider a ResultStreamProvider for computing a custom data source for\n   *     result-file streams\n   * @return processed ResultSetSerializable object\n   * @throws SnowflakeSQLException if failed to parse the result JSON node\n   */\n  public static SnowflakeResultSetSerializableV1 create(\n      JsonNode rootNode,\n      SFBaseSession sfSession,\n      SFBaseStatement sfStatement,\n      ResultStreamProvider resultStreamProvider)\n      throws SnowflakeSQLException {\n    return create(rootNode, sfSession, sfStatement, resultStreamProvider, null);\n  }\n\n  public static SnowflakeResultSetSerializableV1 create(\n      JsonNode rootNode,\n      SFBaseSession sfSession,\n      SFBaseStatement sfStatement,\n      ResultStreamProvider resultStreamProvider,\n      InternalCallMarker internalCallMarker)\n      throws SnowflakeSQLException {\n    recordIfExternal(\"SnowflakeResultSetSerializableV1\", \"create\", internalCallMarker);\n    logger.trace(\"Entering create()\", false);\n    return new SnowflakeResultSetSerializableV1(\n        rootNode, sfSession, sfStatement, resultStreamProvider, false);\n  }\n\n  /**\n   * A factory function for internal usage only. It creates SnowflakeResultSetSerializableV1 with\n   * NoOpChunksDownloader which disables chunks prefetch.\n   *\n   * @param rootNode JSON root node\n   * @param sfSession SFBaseSession\n   * @param sfStatement SFBaseStatement\n   * @return SnowflakeResultSetSerializableV1 with NoOpChunksDownloader\n   * @throws SnowflakeSQLException if an error occurs\n   */\n  public static SnowflakeResultSetSerializableV1 createWithChunksPrefetchDisabled(\n      JsonNode rootNode, SFBaseSession sfSession, SFBaseStatement sfStatement)\n      throws SnowflakeSQLException {\n    logger.trace(\"Entering create()\", false);\n    return new SnowflakeResultSetSerializableV1(\n        rootNode, sfSession, sfStatement, new DefaultResultStreamProvider(), true);\n  }\n\n  /**\n   * Some fields are generated from this.parameters, so generate them from this.parameters instead\n   * of serializing them.\n   */\n  private void setupFieldsFromParameters() {\n    String sqlTimestampFormat =\n        (String) ResultUtil.effectiveParamValue(this.parameters, \"TIMESTAMP_OUTPUT_FORMAT\");\n\n    // Special handling of specialized formatters, use a helper function\n    this.timestampNTZFormatter =\n        ResultUtil.specializedFormatter(\n            this.parameters, \"timestamp_ntz\", \"TIMESTAMP_NTZ_OUTPUT_FORMAT\", sqlTimestampFormat);\n\n    this.timestampLTZFormatter =\n        ResultUtil.specializedFormatter(\n            this.parameters, \"timestamp_ltz\", \"TIMESTAMP_LTZ_OUTPUT_FORMAT\", sqlTimestampFormat);\n\n    this.timestampTZFormatter =\n        ResultUtil.specializedFormatter(\n            this.parameters, \"timestamp_tz\", \"TIMESTAMP_TZ_OUTPUT_FORMAT\", sqlTimestampFormat);\n\n    String sqlDateFormat =\n        (String) ResultUtil.effectiveParamValue(this.parameters, \"DATE_OUTPUT_FORMAT\");\n\n    this.dateFormatter = SnowflakeDateTimeFormat.fromSqlFormat(sqlDateFormat);\n\n    logger.debug(\n        \"Sql date format: {}, java date format: {}\",\n        sqlDateFormat,\n        (ArgSupplier) () -> this.dateFormatter.toSimpleDateTimePattern());\n\n    String sqlTimeFormat =\n        (String) ResultUtil.effectiveParamValue(this.parameters, \"TIME_OUTPUT_FORMAT\");\n\n    this.timeFormatter = SnowflakeDateTimeFormat.fromSqlFormat(sqlTimeFormat);\n\n    logger.debug(\n        \"Sql time format: {}, java time format: {}\",\n        sqlTimeFormat,\n        (ArgSupplier) () -> this.timeFormatter.toSimpleDateTimePattern());\n\n    String timeZoneName = (String) ResultUtil.effectiveParamValue(this.parameters, \"TIMEZONE\");\n    this.timeZone = TimeZone.getTimeZone(timeZoneName);\n\n    this.honorClientTZForTimestampNTZ =\n        (boolean)\n            ResultUtil.effectiveParamValue(\n                this.parameters, \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\");\n\n    logger.debug(\"Honoring client TZ for timestamp_ntz? {}\", this.honorClientTZForTimestampNTZ);\n\n    String binaryFmt =\n        (String) ResultUtil.effectiveParamValue(this.parameters, \"BINARY_OUTPUT_FORMAT\");\n    this.binaryFormatter = SFBinaryFormat.getSafeOutputFormat(binaryFmt);\n  }\n\n  /**\n   * Parse the chunk file nodes from result JSON node\n   *\n   * @param rootNode result JSON node received from GS\n   * @param sfStatement the snowflake statement\n   */\n  private void parseChunkFiles(JsonNode rootNode, SFBaseStatement sfStatement) {\n    JsonNode chunksNode = rootNode.path(\"data\").path(\"chunks\");\n\n    if (!chunksNode.isMissingNode()) {\n      this.chunkFileCount = chunksNode.size();\n\n      // Try to get the Query Result Master Key\n      JsonNode qrmkNode = rootNode.path(\"data\").path(\"qrmk\");\n      this.qrmk = qrmkNode.isMissingNode() ? null : qrmkNode.textValue();\n\n      // Determine the prefetch thread count and memoryLimit\n      if (this.chunkFileCount > 0) {\n        logger.debug(\"#chunks: {}, initialize chunk downloader\", this.chunkFileCount);\n\n        adjustMemorySettings(sfStatement);\n\n        // Parse chunk header\n        JsonNode chunkHeaders = rootNode.path(\"data\").path(\"chunkHeaders\");\n        if (chunkHeaders != null && !chunkHeaders.isMissingNode()) {\n          Iterator<Map.Entry<String, JsonNode>> chunkHeadersIter = chunkHeaders.fields();\n\n          while (chunkHeadersIter.hasNext()) {\n            Map.Entry<String, JsonNode> chunkHeader = chunkHeadersIter.next();\n\n            logger.debug(\n                \"Add header key: {}, value: {}\",\n                chunkHeader.getKey(),\n                chunkHeader.getValue().asText());\n            this.chunkHeadersMap.put(chunkHeader.getKey(), chunkHeader.getValue().asText());\n          }\n        }\n\n        // parse chunk files metadata e.g. url and row count\n        for (int idx = 0; idx < this.chunkFileCount; idx++) {\n          JsonNode chunkNode = chunksNode.get(idx);\n          String url = chunkNode.path(\"url\").asText();\n          int rowCount = chunkNode.path(\"rowCount\").asInt();\n          int compressedSize = chunkNode.path(\"compressedSize\").asInt();\n          int uncompressedSize = chunkNode.path(\"uncompressedSize\").asInt();\n\n          this.chunkFileMetadatas.add(\n              new ChunkFileMetadata(url, rowCount, compressedSize, uncompressedSize));\n\n          logger.debug(\n              \"Add chunk, url: {} rowCount: {} \" + \"compressedSize: {} uncompressedSize: {}\",\n              url,\n              rowCount,\n              compressedSize,\n              uncompressedSize);\n        }\n      }\n    }\n  }\n\n  private void adjustMemorySettings(SFBaseStatement sfStatement) {\n    this.resultPrefetchThreads = DEFAULT_CLIENT_PREFETCH_THREADS;\n    if (this.statementType.isSelect()\n        && this.parameters.containsKey(CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE)\n        && (boolean) this.parameters.get(CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE)) {\n      // use conservative memory settings\n      this.resultPrefetchThreads = sfStatement.getConservativePrefetchThreads();\n      this.memoryLimit = sfStatement.getConservativeMemoryLimit();\n      int chunkSize = (int) this.parameters.get(CLIENT_RESULT_CHUNK_SIZE);\n      logger.debug(\n          \"Enable conservative memory usage with prefetchThreads: {} and memoryLimit: {} and \"\n              + \"resultChunkSize: {}\",\n          this.resultPrefetchThreads,\n          this.memoryLimit,\n          chunkSize);\n    } else {\n      // prefetch threads\n      if (this.parameters.get(CLIENT_PREFETCH_THREADS) != null) {\n        this.resultPrefetchThreads = (int) this.parameters.get(CLIENT_PREFETCH_THREADS);\n      }\n      this.memoryLimit = initMemoryLimit(this.parameters);\n    }\n\n    long maxChunkSize = (int) this.parameters.get(CLIENT_RESULT_CHUNK_SIZE) * MB;\n    if (queryResultFormat == QueryResultFormat.ARROW\n        && Runtime.getRuntime().maxMemory() < LOW_MAX_MEMORY\n        && memoryLimit * 2 + maxChunkSize > Runtime.getRuntime().maxMemory()) {\n      memoryLimit = Runtime.getRuntime().maxMemory() / 2 - maxChunkSize;\n      logger.debug(\n          \"To avoid OOM for arrow buffer allocation, \"\n              + \"memoryLimit {} should be less than half of the \"\n              + \"maxMemory {} + maxChunkSize {}\",\n          memoryLimit,\n          Runtime.getRuntime().maxMemory(),\n          maxChunkSize);\n    }\n    if (sfStatement.getSFBaseSession(internalCallMarker()).getMemoryLimitForTesting()\n        != SFBaseSession.MEMORY_LIMIT_UNSET) {\n      memoryLimit = sfStatement.getSFBaseSession(internalCallMarker()).getMemoryLimitForTesting();\n      logger.debug(\"memoryLimit changed for testing purposes to {}\", memoryLimit);\n    }\n  }\n\n  /**\n   * Calculate memory limit in bytes\n   *\n   * @param parameters The parameters for result JSON node\n   * @return memory limit in bytes\n   */\n  private static long initMemoryLimit(Map<String, Object> parameters) {\n    // default setting\n    long memoryLimit = DEFAULT_CLIENT_MEMORY_LIMIT * 1024 * 1024;\n    long maxMemoryToUse = Runtime.getRuntime().maxMemory() * 8 / 10;\n    if (parameters.get(CLIENT_MEMORY_LIMIT) != null) {\n      // use the settings from the customer\n      memoryLimit = (int) parameters.get(CLIENT_MEMORY_LIMIT) * 1024L * 1024L;\n\n      if (DEFAULT_CLIENT_MEMORY_LIMIT == (int) parameters.get(CLIENT_MEMORY_LIMIT)) {\n        // if the memory limit is the default value and best effort memory is enabled\n        // set the memory limit to 80% of the maximum as the best effort\n        memoryLimit = Math.max(memoryLimit, maxMemoryToUse);\n      }\n    }\n\n    // always make sure memoryLimit <= 80% of the maximum\n    memoryLimit = Math.min(memoryLimit, maxMemoryToUse);\n\n    logger.debug(\"Set allowed memory usage to {} bytes\", memoryLimit);\n    return memoryLimit;\n  }\n\n  /**\n   * If statement parameter values are available, set those values in the resultset list of\n   * parameters so they overwrite the session-level cached parameter values.\n   *\n   * @param stmtParamsMap\n   */\n  private void setStatemementLevelParameters(Map<String, Object> stmtParamsMap) {\n    for (Map.Entry<String, Object> entry : stmtParamsMap.entrySet()) {\n      this.parameters.put(entry.getKey(), entry.getValue());\n    }\n  }\n\n  /**\n   * Setup all transient fields based on serialized fields and System Runtime.\n   *\n   * @throws SQLException if fails to setup any transient fields\n   */\n  private void setupTransientFields() throws SQLException {\n    // Setup transient fields from serialized fields\n    setupFieldsFromParameters();\n\n    // Setup memory limitation from parameters and System Runtime.\n    this.memoryLimit = initMemoryLimit(this.parameters);\n\n    this.resultStreamProvider = new DefaultResultStreamProvider();\n\n    // Create below transient fields on the fly.\n    if (QueryResultFormat.ARROW.equals(this.queryResultFormat)) {\n      this.rootAllocator = new RootAllocator(Long.MAX_VALUE);\n      this.firstChunkRowset = null;\n    } else {\n      this.rootAllocator = null;\n      try {\n        this.firstChunkRowset =\n            (this.firstChunkStringData != null) ? mapper.readTree(this.firstChunkStringData) : null;\n      } catch (IOException ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            possibleSession.orElse(/* session = */ null),\n            \"The JSON data is invalid. The error is: \" + ex.getMessage());\n      }\n    }\n\n    // Setup ResultSet metadata\n    this.resultSetMetaData =\n        new SFResultSetMetaData(\n            this.getResultColumnMetadata(),\n            this.queryId,\n            null, // This is session less\n            this.isResultColumnCaseInsensitive,\n            this.timestampNTZFormatter,\n            this.timestampLTZFormatter,\n            this.timestampTZFormatter,\n            this.dateFormatter,\n            this.timeFormatter);\n\n    // Allocate chunk downloader if necessary\n    chunkDownloader =\n        (this.chunkFileCount > 0) ? new SnowflakeChunkDownloader(this) : new NoOpChunkDownloader();\n\n    this.possibleSession = Optional.empty(); // we don't have session object during deserializing\n  }\n\n  /**\n   * Split this object into small pieces based on the user specified data size.\n   *\n   * @param maxSizeInBytes the expected max data size wrapped in the result ResultSetSerializables\n   *     object. NOTE: if a result chunk size is greater than this value, the ResultSetSerializable\n   *     object will include one result chunk.\n   * @return a list of SnowflakeResultSetSerializable\n   * @throws SQLException if fails to split objects.\n   */\n  public List<SnowflakeResultSetSerializable> splitBySize(long maxSizeInBytes) throws SQLException {\n    List<SnowflakeResultSetSerializable> resultSetSerializables = new ArrayList<>();\n\n    if (this.chunkFileMetadatas.isEmpty() && this.firstChunkStringData == null) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          this.possibleSession.orElse(/* session = */ null),\n          \"The Result Set serializable is invalid.\");\n    }\n\n    // In the beginning, only the first data chunk is included in the result\n    // serializable, so the chunk files are removed from the copy.\n    // NOTE: make sure to handle the case that the first data chunk doesn't\n    // exist.\n    SnowflakeResultSetSerializableV1 curResultSetSerializable =\n        new SnowflakeResultSetSerializableV1(this);\n    curResultSetSerializable.chunkFileMetadatas = new ArrayList<>();\n    curResultSetSerializable.chunkFileCount = 0;\n\n    for (int idx = 0; idx < this.chunkFileCount; idx++) {\n      ChunkFileMetadata curChunkFileMetadata = this.getChunkFileMetadatas().get(idx);\n\n      // If the serializable object has reach the max size,\n      // save current one and create new one.\n      if ((curResultSetSerializable.getUncompressedDataSizeInBytes() > 0)\n          && (maxSizeInBytes\n              < (curResultSetSerializable.getUncompressedDataSizeInBytes()\n                  + curChunkFileMetadata.getUncompressedByteSize()))) {\n        resultSetSerializables.add(curResultSetSerializable);\n\n        // Create new result serializable and reset it as empty\n        curResultSetSerializable = new SnowflakeResultSetSerializableV1(this);\n        curResultSetSerializable.chunkFileMetadatas = new ArrayList<>();\n        curResultSetSerializable.chunkFileCount = 0;\n        curResultSetSerializable.firstChunkStringData = null;\n        curResultSetSerializable.firstChunkRowCount = 0;\n        curResultSetSerializable.firstChunkRowset = null;\n        curResultSetSerializable.firstChunkByteData = new byte[0];\n      }\n\n      // Append this chunk file to result serializable object\n      curResultSetSerializable.getChunkFileMetadatas().add(curChunkFileMetadata);\n      curResultSetSerializable.chunkFileCount++;\n    }\n\n    // Add the last result serializable object into result.\n    resultSetSerializables.add(curResultSetSerializable);\n\n    return resultSetSerializables;\n  }\n\n  /**\n   * Get ResultSet from the ResultSet Serializable object so that the user can access the data.\n   *\n   * @param resultSetRetrieveConfig The extra info to retrieve the result set.\n   * @return a ResultSet which represents for the data wrapped in the object\n   */\n  public ResultSet getResultSet(ResultSetRetrieveConfig resultSetRetrieveConfig)\n      throws SQLException {\n    return getResultSet(resultSetRetrieveConfig, null);\n  }\n\n  public ResultSet getResultSet(\n      ResultSetRetrieveConfig resultSetRetrieveConfig, InternalCallMarker internalCallMarker)\n      throws SQLException {\n    recordIfExternal(\"SnowflakeResultSetSerializableV1\", \"getResultSet\", internalCallMarker);\n    // Adjust OCSP cache server if necessary.\n    try {\n      SessionUtil.resetOCSPUrlIfNecessary(resultSetRetrieveConfig.getSfFullURL());\n    } catch (IOException e) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          /*session = */ null, // There is no connection\n          ErrorCode.INTERNAL_ERROR,\n          \"Hit exception when adjusting OCSP cache server. The original message is: \"\n              + e.getMessage());\n    }\n\n    return getResultSetInternal(resultSetRetrieveConfig.getProxyProperties(), internalCallMarker);\n  }\n\n  /**\n   * Get ResultSet from the ResultSet Serializable object so that the user can access the data.\n   *\n   * @param info The proxy sever information if proxy is necessary.\n   * @return a ResultSet which represents for the data wrapped in the object\n   */\n  private ResultSet getResultSetInternal(Properties info, InternalCallMarker internalCallMarker)\n      throws SQLException {\n    // Setup proxy info if necessary\n    this.httpClientKey = SnowflakeUtil.convertProxyPropertiesToHttpClientKey(ocspMode, info);\n\n    // Setup transient fields\n    setupTransientFields();\n\n    // This result set is sessionless, so it doesn't support telemetry.\n    Telemetry telemetryClient = new NoOpTelemetryClient();\n    // The use case is distributed processing, so sortResult is not necessary.\n    boolean sortResult = false;\n    // Setup base result set.\n    SFBaseResultSet sfBaseResultSet = null;\n    switch (getQueryResultFormat()) {\n      case ARROW:\n        {\n          sfBaseResultSet = new SFArrowResultSet(this, telemetryClient, sortResult);\n          break;\n        }\n      case JSON:\n        {\n          sfBaseResultSet =\n              new SFResultSet(\n                  this,\n                  getSession(internalCallMarker).orElse(new SFSession()),\n                  telemetryClient,\n                  sortResult);\n          break;\n        }\n      default:\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            this.possibleSession.orElse(/*session = */ null),\n            ErrorCode.INTERNAL_ERROR,\n            \"Unsupported query result format: \" + getQueryResultFormat().name());\n    }\n\n    // Create result set\n    SnowflakeResultSetV1 resultSetV1 = new SnowflakeResultSetV1(sfBaseResultSet, this);\n\n    return resultSetV1;\n  }\n\n  // Set the row count for first result chunk by parsing the chunk data.\n  private void setFirstChunkRowCountForArrow() throws SnowflakeSQLException {\n    firstChunkRowCount = 0;\n    firstChunkByteData = new byte[0];\n    // If the first chunk doesn't exist or empty, set it as 0\n    if (firstChunkStringData == null || firstChunkStringData.isEmpty()) {\n      firstChunkRowCount = 0;\n      firstChunkByteData = new byte[0];\n    }\n    // Parse the Arrow result chunk\n    else if (getQueryResultFormat().equals(QueryResultFormat.ARROW)) {\n      // Below code is developed based on SFArrowResultSet.buildFirstChunk\n      // and ArrowResultChunk.readArrowStream()\n      byte[] bytes = Base64.getDecoder().decode(firstChunkStringData);\n      firstChunkByteData = bytes;\n      VectorSchemaRoot root = null;\n      RootAllocator localRootAllocator =\n          (rootAllocator != null) ? rootAllocator : new RootAllocator(Long.MAX_VALUE);\n      try (ByteArrayInputStream is = new ByteArrayInputStream(bytes);\n          ArrowStreamReader reader = new ArrowStreamReader(is, localRootAllocator)) {\n        root = reader.getVectorSchemaRoot();\n        while (reader.loadNextBatch()) {\n          firstChunkRowCount += root.getRowCount();\n          root.clear();\n        }\n      } catch (ClosedByInterruptException cbie) {\n        // SNOW-755756: sometimes while reading from arrow stream, this exception can occur with\n        // null message.\n        // Log an interrupted message instead of throwing this exception.\n        logger.debug(\"Interrupted when loading Arrow first chunk row count.\", cbie);\n      } catch (Exception ex) {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            possibleSession.orElse(/* session = */ null),\n            ErrorCode.INTERNAL_ERROR,\n            \"Fail to retrieve row count for first arrow chunk: \" + ex.getMessage());\n      } finally {\n        if (root != null) {\n          root.clear();\n        }\n      }\n    } else {\n      // This shouldn't happen\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          this.possibleSession.orElse(/*session = */ null),\n          ErrorCode.INTERNAL_ERROR,\n          \"setFirstChunkRowCountForArrow() should only be called for Arrow.\");\n    }\n  }\n\n  /**\n   * Retrieve total row count included in the ResultSet Serializable object.\n   *\n   * <p>GS sends the data of first chunk and metadata of the other chunk if exist to client, so this\n   * function calculates the row count for all of them.\n   *\n   * @return the total row count from metadata\n   */\n  public long getRowCount() throws SQLException {\n    // Get row count for first chunk if it exists.\n    long totalRowCount = firstChunkRowCount;\n\n    // Get row count from chunk file metadata\n    for (ChunkFileMetadata chunkFileMetadata : chunkFileMetadatas) {\n      totalRowCount += chunkFileMetadata.rowCount;\n    }\n\n    return totalRowCount;\n  }\n\n  /**\n   * Retrieve compressed data size in the ResultSet Serializable object.\n   *\n   * <p>GS sends the data of first chunk and metadata of the other chunks if exist to client, so\n   * this function calculates the data size for all of them. NOTE: if first chunk exists, this\n   * function uses its uncompressed data size as its compressed data size in this calculation though\n   * it is not compressed.\n   *\n   * @return the total compressed data size in bytes from metadata\n   */\n  public long getCompressedDataSizeInBytes() throws SQLException {\n    long totalCompressedDataSize = 0;\n\n    // Count the data size for the first chunk if it exists.\n    if (firstChunkStringData != null) {\n      totalCompressedDataSize += firstChunkStringData.length();\n    }\n\n    for (ChunkFileMetadata chunkFileMetadata : chunkFileMetadatas) {\n      totalCompressedDataSize += chunkFileMetadata.compressedByteSize;\n    }\n\n    return totalCompressedDataSize;\n  }\n\n  /**\n   * Retrieve Uncompressed data size in the ResultSet Serializable object.\n   *\n   * <p>GS sends the data of first chunk and metadata of the other chunk if exist to client, so this\n   * function calculates the data size for all of them.\n   *\n   * @return the total uncompressed data size in bytes from metadata\n   */\n  public long getUncompressedDataSizeInBytes() throws SQLException {\n    long totalUncompressedDataSize = 0;\n\n    // Count the data size for the first chunk if it exists.\n    if (firstChunkStringData != null) {\n      totalUncompressedDataSize += firstChunkStringData.length();\n    }\n\n    for (ChunkFileMetadata chunkFileMetadata : chunkFileMetadatas) {\n      totalUncompressedDataSize += chunkFileMetadata.uncompressedByteSize;\n    }\n\n    return totalUncompressedDataSize;\n  }\n\n  public String toString() {\n    StringBuilder builder = new StringBuilder(16 * 1024);\n\n    builder.append(\"hasFirstChunk: \").append(this.firstChunkStringData != null).append(\"\\n\");\n\n    builder.append(\"RowCountInFirstChunk: \").append(this.firstChunkRowCount).append(\"\\n\");\n\n    builder.append(\"queryResultFormat: \").append(this.queryResultFormat).append(\"\\n\");\n\n    builder.append(\"chunkFileCount: \").append(this.chunkFileCount).append(\"\\n\");\n\n    for (ChunkFileMetadata chunkFileMetadata : chunkFileMetadatas) {\n      builder.append(\"\\t\").append(chunkFileMetadata.toString()).append(\"\\n\");\n    }\n\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeResultSetV1.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.Date;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.RowId;\nimport java.sql.SQLException;\nimport java.sql.SQLWarning;\nimport java.sql.SQLXML;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.arrow.StructObjectWrapper;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Snowflake ResultSet implementation */\npublic class SnowflakeResultSetV1 extends SnowflakeBaseResultSet\n    implements SnowflakeResultSet, ResultSet {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeResultSetV1.class);\n\n  /**\n   * Constructor takes an inputstream from the API response that we get from executing a SQL\n   * statement.\n   *\n   * <p>The constructor will fetch the first row (if any) so that it can initialize the\n   * ResultSetMetaData.\n   *\n   * @param sfBaseResultSet snowflake core base result rest object\n   * @param statement query statement that generates this result set\n   * @throws SQLException if failed to construct snowflake result set metadata\n   */\n  public SnowflakeResultSetV1(SFBaseResultSet sfBaseResultSet, Statement statement)\n      throws SQLException {\n\n    super(statement);\n    this.sfBaseResultSet = sfBaseResultSet;\n    this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData());\n  }\n\n  /**\n   * Constructor takes a result set serializable object to create a sessionless result set.\n   *\n   * @param sfBaseResultSet snowflake core base result rest object\n   * @param resultSetSerializable The result set serializable object which includes all metadata to\n   *     create the result set\n   * @throws SQLException if fails to create the result set object\n   */\n  public SnowflakeResultSetV1(\n      SFBaseResultSet sfBaseResultSet, SnowflakeResultSetSerializableV1 resultSetSerializable)\n      throws SQLException {\n    super(resultSetSerializable);\n    this.sfBaseResultSet = sfBaseResultSet;\n    this.resultSetMetaData = new SnowflakeResultSetMetaDataV1(sfBaseResultSet.getMetaData());\n  }\n\n  /**\n   * Advance to next row\n   *\n   * @return true if next row exists, false otherwise\n   * @throws SQLException if failed to move to the next row\n   */\n  @Override\n  public boolean next() throws SQLException {\n    // exception\n    try {\n      return sfBaseResultSet.next();\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Override\n  public void close() throws SQLException {\n    close(true);\n  }\n\n  public void close(boolean removeClosedResultSetFromStatement) throws SQLException {\n    // no SQLException is raised.\n    sfBaseResultSet.close();\n    if (removeClosedResultSetFromStatement\n        && statement.isWrapperFor(SnowflakeStatementImpl.class)) {\n      statement.unwrap(SnowflakeStatementImpl.class).removeClosedResultSet(this);\n    }\n  }\n\n  public String getQueryID() {\n    return sfBaseResultSet.getQueryId();\n  }\n\n  public boolean wasNull() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return sfBaseResultSet.wasNull();\n  }\n\n  public String getString(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getString(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public boolean getBoolean(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getBoolean(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Override\n  public byte getByte(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getByte(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public short getShort(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getShort(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public int getInt(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getInt(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public long getLong(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getLong(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public float getFloat(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getFloat(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public double getDouble(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getDouble(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public Date getDate(int columnIndex, TimeZone tz) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getDate(columnIndex, tz);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public Time getTime(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getTime(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public Timestamp getTimestamp(int columnIndex, TimeZone tz) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getTimestamp(columnIndex, tz);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public ResultSetMetaData getMetaData() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    return resultSetMetaData;\n  }\n\n  public Object getObject(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    Object object =\n        SnowflakeUtil.mapSFExceptionToSQLException(() -> sfBaseResultSet.getObject(columnIndex));\n\n    if (object == null) {\n      return null;\n    }\n\n    if (object instanceof StructObjectWrapper) {\n      StructObjectWrapper structObjectWrapper = (StructObjectWrapper) object;\n      if (resultSetMetaData.isStructuredTypeColumn(columnIndex)\n          && structObjectWrapper.getJsonString() != null) {\n        return structObjectWrapper.getJsonString();\n      }\n\n      return structObjectWrapper.getObject();\n    }\n\n    return object;\n  }\n\n  public Array getArray(int columnIndex) throws SQLException {\n    if (!resultSetMetaData.isStructuredTypeColumn(columnIndex)) {\n      throw new SnowflakeLoggedFeatureNotSupportedException(session);\n    }\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getArray(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public BigDecimal getBigDecimal(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getBigDecimal(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  @Deprecated\n  public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getBigDecimal(columnIndex, scale);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public byte[] getBytes(int columnIndex) throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    try {\n      return sfBaseResultSet.getBytes(columnIndex);\n    } catch (SFException ex) {\n      throw new SnowflakeSQLException(\n          ex.getCause(), ex.getSqlState(), ex.getVendorCode(), ex.getParams());\n    }\n  }\n\n  public int getRow() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n\n    return sfBaseResultSet.getRow();\n  }\n\n  public boolean isFirst() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return sfBaseResultSet.isFirst();\n  }\n\n  public boolean isClosed() throws SQLException {\n    // no exception is raised.\n    return sfBaseResultSet.isClosed();\n  }\n\n  @Override\n  public boolean isLast() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return sfBaseResultSet.isLast();\n  }\n\n  @Override\n  public boolean isAfterLast() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return sfBaseResultSet.isAfterLast();\n  }\n\n  @Override\n  public boolean isBeforeFirst() throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return sfBaseResultSet.isBeforeFirst();\n  }\n\n  @Override\n  public boolean isWrapperFor(Class<?> iface) throws SQLException {\n    logger.trace(\"boolean isWrapperFor(Class<?> iface)\", false);\n\n    return iface.isInstance(this);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  @Override\n  public <T> T unwrap(Class<T> iface) throws SQLException {\n    logger.trace(\"<T> T unwrap(Class<T> iface)\", false);\n\n    if (!iface.isInstance(this)) {\n      throw new SQLException(\n          this.getClass().getName() + \" not unwrappable from \" + iface.getName());\n    }\n    return (T) this;\n  }\n\n  /**\n   * Get a list of ResultSetSerializables for the ResultSet in order to parallel processing\n   *\n   * @param maxSizeInBytes The expected max data size wrapped in the ResultSetSerializables object.\n   *     NOTE: this parameter is intended to make the data size in each serializable object to be\n   *     less than it. But if user specifies a small value which may be smaller than the data size\n   *     of one result chunk. So the definition can't be guaranteed completely. For this special\n   *     case, one serializable object is used to wrap the data chunk.\n   * @return a list of ResultSetSerializables.\n   * @throws SQLException If it fails to get the ResultSetSerializable objects.\n   */\n  @Override\n  public List<SnowflakeResultSetSerializable> getResultSetSerializables(long maxSizeInBytes)\n      throws SQLException {\n    raiseSQLExceptionIfResultSetIsClosed();\n    return sfBaseResultSet.getResultSetSerializables(maxSizeInBytes);\n  }\n\n  /** Empty result set */\n  public static class EmptyResultSet implements ResultSet {\n    private boolean isClosed;\n\n    public EmptyResultSet() {\n      isClosed = false;\n    }\n\n    private void raiseSQLExceptionIfResultSetIsClosed() throws SQLException {\n      if (isClosed()) {\n        throw new SnowflakeSQLException(ErrorCode.RESULTSET_ALREADY_CLOSED);\n      }\n    }\n\n    @Override\n    public boolean wasNull() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean next() throws SQLException {\n      return false; // no exception\n    }\n\n    @Override\n    public void close() throws SQLException {\n      isClosed = true;\n    }\n\n    @Override\n    public boolean getBoolean(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public int getInt(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public long getLong(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0L;\n    }\n\n    @Override\n    public float getFloat(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return (float) 0;\n    }\n\n    @Override\n    public double getDouble(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return (double) 0;\n    }\n\n    @Override\n    public short getShort(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return (short) 0;\n    }\n\n    @Override\n    public byte getByte(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return (byte) 0;\n    }\n\n    @Override\n    public String getString(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return \"\";\n    }\n\n    @Override\n    public byte[] getBytes(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return new byte[0];\n    }\n\n    @Override\n    public Date getDate(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Time getTime(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Timestamp getTimestamp(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public InputStream getAsciiStream(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Deprecated\n    @Override\n    public InputStream getUnicodeStream(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public InputStream getBinaryStream(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public String getString(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return \"\";\n    }\n\n    @Override\n    public boolean getBoolean(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public byte getByte(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public short getShort(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public int getInt(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public long getLong(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public float getFloat(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public double getDouble(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Deprecated\n    @Override\n    public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public byte[] getBytes(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return new byte[0];\n    }\n\n    @Override\n    public Date getDate(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Time getTime(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Timestamp getTimestamp(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public BigDecimal getBigDecimal(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public boolean isBeforeFirst() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean isAfterLast() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean isFirst() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean isLast() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public void beforeFirst() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void afterLast() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public boolean first() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean last() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public int getRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public boolean absolute(int row) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean relative(int rows) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean previous() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public void setFetchDirection(int direction) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public int getFetchDirection() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public void setFetchSize(int rows) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public int getFetchSize() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public int getType() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public int getConcurrency() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public boolean rowUpdated() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean rowInserted() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public boolean rowDeleted() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n\n    @Override\n    public void updateNull(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBoolean(int columnIndex, boolean x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateByte(int columnIndex, byte x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateShort(int columnIndex, short x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateInt(int columnIndex, int x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateLong(int columnIndex, long x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateFloat(int columnIndex, float x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateDouble(int columnIndex, double x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateString(int columnIndex, String x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBytes(int columnIndex, byte[] x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateDate(int columnIndex, Date x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateTime(int columnIndex, Time x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateObject(int columnIndex, Object x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNull(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBoolean(String columnLabel, boolean x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateByte(String columnLabel, byte x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateShort(String columnLabel, short x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateInt(String columnLabel, int x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateLong(String columnLabel, long x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateFloat(String columnLabel, float x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateDouble(String columnLabel, double x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateString(String columnLabel, String x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBytes(String columnLabel, byte[] x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateDate(String columnLabel, Date x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateTime(String columnLabel, Time x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateAsciiStream(String columnLabel, InputStream x, int length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBinaryStream(String columnLabel, InputStream x, int length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateCharacterStream(String columnLabel, Reader reader, int length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateObject(String columnLabel, Object x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void insertRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void deleteRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void refreshRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void cancelRowUpdates() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void moveToInsertRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void moveToCurrentRow() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public Statement getStatement() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Object getObject(int columnIndex, Map<String, Class<?>> map) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Ref getRef(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Blob getBlob(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Clob getClob(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Array getArray(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Object getObject(String columnLabel, Map<String, Class<?>> map) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Ref getRef(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Blob getBlob(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Clob getClob(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Array getArray(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Date getDate(int columnIndex, Calendar cal) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Date getDate(String columnLabel, Calendar cal) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Time getTime(int columnIndex, Calendar cal) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Time getTime(String columnLabel, Calendar cal) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public URL getURL(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public URL getURL(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public void updateRef(int columnIndex, Ref x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateRef(String columnLabel, Ref x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBlob(int columnIndex, Blob x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBlob(String columnLabel, Blob x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateClob(int columnIndex, Clob x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateClob(String columnLabel, Clob x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateArray(int columnIndex, Array x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateArray(String columnLabel, Array x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public RowId getRowId(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public RowId getRowId(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public void updateRowId(int columnIndex, RowId x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateRowId(String columnLabel, RowId x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public int getHoldability() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public boolean isClosed() throws SQLException {\n      return isClosed; // no exception\n    }\n\n    @Override\n    public void updateNString(int columnIndex, String nString) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNString(String columnLabel, String nString) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNClob(int columnIndex, NClob nClob) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNClob(String columnLabel, NClob nClob) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public NClob getNClob(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public NClob getNClob(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public SQLXML getSQLXML(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public SQLXML getSQLXML(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public String getNString(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public String getNString(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Reader getNCharacterStream(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Reader getNCharacterStream(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNCharacterStream(String columnLabel, Reader reader, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBinaryStream(int columnIndex, InputStream x, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateAsciiStream(String columnLabel, InputStream x, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBinaryStream(String columnLabel, InputStream x, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateCharacterStream(String columnLabel, Reader reader, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBlob(int columnIndex, InputStream inputStream, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBlob(String columnLabel, InputStream inputStream, long length)\n        throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateClob(int columnIndex, Reader reader) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateClob(String columnLabel, Reader reader) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNClob(int columnIndex, Reader reader) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public void updateNClob(String columnLabel, Reader reader) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Deprecated\n    @Override\n    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public InputStream getAsciiStream(String columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Deprecated\n    @Override\n    public InputStream getUnicodeStream(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public InputStream getBinaryStream(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public SQLWarning getWarnings() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public void clearWarnings() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n    }\n\n    @Override\n    public String getCursorName() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public ResultSetMetaData getMetaData() throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Object getObject(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Object getObject(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public int findColumn(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return 0;\n    }\n\n    @Override\n    public Reader getCharacterStream(int columnIndex) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public Reader getCharacterStream(String columnLabel) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public <T> T unwrap(Class<T> iface) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return null;\n    }\n\n    @Override\n    public boolean isWrapperFor(Class<?> iface) throws SQLException {\n      raiseSQLExceptionIfResultSetIsClosed();\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeRichResultSetSerializableV1.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.QueryResultFormat;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class SnowflakeRichResultSetSerializableV1 extends SnowflakeResultSetSerializableV1 {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeRichResultSetSerializableV1.class);\n\n  private String richResultsFirstChunkStringData;\n  private int richResultsFirstChunkRowCount;\n  private int richResultsChunkFileCount;\n  private int richResultsColumnCount;\n  private final List<ChunkFileMetadata> richResultsChunkFilesMetadata = new ArrayList<>();\n  private byte[] richResultsFirstChunkByteData;\n  private String richResultsQrmk;\n  private final Map<String, String> richResultsChunkHeadersMap = new HashMap<>();\n  private final List<SnowflakeRichResultsColumnMetadata> richResultsColumnMetadata =\n      new ArrayList<>();\n  private QueryResultFormat richResultsQueryResultFormat;\n\n  transient JsonNode richResultFirstChunkRowset = null;\n\n  /**\n   * A factory function for internal usage only. It creates SnowflakeRichResultSetSerializableV1\n   * with NoOpChunksDownloader which disables chunks prefetch.\n   *\n   * @param rootNode JSON root node\n   * @param sfSession SFBaseSession\n   * @param sfStatement SFBaseStatement\n   * @return SnowflakeRichResultSetSerializableV1 with NoOpChunksDownloader\n   * @throws SnowflakeSQLException if an error occurs\n   */\n  public static SnowflakeRichResultSetSerializableV1 createWithChunksPrefetchDisabled(\n      JsonNode rootNode, SFBaseSession sfSession, SFBaseStatement sfStatement)\n      throws SnowflakeSQLException {\n    return new SnowflakeRichResultSetSerializableV1(\n        rootNode, sfSession, sfStatement, new DefaultResultStreamProvider(), true);\n  }\n\n  private SnowflakeRichResultSetSerializableV1(\n      JsonNode rootNode,\n      SFBaseSession sfSession,\n      SFBaseStatement sfStatement,\n      ResultStreamProvider resultStreamProvider,\n      boolean disableChunksPrefetch)\n      throws SnowflakeSQLException {\n    super(rootNode, sfSession, sfStatement, resultStreamProvider, disableChunksPrefetch);\n    if (!rootNode.at(\"/richResult\").isMissingNode()) {\n      JsonNode richResultsNode = rootNode.path(\"richResult\");\n      Optional<QueryResultFormat> queryResultFormat =\n          QueryResultFormat.lookupByName(richResultsNode.path(\"queryResultFormat\").asText());\n      this.richResultsQueryResultFormat = queryResultFormat.orElse(QueryResultFormat.JSON);\n      initializeColumnMetadata(richResultsNode, sfSession);\n      initializeFirstChunkData(richResultsNode, this.queryResultFormat);\n      initializeChunkFiles(richResultsNode);\n    } else {\n      logger.debug(\"Unable to initialize rich results metadata, no \\\"richResult\\\" node\");\n    }\n  }\n\n  private void initializeColumnMetadata(JsonNode richResultsNode, SFBaseSession sfSession)\n      throws SnowflakeSQLException {\n    this.richResultsColumnCount = richResultsNode.path(\"rowtype\").size();\n    for (int i = 0; i < this.richResultsColumnCount; i++) {\n      JsonNode colNode = richResultsNode.path(\"rowtype\").path(i);\n\n      SnowflakeRichResultsColumnMetadata columnMetadata =\n          new SnowflakeRichResultsColumnMetadata(\n              colNode, sfSession.isJdbcTreatDecimalAsInt(), sfSession);\n\n      this.richResultsColumnMetadata.add(columnMetadata);\n      logger.debug(\"Get column metadata: {}\", (ArgSupplier) columnMetadata::toString);\n    }\n  }\n\n  private void initializeFirstChunkData(\n      JsonNode richResultsNode, QueryResultFormat queryResultFormat) {\n    if (queryResultFormat == QueryResultFormat.ARROW) {\n      this.richResultsFirstChunkStringData = richResultsNode.path(\"rowsetBase64\").asText();\n    } else {\n      this.richResultFirstChunkRowset = richResultsNode.path(\"rowset\");\n\n      if (this.richResultFirstChunkRowset == null\n          || this.richResultFirstChunkRowset.isMissingNode()) {\n        this.richResultsFirstChunkRowCount = 0;\n        this.richResultsFirstChunkStringData = null;\n        this.richResultsFirstChunkByteData = new byte[0];\n      } else {\n        this.richResultsFirstChunkRowCount = this.richResultFirstChunkRowset.size();\n        this.richResultsFirstChunkStringData = this.richResultFirstChunkRowset.toString();\n      }\n      logger.debug(\"First rich results chunk row count: {}\", this.richResultsFirstChunkRowCount);\n    }\n  }\n\n  private void initializeChunkFiles(JsonNode richResultsNode) {\n    JsonNode chunksNode = richResultsNode.path(\"chunks\");\n    if (!chunksNode.isMissingNode()) {\n      this.richResultsChunkFileCount = chunksNode.size();\n      JsonNode qrmkNode = richResultsNode.path(\"qrmk\");\n      this.richResultsQrmk = qrmkNode.isMissingNode() ? null : qrmkNode.textValue();\n      if (this.richResultsChunkFileCount > 0) {\n        logger.debug(\"Number of rich results metadata chunks: {}\", this.richResultsChunkFileCount);\n        initializeChunkHeaders(richResultsNode);\n        initializeChunkFilesMetadata(chunksNode);\n      }\n    }\n  }\n\n  private void initializeChunkHeaders(JsonNode richResultsNode) {\n    JsonNode chunkHeaders = richResultsNode.path(\"chunkHeaders\");\n    if (chunkHeaders != null && !chunkHeaders.isMissingNode()) {\n      Iterator<Map.Entry<String, JsonNode>> chunkHeadersIter = chunkHeaders.fields();\n      while (chunkHeadersIter.hasNext()) {\n        Map.Entry<String, JsonNode> chunkHeader = chunkHeadersIter.next();\n        logger.debug(\n            \"Add header key: {}, value: {}\", chunkHeader.getKey(), chunkHeader.getValue().asText());\n        this.richResultsChunkHeadersMap.put(chunkHeader.getKey(), chunkHeader.getValue().asText());\n      }\n    }\n  }\n\n  private void initializeChunkFilesMetadata(JsonNode chunksNode) {\n    for (int idx = 0; idx < this.richResultsChunkFileCount; idx++) {\n      JsonNode chunkNode = chunksNode.get(idx);\n      String url = chunkNode.path(\"url\").asText();\n      int rowCount = chunkNode.path(\"rowCount\").asInt();\n      int compressedSize = chunkNode.path(\"compressedSize\").asInt();\n      int uncompressedSize = chunkNode.path(\"uncompressedSize\").asInt();\n      this.richResultsChunkFilesMetadata.add(\n          new ChunkFileMetadata(url, rowCount, compressedSize, uncompressedSize));\n      logger.debug(\n          \"Add rich results metadata chunk, url: {} rowCount: {} \"\n              + \"compressedSize: {} uncompressedSize: {}\",\n          url,\n          rowCount,\n          compressedSize,\n          uncompressedSize);\n    }\n  }\n\n  public String getRichResultsFirstChunkStringData() {\n    return richResultsFirstChunkStringData;\n  }\n\n  public int getRichResultsFirstChunkRowCount() {\n    return richResultsFirstChunkRowCount;\n  }\n\n  public int getRichResultsChunkFileCount() {\n    return richResultsChunkFileCount;\n  }\n\n  public int getRichResultsColumnCount() {\n    return richResultsColumnCount;\n  }\n\n  public List<ChunkFileMetadata> getRichResultsChunkFilesMetadata() {\n    return richResultsChunkFilesMetadata;\n  }\n\n  public byte[] getRichResultsFirstChunkByteData() {\n    return richResultsFirstChunkByteData;\n  }\n\n  public String getRichResultsQrmk() {\n    return richResultsQrmk;\n  }\n\n  public Map<String, String> getRichResultsChunkHeadersMap() {\n    return richResultsChunkHeadersMap;\n  }\n\n  public List<SnowflakeRichResultsColumnMetadata> getRichResultsColumnMetadata() {\n    return richResultsColumnMetadata;\n  }\n\n  public QueryResultFormat getRichResultsQueryResultFormat() {\n    return richResultsQueryResultFormat;\n  }\n\n  public JsonNode getRichResultFirstChunkRowset() {\n    return richResultFirstChunkRowset;\n  }\n\n  public static class SnowflakeRichResultsColumnMetadata extends SnowflakeColumnMetadata {\n\n    private final int columnIndex;\n\n    public SnowflakeRichResultsColumnMetadata(\n        JsonNode colNode, boolean jdbcTreatDecimalAsInt, SFBaseSession session)\n        throws SnowflakeSQLLoggedException {\n      super(colNode, jdbcTreatDecimalAsInt, session);\n      this.columnIndex = colNode.path(\"columnIndexing\").asInt();\n    }\n\n    public int getColumnIndex() {\n      return columnIndex;\n    }\n\n    @Override\n    public String toString() {\n      return super.toString() + \",columnIndex=\" + columnIndex;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeSQLExceptionWithRetryContext.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\n\n/**\n * Internal exception class that extends SnowflakeSQLException with additional retry context\n * information. This class is used internally by the JDBC driver for retry logic and should not be\n * exposed to customers.\n *\n * <p>This exception carries metadata about retry attempts, timeouts, and elapsed time that is used\n * by internal components (RestRequest, SessionUtil) to manage connection and authentication retry\n * logic.\n */\npublic class SnowflakeSQLExceptionWithRetryContext extends SnowflakeSQLException {\n  private static final long serialVersionUID = 1L;\n\n  private final int retryCount;\n  private final boolean isSocketTimeoutNoBackoff;\n  private final long elapsedSeconds;\n\n  /**\n   * Constructs a new exception with retry context information.\n   *\n   * @param errorCode the error code\n   * @param retryCount the number of retry attempts made\n   * @param isSocketTimeoutNoBackoff whether the socket timeout occurred without backoff\n   * @param elapsedSeconds the elapsed time in seconds\n   */\n  public SnowflakeSQLExceptionWithRetryContext(\n      ErrorCode errorCode, int retryCount, boolean isSocketTimeoutNoBackoff, long elapsedSeconds) {\n    super(errorCode);\n    this.retryCount = retryCount;\n    this.isSocketTimeoutNoBackoff = isSocketTimeoutNoBackoff;\n    this.elapsedSeconds = elapsedSeconds;\n  }\n\n  /**\n   * Gets the retry count for this exception.\n   *\n   * @return the number of retry attempts\n   */\n  public int getRetryCount() {\n    return retryCount;\n  }\n\n  /**\n   * Checks if the socket timeout occurred without backoff.\n   *\n   * @return true if socket timeout had no backoff, false otherwise\n   */\n  public boolean isSocketTimeoutNoBackoff() {\n    return isSocketTimeoutNoBackoff;\n  }\n\n  /**\n   * Gets the elapsed time in seconds.\n   *\n   * @return elapsed seconds\n   */\n  public long getElapsedSeconds() {\n    return elapsedSeconds;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeSimulatedUploadFailure.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Snowflake Loader exception for Test. This should only be valid in tests. */\npublic class SnowflakeSimulatedUploadFailure extends RuntimeException {\n  private static final long serialVersionUID = 1L;\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SnowflakeSimulatedUploadFailure.class);\n\n  public SnowflakeSimulatedUploadFailure() {\n    super();\n    logger.error(\"This constructor should not be used.\", false);\n  }\n\n  public SnowflakeSimulatedUploadFailure(String filename) {\n    super(\"Simulated upload failure for \" + filename);\n\n    logger.debug(\"{}. This should show up only in tests.\", this.getMessage());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeTimeWithTimezone.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.format.DateTimeFormatter;\nimport java.util.TimeZone;\n\n/**\n * Time with toString() overridden to display time values in session timezone. Only relevant for\n * timestamp objects fetched as times. Normal time objects do not have a timezone associated with\n * them.\n */\npublic class SnowflakeTimeWithTimezone extends Time {\n\n  int nanos = 0;\n  boolean useSessionTimeZone = false;\n  ZoneOffset offset = ZoneOffset.UTC;\n\n  public SnowflakeTimeWithTimezone(long time, int nanos, boolean useSessionTimeZone) {\n    super(time);\n    this.nanos = nanos;\n    this.useSessionTimeZone = useSessionTimeZone;\n  }\n\n  public SnowflakeTimeWithTimezone(\n      Timestamp ts, TimeZone sessionTimeZone, boolean useSessionTimeZone) {\n    super(ts.getTime());\n    this.nanos = ts.getNanos();\n    this.useSessionTimeZone = useSessionTimeZone;\n    if (sessionTimeZone != null) {\n      this.offset = ZoneId.of(sessionTimeZone.getID()).getRules().getOffset(ts.toInstant());\n    }\n  }\n\n  public int getNano() {\n    return nanos;\n  }\n\n  public ZoneOffset getOffset() {\n    return offset;\n  }\n\n  /**\n   * Returns a string representation in session's timezone so as to display \"wallclock time\"\n   *\n   * @return a string representation of the object\n   */\n  public synchronized String toString() {\n    if (!useSessionTimeZone) {\n      return super.toString();\n    }\n    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"HH:mm:ss\");\n    LocalDateTime ldt =\n        LocalDateTime.ofEpochSecond(\n            SnowflakeUtil.getSecondsFromMillis(this.getTime()), this.nanos, this.offset);\n    return ldt.format(formatter);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeTimestampWithTimezone.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Timestamp;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.ZonedDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.TimeZone;\n\n/**\n * Timestamp with toString() overridden to display timestamp in session timezone. The default\n * timezone is UTC if no timezone is specified.\n */\npublic class SnowflakeTimestampWithTimezone extends Timestamp {\n  private static final long serialVersionUID = 1L;\n\n  private TimeZone timezone = TimeZone.getTimeZone(\"UTC\");\n\n  public SnowflakeTimestampWithTimezone(long seconds, int nanoseconds, TimeZone timezone) {\n    super(seconds);\n    this.setNanos(nanoseconds);\n    this.timezone = timezone;\n  }\n\n  public SnowflakeTimestampWithTimezone(Timestamp ts, TimeZone timezone) {\n    this(ts.getTime(), ts.getNanos(), timezone);\n  }\n\n  public SnowflakeTimestampWithTimezone(Timestamp ts) {\n    this(ts.getTime(), ts.getNanos(), TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  /**\n   * Gets the timezone.\n   *\n   * @return the timezone.\n   */\n  public TimeZone getTimezone() {\n    return this.timezone;\n  }\n\n  /**\n   * Converts this timestamp to a zoned date time.\n   *\n   * @return the zoned date time corresponding to this timestamp.\n   */\n  public ZonedDateTime toZonedDateTime() {\n    return ZonedDateTime.ofInstant(toInstant(), this.timezone.toZoneId());\n  }\n\n  /**\n   * Returns a string representation in UTC\n   *\n   * @return a string representation of the object\n   */\n  public synchronized String toString() {\n    int trailingZeros = 0;\n    int tmpNanos = this.getNanos();\n    if (tmpNanos == 0) {\n      trailingZeros = 8;\n    } else {\n      while (tmpNanos % 10 == 0) {\n        tmpNanos /= 10;\n        trailingZeros++;\n      }\n    }\n    final String baseFormat = \"uuuu-MM-dd HH:mm:ss.\";\n    StringBuilder buf = new StringBuilder(baseFormat.length() + 9 - trailingZeros);\n    buf.append(baseFormat);\n    for (int i = 0; i < 9 - trailingZeros; ++i) {\n      buf.append(\"S\");\n    }\n    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(buf.toString());\n\n    ZoneOffset offset = ZoneId.of(timezone.getID()).getRules().getOffset(this.toInstant());\n    LocalDateTime ldt =\n        LocalDateTime.ofEpochSecond(\n            SnowflakeUtil.getSecondsFromMillis(this.getTime()), this.getNanos(), offset);\n    return ldt.format(formatter);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeUseDPoPNonceException.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\npublic class SnowflakeUseDPoPNonceException extends RuntimeException {\n\n  private final String nonce;\n\n  public SnowflakeUseDPoPNonceException(String nonce) {\n    this.nonce = nonce;\n  }\n\n  public String getNonce() {\n    return nonce;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/SnowflakeUtil.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static java.util.Arrays.stream;\nimport static net.snowflake.client.api.resultset.SnowflakeType.GEOGRAPHY;\nimport static net.snowflake.client.internal.core.Constants.OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.sql.SQLException;\nimport java.sql.Time;\nimport java.sql.Types;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.api.implementation.resultset.FieldMetadataImpl;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.ThrowingCallable;\nimport net.snowflake.common.core.SqlState;\nimport net.snowflake.common.util.ClassUtil;\nimport net.snowflake.common.util.FixedViewColumn;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.Header;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.NameValuePair;\n\npublic class SnowflakeUtil {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeUtil.class);\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n\n  private static final Set<PosixFilePermission> directoryOwnerOnlyPermission =\n      PosixFilePermissions.fromString(\"rwx------\");\n\n  // reauthenticate\n  private static final int ID_TOKEN_EXPIRED_GS_CODE = 390110;\n  private static final int SESSION_NOT_EXIST_GS_CODE = 390111;\n  private static final int MASTER_TOKEN_NOTFOUND = 390113;\n  private static final int MASTER_EXPIRED_GS_CODE = 390114;\n  private static final int MASTER_TOKEN_INVALID_GS_CODE = 390115;\n  private static final int ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE = 390195;\n\n  public static final String BIG_DECIMAL_STR = \"big decimal\";\n  public static final String FLOAT_STR = \"float\";\n  public static final String DOUBLE_STR = \"double\";\n  public static final String DURATION_STR = \"duration\";\n  public static final String BOOLEAN_STR = \"boolean\";\n  public static final String SHORT_STR = \"short\";\n  public static final String INT_STR = \"int\";\n  public static final String LONG_STR = \"long\";\n  public static final String PERIOD_STR = \"period\";\n  public static final String TIME_STR = \"time\";\n  public static final String TIMESTAMP_STR = \"timestamp\";\n  public static final String DATE_STR = \"date\";\n  public static final String BYTE_STR = \"byte\";\n  public static final String BYTES_STR = \"byte array\";\n\n  public static String mapJson(Object ob) throws JsonProcessingException {\n    return OBJECT_MAPPER.writeValueAsString(ob);\n  }\n\n  public static void checkErrorAndThrowExceptionIncludingReauth(JsonNode rootNode)\n      throws SnowflakeSQLException {\n    checkErrorAndThrowExceptionSub(rootNode, true);\n  }\n\n  public static void checkErrorAndThrowException(JsonNode rootNode) throws SnowflakeSQLException {\n    checkErrorAndThrowExceptionSub(rootNode, false);\n  }\n\n  public static long getEpochTimeInMicroSeconds() {\n    Instant timestamp = Instant.now();\n    long micros =\n        TimeUnit.SECONDS.toMicros(timestamp.getEpochSecond())\n            + TimeUnit.NANOSECONDS.toMicros(timestamp.getNano());\n    return micros;\n  }\n\n  /**\n   * Check the error in the JSON node and generate an exception based on information extracted from\n   * the node.\n   *\n   * @param rootNode json object contains error information\n   * @param raiseReauthenticateError raises SnowflakeReauthenticationRequest if true\n   * @throws SnowflakeSQLException the exception get from the error in the json\n   */\n  private static void checkErrorAndThrowExceptionSub(\n      JsonNode rootNode, boolean raiseReauthenticateError) throws SnowflakeSQLException {\n    // no need to throw exception if success\n    if (rootNode.path(\"success\").asBoolean()) {\n      return;\n    }\n\n    String errorMessage;\n    String sqlState;\n    int errorCode;\n    String queryId = \"unknown\";\n\n    // if we have sqlstate in data, it's a sql error\n    if (!rootNode.path(\"data\").path(\"sqlState\").isMissingNode()) {\n      sqlState = rootNode.path(\"data\").path(\"sqlState\").asText();\n      errorCode = rootNode.path(\"data\").path(\"errorCode\").asInt();\n      queryId = rootNode.path(\"data\").path(\"queryId\").asText();\n      errorMessage = rootNode.path(\"message\").asText();\n    } else {\n      sqlState = SqlState.INTERNAL_ERROR; // use internal error sql state\n\n      // check if there is an error code in the envelope\n      if (!rootNode.path(\"code\").isMissingNode()) {\n        errorCode = rootNode.path(\"code\").asInt();\n        errorMessage = rootNode.path(\"message\").asText();\n      } else {\n        errorCode = ErrorCode.INTERNAL_ERROR.getMessageCode();\n        errorMessage = \"no_error_code_from_server\";\n\n        try (PrintWriter writer = new PrintWriter(\"output.json\", \"UTF-8\")) {\n          writer.print(rootNode.toString());\n        } catch (Exception ex) {\n          logger.debug(\"{}\", ex);\n        }\n      }\n    }\n\n    if (raiseReauthenticateError) {\n      switch (errorCode) {\n        case ID_TOKEN_EXPIRED_GS_CODE:\n        case SESSION_NOT_EXIST_GS_CODE:\n        case MASTER_TOKEN_NOTFOUND:\n        case MASTER_EXPIRED_GS_CODE:\n        case MASTER_TOKEN_INVALID_GS_CODE:\n        case ID_TOKEN_INVALID_LOGIN_REQUEST_GS_CODE:\n        case OAUTH_ACCESS_TOKEN_EXPIRED_GS_CODE:\n          throw new SnowflakeReauthenticationRequest(queryId, errorMessage, sqlState, errorCode);\n      }\n    }\n    throw new SnowflakeSQLException(queryId, errorMessage, sqlState, errorCode);\n  }\n\n  /**\n   * This method should only be used internally\n   *\n   * @param colNode JsonNode\n   * @param jdbcTreatDecimalAsInt true if should treat Decimal as Int\n   * @param session SFBaseSession\n   * @return SnowflakeColumnMetadata\n   * @throws SnowflakeSQLException if an error occurs\n   */\n  @Deprecated\n  public static SnowflakeColumnMetadata extractColumnMetadata(\n      JsonNode colNode, boolean jdbcTreatDecimalAsInt, SFBaseSession session)\n      throws SnowflakeSQLException {\n    return new SnowflakeColumnMetadata(colNode, jdbcTreatDecimalAsInt, session);\n  }\n\n  static ColumnTypeInfo getSnowflakeType(\n      String internalColTypeName,\n      String extColTypeName,\n      JsonNode udtOutputType,\n      SFBaseSession session,\n      int fixedColType,\n      boolean isStructuredType,\n      boolean isVectorType)\n      throws SnowflakeSQLLoggedException {\n    SnowflakeType baseType = SnowflakeTypeUtil.fromStringOrNull(internalColTypeName);\n    if (baseType == null) {\n      // Unknown Snowflake type (e.g. UUID) — report as OTHER with the actual type name\n      return new ColumnTypeInfo(\n          Types.OTHER,\n          defaultIfNull(extColTypeName, internalColTypeName.toUpperCase(Locale.ROOT)),\n          SnowflakeType.ANY);\n    }\n    ColumnTypeInfo columnTypeInfo;\n\n    switch (baseType) {\n      case TEXT:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.VARCHAR, defaultIfNull(extColTypeName, \"VARCHAR\"), baseType);\n        break;\n      case CHAR:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.CHAR, defaultIfNull(extColTypeName, \"CHAR\"), baseType);\n        break;\n      case INTEGER:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.INTEGER, defaultIfNull(extColTypeName, \"INTEGER\"), baseType);\n        break;\n      case DECFLOAT:\n        columnTypeInfo = new ColumnTypeInfo(Types.DECIMAL, \"DECFLOAT\", baseType);\n        break;\n      case FIXED:\n        if (isVectorType) {\n          columnTypeInfo =\n              new ColumnTypeInfo(Types.INTEGER, defaultIfNull(extColTypeName, \"INTEGER\"), baseType);\n        } else {\n          columnTypeInfo =\n              new ColumnTypeInfo(fixedColType, defaultIfNull(extColTypeName, \"NUMBER\"), baseType);\n        }\n        break;\n\n      case REAL:\n        if (isVectorType) {\n          columnTypeInfo =\n              new ColumnTypeInfo(Types.FLOAT, defaultIfNull(extColTypeName, \"FLOAT\"), baseType);\n        } else {\n          columnTypeInfo =\n              new ColumnTypeInfo(Types.DOUBLE, defaultIfNull(extColTypeName, \"DOUBLE\"), baseType);\n        }\n        break;\n\n      case TIMESTAMP:\n      case TIMESTAMP_LTZ:\n        columnTypeInfo =\n            new ColumnTypeInfo(\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                defaultIfNull(extColTypeName, \"TIMESTAMPLTZ\"),\n                baseType);\n        break;\n\n      case INTERVAL_YEAR_MONTH:\n        columnTypeInfo =\n            new ColumnTypeInfo(\n                SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL,\n                defaultIfNull(extColTypeName, \"INTERVAL_YEAR_MONTH\"),\n                baseType);\n        break;\n\n      case INTERVAL_DAY_TIME:\n        columnTypeInfo =\n            new ColumnTypeInfo(\n                SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL,\n                defaultIfNull(extColTypeName, \"INTERVAL_DAY_TIME\"),\n                baseType);\n        break;\n\n      case TIMESTAMP_NTZ:\n        // if the column type is changed to EXTRA_TYPES_TIMESTAMP_NTZ, update also JsonSqlInput\n        columnTypeInfo =\n            new ColumnTypeInfo(\n                Types.TIMESTAMP, defaultIfNull(extColTypeName, \"TIMESTAMPNTZ\"), baseType);\n        break;\n\n      case TIMESTAMP_TZ:\n        columnTypeInfo =\n            new ColumnTypeInfo(\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                defaultIfNull(extColTypeName, \"TIMESTAMPTZ\"),\n                baseType);\n        break;\n\n      case DATE:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.DATE, defaultIfNull(extColTypeName, \"DATE\"), baseType);\n        break;\n\n      case TIME:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.TIME, defaultIfNull(extColTypeName, \"TIME\"), baseType);\n        break;\n\n      case BOOLEAN:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.BOOLEAN, defaultIfNull(extColTypeName, \"BOOLEAN\"), baseType);\n        break;\n\n      case VECTOR:\n        columnTypeInfo =\n            new ColumnTypeInfo(\n                SnowflakeType.EXTRA_TYPES_VECTOR,\n                defaultIfNull(extColTypeName, \"VECTOR\"),\n                baseType);\n        break;\n\n      case ARRAY:\n        int columnType = isStructuredType ? Types.ARRAY : Types.VARCHAR;\n        columnTypeInfo =\n            new ColumnTypeInfo(columnType, defaultIfNull(extColTypeName, \"ARRAY\"), baseType);\n        break;\n\n      case MAP:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.STRUCT, defaultIfNull(extColTypeName, \"OBJECT\"), baseType);\n        break;\n\n      case OBJECT:\n        if (isStructuredType) {\n          boolean isGeoType =\n              \"GEOMETRY\".equals(extColTypeName) || \"GEOGRAPHY\".equals(extColTypeName);\n          int type = isGeoType ? Types.VARCHAR : Types.STRUCT;\n          columnTypeInfo =\n              new ColumnTypeInfo(type, defaultIfNull(extColTypeName, \"OBJECT\"), baseType);\n        } else {\n          columnTypeInfo =\n              new ColumnTypeInfo(Types.VARCHAR, defaultIfNull(extColTypeName, \"OBJECT\"), baseType);\n        }\n        break;\n\n      case VARIANT:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.VARCHAR, defaultIfNull(extColTypeName, \"VARIANT\"), baseType);\n        break;\n\n      case BINARY:\n        columnTypeInfo =\n            new ColumnTypeInfo(Types.BINARY, defaultIfNull(extColTypeName, \"BINARY\"), baseType);\n        break;\n\n      case GEOGRAPHY:\n      case GEOMETRY:\n        int colType = Types.VARCHAR;\n        extColTypeName = (baseType == GEOGRAPHY) ? \"GEOGRAPHY\" : \"GEOMETRY\";\n\n        if (!udtOutputType.isMissingNode()) {\n          SnowflakeType outputType = SnowflakeTypeUtil.fromStringOrNull(udtOutputType.asText());\n          if (outputType != null) {\n            switch (outputType) {\n              case OBJECT:\n              case TEXT:\n                colType = Types.VARCHAR;\n                break;\n              case BINARY:\n                colType = Types.BINARY;\n            }\n          }\n        }\n        columnTypeInfo = new ColumnTypeInfo(colType, extColTypeName, baseType);\n        break;\n\n      default:\n        throw new SnowflakeSQLLoggedException(\n            session,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            SqlState.INTERNAL_ERROR,\n            \"Unknown column type: \" + internalColTypeName);\n    }\n\n    return columnTypeInfo;\n  }\n\n  private static String defaultIfNull(String extColTypeName, String defaultValue) {\n    return Optional.ofNullable(extColTypeName).orElse(defaultValue);\n  }\n\n  static List<FieldMetadata> createFieldsMetadata(\n      ArrayNode fieldsJson, boolean jdbcTreatDecimalAsInt, String parentInternalColumnTypeName)\n      throws SnowflakeSQLLoggedException {\n    List<FieldMetadata> fields = new ArrayList<>();\n    for (JsonNode node : fieldsJson) {\n      String colName;\n      if (!node.path(\"fieldType\").isEmpty()) {\n        colName = node.path(\"fieldName\").asText();\n        node = node.path(\"fieldType\");\n      } else {\n        colName = node.path(\"name\").asText();\n      }\n      int scale = node.path(\"scale\").asInt();\n      int precision = node.path(\"precision\").asInt();\n      String internalColTypeName = node.path(\"type\").asText();\n      boolean nullable = node.path(\"nullable\").asBoolean();\n      int length = node.path(\"length\").asInt();\n      boolean fixed = node.path(\"fixed\").asBoolean();\n      int fixedColType = jdbcTreatDecimalAsInt && scale == 0 ? Types.BIGINT : Types.DECIMAL;\n      List<FieldMetadata> internalFields =\n          getFieldMetadata(jdbcTreatDecimalAsInt, parentInternalColumnTypeName, node);\n      JsonNode outputType = node.path(\"outputType\");\n      JsonNode extColTypeNameNode = node.path(\"extTypeName\");\n      String extColTypeName = null;\n      if (!extColTypeNameNode.isMissingNode() && !isNullOrEmpty(extColTypeNameNode.asText())) {\n        extColTypeName = extColTypeNameNode.asText();\n      }\n      ColumnTypeInfo columnTypeInfo =\n          getSnowflakeType(\n              internalColTypeName,\n              extColTypeName,\n              outputType,\n              null,\n              fixedColType,\n              internalFields.size() > 0,\n              isVectorType(parentInternalColumnTypeName));\n      fields.add(\n          new FieldMetadataImpl(\n              colName,\n              columnTypeInfo.getExtColTypeName(),\n              columnTypeInfo.getColumnType(),\n              nullable,\n              length,\n              precision,\n              scale,\n              fixed,\n              columnTypeInfo.getSnowflakeType(),\n              internalFields));\n    }\n    return fields;\n  }\n\n  static boolean isVectorType(String internalColumnTypeName) {\n    return internalColumnTypeName.equalsIgnoreCase(\"vector\");\n  }\n\n  static List<FieldMetadata> getFieldMetadata(\n      boolean jdbcTreatDecimalAsInt, String internalColumnTypeName, JsonNode node)\n      throws SnowflakeSQLLoggedException {\n    if (!node.path(\"fields\").isEmpty()) {\n      ArrayNode internalFieldsJson = (ArrayNode) node.path(\"fields\");\n      return createFieldsMetadata(\n          internalFieldsJson, jdbcTreatDecimalAsInt, internalColumnTypeName);\n    } else {\n      return new ArrayList<>();\n    }\n  }\n\n  public static String javaTypeToSFTypeString(int javaType, SFBaseSession session)\n      throws SnowflakeSQLException {\n    return SnowflakeTypeUtil.javaTypeToSFType(javaType, session).name();\n  }\n\n  public static SnowflakeType javaTypeToSFType(int javaType, SFBaseSession session)\n      throws SnowflakeSQLException {\n    return SnowflakeTypeUtil.javaTypeToSFType(javaType, session);\n  }\n\n  /**\n   * A small function for concatenating two file paths by making sure one and only one path\n   * separator is placed between the two paths.\n   *\n   * <p>This is necessary since for S3 file name, having different number of file separators in a\n   * path will mean different files.\n   *\n   * <p>Typical use case is to concatenate a file name to a directory.\n   *\n   * @param leftPath left path\n   * @param rightPath right path\n   * @param fileSep file separator\n   * @return concatenated file path\n   */\n  static String concatFilePathNames(String leftPath, String rightPath, String fileSep) {\n    String leftPathTrimmed = leftPath.trim();\n    String rightPathTrimmed = rightPath.trim();\n\n    if (leftPathTrimmed.isEmpty()) {\n      return rightPath;\n    }\n\n    if (leftPathTrimmed.endsWith(fileSep) && rightPathTrimmed.startsWith(fileSep)) {\n      return leftPathTrimmed + rightPathTrimmed.substring(1);\n    } else if (!leftPathTrimmed.endsWith(fileSep) && !rightPathTrimmed.startsWith(fileSep)) {\n      return leftPathTrimmed + fileSep + rightPathTrimmed;\n    } else {\n      return leftPathTrimmed + rightPathTrimmed;\n    }\n  }\n\n  static String greatestCommonPrefix(String val1, String val2) {\n    if (val1 == null || val2 == null) {\n      return null;\n    }\n\n    StringBuilder greatestCommonPrefix = new StringBuilder();\n\n    int len = Math.min(val1.length(), val2.length());\n\n    for (int idx = 0; idx < len; idx++) {\n      if (val1.charAt(idx) == val2.charAt(idx)) {\n        greatestCommonPrefix.append(val1.charAt(idx));\n      } else {\n        break;\n      }\n    }\n\n    return greatestCommonPrefix.toString();\n  }\n\n  static List<SnowflakeColumnMetadata> describeFixedViewColumns(\n      Class<?> clazz, SFBaseSession session) throws SnowflakeSQLException {\n    Field[] columns = ClassUtil.getAnnotatedDeclaredFields(clazz, FixedViewColumn.class, true);\n\n    Arrays.sort(columns, new FixedViewColumn.OrdinalComparatorForFields());\n\n    List<SnowflakeColumnMetadata> rowType = new ArrayList<SnowflakeColumnMetadata>();\n\n    for (Field column : columns) {\n      FixedViewColumn columnAnnotation = column.getAnnotation(FixedViewColumn.class);\n\n      String typeName;\n      int colType;\n\n      Class<?> type = column.getType();\n      SnowflakeType stype = SnowflakeType.TEXT;\n\n      if (type == Integer.TYPE) {\n        colType = Types.INTEGER;\n        typeName = \"INTEGER\";\n        stype = SnowflakeType.INTEGER;\n      }\n      if (type == Long.TYPE) {\n        colType = Types.DECIMAL;\n        typeName = \"DECIMAL\";\n        stype = SnowflakeType.INTEGER;\n      } else if (type == String.class) {\n        colType = Types.VARCHAR;\n        typeName = \"VARCHAR\";\n        stype = SnowflakeType.TEXT;\n      } else {\n        throw new SnowflakeSQLLoggedException(\n            session,\n            ErrorCode.INTERNAL_ERROR.getMessageCode(),\n            SqlState.INTERNAL_ERROR,\n            \"Unsupported column type: \" + type.getName());\n      }\n\n      // TODO: we hard code some of the values below but can change them\n      // later to derive from annotation as well.\n      rowType.add(\n          new SnowflakeColumnMetadata(\n              columnAnnotation.name(), // column name\n              colType, // column type\n              false, // nullable\n              20480, // length\n              10, // precision\n              0, // scale\n              typeName, // type name\n              true,\n              stype, // fixed\n              new ArrayList<>(),\n              \"\", // database\n              \"\", // schema\n              \"\",\n              false, // isAutoincrement\n              0 // dimension\n              ));\n    }\n\n    return rowType;\n  }\n\n  /**\n   * A utility to log response details.\n   *\n   * <p>Used when there is an error in http response\n   *\n   * @param response http response get from server\n   * @param logger logger object\n   */\n  public static void logResponseDetails(HttpResponse response, SFLogger logger) {\n    if (response == null) {\n      logger.error(\"null response\", false);\n      return;\n    }\n\n    // log the response\n    if (response.getStatusLine() != null) {\n      logger.error(\"Response status line reason: {}\", response.getStatusLine().getReasonPhrase());\n    }\n\n    // log each header from response\n    Header[] headers = response.getAllHeaders();\n    if (headers != null) {\n      for (Header header : headers) {\n        logger.debug(\"Header name: {}, value: {}\", header.getName(), header.getValue());\n      }\n    }\n\n    // log response\n    if (response.getEntity() != null) {\n      try {\n        StringWriter writer = new StringWriter();\n        BufferedReader bufferedReader =\n            new BufferedReader(new InputStreamReader((response.getEntity().getContent())));\n        IOUtils.copy(bufferedReader, writer);\n        logger.error(\"Response content: {}\", writer.toString());\n      } catch (IOException ex) {\n        logger.error(\"Failed to read content due to exception: \" + \"{}\", ex.getMessage());\n      }\n    }\n  }\n\n  /**\n   * Returns a new thread pool configured with the default settings.\n   *\n   * @param threadNamePrefix prefix of the thread name\n   * @param parallel the number of concurrency\n   * @return A new thread pool configured with the default settings.\n   */\n  public static ThreadPoolExecutor createDefaultExecutorService(\n      final String threadNamePrefix, final int parallel) {\n    ThreadFactory threadFactory =\n        new ThreadFactory() {\n          private int threadCount = 1;\n\n          public Thread newThread(Runnable r) {\n            Thread thread = new Thread(r);\n            thread.setName(threadNamePrefix + threadCount++);\n            return thread;\n          }\n        };\n    return (ThreadPoolExecutor) Executors.newFixedThreadPool(parallel, threadFactory);\n  }\n\n  public static Throwable getRootCause(Exception ex) {\n    Throwable cause = ex;\n    while (cause.getCause() != null) {\n      cause = cause.getCause();\n    }\n\n    return cause;\n  }\n\n  public static boolean isBlank(String input) {\n    if (\"\".equals(input) || input == null) {\n      return true;\n    }\n\n    for (char c : input.toCharArray()) {\n      if (!Character.isWhitespace(c)) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  private static final String ALPHA_NUMERIC_STRING = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\n  public static String randomAlphaNumeric(int count) {\n    StringBuilder builder = new StringBuilder();\n    Random random = new Random();\n    while (count-- != 0) {\n      int character = random.nextInt(ALPHA_NUMERIC_STRING.length());\n      builder.append(ALPHA_NUMERIC_STRING.charAt(character));\n    }\n    return builder.toString();\n  }\n\n  /**\n   * System.getProperty wrapper. If System.getProperty raises a SecurityException, it is ignored and\n   * returns null.\n   *\n   * @param property the property name\n   * @return the property value if set, otherwise null.\n   */\n  public static String systemGetProperty(String property) {\n    try {\n      return System.getProperty(property);\n    } catch (SecurityException ex) {\n      // logger may be null during SnowflakeUtil.<clinit> (circular init via SFLoggerFactory)\n      if (logger != null) {\n        logger.debug(\"Security exception raised: {}\", ex.getMessage());\n      }\n      return null;\n    }\n  }\n\n  /**\n   * System.setProperty wrapper. If System.setProperty raises a SecurityException, it is ignored.\n   *\n   * @param property the property name\n   * @param value the property value\n   */\n  public static void systemSetProperty(String property, String value) {\n    try {\n      System.setProperty(property, value);\n    } catch (SecurityException ex) {\n      // logger may be null during SnowflakeUtil.<clinit> (circular init via SFLoggerFactory)\n      if (logger != null) {\n        logger.debug(\"Security exception raised: {}\", ex.getMessage());\n      }\n    }\n  }\n\n  /**\n   * System.getenv wrapper. If System.getenv raises a SecurityException, it is ignored and returns\n   * null.\n   *\n   * @param env the environment variable name.\n   * @return the environment variable value if set, otherwise null.\n   */\n  public static String systemGetEnv(String env) {\n    try {\n      return System.getenv(env);\n    } catch (SecurityException ex) {\n      // logger may be null during SnowflakeUtil.<clinit> (circular init via SFLoggerFactory)\n      if (logger != null) {\n        logger.debug(\n            \"Failed to get environment variable {}. Security exception raised: {}\",\n            env,\n            ex.getMessage());\n      }\n    }\n    return null;\n  }\n\n  /**\n   * System.setEnv function. Can be used for unit tests.\n   *\n   * @param key key\n   * @param value value\n   */\n  public static void systemSetEnv(String key, String value) {\n    try {\n      Map<String, String> env = System.getenv();\n      Class<?> cl = env.getClass();\n      Field field = cl.getDeclaredField(\"m\");\n      field.setAccessible(true);\n      Map<String, String> writableEnv = (Map<String, String>) field.get(env);\n      writableEnv.put(key, value);\n\n      // To an environment variable is set on Windows, it uses a different map to store the values\n      // when the system.getenv(VAR_NAME) is used its required to update in this additional place.\n      if (Constants.getOS() == Constants.OS.WINDOWS) {\n        Class<?> pe = Class.forName(\"java.lang.ProcessEnvironment\");\n        Method getenv = pe.getDeclaredMethod(\"getenv\", String.class);\n        getenv.setAccessible(true);\n        Field props = pe.getDeclaredField(\"theCaseInsensitiveEnvironment\");\n        props.setAccessible(true);\n        Map<String, String> writableEnvForGet = (Map<String, String>) props.get(null);\n        writableEnvForGet.put(key, value);\n      }\n    } catch (Exception e) {\n      logger.error(\n          \"Failed to set environment variable {}. Exception raised: {}\", key, e.getMessage());\n    }\n  }\n\n  /**\n   * System.unsetEnv function to remove a system environment parameter in the map\n   *\n   * @param key key value\n   */\n  public static void systemUnsetEnv(String key) {\n    try {\n      Map<String, String> env = System.getenv();\n      Class<?> cl = env.getClass();\n      Field field = cl.getDeclaredField(\"m\");\n      field.setAccessible(true);\n      Map<String, String> writableEnv = (Map<String, String>) field.get(env);\n      writableEnv.remove(key);\n    } catch (Exception e) {\n      logger.error(\n          \"Failed to remove environment variable {}. Exception raised: {}\", key, e.getMessage());\n    }\n  }\n\n  /**\n   * Setup JDBC proxy properties if necessary.\n   *\n   * @param mode OCSP mode\n   * @param info proxy server properties.\n   * @return HttpClientSettingsKey\n   * @throws SnowflakeSQLException if an error occurs\n   */\n  public static HttpClientSettingsKey convertProxyPropertiesToHttpClientKey(\n      OCSPMode mode, Properties info) throws SnowflakeSQLException {\n    // Setup proxy properties.\n    if (info != null\n        && info.size() > 0\n        && info.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()) != null) {\n      Boolean useProxy =\n          Boolean.valueOf(info.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()));\n      if (useProxy) {\n        // set up other proxy related values.\n        String proxyHost = info.getProperty(SFSessionProperty.PROXY_HOST.getPropertyKey());\n        int proxyPort;\n        try {\n          proxyPort =\n              Integer.parseInt(info.getProperty(SFSessionProperty.PROXY_PORT.getPropertyKey()));\n        } catch (NumberFormatException | NullPointerException e) {\n          throw new SnowflakeSQLException(\n              ErrorCode.INVALID_PROXY_PROPERTIES, \"Could not parse port number\");\n        }\n        String proxyUser = info.getProperty(SFSessionProperty.PROXY_USER.getPropertyKey());\n        String proxyPassword = info.getProperty(SFSessionProperty.PROXY_PASSWORD.getPropertyKey());\n        String nonProxyHosts = info.getProperty(SFSessionProperty.NON_PROXY_HOSTS.getPropertyKey());\n        String proxyProtocol = info.getProperty(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey());\n        String userAgentSuffix =\n            info.getProperty(SFSessionProperty.USER_AGENT_SUFFIX.getPropertyKey());\n        Boolean gzipDisabled =\n            isNullOrEmpty(info.getProperty(SFSessionProperty.GZIP_DISABLED.getPropertyKey()))\n                ? false\n                : Boolean.valueOf(\n                    info.getProperty(SFSessionProperty.GZIP_DISABLED.getPropertyKey()));\n\n        // create key for proxy properties\n        return new HttpClientSettingsKey(\n            mode,\n            proxyHost,\n            proxyPort,\n            nonProxyHosts,\n            proxyUser,\n            proxyPassword,\n            proxyProtocol,\n            userAgentSuffix,\n            gzipDisabled);\n      }\n    }\n    // if no proxy properties, return key with only OCSP mode\n    return new HttpClientSettingsKey(mode);\n  }\n\n  /**\n   * Round the time value from milliseconds to seconds so the seconds can be used to create\n   * SimpleDateFormatter. Negative values have to be rounded to the next negative value, while\n   * positive values should be cut off with no rounding.\n   *\n   * @param millis milliseconds\n   * @return seconds as long value\n   */\n  public static long getSecondsFromMillis(long millis) {\n    long returnVal;\n    if (millis < 0) {\n      returnVal = (long) Math.ceil((double) Math.abs(millis) / 1000);\n      returnVal *= -1;\n    } else {\n      returnVal = millis / 1000;\n    }\n    return returnVal;\n  }\n\n  /**\n   * Get the time value in session timezone instead of UTC calculation done by java.sql.Time.\n   *\n   * @param time time in seconds\n   * @param nanos nanoseconds\n   * @return time in session timezone\n   */\n  public static Time getTimeInSessionTimezone(Long time, int nanos) {\n    LocalDateTime lcd = LocalDateTime.ofEpochSecond(time, nanos, ZoneOffset.UTC);\n    Time ts = Time.valueOf(lcd.toLocalTime());\n    // Time.valueOf() will create the time without the nanoseconds i.e. only hh:mm:ss\n    // Using calendar to add the nanoseconds back to time\n    Calendar c = Calendar.getInstance();\n    c.setTimeInMillis(ts.getTime());\n    c.add(Calendar.MILLISECOND, nanos / 1000000);\n    ts.setTime(c.getTimeInMillis());\n    return ts;\n  }\n\n  /**\n   * Helper function to convert system properties to boolean\n   *\n   * @param systemProperty name of the system property\n   * @param defaultValue default value used\n   * @return the value of the system property as boolean, else the default value\n   */\n  public static boolean convertSystemPropertyToBooleanValue(\n      String systemProperty, boolean defaultValue) {\n    String systemPropertyValue = systemGetProperty(systemProperty);\n    if (systemPropertyValue != null) {\n      return Boolean.parseBoolean(systemPropertyValue);\n    }\n    return defaultValue;\n  }\n  /**\n   * Helper function to convert environment variable to boolean\n   *\n   * @param envVariableKey property name of the environment variable\n   * @param defaultValue default value used\n   * @return the value of the environment variable as boolean, else the default value\n   */\n  public static boolean convertSystemGetEnvToBooleanValue(\n      String envVariableKey, boolean defaultValue) {\n    String environmentVariableValue = systemGetEnv(envVariableKey);\n    if (environmentVariableValue != null) {\n      return Boolean.parseBoolean(environmentVariableValue);\n    }\n    return defaultValue;\n  }\n\n  public static <T> T mapSFExceptionToSQLException(ThrowingCallable<T, SFException> action)\n      throws SQLException {\n    try {\n      return action.call();\n    } catch (SFException e) {\n      throw new SQLException(e);\n    }\n  }\n\n  public static String getJsonNodeStringValue(JsonNode node) throws SFException {\n    if (node.isNull()) {\n      return null;\n    }\n    return node.isValueNode() ? node.asText() : node.toString();\n  }\n\n  /**\n   * Method introduced to avoid inconsistencies in custom headers handling, since these are defined\n   * on drivers side e.g. some drivers might internally convert headers to canonical form.\n   *\n   * @param input map input\n   * @return case insensitive map\n   */\n  public static Map<String, String> createCaseInsensitiveMap(Map<String, String> input) {\n    Map<String, String> caseInsensitiveMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);\n    if (input != null) {\n      caseInsensitiveMap.putAll(input);\n    }\n    return caseInsensitiveMap;\n  }\n\n  /**\n   * toCaseInsensitiveMap, but adjusted to Headers[] argument type\n   *\n   * @param headers array of headers\n   * @return case insensitive map\n   */\n  public static Map<String, String> createCaseInsensitiveMap(Header[] headers) {\n    if (headers != null) {\n      return createCaseInsensitiveMap(\n          stream(headers)\n              .collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)));\n    } else {\n      return new TreeMap<>(String.CASE_INSENSITIVE_ORDER);\n    }\n  }\n\n  /**\n   * create a directory with Owner only permission (0600)\n   *\n   * @param location the directory location\n   * @return true if directory was created successfully, false otherwise\n   */\n  public static boolean createOwnerOnlyPermissionDir(String location) {\n    if (isWindows()) {\n      File dir = new File(location);\n      return dir.mkdirs();\n    }\n\n    boolean isDirCreated = true;\n    Path dir = Paths.get(location);\n    try {\n      Files.createDirectory(\n          dir, PosixFilePermissions.asFileAttribute(directoryOwnerOnlyPermission));\n    } catch (IOException e) {\n      logger.error(\n          \"Failed to set OwnerOnly permission for {}. This may cause the file download to fail \",\n          location);\n      isDirCreated = false;\n    }\n    return isDirCreated;\n  }\n\n  public static void assureOnlyUserAccessibleFilePermissions(\n      File file, boolean isOwnerOnlyStageFilePermissionsEnabled) throws IOException {\n    if (isWindows()) {\n      return;\n    }\n    if (!isOwnerOnlyStageFilePermissionsEnabled) {\n      // If the owner only stage file permissions are not enabled, we do not need to set the file\n      // permissions.\n      return;\n    }\n    boolean disableUserPermissions =\n        file.setReadable(false, false)\n            && file.setWritable(false, false)\n            && file.setExecutable(false, false);\n    boolean setOwnerPermissionsOnly = file.setReadable(true, true) && file.setWritable(true, true);\n\n    if (disableUserPermissions && setOwnerPermissionsOnly) {\n      logger.info(\"Successfuly set OwnerOnly permission for {}. \", file.getAbsolutePath());\n    } else {\n      file.delete();\n      logger.error(\n          \"Failed to set OwnerOnly permission for {}. Failed to download\", file.getAbsolutePath());\n      throw new IOException(\n          String.format(\n              \"Failed to set OwnerOnly permission for %s. Failed to download\",\n              file.getAbsolutePath()));\n    }\n  }\n\n  /**\n   * Check whether the OS is Windows\n   *\n   * @return boolean\n   */\n  public static boolean isWindows() {\n    return Constants.getOS() == Constants.OS.WINDOWS;\n  }\n\n  public static boolean isNullOrEmpty(String str) {\n    return str == null || str.isEmpty();\n  }\n\n  /** Returns {@code true} when the node exists and carries a non-null value. */\n  public static boolean isJsonNodePresent(JsonNode node) {\n    return !node.isMissingNode() && !node.isNull();\n  }\n\n  /**\n   * Converts Byte array to hex string\n   *\n   * @param bytes a byte array\n   * @return a string in hexadecimal code\n   */\n  public static String byteToHexString(byte[] bytes) {\n    final char[] hexArray = \"0123456789ABCDEF\".toCharArray();\n    char[] hexChars = new char[bytes.length * 2];\n    for (int j = 0; j < bytes.length; j++) {\n      int v = bytes[j] & 0xFF;\n      hexChars[j * 2] = hexArray[v >>> 4];\n      hexChars[j * 2 + 1] = hexArray[v & 0x0F];\n    }\n    return new String(hexChars);\n  }\n\n  /**\n   * Converts a simple wildcard pattern (where only '*' is special) into a safe regex string by\n   * quoting all literal parts via Pattern.quote(), preventing any regex metacharacters from being\n   * interpreted. This avoids ReDoS vulnerabilities when patterns originate from user input.\n   */\n  public static Pattern globToSafePattern(String glob) {\n    String safeRegex =\n        Arrays.stream(glob.split(\"\\\\*\", -1)).map(Pattern::quote).collect(Collectors.joining(\".*\"));\n    return Pattern.compile(safeRegex, Pattern.CASE_INSENSITIVE);\n  }\n\n  public static boolean hostnameMatchesGlob(String hostname, String pattern) {\n    if (hostname == null || pattern == null) {\n      return false;\n    }\n    return globToSafePattern(pattern.trim()).matcher(hostname).matches();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/AwsSdkGCPSigner.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport software.amazon.awssdk.core.interceptor.ExecutionAttributes;\nimport software.amazon.awssdk.core.signer.Signer;\nimport software.amazon.awssdk.http.SdkHttpFullRequest;\nimport software.amazon.awssdk.http.SdkHttpMethod;\n\npublic class AwsSdkGCPSigner implements Signer {\n  private final String bearerToken;\n\n  public AwsSdkGCPSigner(String bearerToken) {\n    this.bearerToken = bearerToken;\n  }\n\n  private static final Map<String, String> headerMap =\n      new HashMap<String, String>() {\n        {\n          put(\"x-amz-storage-class\", \"x-goog-storage-class\");\n          put(\"x-amz-acl\", \"x-goog-acl\");\n          put(\"x-amz-date\", \"x-goog-date\");\n          put(\"x-amz-copy-source\", \"x-goog-copy-source\");\n          put(\"x-amz-metadata-directive\", \"x-goog-metadata-directive\");\n          put(\"x-amz-copy-source-if-match\", \"x-goog-copy-source-if-match\");\n          put(\"x-amz-copy-source-if-none-match\", \"x-goog-copy-source-if-none-match\");\n          put(\"x-amz-copy-source-if-unmodified-since\", \"x-goog-copy-source-if-unmodified-since\");\n          put(\"x-amz-copy-source-if-modified-since\", \"x-goog-copy-source-if-modified-since\");\n        }\n      };\n\n  @Override\n  public SdkHttpFullRequest sign(\n      SdkHttpFullRequest request, ExecutionAttributes executionAttributes) {\n    SdkHttpFullRequest.Builder requestBuilder = request.toBuilder();\n\n    // Remove any existing Authorization header (from AWS signing)\n    requestBuilder.removeHeader(\"Authorization\");\n\n    // Add the Bearer token for GCP authentication\n    if (bearerToken != null && !bearerToken.isEmpty()) {\n      requestBuilder.putHeader(\"Authorization\", \"Bearer \" + bearerToken);\n    }\n\n    if (request.method() == SdkHttpMethod.GET) {\n      requestBuilder.putHeader(\"Accept-Encoding\", \"gzip,deflate\");\n    }\n\n    // Create a copy of headers for iteration to avoid concurrent modification\n    Map<String, List<String>> headersCopy = new HashMap<>(request.headers());\n\n    for (Map.Entry<String, List<String>> entry : headersCopy.entrySet()) {\n      String entryKey = entry.getKey().toLowerCase();\n      if (headerMap.containsKey(entryKey)) {\n        // Add the mapped Google Cloud header\n        for (String value : entry.getValue()) {\n          requestBuilder.putHeader(headerMap.get(entryKey), value);\n        }\n      } else if (entryKey.startsWith(\"x-amz-meta-\")) {\n        // Transform x-amz-meta- headers to x-goog-meta-\n        String googleMetaHeader = entryKey.replace(\"x-amz-meta-\", \"x-goog-meta-\");\n        for (String value : entry.getValue()) {\n          requestBuilder.putHeader(googleMetaHeader, value);\n        }\n      }\n    }\n\n    return requestBuilder.build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/AzureObjectSummariesIterator.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport com.azure.storage.blob.models.BlobItem;\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Iterator class for ObjectSummary objects on Azure Returns platform-independent instances\n * (StorageObjectSummary)\n */\npublic class AzureObjectSummariesIterator implements Iterator<StorageObjectSummary> {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(AzureObjectSummariesIterator.class);\n  private final Iterator<BlobItem> itemIterator;\n  private final String location;\n\n  /*\n   * Constructs a summaries iterator object from an iterable derived by a\n   * lostBlobs method\n   * @param azCloudBlobIterable an iterable set of ListBlobItems\n   */\n  public AzureObjectSummariesIterator(Iterable<BlobItem> azCloudBlobIterable, String location) {\n    itemIterator = azCloudBlobIterable.iterator();\n    this.location = location;\n  }\n\n  public boolean hasNext() {\n    // SNOW-442579 azure itemIterator.hasNext() is a lazy operation, which may cause\n    // StorageException. And it seems Azure wraps the StorageException within the\n    // NoSuchElementException.\n    try {\n      return itemIterator.hasNext();\n    } catch (NoSuchElementException ex) {\n      logger.debug(\"Failed to run azure iterator.hasNext().\", ex);\n      throw new StorageProviderException(\n          (Exception) ex.getCause()); // ex.getCause() should be StorageException\n    }\n  }\n\n  public StorageObjectSummary next() {\n    BlobItem blobItem = itemIterator.next();\n\n    // In the new Azure SDK, BlobItem is the standard type for blob listings\n    // No need to check for CloudBlob vs CloudDirectory as in the old SDK\n\n    return StorageObjectSummary.createFromAzureBlobItem(blobItem, location);\n  }\n\n  public void remove() {\n    throw new UnsupportedOperationException(\"remove() method not supported\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/CloudStorageProxyFactory.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.azure.core.http.ProxyOptions;\nimport com.google.api.client.http.apache.v2.ApacheHttpTransport;\nimport com.google.auth.http.HttpTransportFactory;\nimport java.net.InetSocketAddress;\nimport java.util.Arrays;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpProtocol;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.core.SdkProxyRoutePlanner;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.log.SFLoggerUtil;\nimport org.apache.http.HttpHost;\nimport org.apache.http.auth.AuthScope;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.impl.client.BasicCredentialsProvider;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport software.amazon.awssdk.http.nio.netty.ProxyConfiguration;\n\npublic class CloudStorageProxyFactory {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CloudStorageProxyFactory.class);\n\n  // ── Extraction (shared logic) ──────────────────────────────────────────────\n\n  /**\n   * Extracts proxy settings from a session's HttpClientSettingsKey.\n   *\n   * @return ProxySettings or null if no proxy is configured\n   */\n  static ProxySettings extractFromKey(HttpClientSettingsKey key) {\n    if (key != null && key.usesProxy()) {\n      return new ProxySettings(\n          key.getProxyHost(),\n          key.getProxyPort(),\n          key.getProxyHttpProtocol(),\n          key.getProxyUser(),\n          key.getProxyPassword(),\n          key.getNonProxyHosts());\n    }\n    return null;\n  }\n\n  /**\n   * Extracts proxy settings from sessionless proxy properties.\n   *\n   * @return ProxySettings or null if no proxy is configured\n   * @throws SnowflakeSQLException on invalid port number\n   */\n  static ProxySettings extractFromProperties(Properties proxyProperties)\n      throws SnowflakeSQLException {\n    if (proxyProperties != null\n        && proxyProperties.size() > 0\n        && proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()) != null) {\n      Boolean useProxy =\n          Boolean.valueOf(\n              proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()));\n      if (useProxy) {\n        String proxyHost =\n            proxyProperties.getProperty(SFSessionProperty.PROXY_HOST.getPropertyKey());\n        int proxyPort;\n        try {\n          proxyPort =\n              Integer.parseInt(\n                  proxyProperties.getProperty(SFSessionProperty.PROXY_PORT.getPropertyKey()));\n        } catch (NumberFormatException | NullPointerException e) {\n          throw new SnowflakeSQLException(\n              ErrorCode.INVALID_PROXY_PROPERTIES, \"Could not parse port number\");\n        }\n        String proxyUser =\n            proxyProperties.getProperty(SFSessionProperty.PROXY_USER.getPropertyKey());\n        String proxyPassword =\n            proxyProperties.getProperty(SFSessionProperty.PROXY_PASSWORD.getPropertyKey());\n        String nonProxyHosts =\n            proxyProperties.getProperty(SFSessionProperty.NON_PROXY_HOSTS.getPropertyKey());\n        String proxyProtocol =\n            proxyProperties.getProperty(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey());\n\n        HttpProtocol protocol =\n            (!isNullOrEmpty(proxyProtocol) && proxyProtocol.equalsIgnoreCase(\"https\"))\n                ? HttpProtocol.HTTPS\n                : HttpProtocol.HTTP;\n\n        return new ProxySettings(\n            proxyHost, proxyPort, protocol, proxyUser, proxyPassword, nonProxyHosts);\n      }\n    }\n    return null;\n  }\n\n  // ── CSP conversion methods ─────────────────────────────────────────────────\n\n  static ProxyConfiguration toS3ProxyConfiguration(ProxySettings s) {\n    ProxyConfiguration.Builder proxyBuilder =\n        ProxyConfiguration.builder()\n            .scheme(s.getProtocol().getScheme())\n            .host(s.getHost())\n            .port(s.getPort())\n            .useEnvironmentVariableValues(false)\n            .useSystemPropertyValues(false);\n\n    if (s.hasNonProxyHosts()) {\n      proxyBuilder.nonProxyHosts(prepareNonProxyHostsForS3(s.getNonProxyHosts()));\n    }\n\n    if (s.hasCredentials()) {\n      proxyBuilder.username(s.getUser());\n      proxyBuilder.password(s.getPassword());\n    }\n    return proxyBuilder.build();\n  }\n\n  static ProxyOptions toAzureProxyOptions(ProxySettings s) {\n    ProxyOptions proxyOptions =\n        new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress(s.getHost(), s.getPort()));\n    if (s.hasCredentials()) {\n      proxyOptions.setCredentials(s.getUser(), s.getPassword());\n    }\n    proxyOptions.setNonProxyHosts(s.getNonProxyHosts());\n    return proxyOptions;\n  }\n\n  static HttpTransportFactory toGCSHttpTransportFactory(ProxySettings s) {\n    HttpClientBuilder clientBuilder = HttpClientBuilder.create();\n\n    clientBuilder.setProxy(new HttpHost(s.getHost(), s.getPort(), s.getProtocol().getScheme()));\n\n    SdkProxyRoutePlanner routePlanner =\n        new SdkProxyRoutePlanner(s.getHost(), s.getPort(), s.getProtocol(), s.getNonProxyHosts());\n    clientBuilder.setRoutePlanner(routePlanner);\n\n    if (s.hasCredentials()) {\n      BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n      credentialsProvider.setCredentials(\n          new AuthScope(s.getHost(), s.getPort()),\n          new UsernamePasswordCredentials(s.getUser(), s.getPassword()));\n      clientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n    }\n\n    final ApacheHttpTransport transport = new ApacheHttpTransport(clientBuilder.build());\n    return () -> transport;\n  }\n\n  // ── S3 convenience methods ─────────────────────────────────────────────────\n\n  public static ProxyConfiguration createProxyConfigurationForS3(HttpClientSettingsKey key) {\n    ProxySettings s = extractFromKey(key);\n    if (s == null) {\n      logger.debug(\"Omitting S3 proxy setup\");\n      return null;\n    }\n    logProxySettings(\"S3\", s);\n    return toS3ProxyConfiguration(s);\n  }\n\n  public static ProxyConfiguration createSessionlessProxyConfigurationForS3(\n      Properties proxyProperties) throws SnowflakeSQLException {\n    ProxySettings s = extractFromProperties(proxyProperties);\n    if (s == null) {\n      logger.debug(\"Omitting sessionless S3 proxy setup\");\n      return null;\n    }\n    logProxySettings(\"sessionless S3\", s);\n    return toS3ProxyConfiguration(s);\n  }\n\n  // ── Azure convenience methods ──────────────────────────────────────────────\n\n  public static ProxyOptions createProxyOptionsForAzure(HttpClientSettingsKey key) {\n    ProxySettings s = extractFromKey(key);\n    if (s == null) {\n      logger.debug(\"Omitting Azure proxy setup\");\n      return null;\n    }\n    logProxySettings(\"Azure\", s);\n    return toAzureProxyOptions(s);\n  }\n\n  public static ProxyOptions createSessionlessProxyOptionsForAzure(Properties proxyProperties)\n      throws SnowflakeSQLException {\n    ProxySettings s = extractFromProperties(proxyProperties);\n    if (s == null) {\n      logger.debug(\"Omitting sessionless Azure proxy setup\");\n      return null;\n    }\n    logProxySettings(\"sessionless Azure\", s);\n    return toAzureProxyOptions(s);\n  }\n\n  // ── GCS convenience methods ────────────────────────────────────────────────\n\n  public static HttpTransportFactory createHttpTransportForGCS(HttpClientSettingsKey key) {\n    ProxySettings s = extractFromKey(key);\n    if (s == null) {\n      logger.debug(\"Omitting GCS proxy setup\");\n      return null;\n    }\n    logProxySettings(\"GCS\", s);\n    return toGCSHttpTransportFactory(s);\n  }\n\n  public static HttpTransportFactory createSessionlessHttpTransportForGCS(\n      Properties proxyProperties) throws SnowflakeSQLException {\n    ProxySettings s = extractFromProperties(proxyProperties);\n    if (s == null) {\n      logger.debug(\"Omitting sessionless GCS proxy setup\");\n      return null;\n    }\n    logProxySettings(\"sessionless GCS\", s);\n    return toGCSHttpTransportFactory(s);\n  }\n\n  // ── Helpers ────────────────────────────────────────────────────────────────\n\n  static Set<String> prepareNonProxyHostsForS3(String nonProxyHosts) {\n    return Arrays.stream(nonProxyHosts.split(\"\\\\|\"))\n        .map(String::trim)\n        .map(host -> SnowflakeUtil.globToSafePattern(host).pattern())\n        .collect(Collectors.toSet());\n  }\n\n  private static void logProxySettings(String label, ProxySettings s) {\n    logger.debug(\n        \"Setting {}, proxy. Host: {}, port: {}, protocol: {}, non-proxy hosts: {}, user: {}, password is {}\",\n        label,\n        s.getHost(),\n        s.getPort(),\n        s.getProtocol(),\n        s.getNonProxyHosts(),\n        s.getUser(),\n        SFLoggerUtil.isVariableProvided(s.getPassword()));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/CommonObjectMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.util.Map;\nimport java.util.TreeMap;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\n/**\n * Implements platform-independent interface Azure BLOB and GCS object metadata\n *\n * <p>Only the metadata accessors and mutators used by the JDBC client currently are supported,\n * additional methods should be added as needed\n */\npublic class CommonObjectMetadata implements StorageObjectMetadata {\n  private long contentLength;\n  private final Map<String, String> userDefinedMetadata;\n  private String contentEncoding;\n\n  CommonObjectMetadata() {\n    userDefinedMetadata = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);\n  }\n\n  /*\n   * Constructs a common metadata object\n   * from the set of parameters that the JDBC client is using\n   */\n  CommonObjectMetadata(\n      long contentLength, String contentEncoding, Map<String, String> userDefinedMetadata) {\n    this.contentEncoding = contentEncoding;\n    this.contentLength = contentLength;\n    this.userDefinedMetadata = SnowflakeUtil.createCaseInsensitiveMap(userDefinedMetadata);\n  }\n\n  /**\n   * @return returns a Map/key-value pairs of metadata properties\n   */\n  @Override\n  public Map<String, String> getUserMetadata() {\n    return userDefinedMetadata;\n  }\n\n  /**\n   * @return returns the size of object in bytes\n   */\n  @Override\n  public long getContentLength() {\n    return contentLength;\n  }\n\n  /** Sets size of the associated object in bytes */\n  @Override\n  public void setContentLength(long contentLength) {\n    this.contentLength = contentLength;\n  }\n\n  /** Adds the key value pair of custom user-metadata for the associated object. */\n  @Override\n  public void addUserMetadata(String key, String value) {\n    userDefinedMetadata.put(key, value);\n  }\n\n  /**\n   * Sets the optional Content-Encoding HTTP header specifying what content encodings, have been\n   * applied to the object and what decoding mechanisms must be applied, in order to obtain the\n   * media-type referenced by the Content-Type field.\n   */\n  @Override\n  public void setContentEncoding(String encoding) {\n    contentEncoding = encoding;\n  }\n\n  /*\n   * @return returns the content encoding type\n   */\n  @Override\n  public String getContentEncoding() {\n    return contentEncoding;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/EncryptionProvider.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static java.nio.file.StandardOpenOption.CREATE;\nimport static java.nio.file.StandardOpenOption.READ;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.NoSuchProviderException;\nimport java.security.SecureRandom;\nimport java.util.Base64;\nimport javax.crypto.BadPaddingException;\nimport javax.crypto.Cipher;\nimport javax.crypto.CipherInputStream;\nimport javax.crypto.IllegalBlockSizeException;\nimport javax.crypto.NoSuchPaddingException;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\n\n/** Handles encryption and decryption using AES CBC (for files) and ECB (for keys). */\npublic class EncryptionProvider {\n  private static final String AES = \"AES\";\n  private static final String FILE_CIPHER = \"AES/CBC/PKCS5Padding\";\n  private static final String KEY_CIPHER = \"AES/ECB/PKCS5Padding\";\n  private static final int BUFFER_SIZE = 2 * 1024 * 1024; // 2 MB\n  private static ThreadLocal<SecureRandom> secRnd =\n      new ThreadLocal<>().withInitial(SecureRandom::new);\n\n  /**\n   * Decrypt a InputStream\n   *\n   * @param inputStream input stream\n   * @param keyBase64 keyBase64\n   * @param ivBase64 ivBase64\n   * @param encMat RemoteStoreFileEncryptionMaterial\n   * @return InputStream\n   * @throws NoSuchPaddingException when padding mechanism is not available for this environment\n   * @throws NoSuchAlgorithmException when the requested algorithm is not available for this\n   *     environment\n   * @throws InvalidKeyException when there is an issue with the key value\n   * @throws BadPaddingException when the data is not padded as expected\n   * @throws IllegalBlockSizeException when the length of data is incorrect\n   * @throws InvalidAlgorithmParameterException when the provided KeyStore has no trustAnchors\n   */\n  public static InputStream decryptStream(\n      InputStream inputStream,\n      String keyBase64,\n      String ivBase64,\n      RemoteStoreFileEncryptionMaterial encMat)\n      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,\n          BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {\n    byte[] kekBytes = Base64.getDecoder().decode(encMat.getQueryStageMasterKey());\n    byte[] keyBytes = Base64.getDecoder().decode(keyBase64);\n    byte[] ivBytes = Base64.getDecoder().decode(ivBase64);\n    SecretKey kek = new SecretKeySpec(kekBytes, 0, kekBytes.length, AES);\n    Cipher keyCipher = Cipher.getInstance(KEY_CIPHER);\n    keyCipher.init(Cipher.DECRYPT_MODE, kek);\n    byte[] fileKeyBytes = keyCipher.doFinal(keyBytes);\n    SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES);\n    Cipher dataCipher = Cipher.getInstance(FILE_CIPHER);\n    IvParameterSpec ivy = new IvParameterSpec(ivBytes);\n    dataCipher.init(Cipher.DECRYPT_MODE, fileKey, ivy);\n    return new CipherInputStream(inputStream, dataCipher);\n  }\n\n  /*\n   * decrypt\n   * Decrypts a file given the key and iv. Uses AES decryption.\n   */\n  public static void decrypt(\n      File file, String keyBase64, String ivBase64, RemoteStoreFileEncryptionMaterial encMat)\n      throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,\n          IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException,\n          IOException {\n    byte[] keyBytes = Base64.getDecoder().decode(keyBase64);\n    byte[] ivBytes = Base64.getDecoder().decode(ivBase64);\n    byte[] kekBytes = Base64.getDecoder().decode(encMat.getQueryStageMasterKey());\n    final SecretKey fileKey;\n\n    // Decrypt file key\n    {\n      final Cipher keyCipher = Cipher.getInstance(KEY_CIPHER);\n      SecretKey kek = new SecretKeySpec(kekBytes, 0, kekBytes.length, AES);\n      keyCipher.init(Cipher.DECRYPT_MODE, kek);\n      byte[] fileKeyBytes = keyCipher.doFinal(keyBytes);\n\n      // previous version: fileKey = new SecretKeySpec(fileKeyBytes, offset = 0, len = qsmk.length,\n      // AES);\n      // This incorrectly assumes fileKey is always same length as qsmk. If we perform put from\n      // jdbc, fileKey and qsmk are same length,\n      // but in the case of AwsStorageClient.putObjectInternal() in GS code, they are not. This\n      // leads to some decryption bugs.\n      // See: SnowflakeDriverLatestIt.testS3PutInGs\n      fileKey = new SecretKeySpec(fileKeyBytes, AES);\n    }\n\n    // Decrypt file\n    {\n      final Cipher fileCipher = Cipher.getInstance(FILE_CIPHER);\n      final IvParameterSpec iv = new IvParameterSpec(ivBytes);\n      final byte[] buffer = new byte[BUFFER_SIZE];\n      fileCipher.init(Cipher.DECRYPT_MODE, fileKey, iv);\n\n      long totalBytesRead = 0;\n      // Overwrite file contents buffer-wise with decrypted data\n      try (InputStream is = Files.newInputStream(file.toPath(), READ);\n          InputStream cis = new CipherInputStream(is, fileCipher);\n          OutputStream os = Files.newOutputStream(file.toPath(), CREATE); ) {\n        int bytesRead;\n        while ((bytesRead = cis.read(buffer)) > -1) {\n          os.write(buffer, 0, bytesRead);\n          totalBytesRead += bytesRead;\n        }\n      }\n\n      // Discard any padding that the encrypted file had\n      try (FileChannel fc = new FileOutputStream(file, true).getChannel()) {\n        fc.truncate(totalBytesRead);\n      }\n    }\n  }\n\n  /**\n   * encrypt Encrypts a file using AES encryption. The key and iv are generated. The matdesc field\n   * is added to the metadata object. The key and iv are added to the JSON block in the\n   * encryptionData metadata object.\n   */\n  public static CipherInputStream encrypt(\n      StorageObjectMetadata meta,\n      long originalContentLength,\n      InputStream src,\n      RemoteStoreFileEncryptionMaterial encMat,\n      SnowflakeStorageClient client)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,\n          NoSuchProviderException, NoSuchPaddingException, FileNotFoundException,\n          IllegalBlockSizeException, BadPaddingException {\n    final byte[] decodedKey = Base64.getDecoder().decode(encMat.getQueryStageMasterKey());\n    final int keySize = decodedKey.length;\n    final byte[] fileKeyBytes = new byte[keySize];\n    final byte[] ivData;\n    final CipherInputStream cis;\n    final int blockSize;\n    {\n      final Cipher fileCipher = Cipher.getInstance(FILE_CIPHER);\n      blockSize = fileCipher.getBlockSize();\n\n      // Create IV\n      ivData = new byte[blockSize];\n      secRnd.get().nextBytes(ivData);\n      final IvParameterSpec iv = new IvParameterSpec(ivData);\n\n      // Create file key\n      secRnd.get().nextBytes(fileKeyBytes);\n      SecretKey fileKey = new SecretKeySpec(fileKeyBytes, 0, keySize, AES);\n\n      // Init cipher\n      fileCipher.init(Cipher.ENCRYPT_MODE, fileKey, iv);\n\n      // Create encrypting input stream\n      cis = new CipherInputStream(src, fileCipher);\n    }\n\n    // Encrypt the file key with the QSMK\n    {\n      final Cipher keyCipher = Cipher.getInstance(KEY_CIPHER);\n      SecretKey queryStageMasterKey = new SecretKeySpec(decodedKey, 0, keySize, AES);\n\n      // Init cipher\n      keyCipher.init(Cipher.ENCRYPT_MODE, queryStageMasterKey);\n      byte[] encryptedKey = keyCipher.doFinal(fileKeyBytes);\n\n      // Store metadata\n      MatDesc matDesc = new MatDesc(encMat.getSmkId(), encMat.getQueryId(), keySize * 8);\n      // Round up length to next multiple of the block size\n      // Sizes that are multiples of the block size need to be padded to next\n      // multiple\n      long contentLength = ((originalContentLength + blockSize) / blockSize) * blockSize;\n      // THIS MUTATES METADATA TO ADD ENCRYPTION DATA\n      client.addEncryptionMetadata(meta, matDesc, ivData, encryptedKey, contentLength);\n    }\n\n    return cis;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/GCSAccessStrategy.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.util.SFPair;\n\ninterface GCSAccessStrategy {\n  StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix);\n\n  StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix);\n\n  Map<String, String> download(\n      int parallelism, String remoteStorageLocation, String stageFilePath, File localFile)\n      throws InterruptedException;\n\n  SFPair<InputStream, Map<String, String>> downloadToStream(\n      String remoteStorageLocation, String stageFilePath, boolean isEncrypting);\n\n  void uploadWithDownScopedToken(\n      int parallelism,\n      String remoteStorageLocation,\n      String destFileName,\n      String contentEncoding,\n      Map<String, String> metadata,\n      long contentLength,\n      InputStream content,\n      String queryId)\n      throws InterruptedException;\n\n  boolean handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      String queryId,\n      SnowflakeGCSClient gcsClient)\n      throws SnowflakeSQLException;\n\n  void shutdown();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/GCSAccessStrategyAwsSdk.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.createDefaultExecutorService;\nimport static net.snowflake.client.internal.jdbc.cloud.storage.S3ErrorHandler.retryRequestWithExponentialBackoff;\nimport static net.snowflake.client.internal.jdbc.cloud.storage.S3ErrorHandler.throwIfClientExceptionOrMaxRetryReached;\n\nimport java.io.BufferedInputStream;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.HeaderCustomizerHttpRequestInterceptor;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SFPair;\nimport software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;\nimport software.amazon.awssdk.core.ResponseInputStream;\nimport software.amazon.awssdk.core.async.AsyncRequestBody;\nimport software.amazon.awssdk.core.async.AsyncResponseTransformer;\nimport software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;\nimport software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;\nimport software.amazon.awssdk.core.exception.SdkException;\nimport software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;\nimport software.amazon.awssdk.http.nio.netty.ProxyConfiguration;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.s3.S3AsyncClient;\nimport software.amazon.awssdk.services.s3.S3AsyncClientBuilder;\nimport software.amazon.awssdk.services.s3.model.GetObjectRequest;\nimport software.amazon.awssdk.services.s3.model.GetObjectResponse;\nimport software.amazon.awssdk.services.s3.model.HeadObjectRequest;\nimport software.amazon.awssdk.services.s3.model.HeadObjectResponse;\nimport software.amazon.awssdk.services.s3.model.ListObjectsRequest;\nimport software.amazon.awssdk.services.s3.model.ListObjectsResponse;\nimport software.amazon.awssdk.services.s3.model.PutObjectRequest;\nimport software.amazon.awssdk.transfer.s3.S3TransferManager;\nimport software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;\nimport software.amazon.awssdk.transfer.s3.model.FileDownload;\nimport software.amazon.awssdk.transfer.s3.model.Upload;\nimport software.amazon.awssdk.transfer.s3.model.UploadRequest;\n\nclass GCSAccessStrategyAwsSdk implements GCSAccessStrategy {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(GCSAccessStrategyAwsSdk.class);\n  private final S3AsyncClient amazonClient;\n\n  GCSAccessStrategyAwsSdk(StageInfo stage, SFBaseSession session) throws SnowflakeSQLException {\n    String accessToken = (String) stage.getCredentials().get(\"GCS_ACCESS_TOKEN\");\n\n    Optional<String> oEndpoint = stage.gcsCustomEndpoint();\n    String endpoint = \"https://storage.googleapis.com\";\n    if (oEndpoint.isPresent()) {\n      endpoint = oEndpoint.get();\n    }\n    if (stage.getStorageAccount() != null && endpoint.startsWith(stage.getStorageAccount())) {\n      endpoint = endpoint.replaceFirst(stage.getStorageAccount() + \".\", \"\");\n    }\n    S3AsyncClientBuilder clientBuilder;\n    try {\n      clientBuilder =\n          S3AsyncClient.builder()\n              .region(Region.US_WEST_2) // dummy region, just to satisfy the builder\n              .forcePathStyle(false)\n              .endpointOverride(new URI(endpoint));\n    } catch (URISyntaxException e) {\n      throw new SnowflakeSQLException(\n          ErrorCode.FILE_TRANSFER_ERROR, \"Could not parse Google storage endpoint: \" + endpoint);\n    }\n\n    ClientOverrideConfiguration.Builder overrideConfiguration =\n        ClientOverrideConfiguration.builder();\n\n    // Add signer interceptor for bearer token auth and header mapping\n    overrideConfiguration.putAdvancedOption(\n        SdkAdvancedClientOption.SIGNER, new AwsSdkGCPSigner(accessToken));\n\n    ProxyConfiguration proxyConfiguration;\n    if (session != null) {\n      proxyConfiguration =\n          CloudStorageProxyFactory.createProxyConfigurationForS3(session.getHttpClientKey());\n    } else {\n      proxyConfiguration =\n          CloudStorageProxyFactory.createSessionlessProxyConfigurationForS3(\n              stage.getProxyProperties());\n    }\n\n    if (session instanceof SFSession) {\n      List<HttpHeadersCustomizer> headersCustomizers =\n          ((SFSession) session).getHttpHeadersCustomizers();\n      if (headersCustomizers != null && !headersCustomizers.isEmpty()) {\n        overrideConfiguration.addExecutionInterceptor(\n            new HeaderCustomizerHttpRequestInterceptor(headersCustomizers));\n      }\n    }\n\n    clientBuilder.overrideConfiguration(overrideConfiguration.build());\n\n    // Use anonymous credentials to minimize AWS signing\n    clientBuilder.credentialsProvider(AnonymousCredentialsProvider.create());\n\n    clientBuilder.httpClientBuilder(\n        NettyNioAsyncHttpClient.builder().proxyConfiguration(proxyConfiguration));\n\n    amazonClient = clientBuilder.build();\n  }\n\n  @Override\n  public StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    ListObjectsResponse objListing =\n        amazonClient\n            .listObjects(\n                ListObjectsRequest.builder().bucket(remoteStorageLocation).prefix(prefix).build())\n            .join();\n\n    return new StorageObjectSummaryCollection(objListing.contents(), remoteStorageLocation);\n  }\n\n  @Override\n  public StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix) {\n    HeadObjectResponse response =\n        amazonClient\n            .headObject(\n                HeadObjectRequest.builder().bucket(remoteStorageLocation).key(prefix).build())\n            .join();\n\n    S3ObjectMetadata metadata = new S3ObjectMetadata(response);\n\n    Map<String, String> userMetadata =\n        response.metadata().entrySet().stream()\n            .filter(entry -> entry.getKey().startsWith(\"x-goog-meta-\"))\n            .collect(\n                Collectors.toMap(\n                    e -> e.getKey().replaceFirst(\"x-goog-meta-\", \"\"), Map.Entry::getValue));\n\n    metadata.setUserMetadata(userMetadata);\n    return metadata;\n  }\n\n  @Override\n  public Map<String, String> download(\n      int parallelism, String remoteStorageLocation, String stageFilePath, File localFile)\n      throws InterruptedException {\n\n    logger.debug(\n        \"Starting download of file from S3 stage path: {} to {}\",\n        stageFilePath,\n        localFile.getAbsolutePath());\n\n    logger.debug(\"Creating executor service for transfer manager with {} threads\", parallelism);\n    try (S3TransferManager tx =\n        S3TransferManager.builder()\n            .s3Client(amazonClient)\n            .executor(createDefaultExecutorService(\"s3-transfer-manager-downloader-\", parallelism))\n            .build()) {\n      // download files from s3\n\n      FileDownload fileDownload =\n          tx.downloadFile(\n              DownloadFileRequest.builder()\n                  .getObjectRequest(\n                      GetObjectRequest.builder()\n                          .bucket(remoteStorageLocation)\n                          .key(stageFilePath)\n                          .build())\n                  .destination(localFile.toPath())\n                  .build());\n\n      // Pull object metadata from S3\n      StorageObjectMetadata meta = this.getObjectMetadata(remoteStorageLocation, stageFilePath);\n\n      Map<String, String> metaMap = SnowflakeUtil.createCaseInsensitiveMap(meta.getUserMetadata());\n      fileDownload.completionFuture().join();\n      return metaMap;\n    }\n  }\n\n  @Override\n  public SFPair<InputStream, Map<String, String>> downloadToStream(\n      String remoteStorageLocation, String stageFilePath, boolean isEncrypting) {\n    CompletableFuture<ResponseInputStream<GetObjectResponse>> streamFuture =\n        amazonClient.getObject(\n            GetObjectRequest.builder().bucket(remoteStorageLocation).key(stageFilePath).build(),\n            AsyncResponseTransformer.toBlockingInputStream());\n    CompletableFuture<HeadObjectResponse> metaFuture =\n        amazonClient.headObject(\n            HeadObjectRequest.builder().bucket(remoteStorageLocation).key(stageFilePath).build());\n\n    HeadObjectResponse meta = metaFuture.join();\n    InputStream stream = streamFuture.join();\n\n    Map<String, String> metaMap = SnowflakeUtil.createCaseInsensitiveMap(meta.metadata());\n\n    return SFPair.of(stream, metaMap);\n  }\n\n  @Override\n  public void uploadWithDownScopedToken(\n      int parallelism,\n      String remoteStorageLocation,\n      String destFileName,\n      String contentEncoding,\n      Map<String, String> metadata,\n      long contentLength,\n      InputStream content,\n      String queryId) {\n    S3ObjectMetadata s3ObjectMetadata = new S3ObjectMetadata();\n    s3ObjectMetadata.setContentEncoding(contentEncoding);\n    s3ObjectMetadata.setContentLength(contentLength);\n    s3ObjectMetadata.setUserMetadata(metadata);\n\n    PutObjectRequest request =\n        (s3ObjectMetadata)\n            .getS3PutObjectRequest().toBuilder()\n                .bucket(remoteStorageLocation)\n                .key(destFileName)\n                .build();\n\n    logger.debug(\"Creating executor service for transfer manager with {} threads\", parallelism);\n    ThreadPoolExecutor executorService =\n        createDefaultExecutorService(\"s3-transfer-manager-uploader-\", parallelism);\n    try (S3TransferManager tx =\n        S3TransferManager.builder().s3Client(amazonClient).executor(executorService).build()) {\n      // upload files to s3\n      final Upload upload =\n          tx.upload(\n              UploadRequest.builder()\n                  .putObjectRequest(request)\n                  .requestBody(\n                      AsyncRequestBody.fromInputStream(\n                          // wrapping with BufferedInputStream to mitigate\n                          // https://github.com/aws/aws-sdk-java-v2/issues/6174\n                          new BufferedInputStream(content),\n                          request.contentLength(),\n                          executorService))\n                  .build());\n      upload.completionFuture().join();\n\n      logger.info(\"Uploaded data from input stream to S3 location: {}.\", destFileName);\n    }\n  }\n\n  @Override\n  public boolean handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      String queryId,\n      SnowflakeGCSClient gcsClient)\n      throws SnowflakeSQLException {\n    Throwable cause = ex.getCause();\n    if (cause instanceof SdkException) {\n      logger.debug(\"GCSAccessStrategyAwsSdk: \" + cause.getMessage());\n\n      if (retryCount > gcsClient.getMaxRetries()\n          || S3ErrorHandler.isClientException400Or404(cause)) {\n        throwIfClientExceptionOrMaxRetryReached(\n            operation, session, command, queryId, gcsClient, cause);\n      } else {\n        retryRequestWithExponentialBackoff(\n            ex, retryCount, operation, session, command, gcsClient, queryId, cause);\n      }\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  @Override\n  public void shutdown() {\n    if (this.amazonClient != null) {\n      this.amazonClient.close();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/GCSDefaultAccessStrategy.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.core.Constants.CLOUD_STORAGE_CREDENTIALS_EXPIRED;\n\nimport com.google.api.gax.paging.Page;\nimport com.google.api.gax.rpc.FixedHeaderProvider;\nimport com.google.auth.http.HttpTransportFactory;\nimport com.google.auth.oauth2.AccessToken;\nimport com.google.auth.oauth2.GoogleCredentials;\nimport com.google.cloud.NoCredentials;\nimport com.google.cloud.http.HttpTransportOptions;\nimport com.google.cloud.storage.Blob;\nimport com.google.cloud.storage.BlobId;\nimport com.google.cloud.storage.BlobInfo;\nimport com.google.cloud.storage.HttpStorageOptions;\nimport com.google.cloud.storage.Storage;\nimport com.google.cloud.storage.StorageException;\nimport com.google.cloud.storage.StorageOptions;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.nio.channels.Channels;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SFPair;\nimport net.snowflake.common.core.SqlState;\n\nclass GCSDefaultAccessStrategy implements GCSAccessStrategy {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(GCSDefaultAccessStrategy.class);\n  private Storage gcsClient = null;\n\n  GCSDefaultAccessStrategy(StageInfo stage, SFSession session) throws SnowflakeSQLException {\n    HttpTransportFactory transportFactory;\n    if (session != null) {\n      transportFactory =\n          CloudStorageProxyFactory.createHttpTransportForGCS(session.getHttpClientKey());\n    } else {\n      transportFactory =\n          CloudStorageProxyFactory.createSessionlessHttpTransportForGCS(stage.getProxyProperties());\n    }\n\n    String accessToken = (String) stage.getCredentials().get(\"GCS_ACCESS_TOKEN\");\n\n    if (accessToken != null) {\n      // We are authenticated with an oauth access token.\n      StorageOptions.Builder builder = StorageOptions.newBuilder();\n      overrideHost(stage, builder);\n\n      if (SnowflakeGCSClient.areDisabledGcsDefaultCredentials(session)) {\n        logger.debug(\n            \"Adding explicit credentials to avoid default credential lookup by the GCS client\");\n        builder.setCredentials(GoogleCredentials.create(new AccessToken(accessToken, null)));\n      }\n\n      if (transportFactory != null) {\n        builder.setTransportOptions(\n            HttpTransportOptions.newBuilder().setHttpTransportFactory(transportFactory).build());\n      }\n\n      // Using GoogleCredential with access token will cause IllegalStateException when the token\n      // is expired and trying to refresh, which cause error cannot be caught. Instead, set a\n      // header so we can caught the error code.\n      this.gcsClient =\n          builder\n              .setHeaderProvider(\n                  FixedHeaderProvider.create(\"Authorization\", \"Bearer \" + accessToken))\n              .build()\n              .getService();\n    } else {\n      // Use anonymous authentication.\n      HttpStorageOptions.Builder builder =\n          HttpStorageOptions.newBuilder().setCredentials(NoCredentials.getInstance());\n      overrideHost(stage, builder);\n      if (transportFactory != null) {\n        builder.setTransportOptions(\n            HttpTransportOptions.newBuilder().setHttpTransportFactory(transportFactory).build());\n      }\n      this.gcsClient = builder.build().getService();\n    }\n  }\n\n  private static void overrideHost(StageInfo stage, StorageOptions.Builder builder) {\n    stage\n        .gcsCustomEndpoint()\n        .ifPresent(\n            host -> {\n              if (host.startsWith(\"https://\")) {\n                builder.setHost(host);\n              } else {\n                builder.setHost(\"https://\" + host);\n              }\n            });\n  }\n\n  @Override\n  public StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix) {\n    try {\n      logger.debug(\n          \"Listing objects in the bucket {} with prefix {}\", remoteStorageLocation, prefix);\n      Page<Blob> blobs =\n          this.gcsClient.list(remoteStorageLocation, Storage.BlobListOption.prefix(prefix));\n      return new StorageObjectSummaryCollection(blobs);\n    } catch (Exception e) {\n      logger.debug(\"Failed to list objects\", false);\n      throw new StorageProviderException(e);\n    }\n  }\n\n  @Override\n  public StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix) {\n    try {\n      BlobId blobId = BlobId.of(remoteStorageLocation, prefix);\n      Blob blob = gcsClient.get(blobId);\n\n      // GCS returns null if the blob was not found\n      // By design, our storage platform expects to see a \"blob not found\" situation\n      // as a RemoteStorageProviderException\n      // Hence, we throw a RemoteStorageProviderException\n      if (blob == null) {\n        throw new StorageProviderException(\n            new StorageException(\n                404, // because blob not found\n                \"Blob\" + blobId.getName() + \" not found in bucket \" + blobId.getBucket()));\n      }\n\n      return new CommonObjectMetadata(\n          blob.getSize(), blob.getContentEncoding(), blob.getMetadata());\n    } catch (StorageException ex) {\n      throw new StorageProviderException(ex);\n    }\n  }\n\n  @Override\n  public Map<String, String> download(\n      int parallelism, String remoteStorageLocation, String stageFilePath, File localFile) {\n    BlobId blobId = BlobId.of(remoteStorageLocation, stageFilePath);\n    Blob blob = gcsClient.get(blobId);\n    if (blob == null) {\n      throw new StorageProviderException(\n          new StorageException(\n              404, // because blob not found\n              \"Blob\" + blobId.getName() + \" not found in bucket \" + blobId.getBucket()));\n    }\n\n    logger.debug(\"Starting download without presigned URL\", false);\n    blob.downloadTo(localFile.toPath(), Blob.BlobSourceOption.shouldReturnRawInputStream(true));\n\n    // Get the user-defined BLOB metadata\n    return SnowflakeUtil.createCaseInsensitiveMap(blob.getMetadata());\n  }\n\n  @Override\n  public SFPair<InputStream, Map<String, String>> downloadToStream(\n      String remoteStorageLocation, String stageFilePath, boolean isEncrypting) {\n    BlobId blobId = BlobId.of(remoteStorageLocation, stageFilePath);\n    Blob blob = gcsClient.get(blobId);\n    if (blob == null) {\n      throw new StorageProviderException(\n          new StorageException(\n              404, // because blob not found\n              \"Blob\" + blobId.getName() + \" not found in bucket \" + blobId.getBucket()));\n    }\n    InputStream inputStream = Channels.newInputStream(blob.reader());\n    Map<String, String> userDefinedMetadata = null;\n    if (isEncrypting) {\n      // Get the user-defined BLOB metadata\n      userDefinedMetadata = SnowflakeUtil.createCaseInsensitiveMap(blob.getMetadata());\n    }\n\n    return SFPair.of(inputStream, userDefinedMetadata);\n  }\n\n  @Override\n  public void uploadWithDownScopedToken(\n      int parallelism,\n      String remoteStorageLocation,\n      String destFileName,\n      String contentEncoding,\n      Map<String, String> metadata,\n      long contentLength,\n      InputStream content,\n      String queryId) {\n    BlobId blobId = BlobId.of(remoteStorageLocation, destFileName);\n    BlobInfo blobInfo =\n        BlobInfo.newBuilder(blobId)\n            .setContentEncoding(contentEncoding)\n            .setMetadata(metadata)\n            .build();\n\n    gcsClient.create(blobInfo, content);\n  }\n\n  @Override\n  public boolean handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      String queryId,\n      SnowflakeGCSClient gcsClient)\n      throws SnowflakeSQLException {\n    if (ex instanceof StorageException) {\n      // NOTE: this code path only handle Access token based operation,\n      // presigned URL is not covered. Presigned Url do not raise\n      // StorageException\n\n      StorageException se = (StorageException) ex;\n      // If we have exceeded the max number of retries, propagate the error\n      if (retryCount > gcsClient.getMaxRetries()) {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.SYSTEM_ERROR,\n            StorageHelper.getOperationException(operation).getMessageCode(),\n            se,\n            operation,\n            se.getCode(),\n            se.getMessage(),\n            se.getReason());\n      } else {\n        logger.debug(\n            \"Encountered exception ({}) during {}, retry count: {}\",\n            ex.getMessage(),\n            operation,\n            retryCount);\n        logger.debug(\"Stack trace: \", ex);\n\n        // exponential backoff up to a limit\n        int backoffInMillis = gcsClient.getRetryBackoffMin();\n\n        if (retryCount > 1) {\n          backoffInMillis <<= (Math.min(retryCount - 1, gcsClient.getRetryBackoffMaxExponent()));\n        }\n\n        try {\n          logger.debug(\"Sleep for {} milliseconds before retry\", backoffInMillis);\n\n          Thread.sleep(backoffInMillis);\n        } catch (InterruptedException ex1) {\n          // ignore\n        }\n\n        if (se.getCode() == 401 && command != null) {\n          if (session != null) {\n            // A 401 indicates that the access token has expired,\n            // we need to refresh the GCS client with the new token\n            SnowflakeFileTransferAgent.renewExpiredToken(session, command, gcsClient);\n          } else {\n            throw new SnowflakeSQLException(\n                queryId,\n                se.getMessage(),\n                CLOUD_STORAGE_CREDENTIALS_EXPIRED,\n                \"GCS credentials have expired\");\n          }\n        }\n      }\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  @Override\n  public void shutdown() {\n    // nothing to do here\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/GcmEncryptionProvider.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static java.nio.file.StandardOpenOption.CREATE;\nimport static java.nio.file.StandardOpenOption.READ;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.nio.channels.FileChannel;\nimport java.nio.file.Files;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.util.Base64;\nimport javax.crypto.BadPaddingException;\nimport javax.crypto.Cipher;\nimport javax.crypto.CipherInputStream;\nimport javax.crypto.IllegalBlockSizeException;\nimport javax.crypto.NoSuchPaddingException;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.GCMParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\n\nclass GcmEncryptionProvider {\n  private static final int TAG_LENGTH_IN_BITS = 128;\n  private static final int IV_LENGTH_IN_BYTES = 12;\n  private static final String AES = \"AES\";\n  private static final String FILE_CIPHER = \"AES/GCM/NoPadding\";\n  private static final String KEY_CIPHER = \"AES/GCM/NoPadding\";\n  private static final int BUFFER_SIZE = 8 * 1024 * 1024; // 2 MB\n  private static final ThreadLocal<SecureRandom> random =\n      new ThreadLocal<>().withInitial(SecureRandom::new);\n  private static final Base64.Decoder base64Decoder = Base64.getDecoder();\n\n  static InputStream encrypt(\n      StorageObjectMetadata meta,\n      long originalContentLength,\n      InputStream src,\n      RemoteStoreFileEncryptionMaterial encMat,\n      SnowflakeStorageClient client,\n      byte[] dataAad,\n      byte[] keyAad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,\n          BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException {\n\n    byte[] kek = base64Decoder.decode(encMat.getQueryStageMasterKey());\n    int keySize = kek.length;\n    byte[] keyBytes = new byte[keySize];\n    byte[] dataIvBytes = new byte[IV_LENGTH_IN_BYTES];\n    byte[] keyIvBytes = new byte[IV_LENGTH_IN_BYTES];\n    initRandomIvsAndFileKey(dataIvBytes, keyIvBytes, keyBytes);\n    byte[] encryptedKey = encryptKey(kek, keyBytes, keyIvBytes, keyAad);\n    CipherInputStream cis = encryptContent(src, keyBytes, dataIvBytes, dataAad);\n    addEncryptionMetadataToStorageClient(\n        meta,\n        originalContentLength,\n        encMat,\n        client,\n        keySize,\n        encryptedKey,\n        dataIvBytes,\n        keyIvBytes,\n        keyAad,\n        dataAad);\n    return cis;\n  }\n\n  private static void initRandomIvsAndFileKey(\n      byte[] dataIvData, byte[] fileKeyIvData, byte[] fileKeyBytes) {\n    random.get().nextBytes(dataIvData);\n    random.get().nextBytes(fileKeyIvData);\n    random.get().nextBytes(fileKeyBytes);\n  }\n\n  private static byte[] encryptKey(byte[] kekBytes, byte[] keyBytes, byte[] keyIvData, byte[] aad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,\n          BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException {\n    SecretKey kek = new SecretKeySpec(kekBytes, 0, kekBytes.length, AES);\n    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, keyIvData);\n    Cipher keyCipher = Cipher.getInstance(KEY_CIPHER);\n    keyCipher.init(Cipher.ENCRYPT_MODE, kek, gcmParameterSpec);\n    if (aad != null) {\n      keyCipher.updateAAD(aad);\n    }\n    return keyCipher.doFinal(keyBytes);\n  }\n\n  private static CipherInputStream encryptContent(\n      InputStream src, byte[] keyBytes, byte[] dataIvBytes, byte[] aad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException,\n          NoSuchAlgorithmException {\n    SecretKey fileKey = new SecretKeySpec(keyBytes, 0, keyBytes.length, AES);\n    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, dataIvBytes);\n    Cipher fileCipher = Cipher.getInstance(FILE_CIPHER);\n    fileCipher.init(Cipher.ENCRYPT_MODE, fileKey, gcmParameterSpec);\n    if (aad != null) {\n      fileCipher.updateAAD(aad);\n    }\n    return new CipherInputStream(src, fileCipher);\n  }\n\n  private static void addEncryptionMetadataToStorageClient(\n      StorageObjectMetadata meta,\n      long contentLength,\n      RemoteStoreFileEncryptionMaterial encMat,\n      SnowflakeStorageClient client,\n      int keySize,\n      byte[] encryptedKey,\n      byte[] dataIvData,\n      byte[] keyIvData,\n      byte[] keyAad,\n      byte[] dataAad) {\n    MatDesc matDesc = new MatDesc(encMat.getSmkId(), encMat.getQueryId(), keySize * 8);\n    client.addEncryptionMetadataForGcm(\n        meta, matDesc, encryptedKey, dataIvData, keyIvData, keyAad, dataAad, contentLength);\n  }\n\n  static void decryptFile(\n      File file,\n      String encryptedFileKeyBase64,\n      String dataIvBase64,\n      String keyIvBase64,\n      RemoteStoreFileEncryptionMaterial encMat,\n      String dataAadBase64,\n      String keyAadBase64)\n      throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,\n          InvalidAlgorithmParameterException, IOException, NoSuchPaddingException,\n          NoSuchAlgorithmException {\n    byte[] encryptedKeyBytes = base64Decoder.decode(encryptedFileKeyBase64);\n    byte[] dataIvBytes = base64Decoder.decode(dataIvBase64);\n    byte[] keyIvBytes = base64Decoder.decode(keyIvBase64);\n    byte[] kekBytes = base64Decoder.decode(encMat.getQueryStageMasterKey());\n    byte[] keyAad = base64Decoder.decode(keyAadBase64);\n    byte[] dataAad = base64Decoder.decode(dataAadBase64);\n\n    byte[] keyBytes = decryptKey(kekBytes, keyIvBytes, encryptedKeyBytes, keyAad);\n    decryptContentFromFile(file, keyBytes, dataIvBytes, dataAad);\n  }\n\n  static InputStream decryptStream(\n      InputStream inputStream,\n      String encryptedKeyBase64,\n      String dataIvBase64,\n      String keyIvBase64,\n      RemoteStoreFileEncryptionMaterial encMat,\n      String dataAad,\n      String keyAad)\n      throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException,\n          InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException {\n    byte[] encryptedKeyBytes = base64Decoder.decode(encryptedKeyBase64);\n    byte[] ivBytes = base64Decoder.decode(dataIvBase64);\n    byte[] kekIvBytes = base64Decoder.decode(keyIvBase64);\n    byte[] dataAadBytes = base64Decoder.decode(dataAad);\n    byte[] keyAadBytes = base64Decoder.decode(keyAad);\n    byte[] kekBytes = base64Decoder.decode(encMat.getQueryStageMasterKey());\n\n    byte[] fileKeyBytes = decryptKey(kekBytes, kekIvBytes, encryptedKeyBytes, keyAadBytes);\n    return decryptContentFromStream(inputStream, ivBytes, fileKeyBytes, dataAadBytes);\n  }\n\n  private static CipherInputStream decryptContentFromStream(\n      InputStream inputStream, byte[] ivBytes, byte[] fileKeyBytes, byte[] aad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchPaddingException,\n          NoSuchAlgorithmException {\n    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, ivBytes);\n    SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES);\n    Cipher fileCipher = Cipher.getInstance(FILE_CIPHER);\n    fileCipher.init(Cipher.DECRYPT_MODE, fileKey, gcmParameterSpec);\n    if (aad != null) {\n      fileCipher.updateAAD(aad);\n    }\n    return new CipherInputStream(inputStream, fileCipher);\n  }\n\n  private static void decryptContentFromFile(\n      File file, byte[] fileKeyBytes, byte[] cekIvBytes, byte[] aad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, IOException,\n          NoSuchPaddingException, NoSuchAlgorithmException {\n    SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES);\n    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, cekIvBytes);\n    byte[] buffer = new byte[BUFFER_SIZE];\n    Cipher fileCipher = Cipher.getInstance(FILE_CIPHER);\n    fileCipher.init(Cipher.DECRYPT_MODE, fileKey, gcmParameterSpec);\n    if (aad != null) {\n      fileCipher.updateAAD(aad);\n    }\n\n    long totalBytesRead = 0;\n    try (InputStream is = Files.newInputStream(file.toPath(), READ);\n        InputStream cis = new CipherInputStream(is, fileCipher);\n        OutputStream os = Files.newOutputStream(file.toPath(), CREATE)) {\n      int bytesRead;\n      while ((bytesRead = cis.read(buffer)) > -1) {\n        os.write(buffer, 0, bytesRead);\n        totalBytesRead += bytesRead;\n      }\n    }\n\n    try (FileOutputStream fos = new FileOutputStream(file, true);\n        FileChannel fc = fos.getChannel()) {\n      fc.truncate(totalBytesRead);\n    }\n  }\n\n  private static byte[] decryptKey(byte[] kekBytes, byte[] ivBytes, byte[] keyBytes, byte[] aad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,\n          BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException {\n    SecretKey kek = new SecretKeySpec(kekBytes, 0, kekBytes.length, AES);\n    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_IN_BITS, ivBytes);\n    Cipher keyCipher = Cipher.getInstance(KEY_CIPHER);\n    keyCipher.init(Cipher.DECRYPT_MODE, kek, gcmParameterSpec);\n    if (aad != null) {\n      keyCipher.updateAAD(aad);\n    }\n    return keyCipher.doFinal(keyBytes);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/GcsObjectSummariesIterator.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport com.google.api.gax.paging.Page;\nimport com.google.cloud.storage.Blob;\nimport java.util.Iterator;\n\n/**\n * Iterator class for ObjectSummary objects on GCS objects. Returns platform-independent instances\n * (StorageObjectSummary)\n */\npublic class GcsObjectSummariesIterator implements Iterator<StorageObjectSummary> {\n  private final Iterator<Blob> blobIterator;\n\n  public GcsObjectSummariesIterator(Page<Blob> blobs) {\n    this.blobIterator = blobs.iterateAll().iterator();\n  }\n\n  @Override\n  public boolean hasNext() {\n    return this.blobIterator.hasNext();\n  }\n\n  @Override\n  public StorageObjectSummary next() {\n    Blob blob = this.blobIterator.next();\n    return StorageObjectSummary.createFromGcsBlob(blob);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/ProxySettings.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport net.snowflake.client.internal.core.HttpProtocol;\n\n/** Immutable POJO holding extracted proxy parameters, shared across all CSP proxy builders. */\nclass ProxySettings {\n  private final String host;\n  private final int port;\n  private final HttpProtocol protocol;\n  private final String user;\n  private final String password;\n  private final String nonProxyHosts;\n\n  ProxySettings(\n      String host,\n      int port,\n      HttpProtocol protocol,\n      String user,\n      String password,\n      String nonProxyHosts) {\n    this.host = host;\n    this.port = port;\n    this.protocol = protocol;\n    this.user = user;\n    this.password = password;\n    this.nonProxyHosts = nonProxyHosts;\n  }\n\n  String getHost() {\n    return host;\n  }\n\n  int getPort() {\n    return port;\n  }\n\n  HttpProtocol getProtocol() {\n    return protocol;\n  }\n\n  String getUser() {\n    return user;\n  }\n\n  String getPassword() {\n    return password;\n  }\n\n  String getNonProxyHosts() {\n    return nonProxyHosts;\n  }\n\n  boolean hasCredentials() {\n    return !isNullOrEmpty(user) && !isNullOrEmpty(password);\n  }\n\n  boolean hasNonProxyHosts() {\n    return !isNullOrEmpty(nonProxyHosts);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/QueryIdHelper.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\n\nclass QueryIdHelper {\n  static String queryIdFromEncMatOr(RemoteStoreFileEncryptionMaterial encMat, String queryId) {\n    return encMat != null && encMat.getQueryId() != null ? encMat.getQueryId() : queryId;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/S3ErrorHandler.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.core.Constants.CLOUD_STORAGE_CREDENTIALS_EXPIRED;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.HttpStatus;\nimport software.amazon.awssdk.core.exception.SdkServiceException;\nimport software.amazon.awssdk.services.s3.model.S3Exception;\n\npublic class S3ErrorHandler {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(S3ErrorHandler.class);\n\n  /** Checks the status code of the exception to see if it's a 400 or 404. */\n  static boolean isClientException400Or404(Throwable ex) {\n    if (ex instanceof SdkServiceException) {\n      SdkServiceException sdkEx = (SdkServiceException) ex;\n      return sdkEx.statusCode() == HttpStatus.SC_NOT_FOUND\n          || sdkEx.statusCode() == HttpStatus.SC_BAD_REQUEST;\n    }\n    return false;\n  }\n\n  static void retryRequestWithExponentialBackoff(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      SnowflakeStorageClient s3Client,\n      String queryId,\n      Throwable cause)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"Encountered exception ({}) during {}, retry count: {}\",\n        ex.getMessage(),\n        operation,\n        retryCount);\n    logger.debug(\"Stack trace: \", ex);\n\n    // exponential backoff up to a limit\n    int backoffInMillis = s3Client.getRetryBackoffMin();\n\n    if (retryCount > 1) {\n      backoffInMillis <<= (Math.min(retryCount - 1, s3Client.getRetryBackoffMaxExponent()));\n    }\n\n    try {\n      logger.debug(\"Sleep for {} milliseconds before retry\", backoffInMillis);\n\n      Thread.sleep(backoffInMillis);\n    } catch (InterruptedException ex1) {\n      // ignore\n    }\n\n    // If the exception indicates that the AWS token has expired,\n    // we need to refresh our S3 client with the new token\n    if (cause instanceof S3Exception) {\n      S3Exception e = (S3Exception) cause;\n      if (e.awsErrorDetails() != null\n          && SnowflakeS3Client.EXPIRED_AWS_TOKEN_ERROR_CODE.equalsIgnoreCase(\n              e.awsErrorDetails().errorCode())) {\n        // If session is null we cannot renew the token so throw the ExpiredToken exception\n        if (session != null) {\n          SnowflakeFileTransferAgent.renewExpiredToken(session, command, s3Client);\n        } else {\n          throw new SnowflakeSQLException(\n              queryId,\n              e.awsErrorDetails().errorCode(),\n              CLOUD_STORAGE_CREDENTIALS_EXPIRED,\n              \"S3 credentials have expired\");\n        }\n      }\n    }\n  }\n\n  static void throwIfClientExceptionOrMaxRetryReached(\n      String operation,\n      SFSession session,\n      String command,\n      String queryId,\n      SnowflakeStorageClient s3Client,\n      Throwable cause)\n      throws SnowflakeSQLException {\n    String extendedRequestId = \"none\";\n\n    if (cause instanceof S3Exception) {\n      S3Exception e = (S3Exception) cause;\n      extendedRequestId = e.extendedRequestId();\n    }\n\n    if (cause instanceof SdkServiceException) {\n      SdkServiceException ex1 = (SdkServiceException) cause;\n\n      // The AWS credentials might have expired when server returns error 400 and\n      // does not return the ExpiredToken error code.\n      // If session is null we cannot renew the token so throw the exception\n      if (ex1.statusCode() == HttpStatus.SC_BAD_REQUEST && session != null) {\n        SnowflakeFileTransferAgent.renewExpiredToken(session, command, s3Client);\n      } else {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.SYSTEM_ERROR,\n            StorageHelper.getOperationException(operation).getMessageCode(),\n            ex1,\n            operation,\n            ex1.getMessage(),\n            ex1.requestId(),\n            extendedRequestId);\n      }\n    } else {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.SYSTEM_ERROR,\n          StorageHelper.getOperationException(operation).getMessageCode(),\n          cause,\n          operation,\n          cause.getMessage());\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/S3ObjectMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport software.amazon.awssdk.services.s3.model.ChecksumAlgorithm;\nimport software.amazon.awssdk.services.s3.model.HeadObjectResponse;\nimport software.amazon.awssdk.services.s3.model.PutObjectRequest;\n\n/**\n * s3 implementation of platform independent StorageObjectMetadata interface\n *\n * <p>It only supports a limited set of metadata properties currently used by the JDBC client\n */\npublic class S3ObjectMetadata implements StorageObjectMetadata {\n  private Map<String, String> userMetadata = new HashMap<>();\n  private Long contentLength;\n  private String contentEncoding;\n\n  S3ObjectMetadata() {}\n\n  public S3ObjectMetadata(HeadObjectResponse meta) {\n    userMetadata = meta.metadata();\n    contentLength = meta.contentLength();\n    contentEncoding = meta.contentEncoding();\n  }\n\n  public S3ObjectMetadata(PutObjectRequest meta) {\n    userMetadata = meta.metadata();\n    contentLength = meta.contentLength();\n    contentEncoding = meta.contentEncoding();\n  }\n\n  @Override\n  public Map<String, String> getUserMetadata() {\n    return SnowflakeUtil.createCaseInsensitiveMap(userMetadata);\n  }\n\n  public Map<String, String> setUserMetadata(Map<String, String> metadata) {\n    return this.userMetadata = metadata;\n  }\n\n  @Override\n  public long getContentLength() {\n    return this.contentLength;\n  }\n\n  @Override\n  public void setContentLength(long contentLength) {\n    this.contentLength = contentLength;\n  }\n\n  @Override\n  public void addUserMetadata(String key, String value) {\n    userMetadata.put(key, value);\n  }\n\n  @Override\n  public void setContentEncoding(String encoding) {\n    this.contentEncoding = encoding;\n  }\n\n  @Override\n  public String getContentEncoding() {\n    return contentEncoding;\n  }\n\n  /**\n   * @return Returns the encapsulated AWS S3 metadata request\n   */\n  PutObjectRequest getS3PutObjectRequest() {\n    return PutObjectRequest.builder()\n        .metadata(userMetadata)\n        .contentLength(contentLength)\n        .contentEncoding(contentEncoding)\n        .checksumAlgorithm(ChecksumAlgorithm.CRC32)\n        .build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/S3ObjectSummariesIterator.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.util.Iterator;\nimport java.util.List;\nimport software.amazon.awssdk.services.s3.model.S3Object;\n\n/**\n * Iterator class for ObjectSummary objects on S3 Wraps an iterator of S3 object summaries and\n * returns platform independent instances (StorageObjectSummary)\n */\npublic class S3ObjectSummariesIterator implements Iterator<StorageObjectSummary> {\n\n  // Encapsulated S3 iterator\n  private Iterator<S3Object> s3ObjSummariesIterator;\n  private String bucket;\n\n  /*\n   * Constructs a summaries iterator object from S3Object summary list\n   * derived from the AWS client\n   * @param s3ObjectSummaries a list of S3ObjectSummaries to construct from\n   */\n  public S3ObjectSummariesIterator(List<S3Object> s3ObjectSummaries, String bucket) {\n    s3ObjSummariesIterator = s3ObjectSummaries.iterator();\n    this.bucket = bucket;\n  }\n\n  public boolean hasNext() {\n    return s3ObjSummariesIterator.hasNext();\n  }\n\n  public StorageObjectSummary next() {\n    // Get the next S3 summary object and return it as a platform-agnostic object\n    // (StorageObjectSummary)\n    S3Object s3Obj = s3ObjSummariesIterator.next();\n\n    return StorageObjectSummary.createFromS3ObjectSummary(s3Obj, bucket);\n  }\n\n  public void remove() {\n    throw new UnsupportedOperationException(\"remove() method not supported\");\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeAzureClient.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.core.Constants.CLOUD_STORAGE_CREDENTIALS_EXPIRED;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.cloud.storage.CloudStorageProxyFactory.createProxyOptionsForAzure;\nimport static net.snowflake.client.internal.jdbc.cloud.storage.CloudStorageProxyFactory.createSessionlessProxyOptionsForAzure;\n\nimport com.azure.core.http.ProxyOptions;\nimport com.azure.core.http.rest.Response;\nimport com.azure.core.util.HttpClientOptions;\nimport com.azure.storage.blob.BlobClient;\nimport com.azure.storage.blob.BlobContainerClient;\nimport com.azure.storage.blob.BlobServiceClient;\nimport com.azure.storage.blob.BlobServiceClientBuilder;\nimport com.azure.storage.blob.models.BlobItem;\nimport com.azure.storage.blob.models.BlobProperties;\nimport com.azure.storage.blob.models.BlobStorageException;\nimport com.azure.storage.blob.models.DownloadRetryOptions;\nimport com.azure.storage.blob.models.ListBlobsOptions;\nimport com.azure.storage.blob.models.ParallelTransferOptions;\nimport com.azure.storage.blob.options.BlobDownloadToFileOptions;\nimport com.azure.storage.blob.options.BlobParallelUploadOptions;\nimport com.azure.storage.blob.specialized.BlockBlobClient;\nimport com.fasterxml.jackson.core.JsonFactory;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.file.OpenOption;\nimport java.nio.file.StandardOpenOption;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.AbstractMap.SimpleEntry;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.crypto.BadPaddingException;\nimport javax.crypto.IllegalBlockSizeException;\nimport javax.crypto.NoSuchPaddingException;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.FileBackedOutputStream;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SFPair;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.IOUtils;\n\n/** Encapsulates the Azure Storage client and all Azure Storage operations and logic */\npublic class SnowflakeAzureClient implements SnowflakeStorageClient {\n\n  private static final String localFileSep = systemGetProperty(\"file.separator\");\n  private static final String AZ_ENCRYPTIONDATAPROP = \"encryptiondata\";\n  private static final String AZ_STREAMING_INGEST_CLIENT_NAME = \"ingestclientname\";\n  private static final String AZ_STREAMING_INGEST_CLIENT_KEY = \"ingestclientkey\";\n\n  private int encryptionKeySize = 0; // used for PUTs\n  private StageInfo stageInfo;\n  private RemoteStoreFileEncryptionMaterial encMat;\n  private BlobServiceClient azStorageClient;\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeAzureClient.class);\n  private SFBaseSession session;\n\n  private SnowflakeAzureClient() {}\n  ;\n\n  /*\n   * Factory method for a SnowflakeAzureClient object\n   * @param stage   The stage information that the client will operate on\n   * @param encMat  The encryption material\n   *                required to decrypt/encrypt content in stage\n   */\n  public static SnowflakeAzureClient createSnowflakeAzureClient(\n      StageInfo stage, RemoteStoreFileEncryptionMaterial encMat, SFBaseSession sfSession)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"Initializing Snowflake Azure client with encryption: {}\",\n        encMat != null ? \"true\" : \"false\");\n    SnowflakeAzureClient azureClient = new SnowflakeAzureClient();\n    azureClient.setupAzureClient(stage, encMat, sfSession);\n\n    return azureClient;\n  }\n\n  /*\n   * Initializes the Azure client\n   * This method is used during the object construction, but also to\n   * reset/recreate the encapsulated CloudBlobClient object with new\n   * credentials (after SAS token expiration)\n   * @param stage   The stage information that the client will operate on\n   * @param encMat  The encryption material\n   *                required to decrypt/encrypt content in stage\n   * @throws IllegalArgumentException when invalid credentials are used\n   */\n  private void setupAzureClient(\n      StageInfo stage, RemoteStoreFileEncryptionMaterial encMat, SFBaseSession sfSession)\n      throws IllegalArgumentException, SnowflakeSQLException {\n    // Save the client creation parameters so that we can reuse them,\n    // to reset the Azure client.\n    this.stageInfo = stage;\n    this.encMat = encMat;\n    this.session = sfSession;\n\n    logger.debug(\"Setting up the Azure client \", false);\n\n    try {\n      BlobServiceClientBuilder builder = new BlobServiceClientBuilder();\n      builder.endpoint(\n          buildAzureStorageEndpointURI(stage.getEndPoint(), stage.getStorageAccount()).toString());\n\n      String sasToken = (String) stage.getCredentials().get(\"AZURE_SAS_TOKEN\");\n      if (sasToken != null) {\n        builder.sasToken(sasToken);\n      }\n\n      if (stage.getIsClientSideEncrypted() && encMat != null) {\n        byte[] decodedKey = Base64.getDecoder().decode(encMat.getQueryStageMasterKey());\n        encryptionKeySize = decodedKey.length * 8;\n\n        if (encryptionKeySize != 128 && encryptionKeySize != 192 && encryptionKeySize != 256) {\n          throw new SnowflakeSQLLoggedException(\n              QueryIdHelper.queryIdFromEncMatOr(encMat, null),\n              session,\n              ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(),\n              SqlState.INTERNAL_ERROR,\n              \"unsupported key size\",\n              encryptionKeySize);\n        }\n      }\n      ProxyOptions proxyOptions;\n      if (session != null) {\n        proxyOptions = createProxyOptionsForAzure(session.getHttpClientKey());\n      } else {\n        proxyOptions = createSessionlessProxyOptionsForAzure(stage.getProxyProperties());\n      }\n      HttpClientOptions clientOptions = new HttpClientOptions();\n      clientOptions.setProxyOptions(proxyOptions);\n      builder.clientOptions(clientOptions);\n      this.azStorageClient = builder.buildClient();\n    } catch (URISyntaxException ex) {\n      throw new IllegalArgumentException(\"invalid_azure_credentials\");\n    }\n  }\n\n  // Returns the Max number of retry attempts\n  @Override\n  public int getMaxRetries() {\n    if (session != null\n        && session\n            .getConnectionPropertiesMap()\n            .containsKey(SFSessionProperty.PUT_GET_MAX_RETRIES)) {\n      return (int) session.getConnectionPropertiesMap().get(SFSessionProperty.PUT_GET_MAX_RETRIES);\n    }\n    return 25;\n  }\n\n  // Returns the max exponent for multiplying backoff with the power of 2, the value\n  // of 4 will give us 16secs as the max number of time to sleep before retry\n  @Override\n  public int getRetryBackoffMaxExponent() {\n    return 4;\n  }\n\n  // Returns the min number of milliseconds to sleep before retry\n  @Override\n  public int getRetryBackoffMin() {\n    return 1000;\n  }\n\n  /**\n   * @return Returns true if encryption is enabled\n   */\n  @Override\n  public boolean isEncrypting() {\n    return encryptionKeySize > 0 && this.stageInfo.getIsClientSideEncrypted();\n  }\n\n  /**\n   * @return Returns the size of the encryption key\n   */\n  @Override\n  public int getEncryptionKeySize() {\n    return encryptionKeySize;\n  }\n\n  /**\n   * Re-creates the encapsulated storage client with a fresh access token\n   *\n   * @param stageCredentials a Map (as returned by GS) which contains the new credential properties\n   * @throws SnowflakeSQLException failure to renew the client\n   */\n  @Override\n  public void renew(Map<?, ?> stageCredentials) throws SnowflakeSQLException {\n    logger.debug(\"Renewing the Azure client\");\n    stageInfo.setCredentials(stageCredentials);\n    setupAzureClient(stageInfo, encMat, session);\n  }\n\n  /** shuts down the client */\n  @Override\n  public void shutdown() {\n    /* Not available */\n  }\n\n  /**\n   * For a set of remote storage objects under a remote location and a given prefix/path returns\n   * their properties wrapped in ObjectSummary objects\n   *\n   * @param remoteStorageLocation location, i.e. container for Azure\n   * @param prefix the prefix/path to list under\n   * @return a collection of storage summary objects\n   * @throws StorageProviderException Azure storage exception\n   */\n  @Override\n  public StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    try {\n      BlobContainerClient container = azStorageClient.getBlobContainerClient(remoteStorageLocation);\n      List<BlobItem> blobItems =\n          container.listBlobs(new ListBlobsOptions().setPrefix(prefix), null).stream()\n              .collect(Collectors.toList());\n      return new StorageObjectSummaryCollection(blobItems, remoteStorageLocation);\n    } catch (BlobStorageException ex) {\n      logger.debug(\"Failed to list objects: {}\", ex);\n      throw new StorageProviderException(ex);\n    }\n  }\n\n  /**\n   * Returns the metadata properties for a remote storage object\n   *\n   * @param remoteStorageLocation location, i.e. bucket for S3\n   * @param prefix the prefix/path of the object to retrieve\n   * @return storage metadata object\n   * @throws StorageProviderException azure storage exception\n   */\n  @Override\n  public StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    CommonObjectMetadata azureObjectMetadata;\n    try {\n      BlobContainerClient blobContainerClient =\n          azStorageClient.getBlobContainerClient(remoteStorageLocation);\n      BlobClient blobClient = blobContainerClient.getBlobClient(prefix);\n      BlockBlobClient blockBlobClient = blobClient.getBlockBlobClient();\n\n      BlobProperties properties = blockBlobClient.getProperties();\n\n      Map<String, String> userDefinedMetadata =\n          SnowflakeUtil.createCaseInsensitiveMap(properties.getMetadata());\n\n      long contentLength = properties.getBlobSize();\n      String contentEncoding = properties.getContentEncoding();\n\n      azureObjectMetadata =\n          new CommonObjectMetadata(contentLength, contentEncoding, userDefinedMetadata);\n    } catch (BlobStorageException ex) {\n      logger.debug(\n          \"Failed to retrieve BLOB metadata: {} - {}\",\n          ex.getErrorCode(),\n          formatStorageExtendedErrorInformation(ex));\n      throw new StorageProviderException(ex);\n    }\n\n    return azureObjectMetadata;\n  }\n\n  /**\n   * Download a file from remote storage.\n   *\n   * @param session session object\n   * @param command command to download file\n   * @param localLocation local file path\n   * @param destFileName destination file name\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for S3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Unused in Azure\n   * @param queryId last query id\n   * @throws SnowflakeSQLException download failure\n   */\n  @Override\n  public void download(\n      SFSession session,\n      String command,\n      String localLocation,\n      String destFileName,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    String localFilePath = localLocation + localFileSep + destFileName;\n    logger.debug(\n        \"Starting download of file from Azure stage path: {} to {}\", stageFilePath, localFilePath);\n    localFilePath = trimLeadingSlashIfOnWindows(localFilePath);\n    int retryCount = 0;\n    do {\n      try {\n        File localFile = new File(localFilePath);\n        BlobContainerClient blobContainerClient =\n            azStorageClient.getBlobContainerClient(remoteStorageLocation);\n        BlobClient blobClient = blobContainerClient.getBlobClient(stageFilePath);\n        BlockBlobClient blockBlobClient = blobClient.getBlockBlobClient();\n\n        Set<OpenOption> options = new HashSet<>();\n        options.add(StandardOpenOption.CREATE);\n        options.add(StandardOpenOption.WRITE);\n        options.add(StandardOpenOption.TRUNCATE_EXISTING);\n        BlobDownloadToFileOptions downloadOptions =\n            new BlobDownloadToFileOptions(localFilePath)\n                .setDownloadRetryOptions(new DownloadRetryOptions().setMaxRetryRequests(0))\n                .setOpenOptions(options)\n                .setParallelTransferOptions(\n                    new com.azure.storage.common.ParallelTransferOptions()\n                        .setMaxConcurrency(parallelism));\n\n        Response<BlobProperties> response =\n            blockBlobClient.downloadToFileWithResponse(downloadOptions, null, null);\n        SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(\n            localFile, session.isOwnerOnlyStageFilePermissionsEnabled());\n        stopwatch.stop();\n        long downloadMillis = stopwatch.elapsedMillis();\n\n        Map<String, String> userDefinedMetadata =\n            SnowflakeUtil.createCaseInsensitiveMap(response.getValue().getMetadata());\n\n        if (this.isEncrypting() && this.getEncryptionKeySize() <= 256) {\n          decryptFile(\n              session,\n              remoteStorageLocation,\n              queryId,\n              userDefinedMetadata,\n              stopwatch,\n              localFile,\n              downloadMillis,\n              retryCount);\n        } else {\n          logger.info(\n              \"Azure file {} downloaded to {}. It took {} ms with {} retries\",\n              remoteStorageLocation,\n              localFile.getAbsolutePath(),\n              downloadMillis,\n              retryCount);\n        }\n        return;\n      } catch (Exception ex) {\n        logger.debug(\"Download unsuccessful {}\", ex);\n        handleAzureException(\n            ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, this, queryId);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: download unsuccessful without exception!\");\n  }\n\n  /**\n   * If path on Windows looks like \"/C:/...\" we need to manually trim the leading slash. Otherwise,\n   * it fails with InvalidPathException\n   */\n  private static String trimLeadingSlashIfOnWindows(String localFilePath) {\n    if (SnowflakeUtil.isWindows() && localFilePath.startsWith(\"/\")) {\n      logger.debug(\"Trimming leading slash for Windows path: {}\", localFilePath);\n      return localFilePath.substring(1);\n    }\n    return localFilePath;\n  }\n\n  private void decryptFile(\n      SFSession session,\n      String remoteStorageLocation,\n      String queryId,\n      Map<String, String> userDefinedMetadata,\n      Stopwatch stopwatch,\n      File localFile,\n      long downloadMillis,\n      int retryCount)\n      throws SnowflakeSQLException, NoSuchAlgorithmException, NoSuchPaddingException,\n          InvalidKeyException, IllegalBlockSizeException, BadPaddingException,\n          InvalidAlgorithmParameterException, IOException {\n    EncryptionData encryptionData =\n        validateAndGetEncryptionData(session, queryId, userDefinedMetadata, stopwatch);\n\n    // Decrypt file\n    try {\n      EncryptionProvider.decrypt(localFile, encryptionData.key, encryptionData.iv, this.encMat);\n      stopwatch.stop();\n      long decryptMillis = stopwatch.elapsedMillis();\n      logger.info(\n          \"Azure file {} downloaded to {}. It took {} ms (download: {} ms, decryption: {} ms) with {} retries\",\n          remoteStorageLocation,\n          localFile.getAbsolutePath(),\n          downloadMillis + decryptMillis,\n          downloadMillis,\n          decryptMillis,\n          retryCount);\n    } catch (Exception ex) {\n      logger.error(\"Error decrypting file\", ex);\n      throw ex;\n    }\n  }\n\n  /**\n   * Download a file from remote storage\n   *\n   * @param session session object\n   * @param command command to download file\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for s3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Unused in Azure\n   * @param queryId last query id\n   * @return input file stream\n   * @throws SnowflakeSQLException when download failure\n   */\n  @Override\n  public InputStream downloadToStream(\n      SFSession session,\n      String command,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"Starting download of file from Azure stage path: {} to input stream\", stageFilePath);\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    int retryCount = 0;\n\n    do {\n      try {\n        BlobContainerClient blobContainerClient =\n            azStorageClient.getBlobContainerClient(remoteStorageLocation);\n        BlobClient blobClient = blobContainerClient.getBlobClient(stageFilePath);\n        BlockBlobClient blockBlobClient = blobClient.getBlockBlobClient();\n\n        InputStream stream = blockBlobClient.openInputStream();\n        stopwatch.stop();\n        long downloadMillis = stopwatch.elapsedMillis();\n        Map<String, String> userDefinedMetadata =\n            SnowflakeUtil.createCaseInsensitiveMap(blockBlobClient.getProperties().getMetadata());\n\n        if (this.isEncrypting() && this.getEncryptionKeySize() <= 256) {\n          return decryptStream(\n              session,\n              stageFilePath,\n              queryId,\n              userDefinedMetadata,\n              stopwatch,\n              stream,\n              downloadMillis,\n              retryCount);\n        } else {\n          logger.info(\n              \"Azure file {} downloaded to input stream. Download took {} ms with {} retries\",\n              stageFilePath,\n              downloadMillis,\n              retryCount);\n          return stream;\n        }\n      } catch (Exception ex) {\n        logger.debug(\"Downloading unsuccessful {}\", ex);\n        handleAzureException(\n            ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, this, queryId);\n      }\n    } while (retryCount < getMaxRetries());\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        ErrorCode.INTERNAL_ERROR.getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: download unsuccessful without exception!\");\n  }\n\n  private InputStream decryptStream(\n      SFSession session,\n      String stageFilePath,\n      String queryId,\n      Map<String, String> userDefinedMetadata,\n      Stopwatch stopwatch,\n      InputStream stream,\n      long downloadMillis,\n      int retryCount)\n      throws SnowflakeSQLException, NoSuchPaddingException, NoSuchAlgorithmException,\n          InvalidKeyException, BadPaddingException, IllegalBlockSizeException,\n          InvalidAlgorithmParameterException {\n    EncryptionData encryptionData =\n        validateAndGetEncryptionData(session, queryId, userDefinedMetadata, stopwatch);\n\n    try {\n      InputStream is =\n          EncryptionProvider.decryptStream(stream, encryptionData.key, encryptionData.iv, encMat);\n      stopwatch.stop();\n      long decryptMillis = stopwatch.elapsedMillis();\n      logger.info(\n          \"Azure file {} downloaded to input stream. It took {} ms \"\n              + \"(download: {} ms, decryption: {} ms) with {} retries\",\n          stageFilePath,\n          downloadMillis + decryptMillis,\n          downloadMillis,\n          decryptMillis,\n          retryCount);\n      return is;\n    } catch (Exception ex) {\n      logger.error(\"Error in decrypting file\", ex);\n      throw ex;\n    }\n  }\n\n  private EncryptionData validateAndGetEncryptionData(\n      SFSession session,\n      String queryId,\n      Map<String, String> userDefinedMetadata,\n      Stopwatch stopwatch)\n      throws SnowflakeSQLException {\n    if (!userDefinedMetadata.containsKey(AZ_ENCRYPTIONDATAPROP)) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Encryption data not found in the metadata of a file being downloaded\");\n    }\n    SimpleEntry<String, String> encryptionData =\n        parseEncryptionData(userDefinedMetadata.get(AZ_ENCRYPTIONDATAPROP), queryId);\n\n    String key = encryptionData.getKey();\n    String iv = encryptionData.getValue();\n    stopwatch.restart();\n    if (key == null || iv == null) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"File metadata incomplete\");\n    }\n    return new EncryptionData(key, iv);\n  }\n\n  /**\n   * Upload a file/stream to remote storage\n   *\n   * @param session session object\n   * @param command upload command\n   * @param parallelism number of threads for parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation storage container name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Unused in Azure\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  @Override\n  public void upload(\n      SFSession session,\n      String command,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.info(\n        StorageHelper.getStartUploadLog(\n            \"Azure\", uploadFromStream, inputStream, fileBackedOutputStream, srcFile, destFileName));\n    final List<FileInputStream> toClose = new ArrayList<>();\n    long originalContentLength = meta.getContentLength();\n\n    SFPair<InputStream, Boolean> uploadStreamInfo =\n        createUploadStream(\n            srcFile,\n            uploadFromStream,\n            inputStream,\n            meta,\n            originalContentLength,\n            fileBackedOutputStream,\n            toClose,\n            queryId);\n\n    if (!(meta instanceof CommonObjectMetadata)) {\n      throw new IllegalArgumentException(\"Unexpected metadata object type\");\n    }\n\n    int retryCount = 0;\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    do {\n      try {\n        logger.debug(\"Try uploading retryCount: {}, parallelism: {}\", retryCount, parallelism);\n        InputStream fileInputStream = uploadStreamInfo.left;\n        BlobContainerClient blobContainerClient =\n            azStorageClient.getBlobContainerClient(remoteStorageLocation);\n        BlobClient blobClient = blobContainerClient.getBlobClient(destFileName);\n\n        // Set the user-defined/Snowflake metadata and upload the BLOB\n        logger.info(\"Uploading file: {}, with metadata:\", destFileName);\n        BlobParallelUploadOptions parallelUploadOptions =\n            new BlobParallelUploadOptions(fileInputStream)\n                .setParallelTransferOptions(\n                    new ParallelTransferOptions().setMaxConcurrency(parallelism))\n                .setMetadata(meta.getUserMetadata());\n\n        blobClient.uploadWithResponse(parallelUploadOptions, null, null);\n\n        stopwatch.stop();\n\n        if (uploadFromStream) {\n          logger.info(\n              \"Uploaded data from input stream to Azure location: {}. It took {} ms with {} retries\",\n              remoteStorageLocation,\n              stopwatch.elapsedMillis(),\n              retryCount);\n        } else {\n          logger.info(\n              \"Uploaded file {} to Azure location: {}. It took {} ms with {} retries\",\n              srcFile.getAbsolutePath(),\n              remoteStorageLocation,\n              stopwatch.elapsedMillis(),\n              retryCount);\n        }\n\n        // close any open streams in the \"toClose\" list and return\n        for (FileInputStream is : toClose) {\n          IOUtils.closeQuietly(is);\n        }\n\n        return;\n      } catch (Exception ex) {\n        logger.debug(\"Error while uploading file\", ex);\n        handleAzureException(\n            ex, ++retryCount, StorageHelper.UPLOAD, session, command, this, queryId);\n\n        if (uploadFromStream && fileBackedOutputStream == null) {\n          throw new SnowflakeSQLException(\n              queryId,\n              ex,\n              SqlState.SYSTEM_ERROR,\n              ErrorCode.UPLOAD_ERROR.getMessageCode(),\n              \"Encountered exception during upload: \"\n                  + ex.getMessage()\n                  + \"\\nCannot retry upload from stream.\");\n        }\n        uploadStreamInfo =\n            createUploadStream(\n                srcFile,\n                uploadFromStream,\n                inputStream,\n                meta,\n                originalContentLength,\n                fileBackedOutputStream,\n                toClose,\n                queryId);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    for (FileInputStream is : toClose) {\n      IOUtils.closeQuietly(is);\n    }\n\n    throw new SnowflakeSQLException(\n        queryId,\n        SqlState.INTERNAL_ERROR,\n        ErrorCode.INTERNAL_ERROR.getMessageCode(),\n        \"Unexpected: upload unsuccessful without exception!\");\n  }\n\n  /**\n   * Handles exceptions thrown by Azure Storage\n   *\n   * @param ex the exception to handle\n   * @param retryCount current number of retries, incremented by the caller before each call\n   * @param operation string that indicates the function/operation that was taking place, when the\n   *     exception was raised, for example \"upload\"\n   * @param session the current SFSession object used by the client\n   * @param command the command attempted at the time of the exception\n   * @param queryId last query id\n   * @throws SnowflakeSQLException exceptions not handled\n   */\n  @Override\n  public void handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      String queryId)\n      throws SnowflakeSQLException {\n    handleAzureException(ex, retryCount, operation, session, command, this, queryId);\n  }\n\n  private SFPair<InputStream, Boolean> createUploadStream(\n      File srcFile,\n      boolean uploadFromStream,\n      InputStream inputStream,\n      StorageObjectMetadata meta,\n      long originalContentLength,\n      FileBackedOutputStream fileBackedOutputStream,\n      List<FileInputStream> toClose,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"createUploadStream({}, {}, {}, {}, {}, {})\",\n        this,\n        srcFile,\n        uploadFromStream,\n        inputStream,\n        fileBackedOutputStream,\n        toClose);\n\n    final InputStream stream;\n    FileInputStream srcFileStream = null;\n    try {\n      if (isEncrypting() && getEncryptionKeySize() <= 256) {\n        try {\n          final InputStream uploadStream =\n              uploadFromStream\n                  ? (fileBackedOutputStream != null\n                      ? fileBackedOutputStream.asByteSource().openStream()\n                      : inputStream)\n                  : (srcFileStream = new FileInputStream(srcFile));\n          toClose.add(srcFileStream);\n\n          // Encrypt\n          stream =\n              EncryptionProvider.encrypt(\n                  meta, originalContentLength, uploadStream, this.encMat, this);\n          uploadFromStream = true;\n        } catch (Exception ex) {\n          logger.error(\"Failed to encrypt input\", ex);\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              SqlState.INTERNAL_ERROR,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              ex,\n              \"Failed to encrypt input\",\n              ex.getMessage());\n        }\n      } else {\n        if (uploadFromStream) {\n          if (fileBackedOutputStream != null) {\n            stream = fileBackedOutputStream.asByteSource().openStream();\n          } else {\n            stream = inputStream;\n          }\n        } else {\n          srcFileStream = new FileInputStream(srcFile);\n          toClose.add(srcFileStream);\n          stream = srcFileStream;\n        }\n      }\n    } catch (FileNotFoundException ex) {\n      logger.error(\"Failed to open input file\", ex);\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          \"Failed to open input file\",\n          ex.getMessage());\n    } catch (IOException ex) {\n      logger.error(\"Failed to open input stream\", ex);\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          ex,\n          \"Failed to open input stream\",\n          ex.getMessage());\n    }\n\n    return SFPair.of(stream, uploadFromStream);\n  }\n\n  /**\n   * Handles exceptions thrown by Azure Storage It will retry transient errors as defined by the\n   * Azure Client retry policy It will re-create the client if the SAS token has expired, and re-try\n   *\n   * @param ex the exception to handle\n   * @param retryCount current number of retries, incremented by the caller before each call\n   * @param operation string that indicates the function/operation that was taking place, when the\n   *     exception was raised, for example \"upload\"\n   * @param session the current SFSession object used by the client\n   * @param command the command attempted at the time of the exception\n   * @param azClient the current Snowflake Azure client object\n   * @throws SnowflakeSQLException exceptions not handled\n   */\n  private static void handleAzureException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      SnowflakeAzureClient azClient,\n      String queryId)\n      throws SnowflakeSQLException {\n\n    // no need to retry if it is invalid key exception\n    if (ex.getCause() instanceof InvalidKeyException) {\n      // Most likely cause is that the unlimited strength policy files are not installed\n      // Log the error and throw a message that explains the cause\n      SnowflakeFileTransferAgent.throwJCEMissingError(operation, ex, queryId);\n    }\n\n    // If there is no space left in the download location, java.io.IOException is thrown.\n    // Don't retry.\n    if (SnowflakeUtil.getRootCause(ex) instanceof IOException) {\n      SnowflakeFileTransferAgent.throwNoSpaceLeftError(session, operation, ex, queryId);\n    }\n\n    BlobStorageException se = null;\n    if (ex instanceof BlobStorageException) {\n      se = (BlobStorageException) ex;\n    } else if (SnowflakeUtil.getRootCause(ex) instanceof BlobStorageException) {\n      se = (BlobStorageException) SnowflakeUtil.getRootCause(ex);\n    }\n\n    if (se != null) {\n      if (se.getStatusCode() == 403) {\n        // A 403 indicates that the SAS token has expired,\n        // we need to refresh the Azure client with the new token\n        if (session != null) {\n          SnowflakeFileTransferAgent.renewExpiredToken(session, command, azClient);\n        } else {\n          // If session is null we cannot renew the token so throw the ExpiredToken exception\n          throw new SnowflakeSQLException(\n              queryId,\n              se.getErrorCode().toString(),\n              CLOUD_STORAGE_CREDENTIALS_EXPIRED,\n              \"Azure credentials may have expired\");\n        }\n      }\n      // If we have exceeded the max number of retries, propagate the error\n      // no need for back off and retry if the file does not exist\n      if (retryCount > azClient.getMaxRetries() || se.getStatusCode() == 404) {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.SYSTEM_ERROR,\n            ErrorCode.AZURE_SERVICE_ERROR.getMessageCode(),\n            se,\n            operation,\n            se.getErrorCode(),\n            se.getStatusCode(),\n            se.getMessage(),\n            formatStorageExtendedErrorInformation(se));\n      } else {\n        logger.debug(\n            \"Encountered exception ({}) during {}, retry count: {}\",\n            se.getMessage(),\n            operation,\n            retryCount);\n        logger.debug(\"Stack trace: \", ex);\n\n        // exponential backoff up to a limit\n        int backoffInMillis = azClient.getRetryBackoffMin();\n\n        if (retryCount > 1) {\n          backoffInMillis <<= (Math.min(retryCount - 1, azClient.getRetryBackoffMaxExponent()));\n        }\n\n        try {\n          logger.debug(\"Sleep for {} milliseconds before retry\", backoffInMillis);\n\n          Thread.sleep(backoffInMillis);\n        } catch (InterruptedException ex1) {\n          // ignore\n        }\n\n        if (se.getStatusCode() == 403) {\n          // A 403 indicates that the SAS token has expired,\n          // we need to refresh the Azure client with the new token\n          SnowflakeFileTransferAgent.renewExpiredToken(session, command, azClient);\n        }\n      }\n    } else {\n      if (ex instanceof InterruptedException\n          || SnowflakeUtil.getRootCause(ex) instanceof SocketTimeoutException) {\n        if (retryCount > azClient.getMaxRetries()) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              SqlState.SYSTEM_ERROR,\n              ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(),\n              ex,\n              \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n        } else {\n          logger.debug(\n              \"Encountered exception ({}) during {}, retry count: {}\",\n              ex.getMessage(),\n              operation,\n              retryCount);\n        }\n      } else {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.SYSTEM_ERROR,\n            ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(),\n            ex,\n            \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n      }\n    }\n  }\n\n  /**\n   * Format the StorageExtendedErrorInformation to a String.\n   *\n   * @param info the StorageExtendedErrorInformation object\n   * @return\n   */\n  static String formatStorageExtendedErrorInformation(BlobStorageException info) {\n    return \"StorageExceptionExtendedErrorInformation: {ErrorCode=\"\n        + info.getErrorCode()\n        + \", ErrorMessage=\"\n        + info.getServiceMessage()\n        + \"}\";\n  }\n\n  /**\n   * Builds a URI to an Azure Storage account endpoint\n   *\n   * @param storageEndPoint the storage endpoint name\n   * @param storageAccount the storage account name\n   */\n  private static URI buildAzureStorageEndpointURI(String storageEndPoint, String storageAccount)\n      throws URISyntaxException {\n    URI storageEndpoint =\n        new URI(\"https\", storageAccount + \".\" + storageEndPoint + \"/\", null, null);\n\n    return storageEndpoint;\n  }\n\n  /**\n   * buildEncryptionMetadataJSON Takes the base64-encoded iv and key and creates the JSON block to\n   * be used as the encryptiondata metadata field on the blob.\n   */\n  private String buildEncryptionMetadataJSON(String iv64, String key64) {\n    return String.format(\n        \"{\\\"EncryptionMode\\\":\\\"FullBlob\\\",\\\"WrappedContentKey\\\"\"\n            + \":{\\\"KeyId\\\":\\\"symmKey1\\\",\\\"EncryptedKey\\\":\\\"%s\\\"\"\n            + \",\\\"Algorithm\\\":\\\"AES_CBC_256\\\"},\\\"EncryptionAgent\\\":\"\n            + \"{\\\"Protocol\\\":\\\"1.0\\\",\\\"EncryptionAlgorithm\\\":\"\n            + \"\\\"AES_CBC_256\\\"},\\\"ContentEncryptionIV\\\":\\\"%s\\\"\"\n            + \",\\\"KeyWrappingMetadata\\\":{\\\"EncryptionLibrary\\\":\"\n            + \"\\\"Java 5.3.0\\\"}}\",\n        key64, iv64);\n  }\n  /**\n   * parseEncryptionData Takes the json string in the encryptiondata metadata field of the encrypted\n   * blob and parses out the key and iv. Returns the pair as key = key, iv = value.\n   */\n  private SimpleEntry<String, String> parseEncryptionData(String jsonEncryptionData, String queryId)\n      throws SnowflakeSQLException {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    JsonFactory factory = mapper.getFactory();\n    try {\n      JsonParser parser = factory.createParser(jsonEncryptionData);\n      JsonNode encryptionDataNode = mapper.readTree(parser);\n\n      String iv = encryptionDataNode.get(\"ContentEncryptionIV\").asText();\n      String key = encryptionDataNode.get(\"WrappedContentKey\").get(\"EncryptedKey\").asText();\n\n      return new SimpleEntry<String, String>(key, iv);\n    } catch (Exception ex) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.SYSTEM_ERROR,\n          ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(),\n          ex,\n          \"Error parsing encryption data as json\" + \": \" + ex.getMessage());\n    }\n  }\n\n  /** Returns the material descriptor key */\n  @Override\n  public String getMatdescKey() {\n    return \"matdesc\";\n  }\n\n  /** Adds encryption metadata to the StorageObjectMetadata object */\n  @Override\n  public void addEncryptionMetadata(\n      StorageObjectMetadata meta,\n      MatDesc matDesc,\n      byte[] ivData,\n      byte[] encryptedKey,\n      long contentLength) {\n    meta.addUserMetadata(getMatdescKey(), matDesc.toString());\n    meta.addUserMetadata(\n        AZ_ENCRYPTIONDATAPROP,\n        buildEncryptionMetadataJSON(\n            Base64.getEncoder().encodeToString(ivData),\n            Base64.getEncoder().encodeToString(encryptedKey)));\n    meta.setContentLength(contentLength);\n  }\n\n  /** Adds digest metadata to the StorageObjectMetadata object */\n  @Override\n  public void addDigestMetadata(StorageObjectMetadata meta, String digest) {\n    if (!SnowflakeUtil.isBlank(digest)) {\n      // Azure doesn't allow hyphens in the name of a metadata field.\n      meta.addUserMetadata(\"sfcdigest\", digest);\n    }\n  }\n\n  /** Gets digest metadata to the StorageObjectMetadata object */\n  @Override\n  public String getDigestMetadata(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(\"sfcdigest\");\n  }\n\n  /**\n   * Adds streaming ingest metadata to the StorageObjectMetadata object, used for streaming ingest\n   * per client billing calculation\n   */\n  @Override\n  public void addStreamingIngestMetadata(\n      StorageObjectMetadata meta, String clientName, String clientKey) {\n    meta.addUserMetadata(AZ_STREAMING_INGEST_CLIENT_NAME, clientName);\n    meta.addUserMetadata(AZ_STREAMING_INGEST_CLIENT_KEY, clientKey);\n  }\n\n  /** Gets streaming ingest client name to the StorageObjectMetadata object */\n  @Override\n  public String getStreamingIngestClientName(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(AZ_STREAMING_INGEST_CLIENT_NAME);\n  }\n\n  /** Gets streaming ingest client key to the StorageObjectMetadata object */\n  @Override\n  public String getStreamingIngestClientKey(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(AZ_STREAMING_INGEST_CLIENT_KEY);\n  }\n\n  private static class EncryptionData {\n    public final String key;\n    public final String iv;\n\n    public EncryptionData(String key, String iv) {\n      this.key = key;\n      this.iv = iv;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeGCSClient.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.convertSystemPropertyToBooleanValue;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.createCaseInsensitiveMap;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.getRootCause;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isBlank;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.fasterxml.jackson.core.JsonFactory;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.SocketTimeoutException;\nimport java.net.URISyntaxException;\nimport java.security.InvalidKeyException;\nimport java.util.AbstractMap;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpResponseContextDto;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.FileBackedOutputStream;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.client.internal.jdbc.RestRequest;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.log.ArgSupplier;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SFPair;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.HttpResponseException;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPut;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.entity.InputStreamEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.util.EntityUtils;\n\n/** Encapsulates the GCS Storage client and all GCS operations and logic */\npublic class SnowflakeGCSClient implements SnowflakeStorageClient {\n  public static final String DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME =\n      \"net.snowflake.jdbc.disableGcsDefaultCredentials\";\n\n  private static final String GCS_ENCRYPTIONDATAPROP = \"encryptiondata\";\n  private static final String localFileSep = systemGetProperty(\"file.separator\");\n  private static final String GCS_METADATA_PREFIX = \"x-goog-meta-\";\n  private static final String GCS_STREAMING_INGEST_CLIENT_NAME = \"ingestclientname\";\n  private static final String GCS_STREAMING_INGEST_CLIENT_KEY = \"ingestclientkey\";\n\n  private int encryptionKeySize = 0; // used for PUTs\n  private StageInfo stageInfo;\n  private RemoteStoreFileEncryptionMaterial encMat;\n  private SFSession session = null;\n  private GCSAccessStrategy gcsAccessStrategy = null;\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeGCSClient.class);\n\n  private SnowflakeGCSClient() {}\n\n  /*\n   * Factory method for a SnowflakeGCSClient object\n   * @param stage   The stage information that the client will operate on\n   * @param encMat  The encryption material\n   *                required to decrypt/encrypt content in stage\n   */\n  public static SnowflakeGCSClient createSnowflakeGCSClient(\n      StageInfo stage, RemoteStoreFileEncryptionMaterial encMat, SFSession session)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"Initializing Snowflake GCS client with encryption: {}\", encMat != null ? \"true\" : \"false\");\n    SnowflakeGCSClient sfGcsClient = new SnowflakeGCSClient();\n    sfGcsClient.setupGCSClient(stage, encMat, session);\n\n    return sfGcsClient;\n  }\n\n  // Returns the Max number of retry attempts\n  @Override\n  public int getMaxRetries() {\n    if (session != null\n        && session\n            .getConnectionPropertiesMap()\n            .containsKey(SFSessionProperty.PUT_GET_MAX_RETRIES)) {\n      return (int) session.getConnectionPropertiesMap().get(SFSessionProperty.PUT_GET_MAX_RETRIES);\n    }\n    return 25;\n  }\n\n  // Returns the max exponent for multiplying backoff with the power of 2, the value\n  // of 4 will give us 16secs as the max number of time to sleep before retry\n  @Override\n  public int getRetryBackoffMaxExponent() {\n    return 4;\n  }\n\n  // Returns the min number of milliseconds to sleep before retry\n  @Override\n  public int getRetryBackoffMin() {\n    return 1000;\n  }\n\n  /**\n   * @return Returns true if encryption is enabled\n   */\n  @Override\n  public boolean isEncrypting() {\n    return encryptionKeySize > 0 && this.stageInfo.getIsClientSideEncrypted();\n  }\n\n  /**\n   * @return Returns the size of the encryption key\n   */\n  @Override\n  public int getEncryptionKeySize() {\n    return encryptionKeySize;\n  }\n\n  /**\n   * @return Whether this client requires the use of presigned URLs for upload and download instead\n   *     of credentials that work for all files uploaded/ downloaded to a stage path. True for GCS.\n   */\n  @Override\n  public boolean requirePresignedUrl() {\n    Map<?, ?> credentialsMap = stageInfo.getCredentials();\n    return !(credentialsMap != null && credentialsMap.containsKey(\"GCS_ACCESS_TOKEN\"));\n  }\n\n  @Override\n  public void renew(Map<?, ?> stageCredentials) throws SnowflakeSQLException {\n    logger.debug(\"Renewing the Snowflake GCS client\");\n    stageInfo.setCredentials(stageCredentials);\n    setupGCSClient(stageInfo, encMat, session);\n  }\n\n  @Override\n  public void shutdown() {\n    if (this.gcsAccessStrategy != null) {\n      this.gcsAccessStrategy.shutdown();\n    }\n  }\n\n  /**\n   * listObjects gets all the objects in a path\n   *\n   * @param remoteStorageLocation bucket name\n   * @param prefix Path\n   * @return a collection of storage summary objects\n   * @throws StorageProviderException cloud storage provider error\n   */\n  @Override\n  public StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    return this.gcsAccessStrategy.listObjects(remoteStorageLocation, prefix);\n  }\n\n  @Override\n  public StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    return this.gcsAccessStrategy.getObjectMetadata(remoteStorageLocation, prefix);\n  }\n\n  /**\n   * Download a file from remote storage.\n   *\n   * @param session session object\n   * @param command command to download file\n   * @param localLocation local file path\n   * @param destFileName destination file name\n   * @param parallelism [ not used by the GCP implementation ]\n   * @param remoteStorageLocation remote storage location, i.e. bucket for S3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Credential to use for download\n   * @param queryId last query id\n   * @throws SnowflakeSQLException download failure\n   */\n  @Override\n  public void download(\n      SFSession session,\n      String command,\n      String localLocation,\n      String destFileName,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    String localFilePath = localLocation + localFileSep + destFileName;\n    logger.debug(\n        \"Starting download of file from GCS stage path: {} to {}\", stageFilePath, localFilePath);\n    int retryCount = 0;\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    File localFile = new File(localFilePath);\n    do {\n      try {\n        String key = null;\n        String iv = null;\n        long downloadMillis = 0;\n        if (!isNullOrEmpty(presignedUrl)) {\n          logger.debug(\"Starting download with presigned URL\", false);\n          URIBuilder uriBuilder = new URIBuilder(presignedUrl);\n\n          HttpGet httpRequest = new HttpGet(uriBuilder.build());\n          httpRequest.addHeader(\"accept-encoding\", \"GZIP\");\n\n          logger.debug(\"Fetching result: {}\", scrubPresignedUrl(presignedUrl));\n\n          CloseableHttpClient httpClient =\n              HttpUtil.getHttpClientWithoutDecompression(\n                  session.getHttpClientKey(), session.getHttpHeadersCustomizers());\n\n          // Get the file on storage using the presigned url\n          HttpResponseContextDto responseDto =\n              RestRequest.executeWithRetries(\n                  httpClient,\n                  httpRequest,\n                  session.getNetworkTimeoutInMilli() / 1000, // retry timeout\n                  0,\n                  session.getHttpClientSocketTimeout(),\n                  getMaxRetries(),\n                  0, // no socket timeout injection\n                  null, // no canceling\n                  false, // no cookie\n                  false, // no retry\n                  false, // no request_guid\n                  true, // retry on HTTP 403\n                  false,\n                  new ExecTimeTelemetryData(),\n                  session,\n                  session.getHttpClientKey(),\n                  session.getHttpHeadersCustomizers(),\n                  true);\n          HttpResponse response = responseDto.getHttpResponse();\n\n          logger.debug(\n              \"Call returned for URL: {}\",\n              (ArgSupplier) () -> scrubPresignedUrl(this.stageInfo.getPresignedUrl()));\n          if (isSuccessStatusCode(response.getStatusLine().getStatusCode())) {\n            try {\n              InputStream bodyStream = response.getEntity().getContent();\n              byte[] buffer = new byte[8 * 1024];\n              int bytesRead;\n              OutputStream outStream = new FileOutputStream(localFile);\n              while ((bytesRead = bodyStream.read(buffer)) != -1) {\n                outStream.write(buffer, 0, bytesRead);\n              }\n              outStream.flush();\n              outStream.close();\n              bodyStream.close();\n              SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(\n                  localFile, session.isOwnerOnlyStageFilePermissionsEnabled());\n              if (isEncrypting()) {\n                Map<String, String> userDefinedHeaders =\n                    createCaseInsensitiveMap(response.getAllHeaders());\n                AbstractMap.SimpleEntry<String, String> encryptionData =\n                    parseEncryptionData(\n                        userDefinedHeaders.get(GCS_METADATA_PREFIX + GCS_ENCRYPTIONDATAPROP),\n                        queryId);\n                key = encryptionData.getKey();\n                iv = encryptionData.getValue();\n              }\n              stopwatch.stop();\n              downloadMillis = stopwatch.elapsedMillis();\n              logger.debug(\"Download successful\", false);\n            } catch (IOException ex) {\n              logger.debug(\"Download unsuccessful {}\", ex);\n              handleStorageException(\n                  ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, queryId);\n            }\n          } else {\n            Exception ex =\n                new HttpResponseException(\n                    response.getStatusLine().getStatusCode(),\n                    EntityUtils.toString(response.getEntity()));\n            handleStorageException(\n                ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, queryId);\n          }\n        } else {\n          Map<String, String> userDefinedMetadata =\n              this.gcsAccessStrategy.download(\n                  parallelism, remoteStorageLocation, stageFilePath, localFile);\n          SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(\n              localFile, session.isOwnerOnlyStageFilePermissionsEnabled());\n          stopwatch.stop();\n          downloadMillis = stopwatch.elapsedMillis();\n          logger.debug(\"Download successful\", false);\n\n          if (isEncrypting()) {\n            if (!userDefinedMetadata.isEmpty()) {\n              AbstractMap.SimpleEntry<String, String> encryptionData =\n                  parseEncryptionData(userDefinedMetadata.get(GCS_ENCRYPTIONDATAPROP), queryId);\n\n              key = encryptionData.getKey();\n              iv = encryptionData.getValue();\n            }\n          }\n        }\n\n        if (!isNullOrEmpty(iv)\n            && !isNullOrEmpty(key)\n            && this.isEncrypting()\n            && this.getEncryptionKeySize() <= 256) {\n          if (key == null || iv == null) {\n            throw new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"File metadata incomplete\");\n          }\n\n          // Decrypt file\n          try {\n            stopwatch.start();\n            EncryptionProvider.decrypt(localFile, key, iv, this.encMat);\n            stopwatch.stop();\n            long decryptMillis = stopwatch.elapsedMillis();\n            logger.info(\n                \"GCS file {} downloaded to {}. It took {} ms (download: {} ms, decryption: {} ms) with {} retries\",\n                stageFilePath,\n                localFile.getAbsolutePath(),\n                downloadMillis + decryptMillis,\n                downloadMillis,\n                decryptMillis,\n                retryCount);\n          } catch (Exception ex) {\n            logger.error(\"Error decrypting file\", ex);\n            throw new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"Cannot decrypt file\");\n          }\n        } else {\n          logger.info(\n              \"GCS file {} downloaded to {}. It took {} ms with {} retries\",\n              stageFilePath,\n              localFile.getAbsolutePath(),\n              downloadMillis,\n              retryCount);\n        }\n        return;\n      } catch (Exception ex) {\n        logger.debug(\"Download unsuccessful {}\", ex);\n        handleStorageException(ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, queryId);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: download unsuccessful without exception!\");\n  }\n\n  /**\n   * Download a file from remote storage\n   *\n   * @param session session object\n   * @param command command to download file\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for s3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Signed credential for download\n   * @param queryId last query id\n   * @return input file stream\n   * @throws SnowflakeSQLException when download failure\n   */\n  @Override\n  public InputStream downloadToStream(\n      SFSession session,\n      String command,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"Starting download of file from GCS stage path: {} to input stream\", stageFilePath);\n    int retryCount = 0;\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    InputStream inputStream = null;\n    long downloadMillis = 0;\n    do {\n      try {\n        String key = null;\n        String iv = null;\n\n        if (!isNullOrEmpty(presignedUrl)) {\n          logger.debug(\"Starting download with presigned URL\", false);\n          URIBuilder uriBuilder = new URIBuilder(presignedUrl);\n\n          HttpGet httpRequest = new HttpGet(uriBuilder.build());\n          httpRequest.addHeader(\"accept-encoding\", \"GZIP\");\n\n          logger.debug(\"Fetching result: {}\", scrubPresignedUrl(presignedUrl));\n\n          CloseableHttpClient httpClient =\n              HttpUtil.getHttpClientWithoutDecompression(\n                  session.getHttpClientKey(), session.getHttpHeadersCustomizers());\n\n          // Put the file on storage using the presigned url\n          HttpResponse response =\n              RestRequest.executeWithRetries(\n                      httpClient,\n                      httpRequest,\n                      session.getNetworkTimeoutInMilli() / 1000, // retry timeout\n                      0,\n                      session.getHttpClientSocketTimeout(),\n                      getMaxRetries(),\n                      0, // no socket timeout injection\n                      null, // no canceling\n                      false, // no cookie\n                      false, // no retry\n                      false, // no request_guid\n                      true, // retry on HTTP 403\n                      false,\n                      new ExecTimeTelemetryData(),\n                      session,\n                      session.getHttpClientKey(),\n                      session.getHttpHeadersCustomizers(),\n                      true)\n                  .getHttpResponse();\n\n          logger.debug(\n              \"Call returned for URL: {}\",\n              (ArgSupplier) () -> scrubPresignedUrl(this.stageInfo.getPresignedUrl()));\n          if (isSuccessStatusCode(response.getStatusLine().getStatusCode())) {\n            try {\n              inputStream = response.getEntity().getContent();\n\n              if (isEncrypting()) {\n                Map<String, String> userDefinedHeaders =\n                    createCaseInsensitiveMap(response.getAllHeaders());\n                AbstractMap.SimpleEntry<String, String> encryptionData =\n                    parseEncryptionData(\n                        userDefinedHeaders.get(GCS_METADATA_PREFIX + GCS_ENCRYPTIONDATAPROP),\n                        queryId);\n                key = encryptionData.getKey();\n                iv = encryptionData.getValue();\n              }\n              stopwatch.stop();\n              downloadMillis = stopwatch.elapsedMillis();\n              logger.debug(\"Download successful\", false);\n            } catch (IOException ex) {\n              logger.debug(\"Download unsuccessful {}\", ex);\n              handleStorageException(\n                  ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, queryId);\n            }\n          } else {\n            Exception ex =\n                new HttpResponseException(\n                    response.getStatusLine().getStatusCode(),\n                    EntityUtils.toString(response.getEntity()));\n            handleStorageException(\n                ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, queryId);\n          }\n        } else {\n          SFPair<InputStream, Map<String, String>> pair =\n              this.gcsAccessStrategy.downloadToStream(\n                  remoteStorageLocation, stageFilePath, isEncrypting());\n          inputStream = pair.left;\n          if (isEncrypting()) {\n            // Get the user-defined BLOB metadata\n            Map<String, String> userDefinedMetadata = pair.right;\n            AbstractMap.SimpleEntry<String, String> encryptionData =\n                parseEncryptionData(userDefinedMetadata.get(GCS_ENCRYPTIONDATAPROP), queryId);\n\n            key = encryptionData.getKey();\n            iv = encryptionData.getValue();\n          }\n          stopwatch.stop();\n          downloadMillis = stopwatch.elapsedMillis();\n        }\n\n        if (this.isEncrypting() && this.getEncryptionKeySize() <= 256) {\n          stopwatch.restart();\n          if (key == null || iv == null) {\n            throw new SnowflakeSQLException(\n                queryId,\n                SqlState.INTERNAL_ERROR,\n                ErrorCode.INTERNAL_ERROR.getMessageCode(),\n                \"File metadata incomplete\");\n          }\n\n          // Decrypt file\n          try {\n            if (inputStream != null) {\n\n              inputStream = EncryptionProvider.decryptStream(inputStream, key, iv, this.encMat);\n              stopwatch.stop();\n              long decryptMillis = stopwatch.elapsedMillis();\n              logger.info(\n                  \"GCS file {} downloaded to stream. It took {} ms (download: {} ms, decryption: {} ms) with {} retries\",\n                  stageFilePath,\n                  downloadMillis + decryptMillis,\n                  downloadMillis,\n                  decryptMillis,\n                  retryCount);\n              return inputStream;\n            }\n          } catch (Exception ex) {\n            logger.error(\"Error decrypting file\", ex);\n            throw new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"Cannot decrypt file\");\n          }\n        } else {\n          logger.info(\n              \"GCS file {} downloaded to stream. Download took {} ms with {} retries\",\n              stageFilePath,\n              downloadMillis,\n              retryCount);\n        }\n\n        return inputStream;\n      } catch (Exception ex) {\n        logger.debug(\"Download unsuccessful {}\", ex);\n        handleStorageException(ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, queryId);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: download unsuccessful without exception!\");\n  }\n\n  /**\n   * Upload a file (-stream) to remote storage with Pre-signed URL without JDBC session.\n   *\n   * @param networkTimeoutInMilli Network timeout for the upload\n   * @param ocspModeAndProxyKey OCSP mode and proxy settings for the upload.\n   * @param parallelism number of threads do parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation s3 bucket name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for upload. Used by GCP.\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if upload failed\n   */\n  @Override\n  public void uploadWithPresignedUrlWithoutConnection(\n      int networkTimeoutInMilli,\n      HttpClientSettingsKey ocspModeAndProxyKey,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.info(\n        StorageHelper.getStartUploadLog(\n            \"GCS\", uploadFromStream, inputStream, fileBackedOutputStream, srcFile, destFileName));\n    final List<FileInputStream> toClose = new ArrayList<>();\n    long originalContentLength = meta.getContentLength();\n\n    SFPair<InputStream, Boolean> uploadStreamInfo =\n        createUploadStream(\n            srcFile,\n            uploadFromStream,\n            inputStream,\n            meta,\n            originalContentLength,\n            fileBackedOutputStream,\n            toClose,\n            queryId);\n\n    if (!(meta instanceof CommonObjectMetadata)) {\n      throw new IllegalArgumentException(\"Unexpected metadata object type\");\n    }\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    if (isNullOrEmpty(presignedUrl) || \"null\".equalsIgnoreCase(presignedUrl)) {\n      logger.debug(\"Starting upload with downscoped token\");\n      uploadWithDownScopedToken(\n          parallelism,\n          remoteStorageLocation,\n          destFileName,\n          meta.getContentEncoding(),\n          meta.getUserMetadata(),\n          meta.getContentLength(),\n          uploadStreamInfo.left,\n          queryId);\n      logger.debug(\"Upload successful with downscoped token\");\n    } else {\n      logger.debug(\"Starting upload with presigned url\");\n\n      uploadWithPresignedUrl(\n          networkTimeoutInMilli,\n          (int) HttpUtil.getSocketTimeout().toMillis(),\n          meta.getContentEncoding(),\n          meta.getUserMetadata(),\n          uploadStreamInfo.left,\n          presignedUrl,\n          ocspModeAndProxyKey,\n          queryId);\n      logger.debug(\"Upload successfully with presigned url\");\n    }\n    stopwatch.stop();\n\n    if (uploadFromStream) {\n      logger.info(\n          \"Uploaded data from input stream to GCS location: {}. It took {} ms\",\n          remoteStorageLocation,\n          stopwatch.elapsedMillis());\n    } else {\n      logger.info(\n          \"Uploaded file {} to GCS location: {}. It took {} ms\",\n          srcFile.getAbsolutePath(),\n          remoteStorageLocation,\n          stopwatch.elapsedMillis());\n    }\n\n    // close any open streams in the \"toClose\" list and return\n    for (FileInputStream is : toClose) {\n      IOUtils.closeQuietly(is);\n    }\n  }\n\n  /**\n   * Upload a file/stream to remote storage\n   *\n   * @param session session object\n   * @param command upload command\n   * @param parallelism [ not used by the GCP implementation ]\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation storage container name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Credential used for upload of a file\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  @Override\n  public void upload(\n      SFSession session,\n      String command,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.info(\n        StorageHelper.getStartUploadLog(\n            \"GCS\", uploadFromStream, inputStream, fileBackedOutputStream, srcFile, destFileName));\n    final List<FileInputStream> toClose = new ArrayList<>();\n    long originalContentLength = meta.getContentLength();\n\n    SFPair<InputStream, Boolean> uploadStreamInfo =\n        createUploadStream(\n            srcFile,\n            uploadFromStream,\n            inputStream,\n            meta,\n            originalContentLength,\n            fileBackedOutputStream,\n            toClose,\n            queryId);\n\n    if (!(meta instanceof CommonObjectMetadata)) {\n      throw new IllegalArgumentException(\"Unexpected metadata object type\");\n    }\n\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    if (!isNullOrEmpty(presignedUrl)) {\n      logger.debug(\"Starting upload with downscope token\", false);\n      uploadWithPresignedUrl(\n          session.getNetworkTimeoutInMilli(),\n          session.getHttpClientSocketTimeout(),\n          meta.getContentEncoding(),\n          meta.getUserMetadata(),\n          uploadStreamInfo.left,\n          presignedUrl,\n          session.getHttpClientKey(),\n          queryId);\n      stopwatch.stop();\n      logger.debug(\"Upload successful\", false);\n      if (uploadFromStream) {\n        logger.info(\n            \"Uploaded data from input stream to GCS location: {}. It took {} ms\",\n            remoteStorageLocation,\n            stopwatch.elapsedMillis());\n      } else {\n        logger.info(\n            \"Uploaded file {} to GCS location: {}. It took {} ms\",\n            srcFile.getAbsolutePath(),\n            remoteStorageLocation,\n            stopwatch.elapsedMillis());\n      }\n\n      // close any open streams in the \"toClose\" list and return\n      for (FileInputStream is : toClose) {\n        IOUtils.closeQuietly(is);\n      }\n\n      return;\n    }\n\n    // No presigned URL. This codepath is for when we have a token instead.\n    int retryCount = 0;\n    do {\n      try {\n        logger.debug(\"Starting upload\", false);\n\n        uploadWithDownScopedToken(\n            parallelism,\n            remoteStorageLocation,\n            destFileName,\n            meta.getContentEncoding(),\n            meta.getUserMetadata(),\n            meta.getContentLength(),\n            uploadStreamInfo.left,\n            queryId);\n\n        stopwatch.stop();\n        logger.debug(\"Upload successful\", false);\n        if (uploadFromStream) {\n          logger.info(\n              \"Uploaded data from input stream to GCS location: {}. It took {} ms\",\n              remoteStorageLocation,\n              stopwatch.elapsedMillis());\n        } else {\n          logger.info(\n              \"Uploaded file {} to GCS location: {}. It took {} ms\",\n              srcFile.getAbsolutePath(),\n              remoteStorageLocation,\n              stopwatch.elapsedMillis());\n        }\n\n        // close any open streams in the \"toClose\" list and return\n        for (FileInputStream is : toClose) {\n          IOUtils.closeQuietly(is);\n        }\n\n        return;\n      } catch (Exception ex) {\n        handleStorageException(ex, ++retryCount, StorageHelper.UPLOAD, session, command, queryId);\n\n        if (uploadFromStream && fileBackedOutputStream == null) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              SqlState.SYSTEM_ERROR,\n              StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n              ex,\n              \"Encountered exception during upload: \"\n                  + ex.getMessage()\n                  + \"\\nCannot retry upload from stream.\");\n        }\n        uploadStreamInfo =\n            createUploadStream(\n                srcFile,\n                uploadFromStream,\n                inputStream,\n                meta,\n                originalContentLength,\n                fileBackedOutputStream,\n                toClose,\n                queryId);\n      }\n\n    } while (retryCount <= getMaxRetries());\n\n    for (FileInputStream is : toClose) {\n      IOUtils.closeQuietly(is);\n    }\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: upload unsuccessful without exception!\");\n  }\n\n  /**\n   * Upload file with down scoped token.\n   *\n   * @param remoteStorageLocation storage container name\n   * @param destFileName file name on remote storage after upload\n   * @param contentEncoding Object's content encoding. We do special things for \"gzip\"\n   * @param metadata Custom metadata to be uploaded with the object\n   * @param content File content\n   */\n  private void uploadWithDownScopedToken(\n      int parallelism,\n      String remoteStorageLocation,\n      String destFileName,\n      String contentEncoding,\n      Map<String, String> metadata,\n      long contentLength,\n      InputStream content,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\"Uploading file {} to bucket {}\", destFileName, remoteStorageLocation);\n    try {\n      this.gcsAccessStrategy.uploadWithDownScopedToken(\n          parallelism,\n          remoteStorageLocation,\n          destFileName,\n          contentEncoding,\n          metadata,\n          contentLength,\n          content,\n          queryId);\n    } catch (Exception e) {\n      handleStorageException(e, 0, StorageHelper.UPLOAD, session, queryId);\n      SnowflakeSQLException wrappedException;\n      if (e instanceof SnowflakeSQLException) {\n        wrappedException = (SnowflakeSQLException) e;\n\n      } else {\n        wrappedException =\n            new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                SqlState.SYSTEM_ERROR,\n                StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n                e,\n                \"Encountered exception during \" + StorageHelper.UPLOAD + \": \" + e.getMessage());\n      }\n      throw wrappedException;\n    }\n  }\n\n  /**\n   * Performs upload using a presigned URL\n   *\n   * @param networkTimeoutInMilli Network timeout\n   * @param contentEncoding Object's content encoding. We do special things for \"gzip\"\n   * @param metadata Custom metadata to be uploaded with the object\n   * @param content File content\n   * @param presignedUrl Credential to upload the object\n   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient\n   * @throws SnowflakeSQLException\n   */\n  private void uploadWithPresignedUrl(\n      int networkTimeoutInMilli,\n      int httpClientSocketTimeout,\n      String contentEncoding,\n      Map<String, String> metadata,\n      InputStream content,\n      String presignedUrl,\n      HttpClientSettingsKey ocspAndProxyKey,\n      String queryId)\n      throws SnowflakeSQLException {\n    try {\n      URIBuilder uriBuilder = new URIBuilder(presignedUrl);\n\n      HttpPut httpRequest = new HttpPut(uriBuilder.build());\n\n      logger.debug(\"Fetching result: {}\", scrubPresignedUrl(presignedUrl));\n\n      // We set the contentEncoding to blank for GZIP files. We don't want GCS to think\n      // our gzip files are gzips because it makes them download uncompressed, and\n      // none of the other providers do that. There's essentially no way for us\n      // to prevent that behavior. Bad Google.\n      if (\"gzip\".equals(contentEncoding)) {\n        contentEncoding = \"\";\n      }\n      httpRequest.addHeader(\"content-encoding\", contentEncoding);\n\n      for (Entry<String, String> entry : metadata.entrySet()) {\n        httpRequest.addHeader(GCS_METADATA_PREFIX + entry.getKey(), entry.getValue());\n      }\n\n      InputStreamEntity contentEntity = new InputStreamEntity(content, -1);\n      httpRequest.setEntity(contentEntity);\n\n      CloseableHttpClient httpClient =\n          HttpUtil.getHttpClient(ocspAndProxyKey, session.getHttpHeadersCustomizers());\n\n      // Put the file on storage using the presigned url\n      HttpResponse response =\n          RestRequest.executeWithRetries(\n                  httpClient,\n                  httpRequest,\n                  networkTimeoutInMilli / 1000, // retry timeout\n                  0,\n                  httpClientSocketTimeout, // socket timeout in ms\n                  getMaxRetries(),\n                  0, // no socket timeout injection\n                  null, // no canceling\n                  false, // no cookie\n                  false, // no url retry query parameters\n                  false, // no request_guid\n                  true, // retry on HTTP 403\n                  true, // disable retry\n                  new ExecTimeTelemetryData(),\n                  session,\n                  ocspAndProxyKey,\n                  session.getHttpHeadersCustomizers(),\n                  false)\n              .getHttpResponse();\n\n      logger.debug(\n          \"Call returned for URL: {}\",\n          (ArgSupplier) () -> scrubPresignedUrl(this.stageInfo.getPresignedUrl()));\n\n      if (!isSuccessStatusCode(response.getStatusLine().getStatusCode())) {\n        Exception ex =\n            new HttpResponseException(\n                response.getStatusLine().getStatusCode(),\n                EntityUtils.toString(response.getEntity()));\n        handleStorageException(ex, 0, StorageHelper.UPLOAD, session, null, queryId);\n      }\n    } catch (URISyntaxException e) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Unexpected: upload presigned URL invalid\");\n    } catch (Exception e) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          \"Unexpected: upload with presigned url failed\");\n    }\n  }\n\n  /**\n   * When we log the URL, make sure we don't log the credential\n   *\n   * @param presignedUrl Presigned URL with full signature\n   * @return Just the object path\n   */\n  private String scrubPresignedUrl(String presignedUrl) {\n    if (isNullOrEmpty(presignedUrl)) {\n      return \"\";\n    }\n    int indexOfQueryString = presignedUrl.lastIndexOf(\"?\");\n    indexOfQueryString = indexOfQueryString > 0 ? indexOfQueryString : presignedUrl.length() - 1;\n    return presignedUrl.substring(0, indexOfQueryString);\n  }\n\n  private SFPair<InputStream, Boolean> createUploadStream(\n      File srcFile,\n      boolean uploadFromStream,\n      InputStream inputStream,\n      StorageObjectMetadata meta,\n      long originalContentLength,\n      FileBackedOutputStream fileBackedOutputStream,\n      List<FileInputStream> toClose,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"createUploadStream({}, {}, {}, {}, {}, {})\",\n        this,\n        srcFile,\n        uploadFromStream,\n        inputStream,\n        fileBackedOutputStream,\n        toClose);\n\n    final InputStream stream;\n    FileInputStream srcFileStream = null;\n    try {\n      if (isEncrypting() && getEncryptionKeySize() <= 256) {\n        try {\n          final InputStream uploadStream =\n              uploadFromStream\n                  ? (fileBackedOutputStream != null\n                      ? fileBackedOutputStream.asByteSource().openStream()\n                      : inputStream)\n                  : (srcFileStream = new FileInputStream(srcFile));\n          toClose.add(srcFileStream);\n\n          // Encrypt\n          stream =\n              EncryptionProvider.encrypt(\n                  meta, originalContentLength, uploadStream, this.encMat, this);\n          uploadFromStream = true;\n        } catch (Exception ex) {\n          logger.error(\"Failed to encrypt input\", ex);\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              SqlState.INTERNAL_ERROR,\n              StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n              ex,\n              \"Failed to encrypt input\",\n              ex.getMessage());\n        }\n      } else {\n        if (uploadFromStream) {\n          if (fileBackedOutputStream != null) {\n            stream = fileBackedOutputStream.asByteSource().openStream();\n          } else {\n            stream = inputStream;\n          }\n        } else {\n          srcFileStream = new FileInputStream(srcFile);\n          toClose.add(srcFileStream);\n          stream = srcFileStream;\n        }\n      }\n    } catch (FileNotFoundException ex) {\n      logger.error(\"Failed to open input file\", ex);\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n          ex,\n          \"Failed to open input file\",\n          ex.getMessage());\n    } catch (IOException ex) {\n      logger.error(\"Failed to open input stream\", ex);\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.INTERNAL_ERROR,\n          StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n          ex,\n          \"Failed to open input stream\",\n          ex.getMessage());\n    }\n\n    return SFPair.of(stream, uploadFromStream);\n  }\n\n  @Override\n  public void handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      String queryId)\n      throws SnowflakeSQLException {\n    // no need to retry if it is invalid key exception\n    if (ex.getCause() instanceof InvalidKeyException) {\n      // Most likely cause is that the unlimited strength policy files are not installed\n      // Log the error and throw a message that explains the cause\n      SnowflakeFileTransferAgent.throwJCEMissingError(operation, ex, queryId);\n    }\n\n    // If there is no space left in the download location, java.io.IOException is thrown.\n    // Don't retry.\n    if (getRootCause(ex) instanceof IOException) {\n      SnowflakeFileTransferAgent.throwNoSpaceLeftError(session, operation, ex, queryId);\n    }\n\n    if (this.gcsAccessStrategy.handleStorageException(\n        ex, retryCount, operation, session, command, queryId, this)) {\n      // exception is handled in gcsAccessStrategy.handleStorageException\n    } else if (ex instanceof InterruptedException\n        || getRootCause(ex) instanceof SocketTimeoutException) {\n      if (retryCount > getMaxRetries()) {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.SYSTEM_ERROR,\n            StorageHelper.getOperationException(operation).getMessageCode(),\n            ex,\n            \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n      } else {\n        logger.debug(\n            \"Encountered exception ({}) during {}, retry count: {}\",\n            ex.getMessage(),\n            operation,\n            retryCount);\n      }\n    } else {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          session,\n          SqlState.SYSTEM_ERROR,\n          StorageHelper.getOperationException(operation).getMessageCode(),\n          ex,\n          \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n    }\n  }\n\n  /** Returns the material descriptor key */\n  @Override\n  public String getMatdescKey() {\n    return \"matdesc\";\n  }\n\n  /** Adds encryption metadata to the StorageObjectMetadata object */\n  @Override\n  public void addEncryptionMetadata(\n      StorageObjectMetadata meta,\n      MatDesc matDesc,\n      byte[] ivData,\n      byte[] encryptedKey,\n      long contentLength) {\n    meta.addUserMetadata(getMatdescKey(), matDesc.toString());\n    meta.addUserMetadata(\n        GCS_ENCRYPTIONDATAPROP,\n        buildEncryptionMetadataJSON(\n            Base64.getEncoder().encodeToString(ivData),\n            Base64.getEncoder().encodeToString(encryptedKey)));\n    meta.setContentLength(contentLength);\n  }\n\n  /*\n   * buildEncryptionMetadataJSON\n   * Takes the base64-encoded iv and key and creates the JSON block to be\n   * used as the encryptiondata metadata field on the blob.\n   */\n  private String buildEncryptionMetadataJSON(String iv64, String key64) {\n    return String.format(\n        \"{\\\"EncryptionMode\\\":\\\"FullBlob\\\",\\\"WrappedContentKey\\\"\"\n            + \":{\\\"KeyId\\\":\\\"symmKey1\\\",\\\"EncryptedKey\\\":\\\"%s\\\"\"\n            + \",\\\"Algorithm\\\":\\\"AES_CBC_256\\\"},\\\"EncryptionAgent\\\":\"\n            + \"{\\\"Protocol\\\":\\\"1.0\\\",\\\"EncryptionAlgorithm\\\":\"\n            + \"\\\"AES_CBC_256\\\"},\\\"ContentEncryptionIV\\\":\\\"%s\\\"\"\n            + \",\\\"KeyWrappingMetadata\\\":{\\\"EncryptionLibrary\\\":\"\n            + \"\\\"Java 5.3.0\\\"}}\",\n        key64, iv64);\n  }\n\n  /*\n   * parseEncryptionData\n   * Takes the json string in the encryptiondata metadata field of the encrypted\n   * blob and parses out the key and iv. Returns the pair as key = key, iv = value.\n   */\n  private AbstractMap.SimpleEntry<String, String> parseEncryptionData(\n      String jsonEncryptionData, String queryId) throws SnowflakeSQLException {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    JsonFactory factory = mapper.getFactory();\n    try {\n      JsonParser parser = factory.createParser(jsonEncryptionData);\n      JsonNode encryptionDataNode = mapper.readTree(parser);\n\n      String iv = encryptionDataNode.get(\"ContentEncryptionIV\").asText();\n      String key = encryptionDataNode.get(\"WrappedContentKey\").get(\"EncryptedKey\").asText();\n\n      return new AbstractMap.SimpleEntry<>(key, iv);\n    } catch (Exception ex) {\n      throw new SnowflakeSQLException(\n          queryId,\n          ex,\n          SqlState.SYSTEM_ERROR,\n          ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(),\n          \"Error parsing encryption data as json\" + \": \" + ex.getMessage());\n    }\n  }\n\n  /** Adds digest metadata to the StorageObjectMetadata object */\n  @Override\n  public void addDigestMetadata(StorageObjectMetadata meta, String digest) {\n    if (!isBlank(digest)) {\n      meta.addUserMetadata(\"sfc-digest\", digest);\n    }\n  }\n\n  /** Gets digest metadata to the StorageObjectMetadata object */\n  @Override\n  public String getDigestMetadata(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(\"sfc-digest\");\n  }\n\n  /*\n   * Initializes the GCS client\n   * This method is used during the object construction, but also to\n   * reset/recreate the encapsulated CloudBlobClient object with new\n   * credentials (after token expiration)\n   * @param stage   The stage information that the client will operate on\n   * @param encMat  The encryption material\n   *                required to decrypt/encrypt content in stage\n   * @throws IllegalArgumentException when invalid credentials are used\n   */\n  private void setupGCSClient(\n      StageInfo stage, RemoteStoreFileEncryptionMaterial encMat, SFSession session)\n      throws IllegalArgumentException, SnowflakeSQLException {\n    // Save the client creation parameters so that we can reuse them,\n    // to reset the GCS client.\n    this.stageInfo = stage;\n    this.encMat = encMat;\n    this.session = session;\n\n    logger.debug(\"Setting up the GCS client \", false);\n\n    try {\n      boolean overrideAwsAccessStrategy =\n          Boolean.valueOf(systemGetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\"));\n      if (stage.getUseVirtualUrl() || overrideAwsAccessStrategy) {\n        this.gcsAccessStrategy = new GCSAccessStrategyAwsSdk(stage, session);\n      } else {\n        this.gcsAccessStrategy = new GCSDefaultAccessStrategy(stage, session);\n      }\n\n      if (encMat != null) {\n        byte[] decodedKey = Base64.getDecoder().decode(encMat.getQueryStageMasterKey());\n        encryptionKeySize = decodedKey.length * 8;\n\n        if (encryptionKeySize != 128 && encryptionKeySize != 192 && encryptionKeySize != 256) {\n          throw new SnowflakeSQLException(\n              QueryIdHelper.queryIdFromEncMatOr(encMat, null),\n              SqlState.INTERNAL_ERROR,\n              ErrorCode.INTERNAL_ERROR.getMessageCode(),\n              \"unsupported key size\",\n              encryptionKeySize);\n        }\n      }\n    } catch (Exception ex) {\n      throw new IllegalArgumentException(\"invalid_gcs_credentials\");\n    }\n  }\n\n  protected static boolean areDisabledGcsDefaultCredentials(SFSession session) {\n    return session != null && session.getDisableGcsDefaultCredentials()\n        || convertSystemPropertyToBooleanValue(DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME, true);\n  }\n\n  private static boolean isSuccessStatusCode(int code) {\n    return code < 300 && code >= 200;\n  }\n\n  /**\n   * Adds streaming ingest metadata to the StorageObjectMetadata object, used for streaming ingest\n   * per client billing calculation\n   */\n  @Override\n  public void addStreamingIngestMetadata(\n      StorageObjectMetadata meta, String clientName, String clientKey) {\n    meta.addUserMetadata(GCS_STREAMING_INGEST_CLIENT_NAME, clientName);\n    meta.addUserMetadata(GCS_STREAMING_INGEST_CLIENT_KEY, clientKey);\n  }\n\n  @Override\n  public String getStreamingIngestClientName(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(GCS_STREAMING_INGEST_CLIENT_NAME);\n  }\n\n  @Override\n  public String getStreamingIngestClientKey(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(GCS_STREAMING_INGEST_CLIENT_KEY);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeS3Client.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.createDefaultExecutorService;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.getRootCause;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.cloud.storage.S3ErrorHandler.retryRequestWithExponentialBackoff;\nimport static net.snowflake.client.internal.jdbc.cloud.storage.S3ErrorHandler.throwIfClientExceptionOrMaxRetryReached;\n\nimport java.io.BufferedInputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.security.InvalidKeyException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.CompletionException;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.HeaderCustomizerHttpRequestInterceptor;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.FileBackedOutputStream;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SFPair;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.IOUtils;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.AwsCredentials;\nimport software.amazon.awssdk.auth.credentials.AwsSessionCredentials;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.core.ResponseInputStream;\nimport software.amazon.awssdk.core.async.AsyncRequestBody;\nimport software.amazon.awssdk.core.async.AsyncResponseTransformer;\nimport software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;\nimport software.amazon.awssdk.core.exception.SdkException;\nimport software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;\nimport software.amazon.awssdk.http.nio.netty.ProxyConfiguration;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.s3.S3AsyncClient;\nimport software.amazon.awssdk.services.s3.S3AsyncClientBuilder;\nimport software.amazon.awssdk.services.s3.model.GetObjectRequest;\nimport software.amazon.awssdk.services.s3.model.GetObjectResponse;\nimport software.amazon.awssdk.services.s3.model.HeadObjectRequest;\nimport software.amazon.awssdk.services.s3.model.HeadObjectResponse;\nimport software.amazon.awssdk.services.s3.model.ListObjectsRequest;\nimport software.amazon.awssdk.services.s3.model.ListObjectsResponse;\nimport software.amazon.awssdk.services.s3.model.PutObjectRequest;\nimport software.amazon.awssdk.services.s3.model.ServerSideEncryption;\nimport software.amazon.awssdk.services.s3.multipart.MultipartConfiguration;\nimport software.amazon.awssdk.transfer.s3.S3TransferManager;\nimport software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;\nimport software.amazon.awssdk.transfer.s3.model.FileDownload;\nimport software.amazon.awssdk.transfer.s3.model.Upload;\nimport software.amazon.awssdk.transfer.s3.model.UploadRequest;\n\n/** Wrapper around AmazonS3Client. */\npublic class SnowflakeS3Client implements SnowflakeStorageClient {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SnowflakeS3Client.class);\n  private static final String localFileSep = systemGetProperty(\"file.separator\");\n  private static final String AMZ_KEY = \"x-amz-key\";\n  private static final String AMZ_IV = \"x-amz-iv\";\n  private static final String S3_STREAMING_INGEST_CLIENT_NAME = \"ingestclientname\";\n  private static final String S3_STREAMING_INGEST_CLIENT_KEY = \"ingestclientkey\";\n\n  private static final int EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS = 5;\n\n  // expired AWS token error code\n  protected static final String EXPIRED_AWS_TOKEN_ERROR_CODE = \"ExpiredToken\";\n\n  private int encryptionKeySize = 0; // used for PUTs\n  private S3AsyncClient amazonClient = null;\n  private RemoteStoreFileEncryptionMaterial encMat = null;\n  private ClientConfiguration clientConfig = null;\n  private Properties proxyProperties = null;\n  private String stageRegion = null;\n  private String stageEndPoint = null; // FIPS endpoint, if needed\n  private SFBaseSession session = null;\n  private boolean isClientSideEncrypted = true;\n  private boolean isUseS3RegionalUrl = false;\n\n  public SnowflakeS3Client(\n      Map<?, ?> stageCredentials,\n      ClientConfiguration clientConfig,\n      RemoteStoreFileEncryptionMaterial encMat,\n      Properties proxyProperties,\n      String stageRegion,\n      String stageEndPoint,\n      boolean isClientSideEncrypted,\n      SFBaseSession session,\n      boolean useS3RegionalUrl)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"Initializing Snowflake S3 client with encryption: {}, client side encrypted: {}\",\n        encMat != null,\n        isClientSideEncrypted);\n    this.session = session;\n    this.isUseS3RegionalUrl = useS3RegionalUrl;\n    setupSnowflakeS3Client(\n        stageCredentials,\n        clientConfig,\n        encMat,\n        proxyProperties,\n        stageRegion,\n        stageEndPoint,\n        isClientSideEncrypted,\n        session);\n  }\n\n  private void setupSnowflakeS3Client(\n      Map<?, ?> stageCredentials,\n      ClientConfiguration clientConfig,\n      RemoteStoreFileEncryptionMaterial encMat,\n      Properties proxyProperties,\n      String stageRegion,\n      String stageEndPoint,\n      boolean isClientSideEncrypted,\n      SFBaseSession session)\n      throws SnowflakeSQLException {\n    // Save the client creation parameters so that we can reuse them,\n    // to reset the AWS client. We won't save the awsCredentials since\n    // we will be refreshing that, every time we reset the AWS client\n    this.clientConfig = clientConfig;\n    this.stageRegion = stageRegion;\n    this.encMat = encMat;\n    this.proxyProperties = proxyProperties;\n    this.stageEndPoint = stageEndPoint; // FIPS endpoint, if needed\n    this.session = session;\n    this.isClientSideEncrypted = isClientSideEncrypted;\n\n    logger.debug(\"Setting up AWS client \", false);\n\n    // Retrieve S3 stage credentials\n    String awsID = (String) stageCredentials.get(\"AWS_KEY_ID\");\n    String awsKey = (String) stageCredentials.get(\"AWS_SECRET_KEY\");\n    String awsToken = (String) stageCredentials.get(\"AWS_TOKEN\");\n\n    // initialize aws credentials\n    AwsCredentials awsCredentials =\n        (awsToken != null)\n            ? AwsSessionCredentials.create(awsID, awsKey, awsToken)\n            : AwsBasicCredentials.create(awsID, awsKey);\n\n    ProxyConfiguration proxyConfiguration;\n    if (session != null) {\n      proxyConfiguration =\n          CloudStorageProxyFactory.createProxyConfigurationForS3(session.getHttpClientKey());\n    } else {\n      proxyConfiguration =\n          CloudStorageProxyFactory.createSessionlessProxyConfigurationForS3(proxyProperties);\n    }\n\n    S3AsyncClientBuilder clientBuilder =\n        S3AsyncClient.builder()\n            .credentialsProvider(StaticCredentialsProvider.create(awsCredentials));\n\n    Region region = Region.of(stageRegion);\n    if (this.stageEndPoint != null\n        && !this.stageEndPoint.isEmpty()\n        && !\"null\".equals(this.stageEndPoint)) {\n      String endpointForOverride = this.stageEndPoint;\n      String lower = endpointForOverride.toLowerCase(Locale.ROOT);\n      if (!lower.startsWith(\"https://\") && !lower.startsWith(\"http://\")) {\n        logger.debug(\n            \"AWS S3 Client: stage endpoint {} has no scheme, normalizing for URI creation.\",\n            this.stageEndPoint);\n        endpointForOverride = \"https://\" + endpointForOverride;\n      }\n      clientBuilder.endpointOverride(URI.create(endpointForOverride));\n      clientBuilder.region(region);\n    } else {\n      if (this.isUseS3RegionalUrl) {\n        String domainSuffixForRegionalUrl = getDomainSuffixForRegionalUrl(region.id());\n        String regionalEndpoint = \"https://s3.\" + region.id() + \".\" + domainSuffixForRegionalUrl;\n        clientBuilder.endpointOverride(URI.create(regionalEndpoint));\n        clientBuilder.region(region);\n      } else {\n        clientBuilder.region(region);\n      }\n    }\n    // Explicitly force to use virtual address style\n    clientBuilder.forcePathStyle(false);\n\n    clientBuilder.httpClientBuilder(\n        NettyNioAsyncHttpClient.builder()\n            .maxConcurrency(clientConfig.getMaxConnections())\n            .connectionAcquisitionTimeout(Duration.ofSeconds(60))\n            .proxyConfiguration(proxyConfiguration)\n            .connectionTimeout(Duration.ofMillis(clientConfig.connectionTimeout))\n            .readTimeout(Duration.ofMillis(clientConfig.socketTimeout))\n            .writeTimeout(Duration.ofMillis(clientConfig.socketTimeout)));\n    clientBuilder.multipartEnabled(true);\n    clientBuilder.multipartConfiguration(\n        MultipartConfiguration.builder().thresholdInBytes(16L * 1024 * 1024).build());\n\n    ClientOverrideConfiguration.Builder configurationBuilder =\n        ClientOverrideConfiguration.builder();\n\n    if (session instanceof SFSession) {\n      List<HttpHeadersCustomizer> headersCustomizers =\n          ((SFSession) session).getHttpHeadersCustomizers();\n      if (headersCustomizers != null && !headersCustomizers.isEmpty()) {\n        configurationBuilder.addExecutionInterceptor(\n            new HeaderCustomizerHttpRequestInterceptor(headersCustomizers));\n      }\n    }\n\n    clientBuilder.overrideConfiguration(configurationBuilder.build());\n\n    if (encMat != null) {\n      byte[] decodedKey = Base64.getDecoder().decode(encMat.getQueryStageMasterKey());\n      encryptionKeySize = decodedKey.length * 8;\n\n      if (encryptionKeySize != 128 && encryptionKeySize != 192 && encryptionKeySize != 256) {\n        throw new SnowflakeSQLLoggedException(\n            QueryIdHelper.queryIdFromEncMatOr(encMat, null),\n            session,\n            ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(),\n            SqlState.INTERNAL_ERROR,\n            \"unsupported key size\",\n            encryptionKeySize);\n      }\n    }\n\n    amazonClient = clientBuilder.build();\n  }\n\n  static String getDomainSuffixForRegionalUrl(String regionName) {\n    return regionName.toLowerCase().startsWith(\"cn-\") ? \"amazonaws.com.cn\" : \"amazonaws.com\";\n  }\n\n  // Returns the Max number of retry attempts\n  @Override\n  public int getMaxRetries() {\n    if (session != null\n        && session\n            .getConnectionPropertiesMap()\n            .containsKey(SFSessionProperty.PUT_GET_MAX_RETRIES)) {\n      return (int) session.getConnectionPropertiesMap().get(SFSessionProperty.PUT_GET_MAX_RETRIES);\n    }\n    return 25;\n  }\n\n  // Returns the max exponent for multiplying backoff with the power of 2, the value\n  // of 4 will give us 16secs as the max number of time to sleep before retry\n  @Override\n  public int getRetryBackoffMaxExponent() {\n    return 4;\n  }\n\n  // Returns the min number of milliseconds to sleep before retry\n  @Override\n  public int getRetryBackoffMin() {\n    return 1000;\n  }\n\n  @Override\n  public boolean isEncrypting() {\n    return encryptionKeySize > 0 && isClientSideEncrypted;\n  }\n\n  @Override\n  public int getEncryptionKeySize() {\n    return encryptionKeySize;\n  }\n\n  /**\n   * Renew the S3 client with fresh AWS credentials/access token\n   *\n   * @param stageCredentials a Map of new AWS credential properties, to refresh the client with (as\n   *     returned by GS)\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  @Override\n  public void renew(Map<?, ?> stageCredentials) throws SnowflakeSQLException {\n    logger.debug(\"Renewing the Snowflake S3 client\");\n    // We renew the client with fresh credentials and with its original parameters\n    setupSnowflakeS3Client(\n        stageCredentials,\n        this.clientConfig,\n        this.encMat,\n        this.proxyProperties,\n        this.stageRegion,\n        this.stageEndPoint,\n        this.isClientSideEncrypted,\n        this.session);\n  }\n\n  @Override\n  public void shutdown() {\n    logger.debug(\"Shutting down the Snowflake S3 client\");\n    amazonClient.close();\n  }\n\n  @Override\n  public StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    ListObjectsResponse objListing =\n        amazonClient\n            .listObjects(\n                ListObjectsRequest.builder().bucket(remoteStorageLocation).prefix(prefix).build())\n            .join();\n\n    return new StorageObjectSummaryCollection(objListing.contents(), remoteStorageLocation);\n  }\n\n  @Override\n  public StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix)\n      throws StorageProviderException {\n    return new S3ObjectMetadata(\n        amazonClient\n            .headObject(\n                HeadObjectRequest.builder().bucket(remoteStorageLocation).key(prefix).build())\n            .join());\n  }\n\n  /**\n   * Download a file from S3.\n   *\n   * @param session session object\n   * @param command command to download file\n   * @param localLocation local file path\n   * @param destFileName destination file name\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation s3 bucket name\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Not used in S3\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if download failed without an exception\n   * @throws SnowflakeSQLException if failed to decrypt downloaded file\n   * @throws SnowflakeSQLException if file metadata is incomplete\n   */\n  @Override\n  public void download(\n      SFSession session,\n      String command,\n      String localLocation,\n      String destFileName,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    String localFilePath = localLocation + localFileSep + destFileName;\n    logger.debug(\n        \"Starting download of file from S3 stage path: {} to {}\", stageFilePath, localFilePath);\n    int retryCount = 0;\n    do {\n      ThreadPoolExecutor executorService = null;\n      S3TransferManager tx = null;\n      try {\n        File localFile = new File(localFilePath);\n\n        logger.debug(\"Creating executor service for transfer manager with {} threads\", parallelism);\n\n        executorService =\n            createDefaultExecutorService(\"s3-transfer-manager-downloader-\", parallelism);\n        // download files from s3\n        tx = S3TransferManager.builder().s3Client(amazonClient).executor(executorService).build();\n\n        FileDownload fileDownload =\n            tx.downloadFile(\n                DownloadFileRequest.builder()\n                    .getObjectRequest(\n                        GetObjectRequest.builder()\n                            .bucket(remoteStorageLocation)\n                            .key(stageFilePath)\n                            .build())\n                    .destination(localFile.toPath())\n                    .build());\n\n        // Pull object metadata from S3\n        CompletableFuture<HeadObjectResponse> metaFuture =\n            amazonClient.headObject(\n                HeadObjectRequest.builder()\n                    .bucket(remoteStorageLocation)\n                    .key(stageFilePath)\n                    .build());\n\n        fileDownload.completionFuture().join();\n        HeadObjectResponse meta = metaFuture.join();\n\n        Map<String, String> metaMap = SnowflakeUtil.createCaseInsensitiveMap(meta.metadata());\n        String key = metaMap.get(AMZ_KEY);\n        String iv = metaMap.get(AMZ_IV);\n\n        SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(\n            localFile, session.isOwnerOnlyStageFilePermissionsEnabled());\n        stopwatch.stop();\n        long downloadMillis = stopwatch.elapsedMillis();\n\n        if (this.isEncrypting()) {\n          stopwatch.restart();\n          if (key == null || iv == null) {\n            throw new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"File metadata incomplete\");\n          }\n\n          // Decrypt file\n          try {\n            EncryptionProvider.decrypt(localFile, key, iv, this.encMat);\n            stopwatch.stop();\n            long decryptMillis = stopwatch.elapsedMillis();\n            logger.info(\n                \"S3 file {} downloaded to {}. It took {} ms (download: {} ms, decryption: {} ms) with {} retries\",\n                stageFilePath,\n                localFile.getAbsolutePath(),\n                downloadMillis + decryptMillis,\n                downloadMillis,\n                decryptMillis,\n                retryCount);\n          } catch (Exception ex) {\n            logger.error(\"Error decrypting file\", ex);\n            throw ex;\n          }\n        } else {\n          logger.info(\n              \"S3 file {} downloaded to {}. It took {} ms with {} retries\",\n              stageFilePath,\n              localFile.getAbsolutePath(),\n              downloadMillis,\n              retryCount);\n        }\n\n        return;\n      } catch (Exception ex) {\n        handleS3Exception(\n            ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, this, queryId);\n\n      } finally {\n        closeTransferManagerShutdownExecutor(\"download\", tx, executorService);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: download unsuccessful without exception!\");\n  }\n\n  /**\n   * Download a file from remote storage\n   *\n   * @param session session object\n   * @param command command to download file\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for s3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Not used in S3\n   * @param queryId last query id\n   * @return input file stream\n   * @throws SnowflakeSQLException when download failure\n   */\n  @Override\n  public InputStream downloadToStream(\n      SFSession session,\n      String command,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\"Starting download of file from S3 stage path: {} to input stream\", stageFilePath);\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    int retryCount = 0;\n    do {\n      try {\n        CompletableFuture<ResponseInputStream<GetObjectResponse>> streamFuture =\n            amazonClient.getObject(\n                GetObjectRequest.builder().bucket(remoteStorageLocation).key(stageFilePath).build(),\n                AsyncResponseTransformer.toBlockingInputStream());\n        CompletableFuture<HeadObjectResponse> metaFuture =\n            amazonClient.headObject(\n                HeadObjectRequest.builder()\n                    .bucket(remoteStorageLocation)\n                    .key(stageFilePath)\n                    .build());\n\n        HeadObjectResponse meta = metaFuture.join();\n        InputStream stream = streamFuture.join();\n        stopwatch.stop();\n        long downloadMillis = stopwatch.elapsedMillis();\n        Map<String, String> metaMap = SnowflakeUtil.createCaseInsensitiveMap(meta.metadata());\n\n        String key = metaMap.get(AMZ_KEY);\n        String iv = metaMap.get(AMZ_IV);\n\n        if (this.isEncrypting()) {\n          stopwatch.restart();\n          if (key == null || iv == null) {\n            throw new SnowflakeSQLLoggedException(\n                queryId,\n                session,\n                StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n                SqlState.INTERNAL_ERROR,\n                \"File metadata incomplete\");\n          }\n\n          try {\n            InputStream is = EncryptionProvider.decryptStream(stream, key, iv, encMat);\n            stopwatch.stop();\n            long decryptMillis = stopwatch.elapsedMillis();\n            logger.info(\n                \"S3 file {} downloaded to input stream. It took {} ms \"\n                    + \"(download: {} ms, decryption: {} ms) with {} retries\",\n                stageFilePath,\n                downloadMillis + decryptMillis,\n                downloadMillis,\n                decryptMillis,\n                retryCount);\n            return is;\n\n          } catch (Exception ex) {\n            logger.error(\"Error in decrypting file\", ex);\n            throw ex;\n          }\n        } else {\n          logger.info(\n              \"S3 file {} downloaded to input stream. Download took {} ms with {} retries\",\n              stageFilePath,\n              downloadMillis,\n              retryCount);\n        }\n        return stream;\n      } catch (Exception ex) {\n        handleS3Exception(\n            ex, ++retryCount, StorageHelper.DOWNLOAD, session, command, this, queryId);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.DOWNLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: download unsuccessful without exception!\");\n  }\n\n  /**\n   * Upload a file (-stream) to S3.\n   *\n   * @param session session object\n   * @param command upload command\n   * @param parallelism number of threads do parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation s3 bucket name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on s3 after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl Not used in S3\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  @Override\n  public void upload(\n      SFSession session,\n      String command,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.info(\n        StorageHelper.getStartUploadLog(\n            \"S3\", uploadFromStream, inputStream, fileBackedOutputStream, srcFile, destFileName));\n\n    final long originalContentLength = meta.getContentLength();\n    final List<FileInputStream> toClose = new ArrayList<>();\n    SFPair<InputStream, Boolean> uploadStreamInfo =\n        createUploadStream(\n            srcFile,\n            uploadFromStream,\n            inputStream,\n            fileBackedOutputStream,\n            meta,\n            originalContentLength,\n            toClose,\n            queryId);\n\n    S3TransferManager tx = null;\n    int retryCount = 0;\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    do {\n      tx = null;\n      ThreadPoolExecutor executorService = null;\n      try {\n        PutObjectRequest.Builder putRequestBuilder =\n            ((S3ObjectMetadata) meta)\n                .getS3PutObjectRequest().toBuilder()\n                    .bucket(remoteStorageLocation)\n                    .key(destFileName);\n\n        logger.debug(\"Creating executor service for transfer manager with {} threads\", parallelism);\n\n        executorService =\n            createDefaultExecutorService(\"s3-transfer-manager-uploader-\", parallelism);\n        // upload files to s3\n        tx = S3TransferManager.builder().s3Client(amazonClient).executor(executorService).build();\n\n        final Upload myUpload;\n\n        if (!this.isClientSideEncrypted) {\n          // since we're not client-side encrypting, make sure we're server-side encrypting with\n          // SSE-S3\n          putRequestBuilder.serverSideEncryption(ServerSideEncryption.AES256);\n        }\n\n        PutObjectRequest request = putRequestBuilder.build();\n\n        if (uploadStreamInfo.right) {\n          myUpload =\n              tx.upload(\n                  UploadRequest.builder()\n                      .putObjectRequest(request)\n                      .requestBody(\n                          AsyncRequestBody.fromInputStream(\n                              // wrapping with BufferedInputStream to mitigate\n                              // https://github.com/aws/aws-sdk-java-v2/issues/6174\n                              new BufferedInputStream(uploadStreamInfo.left),\n                              request.contentLength(),\n                              executorService))\n                      .build());\n        } else {\n          myUpload =\n              tx.upload(\n                  UploadRequest.builder()\n                      .putObjectRequest(request)\n                      .requestBody(AsyncRequestBody.fromFile(srcFile))\n                      .build());\n        }\n\n        myUpload.completionFuture().join();\n        stopwatch.stop();\n        long uploadMillis = stopwatch.elapsedMillis();\n\n        // get out\n        for (FileInputStream is : toClose) {\n          IOUtils.closeQuietly(is);\n        }\n\n        if (uploadFromStream) {\n          logger.info(\n              \"Uploaded data from input stream to S3 location: {}. It took {} ms with {} retries\",\n              destFileName,\n              uploadMillis,\n              retryCount);\n        } else {\n          logger.info(\n              \"Uploaded file {} to S3 location: {}. It took {} ms with {} retries\",\n              srcFile.getAbsolutePath(),\n              destFileName,\n              uploadMillis,\n              retryCount);\n        }\n        return;\n      } catch (Exception ex) {\n        handleS3Exception(ex, ++retryCount, StorageHelper.UPLOAD, session, command, this, queryId);\n        if (uploadFromStream && fileBackedOutputStream == null) {\n          throw new SnowflakeSQLException(\n              queryId,\n              ex,\n              SqlState.SYSTEM_ERROR,\n              ErrorCode.IO_ERROR.getMessageCode(),\n              \"Encountered exception during upload: \"\n                  + ex.getMessage()\n                  + \"\\nCannot retry upload from stream.\");\n        }\n        uploadStreamInfo =\n            createUploadStream(\n                srcFile,\n                uploadFromStream,\n                inputStream,\n                fileBackedOutputStream,\n                meta,\n                originalContentLength,\n                toClose,\n                queryId);\n      } finally {\n        closeTransferManagerShutdownExecutor(\"upload\", tx, executorService);\n      }\n    } while (retryCount <= getMaxRetries());\n\n    for (FileInputStream is : toClose) {\n      IOUtils.closeQuietly(is);\n    }\n\n    throw new SnowflakeSQLLoggedException(\n        queryId,\n        session,\n        StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n        SqlState.INTERNAL_ERROR,\n        \"Unexpected: upload unsuccessful without exception!\");\n  }\n\n  private SFPair<InputStream, Boolean> createUploadStream(\n      File srcFile,\n      boolean uploadFromStream,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      long originalContentLength,\n      List<FileInputStream> toClose,\n      String queryId)\n      throws SnowflakeSQLException {\n    logger.debug(\n        \"createUploadStream({}, {}, {}, {}, {}, {}, {}) \" + \"keySize: {}\",\n        this,\n        srcFile,\n        uploadFromStream,\n        inputStream,\n        fileBackedOutputStream,\n        meta,\n        toClose,\n        this.getEncryptionKeySize());\n    final InputStream result;\n    FileInputStream srcFileStream = null;\n    if (isEncrypting()) {\n      try {\n        final InputStream uploadStream =\n            uploadFromStream\n                ? (fileBackedOutputStream != null\n                    ? fileBackedOutputStream.asByteSource().openStream()\n                    : inputStream)\n                : (srcFileStream = new FileInputStream(srcFile));\n        toClose.add(srcFileStream);\n\n        // Encrypt\n        result =\n            EncryptionProvider.encrypt(\n                meta, originalContentLength, uploadStream, this.encMat, this);\n        uploadFromStream = true;\n      } catch (Exception ex) {\n        logger.error(\"Failed to encrypt input\", ex);\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.INTERNAL_ERROR,\n            StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n            ex,\n            \"Failed to encrypt input\",\n            ex.getMessage());\n      }\n    } else {\n      try {\n        result =\n            uploadFromStream\n                ? (fileBackedOutputStream != null\n                    ? fileBackedOutputStream.asByteSource().openStream()\n                    : inputStream)\n                : (srcFileStream = new FileInputStream(srcFile));\n        toClose.add(srcFileStream);\n\n      } catch (FileNotFoundException ex) {\n        logger.error(\"Failed to open input file\", ex);\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.INTERNAL_ERROR,\n            StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n            ex,\n            \"Failed to open input file\",\n            ex.getMessage());\n      } catch (IOException ex) {\n        logger.error(\"Failed to open input stream\", ex);\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.INTERNAL_ERROR,\n            StorageHelper.getOperationException(StorageHelper.UPLOAD).getMessageCode(),\n            ex,\n            \"Failed to open input stream\",\n            ex.getMessage());\n      }\n    }\n    return SFPair.of(result, uploadFromStream);\n  }\n\n  @Override\n  public void handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      String queryId)\n      throws SnowflakeSQLException {\n    handleS3Exception(ex, retryCount, operation, session, command, this, queryId);\n  }\n\n  private static void handleS3Exception(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession session,\n      String command,\n      SnowflakeS3Client s3Client,\n      String queryId)\n      throws SnowflakeSQLException {\n    // no need to retry if it is invalid key exception\n    if (ex.getCause() instanceof InvalidKeyException) {\n      // Most likely cause is that the unlimited strength policy files are not installed\n      // Log the error and throw a message that explains the cause\n      SnowflakeFileTransferAgent.throwJCEMissingError(operation, ex, queryId);\n    }\n\n    // If there is no space left in the download location, java.io.IOException is thrown.\n    // Don't retry.\n    if (getRootCause(ex) instanceof IOException) {\n      SnowflakeFileTransferAgent.throwNoSpaceLeftError(session, operation, ex, queryId);\n    }\n\n    // Don't retry if max retries has been reached or the error code is 404/400\n    Throwable cause = ex.getCause();\n    if (cause instanceof SdkException) {\n      logger.debug(\"SdkException: \" + ex.getMessage());\n      if (retryCount > s3Client.getMaxRetries()\n          || S3ErrorHandler.isClientException400Or404(cause)) {\n        throwIfClientExceptionOrMaxRetryReached(\n            operation, session, command, queryId, s3Client, cause);\n      } else {\n        retryRequestWithExponentialBackoff(\n            ex, retryCount, operation, session, command, s3Client, queryId, cause);\n      }\n    } else {\n      if (ex instanceof InterruptedException\n          || getRootCause(ex) instanceof SocketTimeoutException\n          || ex instanceof CompletionException) {\n        if (retryCount > s3Client.getMaxRetries()) {\n          throw new SnowflakeSQLLoggedException(\n              queryId,\n              session,\n              SqlState.SYSTEM_ERROR,\n              StorageHelper.getOperationException(operation).getMessageCode(),\n              ex,\n              \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n        } else {\n          logger.debug(\n              \"Encountered exception ({}) during {}, retry count: {}\",\n              ex.getMessage(),\n              operation,\n              retryCount);\n        }\n      } else {\n        throw new SnowflakeSQLLoggedException(\n            queryId,\n            session,\n            SqlState.SYSTEM_ERROR,\n            StorageHelper.getOperationException(operation).getMessageCode(),\n            ex,\n            \"Encountered exception during \" + operation + \": \" + ex.getMessage());\n      }\n    }\n  }\n\n  /* Returns the material descriptor key */\n  @Override\n  public String getMatdescKey() {\n    return \"x-amz-matdesc\";\n  }\n\n  /* Adds encryption metadata to the StorageObjectMetadata object */\n  @Override\n  public void addEncryptionMetadata(\n      StorageObjectMetadata meta,\n      MatDesc matDesc,\n      byte[] ivData,\n      byte[] encryptedKey,\n      long contentLength) {\n    meta.addUserMetadata(getMatdescKey(), matDesc.toString());\n    meta.addUserMetadata(AMZ_KEY, Base64.getEncoder().encodeToString(encryptedKey));\n    meta.addUserMetadata(AMZ_IV, Base64.getEncoder().encodeToString(ivData));\n    meta.setContentLength(contentLength);\n  }\n\n  /* Adds digest metadata to the StorageObjectMetadata object */\n  @Override\n  public void addDigestMetadata(StorageObjectMetadata meta, String digest) {\n    meta.addUserMetadata(\"sfc-digest\", digest);\n  }\n\n  /* Gets digest metadata to the StorageObjectMetadata object */\n  @Override\n  public String getDigestMetadata(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(\"sfc-digest\");\n  }\n\n  /*\n   * Adds streaming ingest metadata to the StorageObjectMetadata object, used for streaming ingest\n   * per client billing calculation\n   */\n  @Override\n  public void addStreamingIngestMetadata(\n      StorageObjectMetadata meta, String clientName, String clientKey) {\n    meta.addUserMetadata(S3_STREAMING_INGEST_CLIENT_NAME, clientName);\n    meta.addUserMetadata(S3_STREAMING_INGEST_CLIENT_KEY, clientKey);\n  }\n\n  @Override\n  public String getStreamingIngestClientName(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(S3_STREAMING_INGEST_CLIENT_NAME);\n  }\n\n  @Override\n  public String getStreamingIngestClientKey(StorageObjectMetadata meta) {\n    return meta.getUserMetadata().get(S3_STREAMING_INGEST_CLIENT_KEY);\n  }\n\n  public static class ClientConfiguration {\n    private final int maxConnections;\n    private final int maxErrorRetry;\n    private final int connectionTimeout;\n    private final int socketTimeout;\n\n    public ClientConfiguration(\n        int maxConnections, int maxErrorRetry, int connectionTimeout, int socketTimeout) {\n      this.maxConnections = maxConnections;\n      this.maxErrorRetry = maxErrorRetry;\n      this.connectionTimeout = connectionTimeout;\n      this.socketTimeout = socketTimeout;\n    }\n\n    public int getMaxConnections() {\n      return maxConnections;\n    }\n\n    public int getMaxErrorRetry() {\n      return maxErrorRetry;\n    }\n\n    public int getConnectionTimeout() {\n      return connectionTimeout;\n    }\n\n    public int getSocketTimeout() {\n      return socketTimeout;\n    }\n  }\n\n  private static void closeTransferManagerShutdownExecutor(\n      String name, S3TransferManager tx, ThreadPoolExecutor executor) {\n    try {\n      if (tx != null) {\n        tx.close();\n      }\n    } catch (Exception e) {\n      logger.warn(\"Failed to close S3 {} transfer manager\", name, e);\n    } finally {\n      if (executor != null) {\n        try {\n          executor.shutdown();\n          if (!executor.awaitTermination(EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {\n            logger.warn(\n                \"S3 {} executor did not terminate within {} seconds, forcing shutdown\",\n                name,\n                EXECUTOR_SHUTDOWN_TIMEOUT_SECONDS);\n            executor.shutdownNow();\n          }\n        } catch (InterruptedException e) {\n          // The only checked exception from awaitTermination so need to reset the interrupt flag\n          logger.warn(\"S3 {} executor shutdown interrupted, forcing shutdown\", name);\n          executor.shutdownNow();\n          Thread.currentThread().interrupt();\n        } catch (Exception e) {\n          logger.warn(\"Failed to shut down S3 {} executor, forcing shutdown\", name, e);\n          executor.shutdownNow();\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeStorageClient.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.FileBackedOutputStream;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.common.core.SqlState;\n\n/** Interface for storage client provider implementations */\npublic interface SnowflakeStorageClient {\n  /**\n   * @return Returns the Max number of retry attempts\n   */\n  int getMaxRetries();\n\n  /**\n   * Returns the max exponent for multiplying backoff with the power of 2, the value of 4 will give\n   * us 16secs as the max number of time to sleep before retry\n   *\n   * @return Returns the exponent\n   */\n  int getRetryBackoffMaxExponent();\n\n  /**\n   * @return Returns the min number of milliseconds to sleep before retry\n   */\n  int getRetryBackoffMin();\n\n  /**\n   * @return Returns true if encryption is enabled\n   */\n  boolean isEncrypting();\n\n  /**\n   * @return Returns the size of the encryption key\n   */\n  int getEncryptionKeySize();\n\n  /**\n   * @return Whether this client requires the use of presigned URLs for upload and download instead\n   *     of credentials that work for all files uploaded/ downloaded to a stage path. True for GCS.\n   */\n  default boolean requirePresignedUrl() {\n    return false;\n  }\n\n  /**\n   * Re-creates the encapsulated storage client with a fresh access token\n   *\n   * @param stageCredentials a Map (as returned by GS) which contains the new credential properties\n   * @throws SnowflakeSQLException failure to renew the storage client\n   */\n  void renew(Map<?, ?> stageCredentials) throws SnowflakeSQLException;\n\n  /** shuts down the client */\n  void shutdown();\n\n  /**\n   * For a set of remote storage objects under a remote location and a given prefix/path returns\n   * their properties wrapped in ObjectSummary objects\n   *\n   * @param remoteStorageLocation location, i.e. bucket for S3\n   * @param prefix the prefix to list\n   * @return a collection of storage summary objects\n   * @throws StorageProviderException cloud storage provider error\n   */\n  StorageObjectSummaryCollection listObjects(String remoteStorageLocation, String prefix)\n      throws StorageProviderException;\n\n  /**\n   * Returns the metadata properties for a remote storage object\n   *\n   * @param remoteStorageLocation location, i.e. bucket for S3\n   * @param prefix the prefix/path of the object to retrieve\n   * @return storage metadata object\n   * @throws StorageProviderException cloud storage provider error\n   */\n  StorageObjectMetadata getObjectMetadata(String remoteStorageLocation, String prefix)\n      throws StorageProviderException;\n\n  /**\n   * Download a file from remote storage.\n   *\n   * @deprecated use {@link #download(SFSession, String, String, String, int, String, String,\n   *     String, String, String)}\n   * @param connection connection object\n   * @param command command to download file\n   * @param localLocation local file path\n   * @param destFileName destination file name\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for S3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for download. Used by GCP.\n   * @throws SnowflakeSQLException download failure\n   */\n  @Deprecated\n  default void download(\n      SFSession connection,\n      String command,\n      String localLocation,\n      String destFileName,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl)\n      throws SnowflakeSQLException {\n    download(\n        connection,\n        command,\n        localLocation,\n        destFileName,\n        parallelism,\n        remoteStorageLocation,\n        stageFilePath,\n        stageRegion,\n        presignedUrl,\n        null);\n  }\n\n  /**\n   * Download a file from remote storage.\n   *\n   * @param connection connection object\n   * @param command command to download file\n   * @param localLocation local file path\n   * @param destFileName destination file name\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for S3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for download. Used by GCP.\n   * @param queryId last query id\n   * @throws SnowflakeSQLException download failure\n   */\n  void download(\n      SFSession connection,\n      String command,\n      String localLocation,\n      String destFileName,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException;\n\n  /**\n   * Download a file from remote storage\n   *\n   * @deprecated use {@link #download(SFSession, String, String, String, int, String, String,\n   *     String, String, String)}\n   * @param connection connection object\n   * @param command command to download file\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for s3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for download. Used by GCP.\n   * @return input file stream\n   * @throws SnowflakeSQLException when download failure\n   */\n  @Deprecated\n  default InputStream downloadToStream(\n      SFSession connection,\n      String command,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl)\n      throws SnowflakeSQLException {\n    return downloadToStream(\n        connection,\n        command,\n        parallelism,\n        remoteStorageLocation,\n        stageFilePath,\n        stageRegion,\n        presignedUrl,\n        null);\n  }\n\n  /**\n   * Download a file from remote storage\n   *\n   * @param connection connection object\n   * @param command command to download file\n   * @param parallelism number of threads for parallel downloading\n   * @param remoteStorageLocation remote storage location, i.e. bucket for s3\n   * @param stageFilePath stage file path\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for download. Used by GCP.\n   * @param queryId last query id\n   * @return input file stream\n   * @throws SnowflakeSQLException when download failure\n   */\n  InputStream downloadToStream(\n      SFSession connection,\n      String command,\n      int parallelism,\n      String remoteStorageLocation,\n      String stageFilePath,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException;\n\n  /**\n   * Upload a file (-stream) to remote storage\n   *\n   * @deprecated user {@link #upload(SFSession, String, int, boolean, String, File, String,\n   *     InputStream, FileBackedOutputStream, StorageObjectMetadata, String, String, String)}\n   * @param connection connection object\n   * @param command upload command\n   * @param parallelism number of threads do parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation s3 bucket name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for upload. Used by GCP.\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  @Deprecated\n  default void upload(\n      SFSession connection,\n      String command,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl)\n      throws SnowflakeSQLException {\n    upload(\n        connection,\n        command,\n        parallelism,\n        uploadFromStream,\n        remoteStorageLocation,\n        srcFile,\n        destFileName,\n        inputStream,\n        fileBackedOutputStream,\n        meta,\n        stageRegion,\n        presignedUrl,\n        null);\n  }\n\n  /**\n   * Upload a file (-stream) to remote storage\n   *\n   * @param connection connection object\n   * @param command upload command\n   * @param parallelism number of threads do parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation s3 bucket name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for upload. Used by GCP.\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  void upload(\n      SFSession connection,\n      String command,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException;\n\n  /**\n   * Upload a file (-stream) to remote storage with Pre-signed URL without JDBC connection.\n   *\n   * <p>NOTE: This function is only supported when pre-signed URL is used.\n   *\n   * @deprecated use {@link #uploadWithPresignedUrlWithoutConnection(int, HttpClientSettingsKey,\n   *     int, boolean, String, File, String, InputStream, FileBackedOutputStream,\n   *     StorageObjectMetadata, String, String, String)} This method was left to keep backward\n   *     compatibility\n   * @param networkTimeoutInMilli Network timeout for the upload\n   * @param ocspModeAndProxyKey OCSP mode and proxy settings for the upload.\n   * @param parallelism number of threads do parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation s3 bucket name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for upload. Used by GCP.\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  @Deprecated\n  default void uploadWithPresignedUrlWithoutConnection(\n      int networkTimeoutInMilli,\n      HttpClientSettingsKey ocspModeAndProxyKey,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl)\n      throws SnowflakeSQLException {\n    uploadWithPresignedUrlWithoutConnection(\n        networkTimeoutInMilli,\n        ocspModeAndProxyKey,\n        parallelism,\n        uploadFromStream,\n        remoteStorageLocation,\n        srcFile,\n        destFileName,\n        inputStream,\n        fileBackedOutputStream,\n        meta,\n        stageRegion,\n        presignedUrl,\n        null);\n  }\n\n  /**\n   * Upload a file (-stream) to remote storage with Pre-signed URL without JDBC connection.\n   *\n   * <p>NOTE: This function is only supported when pre-signed URL is used.\n   *\n   * @param networkTimeoutInMilli Network timeout for the upload\n   * @param ocspModeAndProxyKey OCSP mode and proxy settings for the upload.\n   * @param parallelism number of threads do parallel uploading\n   * @param uploadFromStream true if upload source is stream\n   * @param remoteStorageLocation s3 bucket name\n   * @param srcFile source file if not uploading from a stream\n   * @param destFileName file name on remote storage after upload\n   * @param inputStream stream used for uploading if fileBackedOutputStream is null\n   * @param fileBackedOutputStream stream used for uploading if not null\n   * @param meta object meta data\n   * @param stageRegion region name where the stage persists\n   * @param presignedUrl presigned URL for upload. Used by GCP.\n   * @param queryId last query id\n   * @throws SnowflakeSQLException if upload failed even after retry\n   */\n  default void uploadWithPresignedUrlWithoutConnection(\n      int networkTimeoutInMilli,\n      HttpClientSettingsKey ocspModeAndProxyKey,\n      int parallelism,\n      boolean uploadFromStream,\n      String remoteStorageLocation,\n      File srcFile,\n      String destFileName,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      StorageObjectMetadata meta,\n      String stageRegion,\n      String presignedUrl,\n      String queryId)\n      throws SnowflakeSQLException {\n    if (!requirePresignedUrl()) {\n      throw new SnowflakeSQLLoggedException(\n          queryId,\n          null,\n          ErrorCode.INTERNAL_ERROR.getMessageCode(),\n          SqlState.INTERNAL_ERROR,\n          /*session = */ \"uploadWithPresignedUrlWithoutConnection\"\n              + \" only works for pre-signed URL.\");\n    }\n  }\n\n  /**\n   * Handles exceptions thrown by the remote storage provider\n   *\n   * @deprecated use {@link #handleStorageException(Exception, int, String, SFSession, String,\n   *     String)}\n   * @param ex the exception to handle\n   * @param retryCount current number of retries, incremented by the caller before each call\n   * @param operation string that indicates the function/operation that was taking place, when the\n   *     exception was raised, for example StorageHelper.UPLOAD\n   * @param connection the current SFSession object used by the client\n   * @param command the command attempted at the time of the exception\n   * @throws SnowflakeSQLException exceptions that were not handled, or retried past what the retry\n   *     policy allows, are propagated\n   */\n  @Deprecated\n  default void handleStorageException(\n      Exception ex, int retryCount, String operation, SFSession connection, String command)\n      throws SnowflakeSQLException {\n    handleStorageException(ex, retryCount, operation, connection, command, null);\n  }\n\n  /**\n   * Handles exceptions thrown by the remote storage provider\n   *\n   * @param ex the exception to handle\n   * @param retryCount current number of retries, incremented by the caller before each call\n   * @param operation string that indicates the function/operation that was taking place, when the\n   *     exception was raised, for example StorageHelper.UPLOAD\n   * @param connection the current SFSession object used by the client\n   * @param command the command attempted at the time of the exception\n   * @param queryId last query id\n   * @throws SnowflakeSQLException exceptions that were not handled, or retried past what the retry\n   *     policy allows, are propagated\n   */\n  void handleStorageException(\n      Exception ex,\n      int retryCount,\n      String operation,\n      SFSession connection,\n      String command,\n      String queryId)\n      throws SnowflakeSQLException;\n\n  /**\n   * Returns the material descriptor key\n   *\n   * @return the material descriptor key\n   */\n  String getMatdescKey();\n\n  /**\n   * Adds encryption metadata to the StorageObjectMetadata object for AES-ECB/AES-CBC\n   *\n   * @param meta the storage metadata object to add the encryption info to\n   * @param matDesc the material descriptor\n   * @param ivData the initialization vector\n   * @param encryptedKey the encrypted content encryption key\n   * @param contentLength the length of the encrypted content\n   */\n  void addEncryptionMetadata(\n      StorageObjectMetadata meta,\n      MatDesc matDesc,\n      byte[] ivData,\n      byte[] encryptedKey,\n      long contentLength);\n\n  /**\n   * Adds encryption metadata to the StorageObjectMetadata object for AES-GCM/AES-GCM\n   *\n   * @param meta the storage metadata object to add the encryption info to\n   * @param matDesc the material descriptor\n   * @param encryptedKey encrypted key\n   * @param dataIvBytes the initialization vector for data\n   * @param keyIvBytes the initialization vector for file key\n   * @param keyAad the additional authenticated data for file key\n   * @param dataAad the additional authenticated data for data\n   * @param contentLength the length of the encrypted content\n   */\n  default void addEncryptionMetadataForGcm(\n      StorageObjectMetadata meta,\n      MatDesc matDesc,\n      byte[] encryptedKey,\n      byte[] dataIvBytes,\n      byte[] keyIvBytes,\n      byte[] keyAad,\n      byte[] dataAad,\n      long contentLength) {\n    // TODO GCM SNOW-1431870\n  }\n\n  /**\n   * Adds digest metadata to the StorageObjectMetadata object\n   *\n   * @param meta the storage metadata object to add the digest to\n   * @param digest the digest metadata to add\n   */\n  void addDigestMetadata(StorageObjectMetadata meta, String digest);\n\n  /**\n   * Gets digest metadata to the StorageObjectMetadata object\n   *\n   * @param meta the metadata object to extract the digest metadata from\n   * @return the digest metadata value\n   */\n  String getDigestMetadata(StorageObjectMetadata meta);\n\n  /**\n   * Adds streaming ingest metadata to the StorageObjectMetadata object, used for streaming ingest\n   * per client billing calculation\n   *\n   * @param meta the storage metadata object to add the digest to\n   * @param clientName streaming ingest client name\n   * @param clientKey streaming ingest client key, provided by Snowflake\n   */\n  void addStreamingIngestMetadata(StorageObjectMetadata meta, String clientName, String clientKey);\n\n  /**\n   * Gets streaming ingest client name to the StorageObjectMetadata object\n   *\n   * @param meta StorageObjectMetadata\n   * @return Client name\n   */\n  String getStreamingIngestClientName(StorageObjectMetadata meta);\n\n  /**\n   * Gets streaming ingest client key to the StorageObjectMetadata object\n   *\n   * @param meta StorageObjectMetadata\n   * @return Client key\n   */\n  String getStreamingIngestClientKey(StorageObjectMetadata meta);\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StageInfo.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.io.Serializable;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\n\n/** Encapsulates all the required stage properties used by GET/PUT for Azure, GCS and S3 stages */\npublic class StageInfo implements Serializable {\n\n  // me-central2 GCS region always use regional urls\n  // TODO SNOW-1818804: the value is hardcoded now, but it should be server driven\n  private static final String GCS_REGION_ME_CENTRAL_2 = \"me-central2\";\n\n  public enum StageType {\n    S3,\n    AZURE,\n    LOCAL_FS,\n    GCS\n  }\n\n  private static final long serialVersionUID = 1L;\n  private StageType stageType; // The stage type\n  private String location; // The container or bucket\n  private Map<?, ?> credentials; // the credentials required for the  stage\n  private String region; // S3/GCS region\n  // An endpoint (Azure, AWS FIPS and GCS custom endpoint override)\n  private String endPoint;\n  private String storageAccount; // The Azure Storage account (Azure only)\n  private String presignedUrl; // GCS gives us back a presigned URL instead of a cred\n  private boolean isClientSideEncrypted; // whether to encrypt/decrypt files on the stage\n  // whether to use s3 regional URL (AWS Only)\n  // TODO SNOW-1818804: this field will be deprecated when the server returns {@link\n  // #useRegionalUrl}\n  private boolean useS3RegionalUrl;\n  // whether to use regional URL (AWS and GCS only)\n  private boolean useRegionalUrl;\n  // whether to use virtual style URLs (GCP in SPCS only)\n  private boolean useVirtualUrl;\n  private Properties proxyProperties;\n\n  /*\n   * Creates a StageInfo object\n   * Validates that the necessary Stage info arguments are specified\n   *\n   * @param locationType the type of stage, i.e. AZURE/S3\n   * @param location The container/bucket\n   * @param credentials Map of cloud provider credentials\n   * @param region The geographic region where the stage is located (S3 only)\n   * @param endPoint The Azure Storage end point (Azure only)\n   * @param storageAccount The Azure Storage account (azure only)\n   * @param isClientSideEncrypted Whether the stage should use client-side encryption\n   * @throws IllegalArgumentException one or more parameters required were missing\n   */\n  public static StageInfo createStageInfo(\n      String locationType,\n      String location,\n      Map<?, ?> credentials,\n      String region,\n      String endPoint,\n      String storageAccount,\n      boolean isClientSideEncrypted)\n      throws IllegalArgumentException {\n    StageType stageType;\n    // Ensure that all the required parameters are specified\n    switch (locationType) {\n      case \"AZURE\":\n        stageType = StageType.AZURE;\n        if (!isSpecified(location)\n            || !isSpecified(endPoint)\n            || !isSpecified(storageAccount)\n            || credentials == null) {\n          throw new IllegalArgumentException(\"Incomplete parameters specified for Azure stage\");\n        }\n        break;\n\n      case \"S3\":\n        stageType = StageType.S3;\n        if (!isSpecified(location) || !isSpecified(region) || credentials == null) {\n          throw new IllegalArgumentException(\"Incomplete parameters specified for S3 stage\");\n        }\n        break;\n\n      case \"GCS\":\n        stageType = StageType.GCS;\n        if (!isSpecified(location) || credentials == null) {\n          throw new IllegalArgumentException(\"Incomplete parameters specified for GCS stage\");\n        }\n        break;\n\n      case \"LOCAL_FS\":\n        stageType = StageType.LOCAL_FS;\n        if (!isSpecified(location)) {\n          throw new IllegalArgumentException(\"Incomplete parameters specific for local stage\");\n        }\n        break;\n\n      default:\n        throw new IllegalArgumentException(\"Invalid stage type: \" + locationType);\n    }\n    return new StageInfo(\n        stageType, location, credentials, region, endPoint, storageAccount, isClientSideEncrypted);\n  }\n\n  /*\n   * StageInfo constructor, accessible only via the createStageInfo method\n   * Assumes valid parameters are specified\n   *clear\n   *\n   * @param stageType the type of stage, i.e. AZURE/S3\n   * @param location The container/bucket\n   * @param credentials Map of cloud provider credentials\n   * @param region The geographic region where the stage is located (S3 only)\n   * @param endPoint The Azure Storage end point (Azure only)\n   * @param storageAccount The Azure Storage account (azure only)\n   * @param isClientSideEncrypted Whether the stage uses client-side encryption\n   */\n  private StageInfo(\n      StageType stageType,\n      String location,\n      Map<?, ?> credentials,\n      String region,\n      String endPoint,\n      String storageAccount,\n      boolean isClientSideEncrypted) {\n    this.stageType = stageType;\n    this.location = location;\n    this.credentials = credentials;\n    this.region = region;\n    this.endPoint = endPoint;\n    this.storageAccount = storageAccount;\n    this.isClientSideEncrypted = isClientSideEncrypted;\n  }\n\n  public StageType getStageType() {\n    return stageType;\n  }\n\n  public String getLocation() {\n    return location;\n  }\n\n  public Map<?, ?> getCredentials() {\n    return credentials;\n  }\n\n  public void setCredentials(Map<?, ?> credentials) {\n    this.credentials = credentials;\n  }\n\n  public String getRegion() {\n    return region;\n  }\n\n  public String getEndPoint() {\n    return endPoint;\n  }\n\n  public String getStorageAccount() {\n    return storageAccount;\n  }\n\n  public String getPresignedUrl() {\n    return presignedUrl;\n  }\n\n  public void setPresignedUrl(String presignedUrl) {\n    this.presignedUrl = presignedUrl;\n  }\n\n  public boolean getIsClientSideEncrypted() {\n    return isClientSideEncrypted;\n  }\n\n  public void setUseS3RegionalUrl(boolean useS3RegionalUrl) {\n    this.useS3RegionalUrl = useS3RegionalUrl;\n  }\n\n  public boolean getUseS3RegionalUrl() {\n    return useS3RegionalUrl;\n  }\n\n  public void setUseRegionalUrl(boolean useRegionalUrl) {\n    this.useRegionalUrl = useRegionalUrl;\n  }\n\n  public boolean getUseRegionalUrl() {\n    return useRegionalUrl;\n  }\n\n  public void setUseVirtualUrl(boolean useVirtualUrl) {\n    this.useVirtualUrl = useVirtualUrl;\n  }\n\n  public boolean getUseVirtualUrl() {\n    return useVirtualUrl;\n  }\n\n  private static boolean isSpecified(String arg) {\n    return !(arg == null || arg.equalsIgnoreCase(\"\"));\n  }\n\n  public void setProxyProperties(Properties proxyProperties) {\n    this.proxyProperties = proxyProperties;\n  }\n\n  public Properties getProxyProperties() {\n    return proxyProperties;\n  }\n\n  public Optional<String> gcsCustomEndpoint() {\n    if (stageType != StageType.GCS) {\n      return Optional.empty();\n    }\n    if (endPoint != null && !endPoint.trim().isEmpty() && !\"null\".equals(endPoint)) {\n      return Optional.of(endPoint);\n    }\n    if (GCS_REGION_ME_CENTRAL_2.equalsIgnoreCase(region) || useRegionalUrl) {\n      return Optional.of(String.format(\"storage.%s.rep.googleapis.com\", region.toLowerCase()));\n    }\n    return Optional.empty();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageClientFactory.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\n\n/**\n * Factory object for abstracting the creation of storage client objects: SnowflakeStorageClient and\n * StorageObjectMetadata\n */\npublic class StorageClientFactory {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(StorageClientFactory.class);\n\n  private static StorageClientFactory factory;\n\n  private StorageClientFactory() {}\n\n  /**\n   * Creates or returns the single instance of the factory object\n   *\n   * @return the storage client instance\n   */\n  public static StorageClientFactory getFactory() {\n    if (factory == null) {\n      factory = new StorageClientFactory();\n    }\n\n    return factory;\n  }\n\n  /**\n   * Creates a storage client based on the value of stageLocationType\n   *\n   * @param stage the stage properties\n   * @param parallel the degree of parallelism to be used by the client\n   * @param encMat encryption material for the client\n   * @param session SFSession\n   * @return a SnowflakeStorageClient interface to the instance created\n   * @throws SnowflakeSQLException if any error occurs\n   */\n  public SnowflakeStorageClient createClient(\n      StageInfo stage, int parallel, RemoteStoreFileEncryptionMaterial encMat, SFSession session)\n      throws SnowflakeSQLException {\n    logger.debug(\"Creating storage client. Client type: {}\", stage.getStageType().name());\n\n    switch (stage.getStageType()) {\n      case S3:\n        boolean useS3RegionalUrl =\n            stage.getUseS3RegionalUrl()\n                || stage.getUseRegionalUrl()\n                || session != null && session.getUseRegionalS3EndpointsForPresignedURL();\n        return createS3Client(\n            stage.getCredentials(),\n            parallel,\n            encMat,\n            stage.getProxyProperties(),\n            stage.getRegion(),\n            stage.getEndPoint(),\n            stage.getIsClientSideEncrypted(),\n            session,\n            useS3RegionalUrl);\n\n      case AZURE:\n        return createAzureClient(stage, encMat, session);\n\n      case GCS:\n        return createGCSClient(stage, encMat, session);\n\n      default:\n        // We don't create a storage client for FS_LOCAL,\n        // so we should only find ourselves here if an unsupported\n        // remote storage client type is specified\n        throw new IllegalArgumentException(\n            \"Unsupported storage client specified: \" + stage.getStageType().name());\n    }\n  }\n\n  /**\n   * Creates a SnowflakeS3ClientObject which encapsulates the Amazon S3 client\n   *\n   * @param stageCredentials Map of stage credential properties\n   * @param parallel degree of parallelism\n   * @param encMat encryption material for the client\n   * @param stageRegion the region where the stage is located\n   * @param stageEndPoint the FIPS endpoint for the stage, if needed\n   * @param isClientSideEncrypted whether client-side encryption should be used\n   * @param session the active session\n   * @param useS3RegionalUrl\n   * @return the SnowflakeS3Client instance created\n   * @throws SnowflakeSQLException failure to create the S3 client\n   */\n  private SnowflakeS3Client createS3Client(\n      Map<?, ?> stageCredentials,\n      int parallel,\n      RemoteStoreFileEncryptionMaterial encMat,\n      Properties proxyProperties,\n      String stageRegion,\n      String stageEndPoint,\n      boolean isClientSideEncrypted,\n      SFBaseSession session,\n      boolean useS3RegionalUrl)\n      throws SnowflakeSQLException {\n    final int S3_TRANSFER_MAX_RETRIES = 3;\n\n    logger.debug(\"Creating S3 client with encryption: {}\", (encMat == null ? \"no\" : \"yes\"));\n\n    SnowflakeS3Client s3Client;\n\n    SnowflakeS3Client.ClientConfiguration clientConfig =\n        new SnowflakeS3Client.ClientConfiguration(\n            parallel + 1,\n            S3_TRANSFER_MAX_RETRIES,\n            (int) HttpUtil.getConnectionTimeout().toMillis(),\n            (int) HttpUtil.getSocketTimeout().toMillis());\n\n    logger.debug(\n        \"S3 client configuration: maxConnection: {}, connectionTimeout: {}, \"\n            + \"socketTimeout: {}, maxErrorRetry: {}\",\n        clientConfig.getMaxConnections(),\n        clientConfig.getConnectionTimeout(),\n        clientConfig.getSocketTimeout(),\n        clientConfig.getMaxErrorRetry());\n\n    try {\n      s3Client =\n          new SnowflakeS3Client(\n              stageCredentials,\n              clientConfig,\n              encMat,\n              proxyProperties,\n              stageRegion,\n              stageEndPoint,\n              isClientSideEncrypted,\n              session,\n              useS3RegionalUrl);\n    } catch (Exception ex) {\n      logger.debug(\"Exception creating s3 client\", ex);\n      throw ex;\n    }\n    logger.debug(\"S3 Storage client created\", false);\n\n    return s3Client;\n  }\n\n  /**\n   * Creates a storage provider specific metadata object, accessible via the platform independent\n   * interface\n   *\n   * @param stageType determines the implementation to be created\n   * @return the implementation of StorageObjectMetadata\n   */\n  public StorageObjectMetadata createStorageMetadataObj(StageInfo.StageType stageType) {\n    switch (stageType) {\n      case S3:\n        return new S3ObjectMetadata();\n\n      case AZURE:\n      case GCS:\n        // GCS's metadata object looks just like Azure's (Map<String, String>),\n        // so for now we'll use the same class.\n        return new CommonObjectMetadata();\n\n      default:\n        // An unsupported remote storage client type was specified\n        // We don't create/implement a storage client for FS_LOCAL,\n        // so we should never end up here while running on local file system\n        throw new IllegalArgumentException(\"Unsupported stage type specified: \" + stageType.name());\n    }\n  }\n\n  /**\n   * Creates a SnowflakeAzureClientObject which encapsulates the Azure Storage client\n   *\n   * @param stage Stage information\n   * @param encMat encryption material for the client\n   * @param session\n   * @return the SnowflakeS3Client instance created\n   */\n  private SnowflakeAzureClient createAzureClient(\n      StageInfo stage, RemoteStoreFileEncryptionMaterial encMat, SFBaseSession session)\n      throws SnowflakeSQLException {\n    logger.debug(\"Creating Azure client with encryption: {}\", (encMat == null ? \"no\" : \"yes\"));\n\n    SnowflakeAzureClient azureClient;\n\n    try {\n      azureClient = SnowflakeAzureClient.createSnowflakeAzureClient(stage, encMat, session);\n    } catch (Exception ex) {\n      logger.debug(\"Exception creating Azure Storage client\", ex);\n      throw ex;\n    }\n    logger.debug(\"Azure Storage client created\", false);\n\n    return azureClient;\n  }\n\n  /**\n   * Creates a SnowflakeGCSClient object which encapsulates the GCS Storage client\n   *\n   * @param stage Stage information\n   * @param encMat encryption material for the client\n   * @return the SnowflakeGCSClient instance created\n   */\n  private SnowflakeGCSClient createGCSClient(\n      StageInfo stage, RemoteStoreFileEncryptionMaterial encMat, SFSession session)\n      throws SnowflakeSQLException {\n    logger.debug(\"Creating GCS client with encryption: {}\", (encMat == null ? \"no\" : \"yes\"));\n\n    SnowflakeGCSClient gcsClient;\n\n    try {\n      gcsClient = SnowflakeGCSClient.createSnowflakeGCSClient(stage, encMat, session);\n    } catch (Exception ex) {\n      logger.debug(\"Exception creating GCS Storage client\", ex);\n      throw ex;\n    }\n    logger.debug(\"GCS Storage client created\", false);\n\n    return gcsClient;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageHelper.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.jdbc.FileBackedOutputStream;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass StorageHelper {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(StorageHelper.class);\n\n  protected static final String DOWNLOAD = \"download\";\n  protected static final String UPLOAD = \"upload\";\n\n  static String getStartUploadLog(\n      String serviceName,\n      boolean uploadFromStream,\n      InputStream inputStream,\n      FileBackedOutputStream fileBackedOutputStream,\n      File srcFile,\n      String destFileName) {\n    if (uploadFromStream && fileBackedOutputStream != null) {\n      File file = fileBackedOutputStream.getFile();\n      String fileBackedOutputStreamType =\n          file == null ? \"byte stream\" : (\"file: \" + file.getAbsolutePath());\n      return \"Starting upload from stream (\"\n          + fileBackedOutputStreamType\n          + \") to \"\n          + serviceName\n          + \" location: \"\n          + destFileName;\n    } else if (uploadFromStream && inputStream != null) {\n      return \"Starting upload from input stream to \" + serviceName + \" location: \" + destFileName;\n    } else {\n      return \"Starting upload from file \"\n          + srcFile.getAbsolutePath()\n          + \" to \"\n          + serviceName\n          + \" location: \"\n          + destFileName;\n    }\n  }\n\n  static ErrorCode getOperationException(String operation) {\n    switch (operation) {\n      case UPLOAD:\n        return ErrorCode.UPLOAD_ERROR;\n      case DOWNLOAD:\n        return ErrorCode.DOWNLOAD_ERROR;\n      default:\n        logger.warn(\n            \"Unknown operation: {}. Returning fallback error code: ErrorCode.FILE_TRANSFER_ERROR\",\n            operation);\n        return ErrorCode.FILE_TRANSFER_ERROR;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageObjectMetadata.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport java.util.Map;\n\n/**\n * Interface for platform-independent remote storage object metadata, modeled after the S3\n * ObjectMetadata class\n *\n * <p>Only the metadata accessors and mutators used by the Client currently are supported,\n * additional methods should be added as needed\n */\npublic interface StorageObjectMetadata {\n  /**\n   * @return returns a Map/key-value pairs of metadata properties\n   */\n  Map<String, String> getUserMetadata();\n\n  /**\n   * @return returns the size of object in bytes\n   */\n  long getContentLength();\n\n  /**\n   * Sets size of the associated object in bytes\n   *\n   * @param contentLength the length of content\n   */\n  void setContentLength(long contentLength);\n\n  /**\n   * Adds the key value pair of custom user-metadata for the associated object.\n   *\n   * @param key the key of user metadata\n   * @param value the value of user metadata\n   */\n  void addUserMetadata(String key, String value);\n\n  /**\n   * Sets the optional Content-Encoding HTTP header specifying what content encodings, have been\n   * applied to the object and what decoding mechanisms must be applied, in order to obtain the\n   * media-type referenced by the Content-Type field.\n   *\n   * @param encoding the encoding name using in HTTP header Content-Encoding\n   */\n  void setContentEncoding(String encoding);\n\n  /*\n   * @return returns the content encoding type\n   */\n  String getContentEncoding();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageObjectSummary.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport com.azure.storage.blob.models.BlobItem;\nimport com.azure.storage.blob.models.BlobItemProperties;\nimport com.google.cloud.storage.Blob;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport software.amazon.awssdk.services.s3.model.S3Object;\n\n/** Storage platform agnostic class that encapsulates remote storage object properties */\npublic class StorageObjectSummary {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(StorageObjectSummary.class);\n  private String location; // location translates to \"bucket\" for S3\n  private String key;\n  private String md5;\n  private long size;\n\n  /**\n   * Constructs a StorageObjectSummary object from the S3 equivalent S3ObjectSummary\n   *\n   * @param location Location of the S3 object\n   * @param key Key of the S3Object\n   * @param md5 The MD5 hash of the object\n   * @param size The size of the S3 object\n   */\n  private StorageObjectSummary(String location, String key, String md5, long size) {\n    this.location = location;\n    this.key = key;\n    this.md5 = md5;\n    this.size = size;\n  }\n\n  /**\n   * Constructs a StorageObjectSummary object from the S3 equivalent S3ObjectSummary\n   *\n   * @param objSummary the AWS S3 ObjectSummary object to copy from\n   * @param bucket the AWS S3 bucket name\n   * @return the ObjectSummary object created\n   */\n  public static StorageObjectSummary createFromS3ObjectSummary(S3Object objSummary, String bucket) {\n\n    return new StorageObjectSummary(\n        bucket,\n        objSummary.key(),\n        // S3 ETag is not always MD5, but since this code path is only\n        // used in skip duplicate files in PUT command, It's not\n        // critical to guarantee that it's MD5\n        objSummary.eTag(),\n        objSummary.size());\n  }\n\n  /**\n   * Creates a platform-agnostic ObjectSummary from an Azure BlobItem\n   *\n   * @param blobItem an Azure BlobItem object\n   * @return the ObjectSummary object created\n   */\n  public static StorageObjectSummary createFromAzureBlobItem(BlobItem blobItem, String location)\n      throws StorageProviderException {\n    try {\n      long size;\n      String key = blobItem.getName();\n      BlobItemProperties blobProperties = blobItem.getProperties();\n\n      byte[] contentMd5 = blobProperties.getContentMd5();\n      String md5 = contentMd5 != null ? SnowflakeUtil.byteToHexString(contentMd5) : null;\n      size = blobProperties.getContentLength();\n      return new StorageObjectSummary(location, key, md5, size);\n    } catch (Exception ex) {\n      logger.debug(\"Failed to create StorageObjectSummary from Azure BlobItem: {}\", ex);\n      throw new StorageProviderException(ex);\n    }\n  }\n\n  /**\n   * createFromGcsBlob creates a StorageObjectSummary from a GCS blob object\n   *\n   * @param blob GCS blob object\n   * @return a new StorageObjectSummary\n   */\n  public static StorageObjectSummary createFromGcsBlob(Blob blob) {\n    String bucketName = blob.getBucket();\n    String path = blob.getName();\n    String hexMD5 = blob.getMd5ToHexString();\n    long size = blob.getSize();\n    return new StorageObjectSummary(bucketName, path, hexMD5, size);\n  }\n\n  /**\n   * @return returns the location of the object\n   */\n  public String getLocation() {\n    return location;\n  }\n\n  /**\n   * @return returns the key property of the object\n   */\n  public String getKey() {\n    return key;\n  }\n\n  /**\n   * @return returns the MD5 hash of the object\n   */\n  public String getMD5() {\n    return md5;\n  }\n\n  /**\n   * @return returns the size property of the object\n   */\n  public long getSize() {\n    return size;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageObjectSummaryCollection.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport com.azure.storage.blob.models.BlobItem;\nimport com.google.api.gax.paging.Page;\nimport com.google.cloud.storage.Blob;\nimport java.util.Iterator;\nimport java.util.List;\nimport software.amazon.awssdk.services.s3.model.S3Object;\n\n/**\n * Provides and iterator over storage object summaries from all supported cloud storage providers\n */\npublic class StorageObjectSummaryCollection implements Iterable<StorageObjectSummary> {\n\n  private enum storageType {\n    S3,\n    AZURE,\n    GCS\n  };\n\n  private final storageType sType;\n  private List<S3Object> s3ObjSummariesList = null;\n  private Iterable<BlobItem> azCLoudBlobIterable = null;\n  private Page<Blob> gcsIterablePage = null;\n  // explicitly store bucket name for S3 because S3Object does not contain bucket info\n  private String bucketName = null;\n\n  // Constructs platform-agnostic collection of object summaries from S3 objects\n  public StorageObjectSummaryCollection(List<S3Object> s3ObjectSummaries, String bucketName) {\n    this.s3ObjSummariesList = s3ObjectSummaries;\n    sType = storageType.S3;\n    this.bucketName = bucketName;\n  }\n\n  // Constructs platform-agnostic collection of object summaries from an Azure CloudBlobDirectory\n  // object\n  public StorageObjectSummaryCollection(Iterable<BlobItem> azCLoudBlobIterable, String bucketName) {\n    this.azCLoudBlobIterable = azCLoudBlobIterable;\n    sType = storageType.AZURE;\n    this.bucketName = bucketName;\n  }\n\n  public StorageObjectSummaryCollection(Page<Blob> gcsIterablePage) {\n    this.gcsIterablePage = gcsIterablePage;\n    sType = storageType.GCS;\n  }\n\n  @Override\n  public Iterator<StorageObjectSummary> iterator() {\n    switch (sType) {\n      case S3:\n        return new S3ObjectSummariesIterator(s3ObjSummariesList, this.bucketName);\n      case AZURE:\n        return new AzureObjectSummariesIterator(azCLoudBlobIterable, bucketName);\n      case GCS:\n        return new GcsObjectSummariesIterator(this.gcsIterablePage);\n      default:\n        throw new IllegalArgumentException(\"Unspecified storage provider\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageProviderException.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport org.apache.http.HttpStatus;\nimport software.amazon.awssdk.core.exception.SdkServiceException;\n\n/**\n * Custom exception class to signal a remote provider exception in a platform-independent manner.\n */\npublic class StorageProviderException extends RuntimeException {\n  private static final long serialVersionUID = 1L;\n\n  /**\n   * Constructor that accepts an arbitrary Exception.\n   *\n   * @param ex An Exception to be treated as transient.\n   */\n  public StorageProviderException(Exception ex) {\n    super(ex);\n  }\n\n  /**\n   * Method to obtain the original provider exception that led to this exception being thrown.\n   *\n   * @return The original provider exception that led to this exception.\n   */\n  public Exception getOriginalProviderException() {\n    return (Exception) (super.getCause());\n  }\n\n  /**\n   * Returns true if this is an exception corresponding to a HTTP 404 error returned by the storage\n   * provider.\n   *\n   * @return true if the specified exception is an SdkServiceException instance and if it was thrown\n   *     because of a 404, false otherwise.\n   */\n  public boolean isServiceException404() {\n    Throwable cause = getCause();\n    if (cause instanceof SdkServiceException) {\n      SdkServiceException asEx = (SdkServiceException) cause;\n      return (asEx.statusCode() == HttpStatus.SC_NOT_FOUND);\n    }\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/CertificateDiagnosticCheck.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass CertificateDiagnosticCheck extends DiagnosticCheck {\n\n  private static final String SECURE_SOCKET_PROTOCOL = \"TLS\";\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(CertificateDiagnosticCheck.class);\n\n  public CertificateDiagnosticCheck(ProxyConfig proxyConfig) {\n    super(\"SSL/TLS Certificate Test\", proxyConfig);\n  }\n\n  @Override\n  protected void doCheck(SnowflakeEndpoint snowflakeEndpoint) {\n    String hostname = snowflakeEndpoint.getHost();\n    String port = Integer.toString(snowflakeEndpoint.getPort());\n    if (snowflakeEndpoint.isSslEnabled()) {\n      String urlString = \"https://\" + hostname + \":\" + port;\n      try {\n        SSLContext sslContext = SSLContext.getInstance(SECURE_SOCKET_PROTOCOL);\n        sslContext.init(null, new TrustManager[] {new DiagnosticTrustManager()}, null);\n        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());\n        Proxy proxy = this.proxyConf.getProxy(snowflakeEndpoint);\n        new URL(urlString).openConnection(proxy).connect();\n      } catch (NoSuchAlgorithmException e) {\n        logger.error(\n            \"None of the security provider's implementation of SSLContextSpi supports \"\n                + SECURE_SOCKET_PROTOCOL,\n            e);\n      } catch (KeyManagementException e) {\n        logger.error(\"Failed to initialize SSLContext\", e);\n      } catch (MalformedURLException e) {\n        logger.error(\"Failed to create new URL object: \" + urlString, e);\n      } catch (IOException e) {\n        logger.error(\"Failed to open a connection to: \" + urlString, e);\n      } catch (Exception e) {\n        logger.error(\n            \"Unexpected error occurred when trying to retrieve certificate from: \" + hostname, e);\n      } finally {\n        HttpsURLConnection.setDefaultSSLSocketFactory(\n            (SSLSocketFactory) SSLSocketFactory.getDefault());\n      }\n    } else {\n      logger.info(\"Host \" + hostname + \":\" + port + \" is not secure. Skipping certificate check.\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/DiagnosticCheck.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nabstract class DiagnosticCheck {\n  protected final String name;\n  protected final ProxyConfig proxyConf;\n  private static final SFLogger logger = SFLoggerFactory.getLogger(DiagnosticCheck.class);\n\n  abstract void doCheck(SnowflakeEndpoint snowflakeEndpoint);\n\n  final void run(SnowflakeEndpoint snowflakeEndpoint) {\n    logger.info(\"JDBC Diagnostics - {}: hostname: {}\", this.name, snowflakeEndpoint.getHost());\n    doCheck(snowflakeEndpoint);\n  }\n\n  protected DiagnosticCheck(String name, ProxyConfig proxyConf) {\n    this.name = name;\n    this.proxyConf = proxyConf;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/DiagnosticContext.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.Proxy;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\npublic class DiagnosticContext {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(DiagnosticContext.class);\n  private static final String JAVAX_NET_DEBUG = \"javax.net.debug\";\n\n  private static final String JAVAX_TRUSTSTORE = \"javax.net.ssl.trustStore\";\n  private ProxyConfig proxyConf;\n\n  private List<SnowflakeEndpoint> endpoints = new ArrayList<>();\n\n  private final DiagnosticCheck[] tests;\n\n  public DiagnosticContext(\n      String allowListFile, Map<SFSessionProperty, Object> connectionPropertiesMap) {\n\n    createProxyConfiguration(connectionPropertiesMap);\n\n    try {\n      JsonNode jsonNode = readAllowListJsonFile(allowListFile);\n      for (JsonNode objectNode : jsonNode) {\n        String type = objectNode.get(\"type\").asText();\n        String host = objectNode.get(\"host\").asText();\n        int port = objectNode.get(\"port\").asInt();\n        SnowflakeEndpoint e = new SnowflakeEndpoint(type, host, port);\n        endpoints.add(e);\n      }\n\n    } catch (IOException e) {\n      logger.error(\"Failed to read allowlist file: \", e);\n    } catch (Exception e) {\n      logger.error(\"Failed to parse data in allowlist file: \" + allowListFile, e);\n    }\n\n    tests =\n        new DiagnosticCheck[] {\n          new DnsDiagnosticCheck(proxyConf),\n          new TcpDiagnosticCheck(proxyConf),\n          new CertificateDiagnosticCheck(proxyConf),\n          new HttpAndHttpsDiagnosticCheck(proxyConf)\n        };\n  }\n\n  /** This constructor is only used for testing */\n  DiagnosticContext(Map<SFSessionProperty, Object> connectionPropertiesMap) {\n    createProxyConfiguration(connectionPropertiesMap);\n    tests = null;\n  }\n\n  private void createProxyConfiguration(Map<SFSessionProperty, Object> connectionPropertiesMap) {\n    String proxyHost = (String) connectionPropertiesMap.get(SFSessionProperty.PROXY_HOST);\n    int proxyPort =\n        (connectionPropertiesMap.get(SFSessionProperty.PROXY_PORT) == null)\n            ? -1\n            : Integer.parseInt((String) connectionPropertiesMap.get(SFSessionProperty.PROXY_PORT));\n    String nonProxyHosts = (String) connectionPropertiesMap.get(SFSessionProperty.NON_PROXY_HOSTS);\n    proxyConf = new ProxyConfig(proxyHost, proxyPort, nonProxyHosts);\n  }\n\n  public void runDiagnostics() {\n\n    logEnvironmentInfo();\n\n    // Loop through endpoints and run diagnostic test on each one of them\n    for (DiagnosticCheck test : tests) {\n      for (SnowflakeEndpoint endpoint : endpoints) {\n        test.run(endpoint);\n      }\n    }\n  }\n\n  private JsonNode readAllowListJsonFile(String jsonFilePath) throws IOException {\n    ObjectMapper objectMapper = new ObjectMapper();\n    File allowListFile = new File(jsonFilePath);\n\n    return objectMapper.readTree(allowListFile);\n  }\n\n  public void logEnvironmentInfo() {\n    logger.info(\"Getting environment information\");\n    logger.info(\"Current truststore used: \" + getTrustStoreLocation());\n    logger.info(\"-Dnetworkaddress.cache.ttl: \" + systemGetProperty(\"networkaddress.cache.ttl\"));\n    logger.info(\n        \"-Dnetworkaddress.cache.negative.ttl: \"\n            + systemGetProperty(\"networkaddress.cache.negative.ttl\"));\n    logger.info(\"-Djavax.net.debug: \" + systemGetProperty(JAVAX_NET_DEBUG));\n  }\n\n  private boolean isNullOrEmpty(String a) {\n    return a == null || a.isEmpty();\n  }\n\n  /**\n   * We determine the truststore in use based on the JSSE documentation:\n   *\n   * <p>1.) If the javax.net.ssl.trustStore property is defined, then the TrustManagerFactory\n   * attempts to find a file using the file name specified by that system property, and uses that\n   * file for the KeyStore parameter. If the javax.net.ssl.trustStorePassword system property is\n   * also defined, then its value is used to check the integrity of the data in the truststore\n   * before opening it.\n   *\n   * <p>If the javax.net.ssl.trustStore property is defined but the specified file does not exist,\n   * then a default TrustManager using an empty keystore is created.\n   *\n   * <p>2.) If the javax.net.ssl.trustStore system property was not specified, then: - if the file\n   * java-home/lib/security/jssecacerts exists, that file is used; - if the file\n   * java-home/lib/security/cacerts exists, that file is used; - if neither of these files exists,\n   * then the SSL cipher suite is anonymous, does not perform any authentication, and thus does not\n   * need a truststore.\n   */\n  private String getTrustStoreLocation() {\n    String trustStore = systemGetProperty(JAVAX_TRUSTSTORE);\n    String javaHome = systemGetProperty(\"java.home\");\n    if (isNullOrEmpty(javaHome)) {\n      return \"<cannot be determined due to missing/inaccessible java.home property>\";\n    }\n    Path javaSecurityPath = FileSystems.getDefault().getPath(javaHome, \"lib\", \"security\");\n    logger.info(\"JAVA_HOME: \" + javaHome);\n\n    if (isNullOrEmpty(trustStore)) {\n      logger.info(\"-D{} is null\", JAVAX_TRUSTSTORE);\n      Path jssecacertsPath =\n          FileSystems.getDefault().getPath(javaSecurityPath.toString(), \"jssecacerts\");\n      Path cacertsPath = FileSystems.getDefault().getPath(javaSecurityPath.toString(), \"cacerts\");\n\n      logger.info(\"Checking if jssecacerts or cacerts exist\");\n      if (Files.exists(jssecacertsPath)) {\n        logger.info(jssecacertsPath.toString() + \" exists\");\n        trustStore = jssecacertsPath.toString();\n      } else if (Files.exists(cacertsPath)) {\n        logger.info(cacertsPath.toString() + \" exists\");\n        trustStore = cacertsPath.toString();\n      }\n    } else {\n      logger.info(\"-D{} is set by user: {}\", JAVAX_TRUSTSTORE, trustStore);\n    }\n    return trustStore;\n  }\n\n  String getHttpProxyHost() {\n    return proxyConf.getHttpProxyHost();\n  }\n\n  int getHttpProxyPort() {\n    return proxyConf.getHttpProxyPort();\n  }\n\n  String getHttpsProxyHost() {\n    return proxyConf.getHttpsProxyHost();\n  }\n\n  int getHttpsProxyPort() {\n    return proxyConf.getHttpsProxyPort();\n  }\n\n  String getHttpNonProxyHosts() {\n    return proxyConf.getNonProxyHosts();\n  }\n\n  List<SnowflakeEndpoint> getEndpoints() {\n    return endpoints;\n  }\n\n  Proxy getProxy(SnowflakeEndpoint snowflakeEndpoint) {\n    return this.proxyConf.getProxy(snowflakeEndpoint);\n  }\n\n  boolean isProxyEnabled() {\n    return proxyConf.isProxyEnabled();\n  }\n\n  boolean isProxyEnabledOnJvm() {\n    return proxyConf.isProxyEnabledOnJvm();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/DiagnosticTrustManager.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport java.net.Socket;\nimport java.security.cert.CertificateParsingException;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass DiagnosticTrustManager extends X509ExtendedTrustManager {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(DiagnosticTrustManager.class);\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] certs, String authType) {\n    printCertificates(certs);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] certs, String authType, SSLEngine engine) {\n    printCertificates(certs);\n  }\n\n  @Override\n  public void checkServerTrusted(X509Certificate[] certs, String authType, Socket sc) {\n    printCertificates(certs);\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType) {\n    // do nothing\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket sc) {\n    // do nothing\n  }\n\n  @Override\n  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) {\n    // do nothing\n  }\n\n  @Override\n  public X509Certificate[] getAcceptedIssuers() {\n    // This implementation is not needed, so we're returning an empty array\n    return new X509Certificate[0];\n  }\n\n  private void printCertificates(X509Certificate[] chainCerts) {\n    logger.info(\"Printing certificate chain\");\n    StringBuilder sb = new StringBuilder();\n    int i = 0;\n    for (X509Certificate x509Cert : chainCerts) {\n      try {\n        sb.append(\"\\nCertificate[\").append(i).append(\"]:\").append(\"\\n\");\n        sb.append(\"Subject: \").append(x509Cert.getSubjectDN()).append(\"\\n\");\n        sb.append(\"Issuer: \").append(x509Cert.getIssuerDN()).append(\"\\n\");\n        sb.append(\"Valid from: \").append(x509Cert.getNotBefore()).append(\"\\n\");\n        sb.append(\"Not Valid After: \").append(x509Cert.getNotAfter()).append(\"\\n\");\n        sb.append(\"Subject Alternative Names: \")\n            .append(x509Cert.getSubjectAlternativeNames())\n            .append(\"\\n\");\n        sb.append(\"Issuer Alternative Names: \")\n            .append(x509Cert.getIssuerAlternativeNames())\n            .append(\"\\n\");\n        sb.append(\"Serial: \").append(x509Cert.getSerialNumber().toString(16)).append(\"\\n\");\n        logger.info(sb.toString());\n        i++;\n      } catch (CertificateParsingException e) {\n        logger.error(\"Error parsing certificate\", e);\n      } catch (Exception e) {\n        logger.error(\"Unexpected error occurred when parsing certificate\", e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/DnsDiagnosticCheck.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.Hashtable;\nimport javax.naming.Context;\nimport javax.naming.NamingEnumeration;\nimport javax.naming.NamingException;\nimport javax.naming.directory.Attribute;\nimport javax.naming.directory.Attributes;\nimport javax.naming.directory.DirContext;\nimport javax.naming.spi.NamingManager;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass DnsDiagnosticCheck extends DiagnosticCheck {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(DnsDiagnosticCheck.class);\n\n  private final String INITIAL_DNS_CONTEXT = \"com.sun.jndi.dns.DnsContextFactory\";\n\n  DnsDiagnosticCheck(ProxyConfig proxyConfig) {\n    super(\"DNS Lookup Test\", proxyConfig);\n  }\n\n  @Override\n  protected void doCheck(SnowflakeEndpoint snowflakeEndpoint) {\n    getCnameRecords(snowflakeEndpoint);\n    getArecords(snowflakeEndpoint);\n  }\n\n  private void getCnameRecords(SnowflakeEndpoint snowflakeEndpoint) {\n    String hostname = snowflakeEndpoint.getHost();\n    try {\n      Hashtable<String, String> env = new Hashtable<>();\n      env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_DNS_CONTEXT);\n      DirContext dirCtx = (DirContext) NamingManager.getInitialContext(env);\n      Attributes attrs1 = dirCtx.getAttributes(snowflakeEndpoint.getHost(), new String[] {\"CNAME\"});\n      NamingEnumeration<? extends Attribute> attrs = attrs1.getAll();\n      StringBuilder sb = new StringBuilder();\n      sb.append(\"\\nCNAME:\\n\");\n      while (attrs.hasMore()) {\n        Attribute a = attrs.next();\n        NamingEnumeration<?> values = a.getAll();\n        while (values.hasMore()) {\n          sb.append(values.next());\n          sb.append(\"\\n\");\n        }\n      }\n      logger.info(sb.toString());\n    } catch (NamingException e) {\n      logger.error(\"Error occurred when getting CNAME record for host \" + hostname, e);\n    } catch (Exception e) {\n      logger.error(\"Unexpected error occurred when getting CNAME record for host \" + hostname, e);\n    }\n  }\n\n  private void getArecords(SnowflakeEndpoint snowflakeEndpoint) {\n    String hostname = snowflakeEndpoint.getHost();\n    try {\n      InetAddress[] addresses = InetAddress.getAllByName(hostname);\n      StringBuilder sb = new StringBuilder();\n      sb.append(\"\\nA Records:\\n\");\n      for (InetAddress ip : addresses) {\n        if (ip instanceof Inet4Address) {\n          sb.append(ip.getHostAddress());\n          sb.append(\"\\n\");\n        }\n        // Check if this is a private link endpoint and if the ip address\n        // returned by the DNS query is a private IP address as expected.\n        if (snowflakeEndpoint.isPrivateLink() && !ip.isSiteLocalAddress()) {\n          logger.error(\n              \"Public IP address was returned for {}. Please review your DNS configurations.\",\n              hostname);\n        }\n      }\n      logger.info(sb.toString());\n    } catch (UnknownHostException e) {\n      logger.error(\"DNS query failed for host: \" + snowflakeEndpoint.getHost(), e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/HttpAndHttpsDiagnosticCheck.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.Proxy;\nimport java.net.URL;\nimport java.util.List;\nimport java.util.Map;\nimport javax.net.ssl.HttpsURLConnection;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass HttpAndHttpsDiagnosticCheck extends DiagnosticCheck {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(HttpAndHttpsDiagnosticCheck.class);\n  private final String HTTP_SCHEMA = \"http://\";\n  private final String HTTPS_SCHEMA = \"https://\";\n\n  HttpAndHttpsDiagnosticCheck(ProxyConfig proxyConfig) {\n    super(\"HTTP/HTTPS Connection Test\", proxyConfig);\n  }\n\n  @Override\n  protected void doCheck(SnowflakeEndpoint snowflakeEndpoint) {\n    // We have to replace underscores with hyphens because the JDK doesn't allow underscores in the\n    // hostname\n    String hostname = snowflakeEndpoint.getHost().replace('_', '-');\n    try {\n      Proxy proxy = this.proxyConf.getProxy(snowflakeEndpoint);\n      StringBuilder sb = new StringBuilder();\n      String urlString =\n          (snowflakeEndpoint.isSslEnabled()) ? HTTPS_SCHEMA + hostname : HTTP_SCHEMA + hostname;\n      URL url = new URL(urlString);\n      HttpURLConnection con =\n          (snowflakeEndpoint.isSslEnabled())\n              ? (HttpsURLConnection) url.openConnection(proxy)\n              : (HttpURLConnection) url.openConnection(proxy);\n      logger.info(\"Response from server: {} {}\", con.getResponseCode(), con.getResponseMessage());\n      sb.append(\"Headers:\\n\");\n\n      Map<String, List<String>> headerFields = con.getHeaderFields();\n      for (Map.Entry<String, List<String>> header : headerFields.entrySet()) {\n        sb.append(header.getKey()).append(\": \").append(header.getValue()).append(\"\\n\");\n      }\n\n      logger.info(sb.toString());\n\n    } catch (MalformedURLException e) {\n      logger.error(\n          \"The URL format is incorrect, please check your allowlist JSON file for errors.\", e);\n    } catch (IOException e) {\n      logger.error(\"Could not send an HTTP/HTTPS request to host \" + hostname, e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/ProxyConfig.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.net.InetSocketAddress;\nimport java.net.Proxy;\nimport java.util.Optional;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * This class is used to represent the proxy configurations passed to the JDBC driver either as JVM\n * arguments or connection parameters. The class determines which proxy settings take precedence and\n * should be used by the diagnostic tests. We normalize configurations where empty strings for\n * hostnames and -1 for ports represent the absence of a configuration.\n *\n * <p>The order of precedence is:\n *\n * <p>1.) Connection parameters (proxy configurations passed to the constructor) 2.) JVM arguments\n *\n * <p>The useProxy parameter is ignored. If the proxy is configured using the JVM and someone wants\n * to bypass that at the connection-level then they would need to set the following connection\n * parameters: proxyHost=127.0.0.1 proxyPort=8080 nonProxyHosts=*\n *\n * <p>i.e. bypass the proxy host when connecting to any host.\n */\nclass ProxyConfig {\n  private String proxyHost;\n  private int proxyPort;\n  private String nonProxyHosts;\n  private String jvmHttpProxyHost;\n  private String jvmHttpsProxyHost;\n  private int jvmHttpProxyPort;\n  private int jvmHttpsProxyPort;\n  private String jvmNonProxyHosts;\n  private String finalHttpProxyHost = \"\";\n  private String finalHttpsProxyHost = \"\";\n  private int finalHttpProxyPort = -1;\n  private int finalHttpsProxyPort = -1;\n  private String finalNonProxyHosts = \"\";\n  private boolean isProxyEnabled = false;\n\n  private boolean isProxyEnabledOnJvm = false;\n\n  private final String JVM_HTTP_PROXY_HOST = \"http.proxyHost\";\n  private final String JVM_HTTPS_PROXY_HOST = \"https.proxyHost\";\n  private final String JVM_HTTP_PROXY_PORT = \"http.proxyPort\";\n  private final String JVM_HTTPS_PROXY_PORT = \"https.proxyPort\";\n  private final String JVM_HTTP_NON_PROXY_HOSTS = \"http.nonProxyHosts\";\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ProxyConfig.class);\n\n  public String getHttpProxyHost() {\n    return finalHttpProxyHost;\n  }\n\n  public String getHttpsProxyHost() {\n    return finalHttpsProxyHost;\n  }\n\n  public int getHttpProxyPort() {\n    return finalHttpProxyPort;\n  }\n\n  public int getHttpsProxyPort() {\n    return finalHttpsProxyPort;\n  }\n\n  public String getNonProxyHosts() {\n    return finalNonProxyHosts;\n  }\n\n  public void setProxyHost(String proxyHost) {\n    this.proxyHost = proxyHost;\n  }\n\n  public void setProxyPort(int proxyPort) {\n    this.proxyPort = proxyPort;\n  }\n\n  public void setNonProxyHosts(String nonProxyHosts) {\n    this.nonProxyHosts = nonProxyHosts;\n  }\n\n  public ProxyConfig(String proxyHost, int proxyPort, String nonProxyHosts) {\n    jvmHttpProxyHost = Optional.ofNullable(systemGetProperty(JVM_HTTP_PROXY_HOST)).orElse(\"\");\n    jvmHttpsProxyHost = Optional.ofNullable(systemGetProperty(JVM_HTTPS_PROXY_HOST)).orElse(\"\");\n    jvmHttpProxyPort =\n        Optional.ofNullable(systemGetProperty(JVM_HTTP_PROXY_PORT))\n            .map(Integer::parseInt)\n            .orElse(-1);\n    jvmHttpsProxyPort =\n        Optional.ofNullable(systemGetProperty(JVM_HTTPS_PROXY_PORT))\n            .map(Integer::parseInt)\n            .orElse(-1);\n    jvmNonProxyHosts = Optional.ofNullable(systemGetProperty(JVM_HTTP_NON_PROXY_HOSTS)).orElse(\"\");\n    this.proxyHost = Optional.ofNullable(proxyHost).orElse(\"\");\n    this.proxyPort = proxyPort;\n    this.nonProxyHosts = Optional.ofNullable(nonProxyHosts).orElse(\"\");\n    resolveProxyConfigurations();\n  }\n\n  public ProxyConfig() {\n    this(null, -1, null);\n  }\n\n  public boolean isProxyEnabled() {\n    return isProxyEnabled;\n  }\n\n  public boolean isProxyEnabledOnJvm() {\n    return isProxyEnabledOnJvm;\n  }\n\n  /**\n   * This method reviews both the JVM and connection parameter configurations then concludes which\n   * settings to use 1.) Check if proxy settings were passed in the connection parameters, if so,\n   * then we use that right away. 2.) If connection parameters were not passed, then review JVM\n   * arguments and use those. 3.) If neither were set, then don't use any proxy settings (default).\n   */\n  private void resolveProxyConfigurations() {\n    // Both proxyHost and proxyPort connection parameters must be present.\n    StringBuilder sb = new StringBuilder();\n    logger.info(\"Resolving proxy configurations\");\n    sb.append(\"Proxy Configurations picked up from \");\n    if (!proxyHost.isEmpty() && proxyPort != -1) {\n      finalHttpProxyHost = proxyHost;\n      finalHttpsProxyHost = proxyHost;\n      finalHttpProxyPort = proxyPort;\n      finalHttpsProxyPort = proxyPort;\n      finalNonProxyHosts = nonProxyHosts;\n      isProxyEnabled = true;\n      sb.append(\"connection parameters:\\n\");\n      sb.append(\"proxyHost: \").append(proxyHost).append(\"\\n\");\n      sb.append(\"proxyPort: \").append(proxyPort).append(\"\\n\");\n      sb.append(\"nonProxyHosts: \").append(nonProxyHosts);\n    } else if ((!jvmHttpProxyHost.isEmpty() && jvmHttpProxyPort != -1)\n        || (!jvmHttpsProxyHost.isEmpty() && jvmHttpsProxyPort != -1)) {\n      finalHttpProxyHost = jvmHttpProxyHost;\n      finalHttpProxyPort = jvmHttpProxyPort;\n      finalHttpsProxyHost = jvmHttpsProxyHost;\n      finalHttpsProxyPort = jvmHttpsProxyPort;\n      finalNonProxyHosts = jvmNonProxyHosts;\n      isProxyEnabled = true;\n      isProxyEnabledOnJvm = true;\n      sb.append(\"JVM arguments:\\n\");\n      sb.append(\"-D\").append(JVM_HTTP_PROXY_HOST).append(\"=\").append(jvmHttpProxyHost).append(\"\\n\");\n      sb.append(\"-D\").append(JVM_HTTP_PROXY_PORT).append(\"=\").append(jvmHttpProxyPort).append(\"\\n\");\n      sb.append(\"-D\")\n          .append(JVM_HTTPS_PROXY_HOST)\n          .append(\"=\")\n          .append(jvmHttpsProxyHost)\n          .append(\"\\n\");\n      sb.append(\"-D\")\n          .append(JVM_HTTPS_PROXY_PORT)\n          .append(\"=\")\n          .append(jvmHttpsProxyPort)\n          .append(\"\\n\");\n    }\n    logger.info(sb.toString());\n  }\n\n  protected boolean isBypassProxy(String hostname) {\n    String nonProxyHosts = getNonProxyHosts();\n    if (nonProxyHosts == null || nonProxyHosts.isEmpty()) {\n      return false;\n    }\n    String[] nonProxyHostsArray = nonProxyHosts.split(\"\\\\|\");\n    for (String pattern : nonProxyHostsArray) {\n      if (SnowflakeUtil.hostnameMatchesGlob(hostname, pattern)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  public Proxy getProxy(SnowflakeEndpoint endpoint) {\n    if (!isProxyEnabled || isBypassProxy(endpoint.getHost())) {\n      return Proxy.NO_PROXY;\n    } else if (endpoint.isSslEnabled()) {\n      return (isHttpsProxyEnabled())\n          ? new Proxy(\n              Proxy.Type.HTTP, new InetSocketAddress(finalHttpsProxyHost, finalHttpsProxyPort))\n          : Proxy.NO_PROXY;\n    }\n    return (isHttpProxyEnabled())\n        ? new Proxy(Proxy.Type.HTTP, new InetSocketAddress(finalHttpProxyHost, finalHttpProxyPort))\n        : Proxy.NO_PROXY;\n  }\n\n  /*\n  Check that both http proxy host and http proxy port are set,\n  only then do we consider that http proxy is enabled.\n  */\n  private boolean isHttpProxyEnabled() {\n    return (!finalHttpProxyHost.isEmpty() || finalHttpProxyPort != -1);\n  }\n\n  /*\n  Check that both https proxy host and http proxy port are set,\n  only then do we consider that http proxy is enabled.\n  */\n  private boolean isHttpsProxyEnabled() {\n    return (!finalHttpsProxyHost.isEmpty() || finalHttpsProxyPort != -1);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/SnowflakeEndpoint.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport net.snowflake.client.internal.core.PrivateLinkDetector;\n\n/*\nThe SnowflakeEndpoint class represents an endpoint as returned by the System$allowlist() SQL\nfunction. Example:\n\n[{\"type\":\"SNOWFLAKE_DEPLOYMENT\",\"host\":\"snowhouse.snowflakecomputing.com\",\"port\":443},{\"type\":\"SNOWFLAKE_DEPLOYMENT_REGIONLESS\",\"host\":\"sfcogsops-snowhouse_aws_us_west_2.snowflakecomputing.com\",\"port\":443},{\"type\":\"STAGE\",\"host\":\"sfc-ds2-customer-stage.s3.amazonaws.com\",\"port\":443},{\"type\":\"STAGE\",\"host\":\"sfc-ds2-customer-stage.s3.us-west-2.amazonaws.com\",\"port\":443},{\"type\":\"STAGE\",\"host\":\"sfc-ds2-customer-stage.s3-us-west-2.amazonaws.com\",\"port\":443},{\"type\":\"SNOWSQL_REPO\",\"host\":\"sfc-repo.snowflakecomputing.com\",\"port\":443},{\"type\":\"OUT_OF_BAND_TELEMETRY\",\"host\":\"client-telemetry.snowflakecomputing.com\",\"port\":443},{\"type\":\"OCSP_CACHE\",\"host\":\"ocsp.snowflakecomputing.com\",\"port\":80},{\"type\":\"DUO_SECURITY\",\"host\":\"api-35a58de5.duosecurity.com\",\"port\":443},{\"type\":\"CLIENT_FAILOVER\",\"host\":\"sfcogsops-snowhouseprimary.snowflakecomputing.com\",\"port\":443},{\"type\":\"OCSP_RESPONDER\",\"host\":\"o.ss2.us\",\"port\":80},{\"type\":\"OCSP_RESPONDER\",\"host\":\"ocsp.r2m02.amazontrust.com\",\"port\":80},{\"type\":\"OCSP_RESPONDER\",\"host\":\"ocsp.sca1b.amazontrust.com\",\"port\":80},{\"type\":\"OCSP_RESPONDER\",\"host\":\"ocsp.rootg2.amazontrust.com\",\"port\":80},{\"type\":\"OCSP_RESPONDER\",\"host\":\"ocsp.rootca1.amazontrust.com\",\"port\":80},{\"type\":\"SNOWSIGHT_DEPLOYMENT\",\"host\":\"app.snowflake.com\",\"port\":443},{\"type\":\"SNOWSIGHT_DEPLOYMENT\",\"host\":\"apps-api.c1.us-west-2.aws.app.snowflake.com\",\"port\":443}]\n\n */\nclass SnowflakeEndpoint {\n  private final String type;\n  private final String host;\n  private final int port;\n  private final boolean isSecure;\n\n  public SnowflakeEndpoint(String type, String host, int port) {\n    this.type = type;\n    this.host = host;\n    this.port = port;\n    this.isSecure = (this.port == 443);\n  }\n\n  public String getType() {\n    return this.type;\n  }\n\n  public String getHost() {\n    return this.host;\n  }\n\n  public boolean isSslEnabled() {\n    return this.isSecure;\n  }\n\n  public int getPort() {\n    return this.port;\n  }\n\n  public boolean isPrivateLink() {\n    return PrivateLinkDetector.isPrivateLink(host);\n  }\n\n  @Override\n  public String toString() {\n    return this.host + \":\" + this.port;\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    boolean isSnowflakeEndpoint = o instanceof SnowflakeEndpoint;\n    if (!isSnowflakeEndpoint) {\n      return false;\n    }\n    if (!((SnowflakeEndpoint) o).getHost().equals(this.host)) {\n      return false;\n    }\n    if (((SnowflakeEndpoint) o).getPort() != this.port) {\n      return false;\n    }\n\n    if (!((SnowflakeEndpoint) o).getType().equals(this.type)) {\n      return false;\n    }\n\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/diagnostic/TcpDiagnosticCheck.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Proxy;\nimport java.net.Socket;\nimport java.net.SocketTimeoutException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\nclass TcpDiagnosticCheck extends DiagnosticCheck {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(TcpDiagnosticCheck.class);\n\n  TcpDiagnosticCheck(ProxyConfig proxyConfig) {\n    super(\"TCP Connection Test\", proxyConfig);\n  }\n\n  protected void doCheck(SnowflakeEndpoint snowflakeEndpoint) {\n    String hostname = snowflakeEndpoint.getHost();\n    int connectTimeoutMillis = 60000;\n    int port = snowflakeEndpoint.getPort();\n    Proxy proxy = proxyConf.getProxy(snowflakeEndpoint);\n    try (Socket socket = new Socket(proxy)) {\n      socket.bind(null);\n      logger.info(\n          \"Establishing TCP connection: {} -> {}:{}\",\n          socket.getLocalSocketAddress(),\n          snowflakeEndpoint.getHost(),\n          snowflakeEndpoint.getPort());\n      socket.connect(new InetSocketAddress(hostname, port), connectTimeoutMillis);\n      logger.info(\n          \"Established a TCP connection successfully: {} -> {}\",\n          socket.getLocalSocketAddress(),\n          socket.getRemoteSocketAddress());\n    } catch (SocketTimeoutException e) {\n      logger.error(\n          \"Could not establish TCP connection within timeout of \" + connectTimeoutMillis + \"ms\", e);\n    } catch (IOException e) {\n      logger.error(\"Error connecting to host \" + hostname + \":\" + port, e);\n    } catch (Exception e) {\n      logger.error(\"Unexpected error occurred when connecting to host \" + hostname + \":\" + port, e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/CSVMetricsExporter.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.BufferedWriter;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n// This class is internal - features implemented in this class should be treated as internal\n// features\n// and may be changed in the future. You shouldn't depend on these metrics.\npublic class CSVMetricsExporter {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(CSVMetricsExporter.class);\n\n  static final String CSV_EXPORTER_FILE_PROPERTY = \"metrics.csv.exporter.file\";\n  static final String CSV_EXPORTER_FLUSH_SIZE_PROPERTY = \"metrics.csv.exporter.flush.size\";\n\n  private final List<ExecTimeTelemetryData> entries = new ArrayList<>();\n  private final String filePath;\n  private final Integer flushSize;\n\n  CSVMetricsExporter(String filePath, int flushSize) {\n    this.filePath = filePath;\n    this.flushSize = flushSize;\n    Runtime.getRuntime().addShutdownHook(new Thread(this::flush));\n  }\n\n  private static CSVMetricsExporter instance;\n\n  public static synchronized CSVMetricsExporter getDefaultInstance() {\n    if (instance == null) {\n      String filePath = systemGetProperty(CSV_EXPORTER_FILE_PROPERTY);\n      int limit =\n          Integer.parseInt(\n              Optional.ofNullable(systemGetProperty(CSV_EXPORTER_FLUSH_SIZE_PROPERTY)).orElse(\"1\"));\n      instance = new CSVMetricsExporter(filePath, limit);\n    }\n    return instance;\n  }\n\n  public synchronized void save(ExecTimeTelemetryData data) {\n    if (isNullOrEmpty(filePath)) {\n      return;\n    }\n    entries.add(data);\n    if (entries.size() >= flushSize) {\n      flush();\n    }\n  }\n\n  private synchronized void flush() {\n    if (isNullOrEmpty(filePath)) {\n      return;\n    }\n\n    if (entries.isEmpty()) {\n      return;\n    }\n\n    Path path = Paths.get(filePath);\n\n    try {\n      Files.createDirectories(path.getParent());\n\n      boolean fileExists = Files.exists(path);\n\n      try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {\n        if (!fileExists) {\n          writer.write(\n              \"timestamp,sessionId,requestId,queryId,queryText,executeToSendTime,bindTime,gzipTime,httpClientTime,responseIOStreamTime,processResultChunkTime,createResultSetTime,queryTime\");\n          writer.newLine();\n        }\n\n        for (ExecTimeTelemetryData data : entries) {\n          writer.write(formatCsvRow(data));\n          writer.newLine();\n        }\n\n        writer.flush();\n      }\n\n    } catch (IOException e) {\n      logger.warn(\"Failed to write metrics to CSV file: {}\", filePath, e);\n    } finally {\n      // it's better to drop some metrics than have OOM\n      entries.clear();\n    }\n  }\n\n  private String formatCsvRow(ExecTimeTelemetryData data) {\n    return String.join(\n        \",\",\n        escapeCsvValue(String.valueOf(data.getTimestamp())),\n        escapeCsvValue(data.getSessionId()),\n        escapeCsvValue(data.getRequestId()),\n        escapeCsvValue(data.getQueryId()),\n        escapeCsvValue(data.getQueryText()),\n        String.valueOf(data.getExecuteToSend().getTime()),\n        String.valueOf(data.getBind().getTime()),\n        String.valueOf(data.getGzip().getTime()),\n        String.valueOf(data.getHttpClient().getTime()),\n        String.valueOf(data.getResponseIOStream().getTime()),\n        String.valueOf(data.getProcessResultChunk().getTime()),\n        String.valueOf(data.getCreateResultSet().getTime()),\n        String.valueOf(data.getQuery().getTime()));\n  }\n\n  private String escapeCsvValue(String value) {\n    if (value == null) {\n      return \"\";\n    }\n    // If value contains comma, quote, or newline, wrap in quotes and escape internal quotes\n    if (value.contains(\",\")\n        || value.contains(\"\\\"\")\n        || value.contains(\"\\n\")\n        || value.contains(\"\\r\")) {\n      return \"\\\"\" + value.replace(\"\\\"\", \"\\\"\\\"\") + \"\\\"\";\n    }\n    return value;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/ExecTimeTelemetryData.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport java.time.LocalDateTime;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.util.TimeMeasurement;\n\npublic class ExecTimeTelemetryData {\n  // Measures time from when the client initiated a query (with executeQuery) until it is sent via\n  // HTTP.\n  private final TimeMeasurement executeToSend = new TimeMeasurement();\n  // Measures time from when the client initiated a query (with executeQuery) until the control is\n  // returned to the user.\n  private final TimeMeasurement query = new TimeMeasurement();\n  // Measures time from when binding preparation is started (including pushing to stage, if needed)\n  // until it is ended.\n  private final TimeMeasurement bind = new TimeMeasurement();\n  // Measures time spent on compressing the request.\n  private final TimeMeasurement gzip = new TimeMeasurement();\n  // Measures time spent on HTTP roundtrip, except for downloading a response body.\n  private final TimeMeasurement httpClient = new TimeMeasurement();\n  // Measures time spent on response body download.\n  private final TimeMeasurement responseIOStream = new TimeMeasurement();\n  // Measures time spent on parsing result chunk.\n  private final TimeMeasurement processResultChunk = new TimeMeasurement();\n  // Measures time spent on creating a result set from parsed data.\n  private final TimeMeasurement createResultSet = new TimeMeasurement();\n\n  private String batchId;\n  private String queryId;\n  private String queryFunction;\n  private int retryCount = 0;\n  private String retryLocations = \"\";\n  private Boolean ocspEnabled = false;\n  boolean sendData = true;\n\n  private String requestId;\n  private String sessionId;\n  private String queryText;\n  private final LocalDateTime timestamp;\n\n  public ExecTimeTelemetryData(String queryFunction, String batchId) {\n    this.timestamp = LocalDateTime.now();\n    this.query.setStart();\n    this.executeToSend.setStart();\n    this.queryFunction = queryFunction;\n    this.batchId = batchId;\n    if (!TelemetryService.getInstance().isHTAPEnabled()) {\n      this.sendData = false;\n    }\n  }\n\n  public ExecTimeTelemetryData() {\n    this.timestamp = LocalDateTime.now();\n    this.sendData = false;\n  }\n\n  public void setBindStart() {\n    bind.setStart();\n  }\n\n  public void setOCSPStatus(Boolean ocspEnabled) {\n    this.ocspEnabled = ocspEnabled;\n  }\n\n  public void setBindEnd() {\n    this.bind.setEnd();\n  }\n\n  public void setHttpClientStart() {\n    httpClient.setStart();\n  }\n\n  public void setHttpClientEnd() {\n    httpClient.setEnd();\n  }\n\n  public void setGzipStart() {\n    gzip.setStart();\n  }\n\n  public void setGzipEnd() {\n    gzip.setEnd();\n  }\n\n  public void setQueryEnd() {\n    query.setEnd();\n  }\n\n  public void setExecuteToSendQueryEnd() {\n    executeToSend.setEnd();\n  }\n\n  public void setQueryId(String queryId) {\n    this.queryId = queryId;\n  }\n\n  public void setProcessResultChunkStart() {\n    processResultChunk.setStart();\n  }\n\n  public void setProcessResultChunkEnd() {\n    processResultChunk.setEnd();\n  }\n\n  public void setResponseIOStreamStart() {\n    responseIOStream.setStart();\n  }\n\n  public void setResponseIOStreamEnd() {\n    responseIOStream.setEnd();\n  }\n\n  public void setCreateResultSetStart() {\n    createResultSet.setStart();\n  }\n\n  public void setCreateResultSetEnd() {\n    createResultSet.setEnd();\n  }\n\n  public void incrementRetryCount() {\n    this.retryCount++;\n  }\n\n  public void setRequestId(String requestId) {\n    this.requestId = requestId;\n  }\n\n  public void addRetryLocation(String location) {\n    if (isNullOrEmpty(this.retryLocations)) {\n      this.retryLocations = location;\n    } else {\n      this.retryLocations = this.retryLocations.concat(\", \").concat(location);\n    }\n  }\n\n  long getTotalQueryTime() {\n    return query.getTime();\n  }\n\n  long getResultProcessingTime() {\n    if (createResultSet.getEnd() == 0 || processResultChunk.getStart() == 0) {\n      return -1;\n    }\n\n    return createResultSet.getEnd() - processResultChunk.getStart();\n  }\n\n  long getHttpRequestTime() {\n    return httpClient.getTime();\n  }\n\n  long getResultSetCreationTime() {\n    return createResultSet.getTime();\n  }\n\n  public void setSessionId(String sessionId) {\n    this.sessionId = sessionId;\n  }\n\n  public void setQueryText(String sql) {\n    this.queryText = sql;\n  }\n\n  public String getSessionId() {\n    return sessionId;\n  }\n\n  public String getQueryText() {\n    return queryText;\n  }\n\n  public String getRequestId() {\n    return requestId;\n  }\n\n  public String getQueryId() {\n    return queryId;\n  }\n\n  public TimeMeasurement getExecuteToSend() {\n    return executeToSend;\n  }\n\n  public TimeMeasurement getBind() {\n    return bind;\n  }\n\n  public TimeMeasurement getGzip() {\n    return gzip;\n  }\n\n  public TimeMeasurement getHttpClient() {\n    return httpClient;\n  }\n\n  public TimeMeasurement getResponseIOStream() {\n    return responseIOStream;\n  }\n\n  public TimeMeasurement getProcessResultChunk() {\n    return processResultChunk;\n  }\n\n  public TimeMeasurement getCreateResultSet() {\n    return createResultSet;\n  }\n\n  public TimeMeasurement getQuery() {\n    return query;\n  }\n\n  public LocalDateTime getTimestamp() {\n    return timestamp;\n  }\n\n  public String generateTelemetry() {\n    CSVMetricsExporter.getDefaultInstance().save(this);\n    if (this.sendData) {\n      String eventType = \"ExecutionTimeRecord\";\n      JSONObject value = new JSONObject();\n      String valueStr;\n      value.put(\"eventType\", eventType);\n      value.put(\"Timestamp\", this.timestamp.toString());\n      value.put(\"QueryStart\", this.query.getStart());\n      value.put(\"ExecuteToSendStart\", this.executeToSend.getStart());\n      value.put(\"ExecuteToSendEnd\", this.executeToSend.getEnd());\n      value.put(\"BindStart\", this.bind.getStart());\n      value.put(\"BindEnd\", this.bind.getEnd());\n      value.put(\"GzipStart\", this.gzip.getStart());\n      value.put(\"GzipEnd\", this.gzip.getEnd());\n      value.put(\"HttpClientStart\", this.httpClient.getStart());\n      value.put(\"HttpClientEnd\", this.httpClient.getEnd());\n      value.put(\"ResponseIOStreamStart\", this.responseIOStream.getStart());\n      value.put(\"ResponseIOStreamEnd\", this.responseIOStream.getEnd());\n      value.put(\"ProcessResultChunkStart\", this.processResultChunk.getStart());\n      value.put(\"ProcessResultChunkEnd\", this.processResultChunk.getEnd());\n      value.put(\"CreateResultSetStart\", this.createResultSet.getStart());\n      value.put(\"CreateResultSetEnd\", this.createResultSet.getEnd());\n      value.put(\"QueryEnd\", this.query.getEnd());\n      value.put(\"BatchID\", this.batchId);\n      value.put(\"QueryID\", this.queryId);\n      value.put(\"RequestID\", this.requestId);\n      value.put(\"QueryFunction\", this.queryFunction);\n      value.put(\"RetryCount\", this.retryCount);\n      value.put(\"RetryLocations\", this.retryLocations);\n      value.put(\"ocspEnabled\", this.ocspEnabled);\n      value.put(\"ElapsedQueryTime\", getTotalQueryTime());\n      value.put(\"ElapsedResultProcessTime\", getResultProcessingTime());\n      value.put(\"Urgent\", true);\n      valueStr = value.toString(); // Avoid adding exception stacktrace to user logs.\n      TelemetryService.getInstance().logExecutionTimeTelemetryEvent(value, eventType);\n      return valueStr;\n    }\n    return \"\";\n  }\n\n  public String toString() {\n    return \"Query id: \"\n        + this.queryId\n        + \", query function: \"\n        + this.queryFunction\n        + \", batch id: \"\n        + this.batchId\n        + \", request id: \"\n        + this.requestId\n        + \", total query time: \"\n        + getTotalQueryTime() / 1000\n        + \" ms\"\n        + \", prepare to sent time: \"\n        + this.executeToSend.getTime() / 1000\n        + \" ms\"\n        + \", result processing time: \"\n        + getResultProcessingTime() / 1000\n        + \" ms\"\n        + \", result set creation time: \"\n        + getResultSetCreationTime() / 1000\n        + \" ms\"\n        + \", http request time: \"\n        + getHttpRequestTime() / 1000\n        + \" ms\"\n        + \", retry count: \"\n        + this.retryCount;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/InternalApiTelemetryTracker.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicLong;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Tracks calls to public methods in internal packages and reports them as in-band telemetry. */\npublic class InternalApiTelemetryTracker {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(InternalApiTelemetryTracker.class);\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  private static final ConcurrentHashMap<String, AtomicLong> methodCallCounts =\n      new ConcurrentHashMap<>();\n\n  /** Marker used by internal call paths to skip external-usage telemetry. */\n  public static final class InternalCallMarker {\n    private InternalCallMarker() {}\n  }\n\n  private static final InternalCallMarker INTERNAL_CALL_MARKER = new InternalCallMarker();\n\n  private InternalApiTelemetryTracker() {}\n\n  public static InternalCallMarker internalCallMarker() {\n    return INTERNAL_CALL_MARKER;\n  }\n\n  public static void recordIfExternal(\n      String className, String methodName, InternalCallMarker internalCallMarker) {\n    if (internalCallMarker == null) {\n      record(className, methodName);\n    }\n  }\n\n  static void record(String className, String methodName) {\n    methodCallCounts\n        .computeIfAbsent(className + \"#\" + methodName, k -> new AtomicLong(0))\n        .incrementAndGet();\n  }\n\n  public static void flush(Telemetry client) {\n    if (client == null || methodCallCounts.isEmpty()) {\n      return;\n    }\n\n    try {\n      ObjectNode message = mapper.createObjectNode();\n      message.put(TelemetryField.TYPE.toString(), TelemetryField.INTERNAL_API_USAGE.toString());\n      message.put(\"source\", \"JDBC\");\n\n      ObjectNode methods = mapper.createObjectNode();\n      methodCallCounts.forEach(\n          (key, count) -> {\n            long value = count.getAndSet(0);\n            if (value > 0) {\n              methods.put(key, value);\n            }\n          });\n      methodCallCounts.entrySet().removeIf(e -> e.getValue().get() == 0);\n\n      if (methods.isEmpty()) {\n        return;\n      }\n\n      message.set(\"methods\", methods);\n      client.addLogToBatch(new TelemetryData(message, System.currentTimeMillis()));\n      logger.debug(\"Flushed internal API usage telemetry with {} distinct methods\", methods.size());\n    } catch (Exception e) {\n      logger.debug(\"Failed to flush internal API telemetry: {}\", e.getMessage());\n    }\n  }\n\n  static void resetForTesting() {\n    methodCallCounts.clear();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/NoOpTelemetryClient.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Future;\n\n/** Telemetry client that is doing nothing. Mainly used in testing code */\npublic class NoOpTelemetryClient implements Telemetry {\n  @Override\n  public void addLogToBatch(TelemetryData log) {}\n\n  @Override\n  public void close() {}\n\n  @Override\n  public Future<Boolean> sendBatchAsync() {\n    return CompletableFuture.completedFuture(true);\n  }\n\n  @Override\n  public void postProcess(String queryId, String sqlState, int vendorCode, Throwable ex) {}\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/PreSessionTelemetryClient.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * A telemetry client that buffers telemetry data until a real telemetry client becomes available.\n * Used for scenario where telemetry needs to be collected before a session is established, such as\n * during SSL/TLS setup and certificate validation.\n */\npublic class PreSessionTelemetryClient implements Telemetry {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(PreSessionTelemetryClient.class);\n\n  private final List<TelemetryData> bufferedData = new ArrayList<>();\n  private final Lock lock = new ReentrantLock();\n  private Telemetry realTelemetryClient = null;\n  private boolean closed = false;\n\n  // Prevent potential memory issues by limiting buffer size\n  private static final int MAX_BUFFER_SIZE = 1000;\n\n  @Override\n  public void addLogToBatch(TelemetryData log) {\n    if (closed || log == null) {\n      return;\n    }\n\n    lock.lock();\n    try {\n      if (realTelemetryClient != null) {\n        // Real client available, use it directly\n        realTelemetryClient.addLogToBatch(log);\n      } else {\n        if (bufferedData.size() < MAX_BUFFER_SIZE) {\n          bufferedData.add(log);\n          logger.debug(\"Buffered telemetry data, buffer size: {}\", bufferedData.size());\n        } else {\n          logger.debug(\n              \"Telemetry buffer full (size: {}), dropping telemetry data to prevent memory issues\",\n              MAX_BUFFER_SIZE);\n        }\n      }\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  public void setRealTelemetryClient(Telemetry realClient) {\n    lock.lock();\n    try {\n      if (closed) {\n        logger.debug(\"PreSessionTelemetryClient is closed, ignoring real client\");\n        return;\n      }\n      this.realTelemetryClient = realClient;\n      flushBufferedData(realClient);\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  private void flushBufferedData(Telemetry realClient) {\n    for (TelemetryData data : bufferedData) {\n      try {\n        realClient.addLogToBatch(data);\n      } catch (Exception e) {\n        logger.debug(\"Failed to flush buffered telemetry data: {}\", e.getMessage());\n      }\n    }\n    bufferedData.clear();\n  }\n\n  @Override\n  public Future<Boolean> sendBatchAsync() {\n    lock.lock();\n    try {\n      if (realTelemetryClient != null) {\n        return realTelemetryClient.sendBatchAsync();\n      }\n      return CompletableFuture.completedFuture(true);\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public void close() {\n    lock.lock();\n    try {\n      if (closed) {\n        return;\n      }\n\n      closed = true;\n\n      if (realTelemetryClient != null) {\n        try {\n          realTelemetryClient.close();\n        } catch (Exception e) {\n          logger.debug(\"Error closing telemetry client: {}\", e.getMessage());\n        }\n      }\n\n      if (!bufferedData.isEmpty()) {\n        logger.debug(\n            \"Closing PreSessionTelemetryClient with {} unflushed entries\", bufferedData.size());\n      }\n\n      bufferedData.clear();\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  @Override\n  public void postProcess(String queryId, String sqlState, int vendorCode, Throwable ex) {\n    lock.lock();\n    try {\n      if (realTelemetryClient != null) {\n        realTelemetryClient.postProcess(queryId, sqlState, vendorCode, ex);\n      }\n    } finally {\n      lock.unlock();\n    }\n  }\n\n  public boolean hasRealTelemetryClient() {\n    return realTelemetryClient != null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/RevocationCheckTelemetryData.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\npublic class RevocationCheckTelemetryData {\n  private String crlUrl;\n  private long timeParsingCrl;\n  private long timeDownloadingCrl;\n  private long crlBytes;\n  private int numberOfRevokedCertificates;\n\n  public void setTimeParsingCrl(long timeParsingCrl) {\n    this.timeParsingCrl = timeParsingCrl;\n  }\n\n  public void setTimeDownloadingCrl(long timeDownloadingCrl) {\n    this.timeDownloadingCrl = timeDownloadingCrl;\n  }\n\n  public void setCrlUrl(String crlUrl) {\n    this.crlUrl = crlUrl;\n  }\n\n  public void setCrlBytes(long crlBytes) {\n    this.crlBytes = crlBytes;\n  }\n\n  public void setNumberOfRevokedCertificates(int numberOfRevokedCertificates) {\n    this.numberOfRevokedCertificates = numberOfRevokedCertificates;\n  }\n\n  public TelemetryData buildTelemetry() {\n    return TelemetryUtil.buildCrlData(\n        crlUrl, crlBytes, numberOfRevokedCertificates, timeDownloadingCrl, timeParsingCrl);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/SqlExceptionTelemetryHandler.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.sql.SQLException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryEvent;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.LoginInfoDTO;\nimport net.snowflake.common.core.SqlState;\n\n/** Handler for SQL exception telemetry reporting. */\npublic class SqlExceptionTelemetryHandler {\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(SqlExceptionTelemetryHandler.class);\n\n  /**\n   * Send telemetry data for a SQL exception. This method attempts to send data via in-band\n   * telemetry if a session is available, falling back to out-of-band telemetry if needed.\n   *\n   * @param queryId query ID if exists\n   * @param sqlState SQL state\n   * @param vendorCode vendor code\n   * @param session session object (needed for in-band telemetry, may be null)\n   * @param ex the SQLException being reported\n   */\n  public static void sendTelemetry(\n      String queryId, String sqlState, int vendorCode, SFBaseSession session, SQLException ex) {\n    Telemetry ibInstance = null;\n    // if session is not null, try sending data using in-band telemetry\n    if (session != null) {\n      ibInstance = session.getTelemetryClient(internalCallMarker());\n    }\n    // if in-band instance is successfully created, compile sql exception data into an in-band\n    // telemetry log\n    if (ibInstance != null) {\n      ObjectNode ibValue =\n          TelemetryUtil.createIBValue(\n              queryId, sqlState, vendorCode, TelemetryField.SQL_EXCEPTION, null, null);\n      // try to send in-band data asynchronously\n      ExecutorService threadExecutor = Executors.newSingleThreadExecutor();\n      Telemetry finalIbInstance = ibInstance;\n      try {\n        threadExecutor.submit(\n            () -> {\n              boolean inBandSuccess;\n              Future<Boolean> sendInBand = sendInBandTelemetryMessage(ibValue, ex, finalIbInstance);\n              // record whether in band telemetry message sent with boolean value inBandSuccess\n              try {\n                inBandSuccess = sendInBand.get(10, TimeUnit.SECONDS);\n              } catch (Exception e) {\n                inBandSuccess = false;\n              }\n              // In-band failed so send OOB telemetry instead\n              if (!inBandSuccess) {\n                logger.debug(\n                    \"In-band telemetry message failed to send. Sending out-of-band message instead\");\n                JSONObject oobValue = createOOBValue(queryId, sqlState, vendorCode);\n                sendOutOfBandTelemetryMessage(oobValue, ex, TelemetryService.getInstance());\n              }\n            });\n      } finally {\n        // Send the shutdown signal to the executor service\n        threadExecutor.shutdown();\n\n        // Add an extra hook in the telemetry client, if extra error handling is needed\n        ibInstance.postProcess(queryId, sqlState, vendorCode, ex);\n      }\n    }\n    // In-band is not possible so send OOB telemetry instead\n    else {\n      JSONObject oobValue = createOOBValue(queryId, sqlState, vendorCode);\n      sendOutOfBandTelemetryMessage(oobValue, ex, TelemetryService.getInstance());\n    }\n  }\n\n  /**\n   * Create a TelemetryEvent log from the JSONObject and exception and send it via OOB telemetry.\n   *\n   * @param value JSONObject containing relevant exception information\n   * @param ex the SQLException being reported\n   * @param oobInstance out-of-band telemetry instance\n   */\n  private static void sendOutOfBandTelemetryMessage(\n      JSONObject value, SQLException ex, TelemetryService oobInstance) {\n    TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n    StringWriter sw = new StringWriter();\n    PrintWriter pw = new PrintWriter(sw);\n    ex.printStackTrace(pw);\n    String stackTrace = maskStacktrace(sw.toString());\n    value.put(\"Stacktrace\", stackTrace);\n    value.put(\"Exception\", ex.getClass().getSimpleName());\n    TelemetryEvent log =\n        logBuilder.withName(\"Exception: \" + ex.getClass().getSimpleName()).withValue(value).build();\n    oobInstance.report(log);\n  }\n\n  /**\n   * Create a TelemetryClient log and send it via in-band telemetry.\n   *\n   * @param value ObjectNode containing exception information\n   * @param ex the SQLException being reported\n   * @param ibInstance telemetry instance\n   * @return future indicating whether the message was sent successfully\n   */\n  private static Future<Boolean> sendInBandTelemetryMessage(\n      ObjectNode value, SQLException ex, Telemetry ibInstance) {\n    StringWriter sw = new StringWriter();\n    PrintWriter pw = new PrintWriter(sw);\n    ex.printStackTrace(pw);\n    String stackTrace = maskStacktrace(sw.toString());\n    value.put(\"Stacktrace\", stackTrace);\n    value.put(\"Exception\", ex.getClass().getSimpleName());\n    // For SQLFeatureNotSupportedExceptions, add in reason for failure as \"<function name> not\n    // supported\"\n    if (value.get(\"SQLState\").toString().contains(SqlState.FEATURE_NOT_SUPPORTED)) {\n      String reason = \"\";\n      StackTraceElement[] stackTraceArray = ex.getStackTrace();\n      if (stackTraceArray.length >= 1) {\n        reason = ex.getStackTrace()[0].getMethodName() + \" not supported\";\n      }\n      value.put(\"reason\", reason);\n    }\n    ibInstance.addLogToBatch(TelemetryUtil.buildJobData(value));\n    return ibInstance.sendBatchAsync();\n  }\n\n  /**\n   * Helper function to remove sensitive data (error message, reason) from the stacktrace.\n   *\n   * @param stackTrace original stacktrace\n   * @return stack trace with sensitive data removed\n   */\n  public static String maskStacktrace(String stackTrace) {\n    Pattern STACKTRACE_BEGINNING =\n        Pattern.compile(\n            \"(com|net)(\\\\.snowflake\\\\.client\\\\.api\\\\.exception\\\\.Snowflake|\\\\.snowflake\\\\.client\\\\.internal\\\\.exception\\\\.Snowflake|\\\\.snowflake\\\\.client\\\\.jdbc\\\\.Snowflake)(SQLLogged|LoggedFeatureNotSupported|SQL)(Exception)([\\\\s\\\\S]*?)(\\\\n\\\\t?at\\\\snet|com\\\\.)\",\n            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);\n    Matcher matcher = STACKTRACE_BEGINNING.matcher(stackTrace);\n    // Remove the reason from after the stack trace (in group #5 of regex pattern)\n    if (matcher.find()) {\n      return matcher.replaceAll(\"$1$2$3$4$6\");\n    }\n    return stackTrace;\n  }\n\n  /**\n   * Helper function to create JSONObject node for OOB telemetry log.\n   *\n   * @param queryId query ID\n   * @param sqlState the SQL state\n   * @param vendorCode the vendor code\n   * @return JSONObject with data about SQLException\n   */\n  public static JSONObject createOOBValue(String queryId, String sqlState, int vendorCode) {\n    JSONObject oobValue = new JSONObject();\n    oobValue.put(TelemetryField.TYPE.toString(), TelemetryField.SQL_EXCEPTION.toString());\n    oobValue.put(TelemetryField.DRIVER_TYPE.toString(), LoginInfoDTO.SF_JDBC_APP_ID);\n    oobValue.put(\n        TelemetryField.DRIVER_VERSION.toString(), SnowflakeDriver.getImplementationVersion());\n    if (!isNullOrEmpty(queryId)) {\n      oobValue.put(TelemetryField.QUERY_ID.toString(), queryId);\n    }\n    if (!isNullOrEmpty(sqlState)) {\n      oobValue.put(TelemetryField.SQL_STATE.toString(), sqlState);\n    }\n    if (vendorCode != TelemetryUtil.NO_VENDOR_CODE) {\n      oobValue.put(TelemetryField.ERROR_NUMBER.toString(), vendorCode);\n    }\n    return oobValue;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/Telemetry.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport java.util.concurrent.Future;\n\npublic interface Telemetry {\n  /**\n   * Attempt to add log to batch, and suppress exceptions thrown in case of failure\n   *\n   * @param log entry to add\n   */\n  void addLogToBatch(TelemetryData log);\n\n  /** Close telemetry connector and send any unsubmitted logs */\n  void close();\n\n  /**\n   * Send all cached logs to server\n   *\n   * @return future indicating whether the logs were sent successfully\n   */\n  Future<Boolean> sendBatchAsync();\n\n  /**\n   * A hook for post-processing after sending telemetry data. Can be used, for example, for\n   * additional error handling.\n   *\n   * @param queryId The query id\n   * @param sqlState The SQL state as defined in net.snowflake.common.core.SqlState\n   * @param vendorCode The vendor code for localized messages\n   * @param ex The throwable that caused this.\n   */\n  void postProcess(String queryId, String sqlState, int vendorCode, Throwable ex);\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryClient.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.internalCallMarker;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.IOException;\nimport java.rmi.UnexpectedException;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.LinkedList;\nimport java.util.Objects;\nimport java.util.concurrent.Future;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryThreadPool;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport org.apache.http.HttpHeaders;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\n\n/** Telemetry Service Interface */\npublic class TelemetryClient implements Telemetry {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(TelemetryClient.class);\n\n  private static final String SF_PATH_TELEMETRY = \"/telemetry/send\";\n  private static final String SF_PATH_TELEMETRY_SESSIONLESS = \"/telemetry/send/sessionless\";\n\n  // if the number of cached logs is larger than this threshold,\n  // the telemetry connector will flush the buffer automatically.\n  private final int forceFlushSize;\n\n  private static final int DEFAULT_FORCE_FLUSH_SIZE = 100;\n\n  private final String serverUrl;\n  private final String telemetryUrl;\n\n  private final SFSession session;\n  private LinkedList<TelemetryData> logBatch;\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  private boolean isClosed;\n\n  // HTTP client object used to communicate with other machine\n  private final CloseableHttpClient httpClient;\n\n  // the authorization type speficied in sessionless header\n  private String authType;\n\n  // JWT/OAuth token\n  private String token;\n\n  private Object locker = new Object();\n\n  // false if meet any error when sending metrics\n  private boolean isTelemetryServiceAvailable = true;\n\n  // Retry timeout for the HTTP request\n  private static final int TELEMETRY_HTTP_RETRY_TIMEOUT_IN_SEC = 1000;\n\n  private TelemetryClient(SFSession session, int flushSize) {\n    this.session = session;\n    this.serverUrl = session.getUrl();\n    this.httpClient = null;\n\n    if (this.serverUrl.endsWith(\"/\")) {\n      this.telemetryUrl =\n          this.serverUrl.substring(0, this.serverUrl.length() - 1) + SF_PATH_TELEMETRY;\n    } else {\n      this.telemetryUrl = this.serverUrl + SF_PATH_TELEMETRY;\n    }\n\n    this.logBatch = new LinkedList<>();\n    this.isClosed = false;\n    this.forceFlushSize = flushSize;\n  }\n\n  /**\n   * Constructor for creating a sessionless telemetry client\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param serverUrl server url\n   * @param authType authorization type, should be either KEYPAIR_JWY or OAUTH\n   * @param flushSize maximum size of telemetry batch before flush\n   */\n  private TelemetryClient(\n      CloseableHttpClient httpClient, String serverUrl, String authType, int flushSize) {\n    this.session = null;\n    this.serverUrl = serverUrl;\n    this.httpClient = httpClient;\n\n    if (!Objects.equals(authType, \"KEYPAIR_JWT\") && !Objects.equals(authType, \"OAUTH\")) {\n      throw new IllegalArgumentException(\n          \"Invalid authType, should be \\\"KEYPAIR_JWT\\\" or \\\"OAUTH\\\"\");\n    }\n    this.authType = authType;\n\n    if (this.serverUrl.endsWith(\"/\")) {\n      this.telemetryUrl =\n          this.serverUrl.substring(0, this.serverUrl.length() - 1) + SF_PATH_TELEMETRY_SESSIONLESS;\n    } else {\n      this.telemetryUrl = this.serverUrl + SF_PATH_TELEMETRY_SESSIONLESS;\n    }\n\n    this.logBatch = new LinkedList<>();\n    this.isClosed = false;\n    this.forceFlushSize = flushSize;\n    logger.debug(\n        \"Initializing telemetry client with telemetry url: {}, flush size: {}, auth type: {}\",\n        telemetryUrl,\n        forceFlushSize,\n        authType);\n  }\n\n  /**\n   * Return whether the client can be used to add/send metrics\n   *\n   * @return whether client is enabled\n   */\n  public boolean isTelemetryEnabled() {\n    return (this.session == null || this.session.isClientTelemetryEnabled())\n        && this.isTelemetryServiceAvailable;\n  }\n\n  /** Disable any use of the client to add/send metrics */\n  public void disableTelemetry() {\n    logger.debug(\"Disabling telemetry\");\n    this.isTelemetryServiceAvailable = false;\n  }\n\n  /**\n   * Initialize the telemetry connector\n   *\n   * @param conn connection with the session to use for the connector\n   * @param flushSize maximum size of telemetry batch before flush\n   * @return a telemetry connector\n   */\n  public static Telemetry createTelemetry(Connection conn, int flushSize) {\n    try {\n      return createTelemetry(\n          (SFSession)\n              conn.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession(internalCallMarker()),\n          flushSize);\n    } catch (SQLException ex) {\n      logger.debug(\"Input connection is not a SnowflakeConnection\", false);\n      return null;\n    }\n  }\n\n  /**\n   * Initialize the telemetry connector\n   *\n   * @param conn connection with the session to use for the connector\n   * @return a telemetry connector\n   */\n  public static Telemetry createTelemetry(Connection conn) {\n    return createTelemetry(conn, DEFAULT_FORCE_FLUSH_SIZE);\n  }\n\n  /**\n   * Initialize the telemetry connector\n   *\n   * @param session session to use for telemetry dumps\n   * @return a telemetry connector\n   */\n  public static Telemetry createTelemetry(SFSession session) {\n    return createTelemetry(session, DEFAULT_FORCE_FLUSH_SIZE);\n  }\n\n  /**\n   * Initialize the telemetry connector\n   *\n   * @param session session to use for telemetry dumps\n   * @param flushSize maximum size of telemetry batch before flush\n   * @return a telemetry connector\n   */\n  public static Telemetry createTelemetry(SFSession session, int flushSize) {\n    return new TelemetryClient(session, flushSize);\n  }\n\n  /**\n   * Initialize the sessionless telemetry connector using KEYPAIR_JWT as the default auth type\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param serverUrl server url\n   * @return a telemetry connector\n   */\n  public static Telemetry createSessionlessTelemetry(\n      CloseableHttpClient httpClient, String serverUrl) {\n    // By default, use KEYPAIR_JWT as the auth type\n    return createSessionlessTelemetry(\n        httpClient, serverUrl, \"KEYPAIR_JWT\", DEFAULT_FORCE_FLUSH_SIZE);\n  }\n\n  /**\n   * Initialize the sessionless telemetry connector\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param serverUrl server url\n   * @param authType authorization type for sessionless telemetry\n   * @return a telemetry connector\n   */\n  public static Telemetry createSessionlessTelemetry(\n      CloseableHttpClient httpClient, String serverUrl, String authType) {\n    return createSessionlessTelemetry(httpClient, serverUrl, authType, DEFAULT_FORCE_FLUSH_SIZE);\n  }\n\n  /**\n   * Initialize the sessionless telemetry connector\n   *\n   * @param httpClient client object used to communicate with other machine\n   * @param serverUrl server url\n   * @param authType authorization type for sessionless telemetry\n   * @param flushSize maximum size of telemetry batch before flush\n   * @return a telemetry connector\n   */\n  public static Telemetry createSessionlessTelemetry(\n      CloseableHttpClient httpClient, String serverUrl, String authType, int flushSize) {\n    return new TelemetryClient(httpClient, serverUrl, authType, flushSize);\n  }\n\n  /**\n   * Add log to batch to be submitted to telemetry. Send batch if forceFlushSize reached\n   *\n   * @param log entry to add\n   */\n  @Override\n  public void addLogToBatch(TelemetryData log) {\n    if (isClosed) {\n      logger.debug(\"Telemetry already closed\", false);\n      return;\n    }\n\n    if (!isTelemetryEnabled()) {\n      return; // if disable, do nothing\n    }\n\n    synchronized (locker) {\n      this.logBatch.add(log);\n    }\n\n    int logBatchSize = this.logBatch.size();\n    if (logBatchSize >= this.forceFlushSize) {\n      logger.debug(\"Force flushing telemetry batch of size: {}\", logBatchSize);\n      this.sendBatchAsync();\n    }\n  }\n\n  /**\n   * Add log to batch to be submitted to telemetry. Send batch if forceFlushSize reached\n   *\n   * @param message json node of log\n   * @param timeStamp timestamp to use for log\n   */\n  public void addLogToBatch(ObjectNode message, long timeStamp) {\n    this.addLogToBatch(new TelemetryData(message, timeStamp));\n  }\n\n  /** Close telemetry connector and send any unsubmitted logs */\n  @Override\n  public void close() {\n    if (isClosed) {\n      logger.debug(\"Telemetry client already closed\", false);\n      return;\n    }\n\n    try {\n      // sendBatch when close is synchronous, otherwise client might be closed\n      // before data was sent.\n      sendBatchAsync().get();\n    } catch (Throwable e) {\n      logger.debug(\"Error when sending batch data, {}\", e);\n    } finally {\n      this.isClosed = true;\n    }\n  }\n\n  /**\n   * Return whether the client has been closed\n   *\n   * @return whether client is closed\n   */\n  public boolean isClosed() {\n    return this.isClosed;\n  }\n\n  @Override\n  public Future<Boolean> sendBatchAsync() {\n    return TelemetryThreadPool.getInstance()\n        .submit(\n            () -> {\n              try {\n                return this.sendBatch();\n              } catch (Throwable e) {\n                logger.debug(\"Failed to send telemetry data, {}\", e);\n                return false;\n              }\n            });\n  }\n\n  @Override\n  public void postProcess(String queryId, String sqlState, int vendorCode, Throwable ex) {\n    // This is a no-op.\n  }\n\n  /**\n   * Send all cached logs to server\n   *\n   * @return whether the logs were sent successfully\n   * @throws IOException if closed or uploading batch fails\n   */\n  private boolean sendBatch() throws IOException {\n    if (isClosed) {\n      throw new IOException(\"Telemetry connector is closed\");\n    }\n    if (!isTelemetryEnabled()) {\n      return false;\n    }\n\n    LinkedList<TelemetryData> tmpList;\n    synchronized (locker) {\n      tmpList = this.logBatch;\n      this.logBatch = new LinkedList<>();\n    }\n\n    if (this.session != null && this.session.isClosed()) {\n      throw new UnexpectedException(\"Session is closed when sending log\");\n    }\n\n    if (!tmpList.isEmpty()) {\n      Stopwatch stopwatch = new Stopwatch();\n      stopwatch.start();\n      // session shared with JDBC\n      String payload = logsToString(tmpList);\n\n      logger.debugNoMask(\"Payload of telemetry is : \" + payload);\n\n      HttpPost post = new HttpPost(this.telemetryUrl);\n      post.setEntity(new StringEntity(payload));\n      post.setHeader(\"Content-type\", \"application/json\");\n\n      if (this.session == null) {\n        post.setHeader(HttpHeaders.AUTHORIZATION, \"Bearer \" + this.token);\n        post.setHeader(\"X-Snowflake-Authorization-Token-Type\", this.authType);\n        post.setHeader(HttpHeaders.ACCEPT, \"application/json\");\n      } else {\n        post.setHeader(\n            HttpHeaders.AUTHORIZATION,\n            \"Snowflake Token=\\\"\" + this.session.getSessionToken(internalCallMarker()) + \"\\\"\");\n      }\n\n      String response = null;\n\n      try {\n        response =\n            this.session == null\n                ? HttpUtil.executeGeneralRequest(\n                    post,\n                    TELEMETRY_HTTP_RETRY_TIMEOUT_IN_SEC,\n                    0,\n                    (int) HttpUtil.getSocketTimeout().toMillis(),\n                    0,\n                    this.httpClient,\n                    null)\n                : HttpUtil.executeGeneralRequest(\n                    post,\n                    TELEMETRY_HTTP_RETRY_TIMEOUT_IN_SEC,\n                    0,\n                    this.session.getHttpClientSocketTimeout(),\n                    0,\n                    this.session.getHttpClientKey(),\n                    this.session);\n        stopwatch.stop();\n        logger.debug(\n            \"Sending telemetry took {} ms. Batch size: {}\",\n            stopwatch.elapsedMillis(),\n            tmpList.size());\n      } catch (SnowflakeSQLException e) {\n        disableTelemetry(); // when got error like 404 or bad request, disable telemetry in this\n        // telemetry instance\n        logger.error(\n            \"Telemetry request failed, response: {}, exception: {}\", response, e.getMessage());\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Send a log to the server, along with any existing logs waiting to be sent\n   *\n   * @param log entry to send\n   * @return whether the logs were sent successfully\n   * @throws IOException if closed or uploading batch fails\n   */\n  public boolean sendLog(TelemetryData log) throws IOException {\n    addLogToBatch(log);\n    return sendBatch();\n  }\n\n  /**\n   * Send a log to the server, along with any existing logs waiting to be sent\n   *\n   * @param message json node of log\n   * @param timeStamp timestamp to use for log\n   * @return whether the logs were sent successfully\n   * @throws IOException if closed or uploading batch fails\n   */\n  public boolean sendLog(ObjectNode message, long timeStamp) throws IOException {\n    return this.sendLog(new TelemetryData(message, timeStamp));\n  }\n\n  /**\n   * convert a list of log to a JSON object\n   *\n   * @param telemetryData a list of log\n   * @return the result json string\n   */\n  static ObjectNode logsToJson(LinkedList<TelemetryData> telemetryData) {\n    ObjectNode node = mapper.createObjectNode();\n    ArrayNode logs = mapper.createArrayNode();\n    for (TelemetryData data : telemetryData) {\n      logs.add(data.toJson());\n    }\n    node.set(\"logs\", logs);\n\n    return node;\n  }\n\n  /**\n   * convert a list of log to a JSON String\n   *\n   * @param telemetryData a list of log\n   * @return the result json string\n   */\n  static String logsToString(LinkedList<TelemetryData> telemetryData) {\n    return logsToJson(telemetryData).toString();\n  }\n\n  /**\n   * For test use only\n   *\n   * @return the number of cached logs\n   */\n  public int bufferSize() {\n    return this.logBatch.size();\n  }\n\n  /**\n   * For test use only\n   *\n   * @return a copy of the logs currently in the buffer\n   */\n  public LinkedList<TelemetryData> logBuffer() {\n    return new LinkedList<>(this.logBatch);\n  }\n\n  /**\n   * Refresh the JWT/OAuth token\n   *\n   * @param token latest JWT/OAuth token\n   */\n  public void refreshToken(String token) {\n    this.token = token;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryData.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\n\npublic class TelemetryData {\n  // message is a json node\n  private final ObjectNode message;\n  private final long timeStamp;\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  // Only allow code in same package to construct TelemetryData\n  TelemetryData(ObjectNode message, long timeStamp) {\n    this.message = (ObjectNode) SecretDetector.maskJacksonNode(message);\n    this.timeStamp = timeStamp;\n  }\n\n  public long getTimeStamp() {\n    return timeStamp;\n  }\n\n  public ObjectNode getMessage() {\n    return message;\n  }\n\n  public ObjectNode toJson() {\n    ObjectNode node = mapper.createObjectNode();\n    node.put(\"timestamp\", this.timeStamp + \"\");\n    node.set(\"message\", this.message);\n    return node;\n  }\n\n  @Override\n  public String toString() {\n\n    return toJson().toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryField.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\n// TODO: SNOW-2223750 Refactor this enum, as it contains the possible values of the field \"type\" and\n//  is misleading. Separate values from the field names.\npublic enum TelemetryField {\n  // Fields\n  TYPE(\"type\"),\n  VALUE(\"value\"),\n  DRIVER_TYPE(\"DriverType\"),\n  DRIVER_VERSION(\"DriverVersion\"),\n  QUERY_ID(\"QueryID\"),\n  SQL_STATE(\"SQLState\"),\n  ERROR_NUMBER(\"ErrorNumber\"),\n  ERROR_MESSAGE(\"ErrorMessage\"),\n  REASON(\"reason\"),\n\n  // Values of the field \"type\"\n  // we use \"client_\" as a prefix for all metrics on the client side\n  TIME_CONSUME_FIRST_RESULT(\"client_time_consume_first_result\"),\n  TIME_CONSUME_LAST_RESULT(\"client_time_consume_last_result\"),\n  TIME_WAITING_FOR_CHUNKS(\"client_time_waiting_for_chunks\"),\n  TIME_DOWNLOADING_CHUNKS(\"client_time_downloading_chunks\"),\n  TIME_PARSING_CHUNKS(\"client_time_parsing_chunks\"),\n  TIME_DOWNLOADING_CRL(\"client_time_downloading_crl\"),\n  TIME_PARSING_CRL(\"client_time_parsing_crl\"),\n\n  CLIENT_CRL_STATS(\"client_crl_stats\"),\n  CRL_URL(\"client_crl_url\"),\n  CRL_BYTES(\"client_crl_bytes\"),\n  CRL_REVOKED_CERTIFICATES(\"client_revoked_certificates\"),\n\n  FAILED_BIND_SERIALIZATION(\"client_failed_bind_serialization\"),\n  FAILED_BIND_UPLOAD(\"client_failed_bind_upload\"),\n  FAILED_BIND_OTHER(\"client_failed_bind_other\"),\n\n  SQL_EXCEPTION(\"client_sql_exception\"),\n\n  METADATA_METRICS(\"client_metadata_api_metrics\"),\n\n  HTTP_EXCEPTION(\"client_http_exception\"),\n  OCSP_EXCEPTION(\"client_ocsp_exception\"),\n\n  INTERNAL_API_USAGE(\"client_internal_api_usage\"),\n\n  HEARTBEAT_MAX_THREADS_EXCEEDED(\"client_heartbeat_max_threads_exceeded\");\n\n  public final String field;\n\n  TelemetryField(String field) {\n    this.field = field;\n  }\n\n  @Override\n  public String toString() {\n    return field;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryUtil.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.common.core.LoginInfoDTO;\n\npublic class TelemetryUtil {\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n  public static final int NO_VENDOR_CODE = -1;\n  @Deprecated public static final String TYPE = \"type\";\n  @Deprecated public static final String QUERY_ID = \"query_id\";\n  @Deprecated public static final String VALUE = \"value\";\n\n  /**\n   * Create a simple TelemetryData instance for Job metrics using given parameters\n   *\n   * @param queryId the id of the query\n   * @param field the field to log (represents the \"type\" field in telemetry)\n   * @param value the value to log for the field\n   * @return TelemetryData instance constructed from parameters\n   */\n  public static TelemetryData buildJobData(String queryId, TelemetryField field, long value) {\n    ObjectNode obj = mapper.createObjectNode();\n    obj.put(TelemetryField.TYPE.toString(), field.toString());\n    obj.put(TelemetryField.QUERY_ID.toString(), queryId);\n    obj.put(TelemetryField.VALUE.toString(), value);\n    return new TelemetryData(obj, System.currentTimeMillis());\n  }\n\n  public static TelemetryData buildJobData(ObjectNode obj) {\n    return new TelemetryData(obj, System.currentTimeMillis());\n  }\n\n  /**\n   * Helper function to create ObjectNode for IB telemetry log\n   *\n   * @param queryId query ID\n   * @param sqlState the SQL state\n   * @param errorNumber the error number\n   * @param type the telemetry field type\n   * @param errorMessage the error message\n   * @param reason the reason for the error\n   * @return ObjectNode for IB telemetry log\n   */\n  public static ObjectNode createIBValue(\n      String queryId,\n      String sqlState,\n      int errorNumber,\n      TelemetryField type,\n      String errorMessage,\n      String reason) {\n    ObjectNode ibValue = mapper.createObjectNode();\n    ibValue.put(TelemetryField.TYPE.toString(), type.toString());\n    ibValue.put(TelemetryField.DRIVER_TYPE.toString(), LoginInfoDTO.SF_JDBC_APP_ID);\n    ibValue.put(\n        TelemetryField.DRIVER_VERSION.toString(), SnowflakeDriver.getImplementationVersion());\n    if (!isNullOrEmpty(queryId)) {\n      ibValue.put(TelemetryField.QUERY_ID.toString(), queryId);\n    }\n    if (!isNullOrEmpty(sqlState)) {\n      ibValue.put(TelemetryField.SQL_STATE.toString(), sqlState);\n    }\n    if (errorNumber != NO_VENDOR_CODE) {\n      ibValue.put(TelemetryField.ERROR_NUMBER.toString(), errorNumber);\n    }\n    if (!isNullOrEmpty(errorMessage)) {\n      ibValue.put(TelemetryField.ERROR_MESSAGE.toString(), errorMessage);\n    }\n    if (!isNullOrEmpty(reason)) {\n      ibValue.put(TelemetryField.REASON.toString(), reason);\n    }\n    return ibValue;\n  }\n\n  public static TelemetryData buildCrlData(\n      String crlUrl, long crlBytes, int revokedCertificates, long downloadTime, long parseTime) {\n    ObjectNode obj = mapper.createObjectNode();\n    obj.put(TelemetryField.TYPE.toString(), TelemetryField.CLIENT_CRL_STATS.toString());\n    obj.put(TelemetryField.CRL_URL.toString(), crlUrl);\n    obj.put(TelemetryField.CRL_BYTES.toString(), crlBytes);\n    obj.put(TelemetryField.CRL_REVOKED_CERTIFICATES.toString(), revokedCertificates);\n    obj.put(TelemetryField.TIME_DOWNLOADING_CRL.toString(), downloadTime);\n    obj.put(TelemetryField.TIME_PARSING_CRL.toString(), parseTime);\n    return new TelemetryData(obj, System.currentTimeMillis());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetryOOB/TelemetryEvent.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetryOOB;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.HashMap;\nimport net.minidev.json.JSONArray;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.UUIDUtils;\nimport net.snowflake.client.internal.util.SFTimestamp;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.common.core.ResourceBundleManager;\n\n/** Telemetry Event Class */\npublic class TelemetryEvent extends JSONObject {\n  private static final long serialVersionUID = 1L;\n  private static final int schemaVersion = 1;\n\n  public enum Type {\n    Metric,\n    Log\n  }\n\n  /** Build metric json object */\n  public static class MetricBuilder extends Builder<MetricBuilder> {\n\n    public MetricBuilder withException(Exception ex) {\n      this.withName(\"Exception:\" + ex.getMessage());\n      this.withValue(1);\n      return this;\n    }\n\n    public MetricBuilder() {\n      super(MetricBuilder.class);\n    }\n\n    public MetricBuilder withValue(int value) {\n      body.put(\"Value\", value);\n      return this;\n    }\n\n    public MetricBuilder withValue(float value) {\n      body.put(\"Value\", value);\n      return this;\n    }\n\n    public TelemetryEvent build() {\n      TelemetryEvent event = super.build();\n      event.put(\"Type\", Type.Metric);\n      return event;\n    }\n  }\n\n  /** Build log json object */\n  public static class LogBuilder extends Builder<LogBuilder> {\n    public LogBuilder() {\n      super(LogBuilder.class);\n    }\n\n    /**\n     * build a log event for an exception including the full stack trace\n     *\n     * @param ex The exception to build a log event\n     * @return The log event builder\n     */\n    public LogBuilder withException(Exception ex) {\n      this.withName(\"Exception:\" + ex.getMessage());\n      StringWriter sw = new StringWriter();\n      PrintWriter pw = new PrintWriter(sw);\n      ex.printStackTrace(pw);\n      String stackTrace = sw.toString(); // stack trace as a string\n      this.withValue(stackTrace);\n      return this;\n    }\n\n    public LogBuilder withException(final SFException ex) {\n      this.withName(\"Exception:\" + ex.getMessage());\n      StringWriter sw = new StringWriter();\n      PrintWriter pw = new PrintWriter(sw);\n      ex.printStackTrace(pw);\n      String stackTrace = sw.toString(); // stack trace as a string\n      this.withValue(stackTrace);\n      return this;\n    }\n\n    public LogBuilder withValue(String value) {\n      body.put(\"Value\", SecretDetector.maskSecrets(value));\n      return this;\n    }\n\n    public LogBuilder withValue(JSONObject value) {\n      body.put(\"Value\", SecretDetector.maskJsonObject(value));\n      return this;\n    }\n\n    public LogBuilder withValue(JSONArray value) {\n      body.put(\"Value\", SecretDetector.maskJsonArray(value));\n      return this;\n    }\n\n    public TelemetryEvent build() {\n      TelemetryEvent event = super.build();\n      event.put(\"Type\", Type.Log);\n      return event;\n    }\n  }\n\n  private static class Builder<T> {\n    protected final Class<T> builderClass;\n    protected TelemetryEvent body = new TelemetryEvent();\n    protected HashMap<String, String> tags = new HashMap<>();\n    private static final String version =\n        ResourceBundleManager.getSingleton(\"net.snowflake.client.jdbc.version\")\n            .getLocalizedMessage(\"version\");\n    private static final String driver = \"JDBC\";\n\n    public Builder(Class<T> builderClass) {\n      this.builderClass = builderClass;\n      withTag(\"driver\", driver);\n      withTag(\"version\", version);\n      TelemetryService instance = TelemetryService.getInstance();\n      withTag(\"telemetryServerDeployment\", instance.getServerDeploymentName());\n      withTag(\"connectionString\", instance.getDriverConnectionString());\n      JSONObject context = instance.getContext();\n      if (context != null) {\n        for (String key : context.keySet()) {\n          Object val = context.get(key);\n          if (val != null) {\n            withTag(\n                \"ctx_\" + key.toLowerCase(), SecretDetector.maskParameterValue(key, val.toString()));\n          }\n        }\n      }\n    }\n\n    public T withName(String name) {\n      body.put(\"Name\", SecretDetector.maskSecrets(name));\n      return builderClass.cast(this);\n    }\n\n    public T withTag(String name, int value) {\n      return withTag(name, Integer.toString(value));\n    }\n\n    public T withTag(String name, String value) {\n      if (value != null && value.length() > 0) {\n        tags.put(name, SecretDetector.maskSecrets(value));\n      }\n      return builderClass.cast(this);\n    }\n\n    protected TelemetryEvent build() {\n      body.put(\"UUID\", UUIDUtils.getUUID().toString());\n      body.put(\"Created_On\", SFTimestamp.getUTCNow());\n      body.put(\"SchemaVersion\", schemaVersion);\n      this.putMap(\"Tags\", tags);\n      return body;\n    }\n\n    private void putMap(String name, HashMap<String, String> map) {\n      JSONObject tags = new JSONObject();\n      for (String key : map.keySet()) {\n        tags.put(key, map.get(key));\n      }\n      body.put(name, tags);\n    }\n  }\n\n  /**\n   * @return the deployment of this event\n   */\n  public String getDeployment() {\n    JSONArray tags = (JSONArray) this.get(\"Tags\");\n    for (Object tag : tags) {\n      JSONObject json = (JSONObject) tag;\n      if (json.get(\"Name\").toString().compareTo(\"deployment\") == 0) {\n        return json.get(\"Value\").toString();\n      }\n    }\n    return \"Unknown\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetryOOB/TelemetryService.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetryOOB;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.security.cert.CertificateException;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport net.minidev.json.JSONArray;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.internal.jdbc.SnowflakeConnectString;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.util.EntityUtils;\n\n/**\n * Out of Band Telemetry Service This is a thread safe singleton queue containing telemetry messages\n */\npublic class TelemetryService {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(TelemetryService.class);\n\n  private static ThreadLocal<TelemetryService> _threadLocal =\n      new ThreadLocal<TelemetryService>() {\n        @Override\n        protected TelemetryService initialValue() {\n          return new TelemetryService();\n        }\n      };\n\n  // Global parameters:\n  private static final String TELEMETRY_SERVER_URL_PATTERN =\n      \"https://(sfcdev\\\\.|sfctest\\\\.|)?client-telemetry\\\\.[a-z0-9\\\\.\\\\-]*snowflake[computing]?\\\\\"\n          + \".com/enqueue\";\n\n  /**\n   * control which deployments are enabled: the service skips all events for the disabled\n   * deployments\n   */\n  private static HashSet<String> ENABLED_DEPLOYMENT =\n      new HashSet<>(\n          Arrays.asList(\n              TELEMETRY_SERVER_DEPLOYMENT.DEV.name,\n              TELEMETRY_SERVER_DEPLOYMENT.REG.name,\n              TELEMETRY_SERVER_DEPLOYMENT.QA1.name,\n              TELEMETRY_SERVER_DEPLOYMENT.PREPROD3.name,\n              TELEMETRY_SERVER_DEPLOYMENT.PROD.name,\n              TELEMETRY_SERVER_DEPLOYMENT.K8TEST.name));\n\n  // connection string for current connection\n  private String connStr = \"\";\n  // current snowflake connection string\n  private SnowflakeConnectString sfConnStr;\n\n  /**\n   * @return return thread local instance\n   */\n  public static TelemetryService getInstance() {\n    return _threadLocal.get();\n  }\n\n  private static final int DEFAULT_NUM_OF_RETRY_TO_TRIGGER_TELEMETRY = 10;\n\n  /** the number of retry to trigger the HTTP timeout telemetry event */\n  private int numOfRetryToTriggerTelemetry = DEFAULT_NUM_OF_RETRY_TO_TRIGGER_TELEMETRY;\n  // local parameters\n  /** the context (e.g., connection properties) to be included in the telemetry events */\n  private JSONObject context;\n\n  public void resetNumOfRetryToTriggerTelemetry() {\n    numOfRetryToTriggerTelemetry = DEFAULT_NUM_OF_RETRY_TO_TRIGGER_TELEMETRY;\n  }\n\n  public int getNumOfRetryToTriggerTelemetry() {\n    return numOfRetryToTriggerTelemetry;\n  }\n\n  public void setNumOfRetryToTriggerTelemetry(int num) {\n    numOfRetryToTriggerTelemetry = num;\n  }\n\n  private TELEMETRY_SERVER_DEPLOYMENT serverDeployment = TELEMETRY_SERVER_DEPLOYMENT.PROD;\n\n  /**\n   * control enable/disable the whole service: disabled service will skip added events and uploading\n   * to the server\n   */\n  private static boolean enabled = true;\n\n  private static boolean htapEnabled = false;\n\n  private static final Object enableLock = new Object();\n\n  private static final Object enableHTAPLock = new Object();\n\n  public static void enable() {\n    synchronized (enableLock) {\n      logger.debug(\"Enabling out-of-band telemetry\", false);\n      enabled = true;\n    }\n  }\n\n  public static void disable() {\n    synchronized (enableLock) {\n      logger.debug(\"Disabling out-of-band telemetry\", false);\n      enabled = false;\n    }\n  }\n\n  public static void enableHTAP() {\n    synchronized (enableHTAPLock) {\n      logger.debug(\"Enabling out-of-band HTAP telemetry\");\n      htapEnabled = true;\n    }\n  }\n\n  public static void disableHTAP() {\n    synchronized (enableHTAPLock) {\n      logger.debug(\"Disabling out-of-band HTAP telemetry\");\n      htapEnabled = false;\n    }\n  }\n\n  public static void disableOOBTelemetry() {\n    disable();\n    disableHTAP();\n  }\n\n  public boolean isEnabled() {\n    synchronized (enableLock) {\n      return enabled;\n    }\n  }\n\n  public boolean isHTAPEnabled() {\n    synchronized (enableHTAPLock) {\n      return htapEnabled;\n    }\n  }\n\n  public JSONObject getContext() {\n    return context;\n  }\n\n  /**\n   * Note: Only used for IT\n   *\n   * @param params parameter map\n   */\n  public void updateContextForIT(Map<String, String> params) {\n    Properties info = new Properties();\n    for (String key : params.keySet()) {\n      Object val = params.get(key);\n      if (val != null) {\n        info.put(key, val);\n      }\n    }\n    SnowflakeConnectString conStr = SnowflakeConnectString.parse(params.get(\"uri\"), info);\n    this.updateContext(conStr);\n  }\n\n  public void updateContext(SnowflakeConnectString conStr) {\n    if (conStr != null) {\n      sfConnStr = conStr;\n      configureDeployment(conStr);\n      context = new JSONObject();\n\n      for (Map.Entry<String, Object> entry : conStr.getParameters().entrySet()) {\n        String k = entry.getKey();\n        Object v = entry.getValue();\n        if (!SecretDetector.isSensitive(k)) {\n          context.put(k, v);\n        }\n      }\n    }\n  }\n\n  private TELEMETRY_SERVER_DEPLOYMENT manuallyConfigureDeployment(String dep) {\n    switch (dep) {\n      case \"K8TEST\":\n        return TELEMETRY_SERVER_DEPLOYMENT.K8TEST;\n      case \"REG\":\n        return TELEMETRY_SERVER_DEPLOYMENT.REG;\n      case \"DEV\":\n        return TELEMETRY_SERVER_DEPLOYMENT.DEV;\n      case \"QA1\":\n        return TELEMETRY_SERVER_DEPLOYMENT.QA1;\n      case \"PREPROD\":\n        return TELEMETRY_SERVER_DEPLOYMENT.PREPROD3;\n      case \"PROD\":\n        return TELEMETRY_SERVER_DEPLOYMENT.PROD;\n      default:\n        return null;\n    }\n  }\n\n  /**\n   * configure telemetry deployment based on connection url and info Note: it is not thread-safe\n   * while connecting to different deployments simultaneously.\n   *\n   * @param conStr Connect String\n   */\n  private void configureDeployment(SnowflakeConnectString conStr) {\n    if (!conStr.isValid()) {\n      return;\n    }\n    connStr = conStr.toString();\n    String account = conStr.getAccount();\n    int port = conStr.getPort();\n    // default value\n    TELEMETRY_SERVER_DEPLOYMENT deployment = TELEMETRY_SERVER_DEPLOYMENT.PROD;\n\n    Map<String, Object> conParams = conStr.getParameters();\n    if (conParams.containsKey(\"TELEMETRYDEPLOYMENT\")) {\n      String conDeployment =\n          String.valueOf(conParams.get(\"TELEMETRYDEPLOYMENT\")).trim().toUpperCase();\n      deployment = manuallyConfigureDeployment(conDeployment);\n      if (deployment != null) {\n        this.setDeployment(deployment);\n        return;\n      }\n    }\n    if (conStr.getHost().contains(\"reg\") || conStr.getHost().contains(\"local\")) {\n      deployment = TELEMETRY_SERVER_DEPLOYMENT.REG;\n      if (port == 8080) {\n        deployment = TELEMETRY_SERVER_DEPLOYMENT.DEV;\n      }\n    } else if (conStr.getHost().contains(\"qa1\") || account.contains(\"qa1\")) {\n      deployment = TELEMETRY_SERVER_DEPLOYMENT.QA1;\n    } else if (conStr.getHost().contains(\"preprod3\")) {\n      deployment = TELEMETRY_SERVER_DEPLOYMENT.PREPROD3;\n    } else if (conStr.getHost().contains(\"snowflake.temptest\")) {\n      deployment = TELEMETRY_SERVER_DEPLOYMENT.QA1;\n    }\n    this.setDeployment(deployment);\n  }\n\n  /**\n   * whether the telemetry service is enabled for current deployment\n   *\n   * @return true if the telemetry service is enabled for current deployment\n   */\n  public boolean isDeploymentEnabled() {\n    return ENABLED_DEPLOYMENT.contains(this.serverDeployment.name);\n  }\n\n  public String getDriverConnectionString() {\n    return this.connStr;\n  }\n\n  public SnowflakeConnectString getSnowflakeConnectionString() {\n    return sfConnStr;\n  }\n\n  private enum TELEMETRY_API {\n    SFCTEST(\n        \"https://sfctest.client-telemetry.snowflakecomputing.com/enqueue\",\n        \"rRNY3EPNsB4U89XYuqsZKa7TSxb9QVX93yNM4tS6\"), // pragma: allowlist secret\n    SFCDEV(\n        \"https://sfcdev.client-telemetry.snowflakecomputing.com/enqueue\",\n        \"kyTKLWpEZSaJnrzTZ63I96QXZHKsgfqbaGmAaIWf\"), // pragma: allowlist secret\n    PROD(\n        \"https://client-telemetry.snowflakecomputing.com/enqueue\",\n        \"wLpEKqnLOW9tGNwTjab5N611YQApOb3t9xOnE1rX\"), // pragma: allowlist secret\n\n    K8TEST(\"https://client-telemetry.ordevmisc1.us-west-2.aws-dev.app.snowflake.com/enqueue\", \"\");\n\n    private final String url;\n\n    // Note that this key is public available and only used as usage plan for\n    // throttling\n    private final String apiKey;\n\n    TELEMETRY_API(String host, String key) {\n      this.url = host;\n      this.apiKey = key;\n    }\n  }\n\n  public enum TELEMETRY_SERVER_DEPLOYMENT {\n    DEV(\"dev\", TELEMETRY_API.SFCDEV),\n    REG(\"reg\", TELEMETRY_API.SFCDEV),\n    QA1(\"qa1\", TELEMETRY_API.SFCDEV),\n    PREPROD3(\"preprod3\", TELEMETRY_API.SFCDEV),\n    PROD(\"prod\", TELEMETRY_API.PROD),\n    K8TEST(\"k8test\", TELEMETRY_API.K8TEST);\n\n    private String name;\n    private String url;\n    private final String apiKey;\n\n    TELEMETRY_SERVER_DEPLOYMENT(String name, TELEMETRY_API api) {\n      this.name = name;\n      this.url = api.url;\n      this.apiKey = api.apiKey;\n    }\n\n    public String getURL() {\n      return url;\n    }\n\n    public String getName() {\n      return name;\n    }\n\n    public String getApiKey() {\n      return apiKey;\n    }\n\n    public void setURL(String url) {\n      this.url = url;\n    }\n  }\n\n  public void setDeployment(TELEMETRY_SERVER_DEPLOYMENT deployment) {\n    logger.debug(\"Setting out-of-band telemetry sever deployment to {}\", deployment);\n    serverDeployment = deployment;\n  }\n\n  public String getServerDeploymentName() {\n    return serverDeployment.name;\n  }\n\n  private AtomicInteger eventCnt = new AtomicInteger();\n\n  private AtomicInteger clientFailureCnt = new AtomicInteger();\n\n  private AtomicInteger serverFailureCnt = new AtomicInteger();\n\n  private String lastClientError = \"\";\n\n  /**\n   * @return the number of events successfully reported by this service\n   */\n  public int getEventCount() {\n    return eventCnt.get();\n  }\n\n  /**\n   * @return the number of times an event was attempted to be reported but failed due to a\n   *     client-side error\n   */\n  public int getClientFailureCount() {\n    return clientFailureCnt.get();\n  }\n\n  /**\n   * @return the number of times an event was attempted to be reported but failed due to a\n   *     server-side error\n   */\n  public int getServerFailureCount() {\n    return serverFailureCnt.get();\n  }\n\n  /**\n   * @return the string containing the most recent failed response\n   */\n  public String getLastClientError() {\n    return this.lastClientError;\n  }\n\n  /** Count one more successfully reported events */\n  public void count() {\n    eventCnt.incrementAndGet();\n  }\n\n  /**\n   * Report the event to the telemetry server in a new thread\n   *\n   * @param event TelemetryEvent\n   */\n  public void report(TelemetryEvent event) {\n    reportChooseEvent(event, /* isHTAP */ false);\n  }\n\n  public void reportChooseEvent(TelemetryEvent event, boolean isHTAP) {\n    if ((!enabled && !isHTAP) || (!htapEnabled && isHTAP) || event == null || event.isEmpty()) {\n      return;\n    }\n\n    // Start a new thread to upload without blocking the current thread\n    Runnable runUpload =\n        new TelemetryUploader(\n            this, exportQueueToString(event), exportQueueToLogString(event), isHTAP);\n    TelemetryThreadPool.getInstance().execute(runUpload);\n  }\n\n  /**\n   * Convert an event to a payload in string\n   *\n   * @param event TelemetryEvent\n   * @return the string payload\n   */\n  public String exportQueueToString(TelemetryEvent event) {\n    JSONArray logs = new JSONArray();\n    logs.add(event);\n    return logs.toString();\n  }\n\n  public String exportQueueToLogString(TelemetryEvent event) {\n    JSONArray logs = new JSONArray();\n    logs.add(event);\n    return JSONArray.toJSONString(logs, new SecretDetector.SecretDetectorJSONStyle());\n  }\n\n  static class TelemetryUploader implements Runnable {\n    private TelemetryService instance;\n    private String payload;\n    private String payloadLogStr;\n    private boolean isHTAP;\n    private static final int TIMEOUT = 5000; // 5 second timeout limit\n    private static final RequestConfig config =\n        RequestConfig.custom()\n            .setConnectionRequestTimeout(TIMEOUT)\n            .setConnectionRequestTimeout(TIMEOUT)\n            .setSocketTimeout(TIMEOUT)\n            .build();\n\n    public TelemetryUploader(\n        TelemetryService _instance, String _payload, String _payloadLogStr, boolean _isHTAP) {\n      instance = _instance;\n      payload = _payload;\n      payloadLogStr = _payloadLogStr;\n      isHTAP = _isHTAP;\n    }\n\n    public void run() {\n      if (!isHTAP && !instance.enabled) {\n        return;\n      }\n\n      if (isHTAP && !instance.htapEnabled) {\n        return;\n      }\n\n      if (!instance.isDeploymentEnabled()) {\n        // skip the disabled deployment\n        logger.debug(\"Skip the disabled deployment: \", instance.serverDeployment.name);\n        return;\n      }\n\n      if (!instance.serverDeployment.url.matches(TELEMETRY_SERVER_URL_PATTERN)) {\n        // skip the disabled deployment\n        logger.debug(\"Ignore invalid url: \", instance.serverDeployment.url);\n        return;\n      }\n\n      uploadPayload();\n    }\n\n    private void uploadPayload() {\n      Stopwatch stopwatch = new Stopwatch();\n      stopwatch.start();\n      logger.debugNoMask(\n          \"Running out-of-band telemetry uploader. The payload is: \" + payloadLogStr);\n      CloseableHttpResponse response = null;\n      boolean success = true;\n\n      try {\n        HttpPost post = new HttpPost(instance.serverDeployment.url);\n        post.setEntity(new StringEntity(payload));\n        post.setHeader(\"Content-type\", \"application/json\");\n        post.setHeader(\"x-api-key\", instance.serverDeployment.getApiKey());\n        try (CloseableHttpClient httpClient =\n            HttpClientBuilder.create().setDefaultRequestConfig(config).build()) {\n          response = httpClient.execute(post);\n          int statusCode = response.getStatusLine().getStatusCode();\n\n          if (statusCode == 200) {\n            logger.debug(\"Out-of-band telemetry server request success: {}\", response, true);\n            instance.count();\n          } else if (statusCode == 429) {\n            logger.debug(\n                \"Out-of-band telemetry server request hit server cap on response: {}\", response);\n            instance.serverFailureCnt.incrementAndGet();\n          } else {\n            logger.debug(\"Out-of-band telemetry server request error: {}\", response, true);\n            instance.lastClientError = response.toString();\n            instance.clientFailureCnt.incrementAndGet();\n            success = false;\n          }\n          logger.debug(EntityUtils.toString(response.getEntity(), \"UTF-8\"), true);\n          response.close();\n        }\n      } catch (Exception e) {\n        // exception from here is always captured\n        logger.debug(\n            \"Out-of-band telemetry request failed, Exception response: {}, exception: {}\",\n            response,\n            e.getMessage());\n        String res = \"null\";\n        if (response != null) {\n          res = response.toString();\n        }\n        instance.lastClientError = \"Response: \" + res + \"; Error: \" + e.getMessage();\n        instance.clientFailureCnt.incrementAndGet();\n        success = false;\n      } finally {\n        stopwatch.stop();\n        logger.debug(\n            \"Out-of-band telemetry request success: {} and clean the current queue. It took {} ms.\"\n                + \" Total successful events: {}, total unsuccessful events: {} (client failures: {}, server failures: {})\",\n            success,\n            stopwatch.elapsedMillis(),\n            instance.eventCnt,\n            instance.clientFailureCnt.get() + instance.serverFailureCnt.get(),\n            instance.clientFailureCnt,\n            instance.serverFailureCnt);\n      }\n    }\n  }\n\n  /**\n   * log OCSP exception to telemetry\n   *\n   * @param eventType event type\n   * @param telemetryData JSON telemetry data\n   * @param ex CertificateException\n   */\n  public void logOCSPExceptionTelemetryEvent(\n      String eventType, JSONObject telemetryData, CertificateException ex) {\n    if (enabled) {\n      String eventName = \"OCSPException\";\n      TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n\n      if (ex != null) {\n        telemetryData.put(\"exceptionMessage\", ex.getLocalizedMessage());\n        StringWriter sw = new StringWriter();\n        ex.printStackTrace(new PrintWriter(sw));\n        telemetryData.put(\"exceptionStackTrace\", sw.toString());\n      }\n\n      TelemetryEvent log =\n          logBuilder\n              .withName(eventName)\n              .withValue(telemetryData)\n              .withTag(\"eventType\", eventType)\n              .build();\n      this.report(log);\n    }\n  }\n\n  /**\n   * log error http response to telemetry\n   *\n   * @param eventName the event name\n   * @param request the HttpRequestBase\n   * @param injectSocketTimeout the socket timeout\n   * @param canceling cancelling\n   * @param withoutCookies without cookies\n   * @param includeRetryParameters include retry parameters\n   * @param includeRequestGuid include rest GUID\n   * @param response the CloseableHttpResponse\n   * @param savedEx the saved exception\n   * @param breakRetryReason the break retry reason\n   * @param retryTimeout the retry timeout\n   * @param retryCount retry count\n   * @param sqlState the SQL state\n   * @param errorCode the error code\n   */\n  public void logHttpRequestTelemetryEvent(\n      String eventName,\n      HttpRequestBase request,\n      int injectSocketTimeout,\n      AtomicBoolean canceling,\n      boolean withoutCookies,\n      boolean includeRetryParameters,\n      boolean includeRequestGuid,\n      CloseableHttpResponse response,\n      final Exception savedEx,\n      String breakRetryReason,\n      long retryTimeout,\n      int retryCount,\n      String sqlState,\n      int errorCode) {\n\n    if (enabled) {\n      TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n      JSONObject value = new JSONObject();\n      value.put(\"request\", request.toString());\n      value.put(\"injectSocketTimeout\", injectSocketTimeout);\n      value.put(\"canceling\", canceling == null ? \"null\" : canceling.get());\n      value.put(\"withoutCookies\", withoutCookies);\n      value.put(\"includeRetryParameters\", includeRetryParameters);\n      value.put(\"includeRequestGuid\", includeRequestGuid);\n      value.put(\"breakRetryReason\", breakRetryReason);\n      value.put(\"retryTimeout\", retryTimeout);\n      value.put(\"retryCount\", retryCount);\n      value.put(\"sqlState\", sqlState);\n      value.put(\"errorCode\", errorCode);\n      int responseStatusCode = -1;\n      if (response != null) {\n        value.put(\"response\", response.toString());\n        value.put(\"responseStatusLine\", response.getStatusLine().toString());\n        if (response.getStatusLine() != null) {\n          responseStatusCode = response.getStatusLine().getStatusCode();\n          value.put(\"responseStatusCode\", responseStatusCode);\n        }\n      } else {\n        value.put(\"response\", null);\n      }\n      if (savedEx != null) {\n        value.put(\"exceptionMessage\", savedEx.getLocalizedMessage());\n        StringWriter sw = new StringWriter();\n        savedEx.printStackTrace(new PrintWriter(sw));\n        value.put(\"exceptionStackTrace\", sw.toString());\n      }\n      TelemetryEvent log =\n          logBuilder\n              .withName(eventName)\n              .withValue(value)\n              .withTag(\"sqlState\", sqlState)\n              .withTag(\"errorCode\", errorCode)\n              .withTag(\"responseStatusCode\", responseStatusCode)\n              .build();\n      this.report(log);\n    }\n  }\n\n  /**\n   * log execution times from various processing slices\n   *\n   * @param telemetryData JSON telemetry data\n   * @param eventName the event name\n   */\n  public void logExecutionTimeTelemetryEvent(JSONObject telemetryData, String eventName) {\n    if (htapEnabled) {\n      TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n      TelemetryEvent log =\n          logBuilder\n              .withName(eventName)\n              .withValue(telemetryData)\n              .withTag(\"eventType\", eventName)\n              .build();\n      this.reportChooseEvent(log, /* isHTAP */ true);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/telemetryOOB/TelemetryThreadPool.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetryOOB;\n\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A singleton class which wrapped the ExecutorService, which is used to submit telemetry data\n * asynchronously to server\n */\npublic class TelemetryThreadPool {\n  private static final int CORE_POOL_SIZE = 10;\n  private final ExecutorService uploader;\n\n  private static TelemetryThreadPool instance;\n\n  public static TelemetryThreadPool getInstance() {\n    if (instance == null) {\n      synchronized (TelemetryThreadPool.class) {\n        if (instance == null) {\n          instance = new TelemetryThreadPool();\n        }\n      }\n    }\n    return instance;\n  }\n\n  /**\n   * Private constructor to initialize the singleton instance.\n   *\n   * <p>Configures a thread pool that scales dynamically based on workload. The pool starts with\n   * zero threads and will create new threads on demand up to a maximum of 10. If all 10 threads are\n   * active, new tasks are placed in an unbounded queue to await execution.\n   *\n   * <p>To conserve resources, threads that are idle for more than 30 seconds are terminated,\n   * allowing the pool to shrink back to zero during periods of inactivity.\n   */\n  private TelemetryThreadPool() {\n    // Create a thread factory that creates daemon threads to prevent blocking JVM termination\n    ThreadFactory daemonThreadFactory =\n        r -> {\n          Thread thread = Executors.defaultThreadFactory().newThread(r);\n          thread.setName(\"telemetry-uploader-\" + thread.getId());\n          thread.setDaemon(true);\n          return thread;\n        };\n\n    uploader =\n        new ThreadPoolExecutor(\n            CORE_POOL_SIZE, // core size\n            CORE_POOL_SIZE, // max size\n            30L, // keep alive time\n            TimeUnit.SECONDS,\n            new LinkedBlockingQueue<>(), // work queue\n            daemonThreadFactory // thread factory\n            );\n    // Allow core threads to time out and be terminated when idle.\n    ((ThreadPoolExecutor) uploader).allowCoreThreadTimeOut(true);\n  }\n\n  public void execute(Runnable task) {\n    uploader.execute(task);\n  }\n\n  public <T> Future<T> submit(Callable<T> task) {\n    return uploader.submit(task);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/util/DriverUtil.java",
    "content": "package net.snowflake.client.internal.jdbc.util;\n\nimport net.snowflake.client.api.driver.SnowflakeDriver;\n\npublic class DriverUtil {\n\n  public static String getImplementationVersion() {\n    return SnowflakeDriver.getImplementationVersion();\n  }\n  /**\n   * Utility method to verify if the standard or fips snowflake-jdbc driver is being used.\n   *\n   * @return the title of the implementation, null is returned if it is not known.\n   */\n  static String getImplementationTitle() {\n    Package pkg = Package.getPackage(\"net.snowflake.client.internal.jdbc\");\n    return pkg != null ? pkg.getImplementationTitle() : \"snowflake-jdbc\";\n  }\n\n  /**\n   * Utility method to get the complete jar name with version.\n   *\n   * @return the jar name with version\n   */\n  public static String getJdbcJarname() {\n    return String.format(\"%s-%s\", getImplementationTitle(), getImplementationVersion());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/util/SnowflakeTypeHelper.java",
    "content": "package net.snowflake.client.internal.jdbc.util;\n\nimport java.math.BigDecimal;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.HashSet;\nimport java.util.Set;\nimport net.snowflake.client.api.resultset.SnowflakeType;\n\n/**\n * Internal helper class for SnowflakeType conversions and utilities. This class contains nested\n * enums, constants, and utility methods that are used internally by the JDBC driver but should not\n * be part of the public API.\n *\n * <p><b>Note:</b> This is an internal API and should not be used by customers.\n */\npublic final class SnowflakeTypeHelper {\n\n  private SnowflakeTypeHelper() {\n    // Prevent instantiation\n  }\n\n  public static final String DATE_OR_TIME_FORMAT_PATTERN = \"yyyy-MM-dd'T'HH:mm:ss.SSSXXX\";\n  public static final String TIMESTAMP_FORMAT_PATTERN = \"yyyy-MM-dd'T'HH:mm:ss.\";\n  public static final String TIMESTAMP_FORMAT_TZ_PATTERN = \"XXX\";\n  public static final String TIME_FORMAT_PATTERN = \"HH:mm:ss.SSS\";\n  private static final byte[] BYTE_ARRAY = new byte[0];\n  public static final String BINARY_CLASS_NAME = BYTE_ARRAY.getClass().getName();\n\n  /**\n   * Converts text of data type (returned from SQL query) into Types type, represented by an int.\n   *\n   * @param typeName type name\n   * @return int representation of type from {@link java.sql.Types}\n   */\n  public static int convertStringToType(String typeName) {\n    int retval = Types.NULL;\n    if (typeName == null || typeName.trim().isEmpty()) {\n      return retval;\n    }\n    // Trim all whitespace and extra information off typeName so it can be interpreted by switch\n    // statement. Ex: turns\n    // \"NUMBER(38,0)\" -> \"NUMBER\" and \"FLOAT NOT NULL\" ->\" FLOAT\"\n    String typeNameTrimmed = typeName.trim();\n    if (typeNameTrimmed.contains(\"(\")) {\n      typeNameTrimmed = typeNameTrimmed.substring(0, typeNameTrimmed.indexOf('('));\n    }\n    if (typeNameTrimmed.contains(\" \")) {\n      typeNameTrimmed = typeNameTrimmed.substring(0, typeNameTrimmed.indexOf(' '));\n    }\n    switch (typeNameTrimmed.toLowerCase()) {\n      case \"number\":\n      case \"numeric\":\n        retval = Types.NUMERIC;\n        break;\n      case \"decfloat\":\n        retval = SnowflakeType.EXTRA_TYPES_DECFLOAT;\n        break;\n      case \"decimal\":\n        retval = Types.DECIMAL;\n        break;\n      case \"int\":\n      case \"integer\":\n      case \"byteint\":\n        retval = Types.INTEGER;\n        break;\n      case \"tinyint\":\n        retval = Types.TINYINT;\n        break;\n      case \"smallint\":\n        retval = Types.SMALLINT;\n        break;\n      case \"bigint\":\n        retval = Types.BIGINT;\n        break;\n      case \"float\":\n      case \"float4\":\n      case \"float8\":\n        retval = Types.FLOAT;\n        break;\n      case \"double\":\n      case \"double precision\":\n        retval = Types.DOUBLE;\n        break;\n      case \"real\":\n        retval = Types.REAL;\n        break;\n      case \"char\":\n      case \"character\":\n        retval = Types.CHAR;\n        break;\n      case \"varchar\":\n      case \"string\":\n      case \"text\":\n        retval = Types.VARCHAR;\n        break;\n      case \"binary\":\n        retval = Types.BINARY;\n        break;\n      case \"varbinary\":\n        retval = Types.VARBINARY;\n        break;\n      case \"boolean\":\n        retval = Types.BOOLEAN;\n        break;\n      case \"date\":\n        retval = Types.DATE;\n        break;\n      case \"time\":\n        retval = Types.TIME;\n        break;\n      case \"timestamp\":\n      case \"datetime\":\n      case \"timestamp_ntz\":\n        retval = Types.TIMESTAMP;\n        break;\n      case \"timestamp_ltz\":\n      case \"timestamp_tz\":\n        retval = Types.TIMESTAMP_WITH_TIMEZONE;\n        break;\n      case \"interval_year_month\":\n        retval = SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL;\n        break;\n      case \"interval_day_time\":\n        retval = SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL;\n        break;\n      case \"variant\":\n        retval = Types.OTHER;\n        break;\n      case \"object\":\n        retval = Types.JAVA_OBJECT;\n        break;\n      case \"vector\":\n        retval = SnowflakeType.EXTRA_TYPES_VECTOR;\n        break;\n      case \"array\":\n        retval = Types.ARRAY;\n        break;\n      default:\n        retval = Types.OTHER;\n        break;\n    }\n    return retval;\n  }\n\n  /**\n   * Determines if a Java SQL type is signed.\n   *\n   * @param type the Java SQL type from {@link java.sql.Types}\n   * @return true if the type is signed (INTEGER, DECIMAL, or DOUBLE)\n   */\n  public static boolean isJavaTypeSigned(int type) {\n    return type == Types.INTEGER || type == Types.DECIMAL || type == Types.DOUBLE;\n  }\n\n  /** Internal enum representing Java data types for Snowflake columns. */\n  public enum JavaDataType {\n    JAVA_STRING(String.class),\n    JAVA_LONG(Long.class),\n    JAVA_DOUBLE(Double.class),\n    JAVA_BIGDECIMAL(BigDecimal.class),\n    JAVA_TIMESTAMP(Timestamp.class),\n    JAVA_PERIOD(Period.class),\n    JAVA_DURATION(Duration.class),\n    JAVA_BYTES(byte[].class),\n    JAVA_BOOLEAN(Boolean.class),\n    JAVA_OBJECT(Object.class);\n\n    JavaDataType(Class<?> c) {\n      this._class = c;\n    }\n\n    private Class<?> _class;\n  }\n\n  /** Internal enum representing Java SQL types with convenient lookup methods. */\n  public enum JavaSQLType {\n    ARRAY(Types.ARRAY),\n    DATALINK(Types.DATALINK),\n    BIGINT(Types.BIGINT),\n    BINARY(Types.BINARY),\n    BIT(Types.BIT),\n    BLOB(Types.BLOB),\n    BOOLEAN(Types.BOOLEAN),\n    CHAR(Types.CHAR),\n    CLOB(Types.CLOB),\n    DATE(Types.DATE),\n    DECIMAL(Types.DECIMAL),\n    DISTINCT(Types.DISTINCT),\n    DOUBLE(Types.DOUBLE),\n    FLOAT(Types.FLOAT),\n    INTEGER(Types.INTEGER),\n    JAVA_OBJECT(Types.JAVA_OBJECT),\n    LONGNVARCHAR(Types.LONGNVARCHAR),\n    LONGVARBINARY(Types.LONGVARBINARY),\n    LONGVARCHAR(Types.LONGVARCHAR),\n    NCHAR(Types.NCHAR),\n    NCLOB(Types.NCLOB),\n    NULL(Types.NULL),\n    NUMERIC(Types.NUMERIC),\n    NVARCHAR(Types.NVARCHAR),\n    OTHER(Types.OTHER),\n    REAL(Types.REAL),\n    REF(Types.REF),\n    REF_CURSOR(Types.REF_CURSOR),\n    ROWID(Types.ROWID),\n    SMALLINT(Types.SMALLINT),\n    SQLXML(Types.SQLXML),\n    STRUCT(Types.STRUCT),\n    TIME(Types.TIME),\n    TIME_WITH_TIMEZONE(Types.TIME_WITH_TIMEZONE),\n    TIMESTAMP(Types.TIMESTAMP),\n    TIMESTAMP_WITH_TIMEZONE(Types.TIMESTAMP_WITH_TIMEZONE),\n    TINYINT(Types.TINYINT),\n    VARBINARY(Types.VARBINARY),\n    VARCHAR(Types.VARCHAR),\n    VECTOR(Types.ARRAY);\n\n    private final int type;\n    public static final Set<JavaSQLType> ALL_TYPES = new HashSet<>();\n\n    static {\n      ALL_TYPES.add(ARRAY);\n      ALL_TYPES.add(DATALINK);\n      ALL_TYPES.add(BIGINT);\n      ALL_TYPES.add(BINARY);\n      ALL_TYPES.add(BIT);\n      ALL_TYPES.add(BLOB);\n      ALL_TYPES.add(BOOLEAN);\n      ALL_TYPES.add(CHAR);\n      ALL_TYPES.add(CLOB);\n      ALL_TYPES.add(DATE);\n      ALL_TYPES.add(DECIMAL);\n      ALL_TYPES.add(DISTINCT);\n      ALL_TYPES.add(DOUBLE);\n      ALL_TYPES.add(FLOAT);\n      ALL_TYPES.add(INTEGER);\n      ALL_TYPES.add(JAVA_OBJECT);\n      ALL_TYPES.add(LONGNVARCHAR);\n      ALL_TYPES.add(LONGVARBINARY);\n      ALL_TYPES.add(LONGVARCHAR);\n      ALL_TYPES.add(NCHAR);\n      ALL_TYPES.add(NCLOB);\n      ALL_TYPES.add(NULL);\n      ALL_TYPES.add(NUMERIC);\n      ALL_TYPES.add(NVARCHAR);\n      ALL_TYPES.add(OTHER);\n      ALL_TYPES.add(REAL);\n      ALL_TYPES.add(REF);\n      ALL_TYPES.add(REF_CURSOR);\n      ALL_TYPES.add(ROWID);\n      ALL_TYPES.add(SMALLINT);\n      ALL_TYPES.add(SQLXML);\n      ALL_TYPES.add(STRUCT);\n      ALL_TYPES.add(TIME);\n      ALL_TYPES.add(TIME_WITH_TIMEZONE);\n      ALL_TYPES.add(TIMESTAMP);\n      ALL_TYPES.add(TIMESTAMP_WITH_TIMEZONE);\n      ALL_TYPES.add(TINYINT);\n      ALL_TYPES.add(VARBINARY);\n      ALL_TYPES.add(VARCHAR);\n      ALL_TYPES.add(VECTOR);\n    }\n\n    JavaSQLType(int type) {\n      this.type = type;\n    }\n\n    public int getType() {\n      return type;\n    }\n\n    /**\n     * Find a JavaSQLType by its integer type value.\n     *\n     * @param type the integer type from {@link java.sql.Types}\n     * @return the corresponding JavaSQLType, or null if not found\n     */\n    public static JavaSQLType find(int type) {\n      for (JavaSQLType t : ALL_TYPES) {\n        if (t.type == type) {\n          return t;\n        }\n      }\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/jdbc/util/SnowflakeTypeUtil.java",
    "content": "package net.snowflake.client.internal.jdbc.util;\n\nimport java.math.BigDecimal;\nimport java.sql.SQLException;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.text.DateFormat;\nimport java.time.Duration;\nimport java.time.Period;\nimport java.util.Date;\nimport java.util.Locale;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.common.core.SqlState;\n\n/**\n * Internal utility class for SnowflakeType conversions and formatting. These methods are used\n * internally by the driver and should not be considered part of the public API.\n */\npublic class SnowflakeTypeUtil {\n\n  /**\n   * Converts a string to a SnowflakeType enum value.\n   *\n   * @param name the type name\n   * @return the corresponding SnowflakeType\n   * @throws IllegalArgumentException if the type name is not a known SnowflakeType\n   */\n  public static SnowflakeType fromString(String name) {\n    return SnowflakeType.valueOf(name.toUpperCase(Locale.ROOT));\n  }\n\n  /**\n   * Converts a string to a SnowflakeType enum value, returning null for unknown types.\n   *\n   * @param name the type name\n   * @return the corresponding SnowflakeType, or null if the type name is not recognized\n   */\n  public static SnowflakeType fromStringOrNull(String name) {\n    try {\n      return fromString(name);\n    } catch (IllegalArgumentException e) {\n      return null;\n    }\n  }\n\n  /**\n   * Gets the Java data type for a Snowflake type.\n   *\n   * @param type the Snowflake type\n   * @return the corresponding Java data type\n   */\n  public static SnowflakeTypeHelper.JavaDataType getJavaType(SnowflakeType type) {\n    return getJavaType(type, false);\n  }\n\n  /**\n   * Gets the Java data type for a Snowflake type.\n   *\n   * @param type the Snowflake type\n   * @param isStructuredType whether this is a structured type\n   * @return the corresponding Java data type\n   */\n  public static SnowflakeTypeHelper.JavaDataType getJavaType(\n      SnowflakeType type, boolean isStructuredType) {\n    // TODO structuredType fill for Array and Map: SNOW-1234216, SNOW-1234214\n    switch (type) {\n      case TEXT:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_STRING;\n      case CHAR:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_STRING;\n      case INTEGER:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_LONG;\n      case FIXED:\n      case DECFLOAT:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_BIGDECIMAL;\n      case REAL:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_DOUBLE;\n      case TIMESTAMP:\n      case TIME:\n      case TIMESTAMP_LTZ:\n      case TIMESTAMP_NTZ:\n      case TIMESTAMP_TZ:\n      case DATE:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP;\n      case INTERVAL_YEAR_MONTH:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_PERIOD;\n      case INTERVAL_DAY_TIME:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_DURATION;\n      case BOOLEAN:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_BOOLEAN;\n      case ARRAY:\n      case VARIANT:\n      case VECTOR:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_STRING;\n      case BINARY:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_BYTES;\n      case ANY:\n        return SnowflakeTypeHelper.JavaDataType.JAVA_OBJECT;\n      case OBJECT:\n        if (isStructuredType) {\n          return SnowflakeTypeHelper.JavaDataType.JAVA_OBJECT;\n        } else {\n          return SnowflakeTypeHelper.JavaDataType.JAVA_STRING;\n        }\n      default:\n        // Those are not supported, but no reason to panic\n        return SnowflakeTypeHelper.JavaDataType.JAVA_STRING;\n    }\n  }\n\n  /**\n   * Returns a lexical value of an object that is suitable for Snowflake import serialization\n   *\n   * @param o Java object representing value in Snowflake.\n   * @param dateFormat java.sql.Date or java.sqlTime format\n   * @param timeFormat java.sql.Time format\n   * @param timestampFormat first part of java.sql.Timestamp format\n   * @param timestampTzFormat last part of java.sql.Timestamp format\n   * @return String representation of it that can be used for creating a load file\n   */\n  public static String lexicalValue(\n      Object o,\n      DateFormat dateFormat,\n      DateFormat timeFormat,\n      DateFormat timestampFormat,\n      DateFormat timestampTzFormat) {\n    if (o == null) {\n      return null;\n    }\n\n    Class<?> c = o.getClass();\n\n    if (c == Date.class || c == java.sql.Date.class) {\n      return synchronizeFormat(o, dateFormat);\n    }\n\n    if (c == java.sql.Time.class) {\n      return synchronizeFormat(o, timeFormat);\n    }\n\n    if (c == java.sql.Timestamp.class) {\n      String stdFmt = o.toString();\n      String nanos = stdFmt.substring(stdFmt.indexOf('.') + 1);\n      String ret1 = synchronizeFormat(o, timestampFormat);\n      String ret2 = synchronizeFormat(o, timestampTzFormat);\n      return ret1 + nanos + ret2;\n    }\n    if (c == Double.class) {\n      return Double.toHexString((Double) o);\n    }\n\n    if (c == Float.class) {\n      return Float.toHexString((Float) o);\n    }\n\n    if (c == Integer.class) {\n      return o.toString();\n    }\n\n    if (c == Period.class) {\n      return o.toString();\n    }\n\n    if (c == Duration.class) {\n      return o.toString();\n    }\n\n    if (c == BigDecimal.class) {\n      return o.toString();\n    }\n\n    if (c == byte[].class) {\n      return new SFBinary((byte[]) o).toHex();\n    }\n\n    return String.valueOf(o);\n  }\n\n  private static synchronized String synchronizeFormat(Object o, DateFormat sdf) {\n    return sdf.format(o);\n  }\n\n  /**\n   * Escapes a string value for CSV format.\n   *\n   * @param value the value to escape\n   * @return the escaped value\n   */\n  public static String escapeForCSV(String value) {\n    if (value == null) {\n      return \"\"; // null => an empty string without quotes\n    }\n    if (value.isEmpty()) {\n      return \"\\\"\\\"\"; // an empty string => an empty string with quotes\n    }\n    if (value.indexOf('\"') >= 0\n        || value.indexOf('\\n') >= 0\n        || value.indexOf(',') >= 0\n        || value.indexOf('\\\\') >= 0) {\n      // anything else including double quotes or commas will have quotes\n      return '\"' + value.replaceAll(\"\\\"\", \"\\\"\\\"\") + '\"';\n    } else {\n      return value;\n    }\n  }\n\n  /**\n   * Converts a Java SQL type to a Snowflake type.\n   *\n   * @param javaType the Java SQL type (from {@link java.sql.Types})\n   * @param session the session object\n   * @return the corresponding Snowflake type\n   * @throws net.snowflake.client.api.exception.SnowflakeSQLException if the type is not supported\n   */\n  public static SnowflakeType javaTypeToSFType(int javaType, SFBaseSession session)\n      throws net.snowflake.client.api.exception.SnowflakeSQLException {\n\n    switch (javaType) {\n      case java.sql.Types.INTEGER:\n      case java.sql.Types.BIGINT:\n      case java.sql.Types.DECIMAL:\n      case java.sql.Types.NUMERIC:\n      case java.sql.Types.SMALLINT:\n      case java.sql.Types.TINYINT:\n        return SnowflakeType.FIXED;\n\n      case java.sql.Types.CHAR:\n      case java.sql.Types.VARCHAR:\n        return SnowflakeType.TEXT;\n\n      case java.sql.Types.BINARY:\n        return SnowflakeType.BINARY;\n\n      case java.sql.Types.FLOAT:\n      case java.sql.Types.DOUBLE:\n        return SnowflakeType.REAL;\n\n      case java.sql.Types.DATE:\n        return SnowflakeType.DATE;\n\n      case java.sql.Types.TIME:\n        return SnowflakeType.TIME;\n\n      case java.sql.Types.TIMESTAMP:\n        return SnowflakeType.TIMESTAMP;\n\n      case java.sql.Types.BOOLEAN:\n        return SnowflakeType.BOOLEAN;\n\n      case java.sql.Types.STRUCT:\n        return SnowflakeType.OBJECT;\n\n      case java.sql.Types.ARRAY:\n        return SnowflakeType.ARRAY;\n\n      case java.sql.Types.NULL:\n        return SnowflakeType.ANY;\n\n      default:\n        throw new SnowflakeSQLLoggedException(\n            session,\n            ErrorCode.DATA_TYPE_NOT_SUPPORTED.getMessageCode(),\n            SqlState.FEATURE_NOT_SUPPORTED,\n            javaType);\n    }\n  }\n\n  /**\n   * Converts a Java SQL type to a Java class name.\n   *\n   * @param type the Java SQL type (from {@link java.sql.Types})\n   * @return the corresponding Java class name\n   * @throws SQLException if the type is not supported\n   */\n  public static String javaTypeToClassName(int type) throws SQLException {\n    switch (type) {\n      case java.sql.Types.VARCHAR:\n      case java.sql.Types.CHAR:\n      case java.sql.Types.STRUCT:\n      case java.sql.Types.ARRAY:\n        return String.class.getName();\n\n      case java.sql.Types.BINARY:\n        return SnowflakeTypeHelper.BINARY_CLASS_NAME;\n\n      case java.sql.Types.INTEGER:\n        return Integer.class.getName();\n\n      case java.sql.Types.DECIMAL:\n        return BigDecimal.class.getName();\n\n      case java.sql.Types.DOUBLE:\n        return Double.class.getName();\n\n      case java.sql.Types.TIMESTAMP:\n      case java.sql.Types.TIMESTAMP_WITH_TIMEZONE:\n        return Timestamp.class.getName();\n\n      case java.sql.Types.DATE:\n        return java.sql.Date.class.getName();\n\n      case java.sql.Types.TIME:\n        return Time.class.getName();\n\n      case java.sql.Types.BOOLEAN:\n        return Boolean.class.getName();\n\n      case java.sql.Types.BIGINT:\n        return Long.class.getName();\n\n      case java.sql.Types.SMALLINT:\n        return Short.class.getName();\n\n      default:\n        throw new java.sql.SQLFeatureNotSupportedException(\n            String.format(\"No corresponding Java type is found for java.sql.Type: %d\", type));\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/BufferStage.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.zip.GZIPOutputStream;\nimport net.snowflake.client.api.loader.Loader;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Class representing a unit of work for uploader. Corresponds to a collection of data files for a\n * single processing stage.\n */\npublic class BufferStage {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(BufferStage.class);\n\n  public enum State {\n    CREATED,\n    LOADING,\n    LOADED,\n    EMPTY,\n    UPLOADED,\n    VALIDATED,\n    VALIDATED_CLEANED,\n    ERROR,\n    PROCESSED,\n    CLEANED,\n    REMOVED\n  };\n\n  public static final int FILE_BUCKET_SIZE = 64; // threshold to schedule processing\n  public static final long FILE_SIZE = 50L * 1024L * 1024L; // individual file, 50Mb\n\n  private State _state;\n\n  private final File _directory;\n\n  private final String _location;\n\n  private final String _stamp;\n\n  private final Operation _op;\n\n  private final long _csvFileBucketSize;\n\n  private final long _csvFileSize;\n\n  // Last stage in a loader gets to terminate\n  private volatile boolean _terminate = false;\n\n  private String _id;\n\n  // Data bytes (uncompressed) in the current file\n  private int _currentSize = 0;\n\n  // Total number of rows submitted to this stage\n  private int _rowCount = 0;\n\n  // Number of files in the stage\n  private int _fileCount = 0;\n\n  // Counter for ID generation\n  private static AtomicLong MARK = new AtomicLong(1);\n\n  // Parent loader\n  private StreamLoader _loader;\n\n  // Current output stream\n  private OutputStream _outstream = null;\n\n  // Current file\n  private File _file = null;\n\n  // List of all scheduled uploaders\n  private ArrayList<FileUploader> _uploaders = new ArrayList<>();\n\n  BufferStage(StreamLoader loader, Operation op, long csvFileBucketSize, long csvFileSize) {\n    logger.debug(\"Operation: {}\", op);\n\n    _state = State.CREATED;\n    _loader = loader;\n\n    _stamp = new SimpleDateFormat(\"yyyyMMdd'_'HHmmss'_'SSS\").format(new Date());\n    _csvFileBucketSize = csvFileBucketSize;\n    _csvFileSize = csvFileSize;\n\n    long mark = MARK.getAndIncrement() % 10000000;\n\n    // Security Fix: A table name can include slashes and dots, so if a table\n    // name is used as part of a file name, the file can be created\n    // outside of the given directory. This replaces slashes with underscores.\n    _location =\n        BufferStage.escapeFileSeparatorChar(_loader.getTable())\n            + File.separatorChar\n            + op.name()\n            + File.separatorChar\n            + _stamp\n            + \"_\"\n            + _loader.getNoise()\n            + '_'\n            + mark;\n\n    _id = BufferStage.escapeFileSeparatorChar(_loader.getTable()) + \"_\" + _stamp + '_' + mark;\n\n    String localStageDirectory = _loader.getBase() + File.separatorChar + _location;\n\n    _directory = new File(localStageDirectory);\n    if (!_directory.mkdirs()) {\n      RuntimeException ex =\n          new RuntimeException(\n              \"Could not initialize the local staging area. \"\n                  + \"Make sure the directory is writable and readable: \"\n                  + localStageDirectory);\n\n      _loader.abort(ex);\n      throw ex;\n    }\n\n    _op = op;\n\n    openFile();\n  }\n\n  /** Create local file for caching data before upload */\n  private synchronized void openFile() {\n    try {\n      String fName =\n          _directory.getAbsolutePath()\n              + File.separatorChar\n              + StreamLoader.FILE_PREFIX\n              + _stamp\n              + _fileCount;\n      if (_loader._compressDataBeforePut) {\n        fName += StreamLoader.FILE_SUFFIX;\n      }\n      logger.debug(\"openFile: {}\", fName);\n\n      OutputStream fileStream = new FileOutputStream(fName);\n      if (_loader._compressDataBeforePut) {\n        OutputStream gzipOutputStream =\n            new GZIPOutputStream(fileStream, 64 * 1024, true) {\n              {\n                def.setLevel((int) _loader._compressLevel);\n              }\n            };\n        _outstream = new BufferedOutputStream(gzipOutputStream);\n      } else {\n        _outstream = new BufferedOutputStream(fileStream);\n      }\n\n      _file = new File(fName);\n\n      _fileCount++;\n    } catch (IOException ex) {\n      _loader.abort(new Loader.ConnectionError(Utils.getCause(ex)));\n    }\n  }\n\n  private static byte[] newLineBytes = \"\\n\".getBytes(UTF_8);\n\n  // not thread safe\n  boolean stageData(final byte[] line) throws IOException {\n    if (this._rowCount % 10000 == 0) {\n      logger.debug(\"rowCount: {}, currentSize: {}\", this._rowCount, _currentSize);\n    }\n    _outstream.write(line);\n    _currentSize += line.length;\n\n    _outstream.write(newLineBytes);\n    this._rowCount++;\n\n    if (_loader._testRemoteBadCSV) {\n      // inject garbage for a negative test case\n      // The file will be uploaded to the stage, but COPY command will\n      // fail and raise LoaderError\n      _outstream.write(new byte[] {(byte) 0x01, (byte) 0x02});\n      _outstream.write(newLineBytes);\n      this._rowCount++;\n    }\n\n    if (_currentSize >= this._csvFileSize) {\n      logger.debug(\n          \"name: {}, currentSize: {}, Threshold: {},\" + \" fileCount: {}, fileBucketSize: {}\",\n          _file.getAbsolutePath(),\n          _currentSize,\n          this._csvFileSize,\n          _fileCount,\n          this._csvFileBucketSize);\n      _outstream.flush();\n      _outstream.close();\n      _outstream = null;\n      FileUploader fu = new FileUploader(_loader, _location, _file);\n      fu.upload();\n      _uploaders.add(fu);\n      openFile();\n      _currentSize = 0;\n    }\n\n    return _fileCount > this._csvFileBucketSize;\n  }\n\n  /**\n   * Wait for all files to finish uploading and schedule stage for processing\n   *\n   * @throws IOException raises an exception if IO error occurs\n   */\n  void completeUploading() throws IOException {\n    logger.debug(\n        \"name: {}, currentSize: {}, Threshold: {},\" + \" fileCount: {}, fileBucketSize: {}\",\n        _file.getAbsolutePath(),\n        _currentSize,\n        this._csvFileSize,\n        _fileCount,\n        this._csvFileBucketSize);\n\n    _outstream.flush();\n    _outstream.close();\n    // last file\n    if (_currentSize > 0) {\n      FileUploader fu = new FileUploader(_loader, _location, _file);\n      fu.upload();\n      _uploaders.add(fu);\n    } else {\n      // delete empty file\n      _file.delete();\n    }\n\n    for (FileUploader fu : _uploaders) {\n      // Finish all files being uploaded\n      fu.join();\n    }\n\n    // Delete the directory once we are done (for easier tracking\n    // of what is going on)\n    _directory.deleteOnExit();\n\n    if (this._rowCount == 0) {\n      setState(State.EMPTY);\n    }\n  }\n\n  public String getRemoteLocation() {\n    return remoteSeparator(_location);\n  }\n\n  Operation getOp() {\n    return _op;\n  }\n\n  public boolean isTerminate() {\n    return _terminate;\n  }\n\n  public void setTerminate(boolean terminate) {\n    this._terminate = terminate;\n  }\n\n  public String getId() {\n    return _id;\n  }\n\n  public void setId(String _id) {\n    this._id = _id;\n  }\n\n  public State state() {\n    return _state;\n  }\n\n  public void setState(State state) {\n    if (_state != state) {\n      // Logging goes here\n      // Need to keep trace of states.\n      _state = state;\n    }\n  }\n\n  int getRowCount() {\n    return _rowCount;\n  }\n\n  // convert any back slashes to forward slashes if necessary when converting\n  // a local filename to a one suitable for S3\n  private String remoteSeparator(String fname) {\n    if (File.separatorChar == '\\\\') {\n      return fname.replace(\"\\\\\", \"/\");\n    } else {\n      return fname;\n    }\n  }\n\n  /**\n   * Escape file separator char to underscore. This prevents the file name from using file path\n   * separator.\n   *\n   * @param fname The file name to escape\n   * @return escaped file name\n   */\n  private static String escapeFileSeparatorChar(String fname) {\n    if (File.separatorChar == '\\\\') {\n      return fname.replaceAll(File.separator + File.separator, \"_\");\n    } else {\n      return fname.replaceAll(File.separator, \"_\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/FileUploader.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport java.io.File;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport net.snowflake.client.api.loader.Loader;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Class responsible for uploading a single data file. */\npublic class FileUploader implements Runnable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(PutQueue.class);\n\n  private static final int RETRY = 6;\n  private final Thread _thread;\n  private final StreamLoader _loader;\n  private final String _stage;\n  private final File _file;\n\n  FileUploader(StreamLoader loader, String stage, File file) {\n    logger.trace(\"Creating new FileUploader\", false);\n    _loader = loader;\n    _thread = new Thread(this);\n    _thread.setName(\"FileUploaderThread\");\n    _stage = stage;\n    _file = file;\n  }\n\n  public synchronized void upload() {\n    // throttle up will wait if too many files are uploading\n    logger.trace(\"Creating new FileUploader\", false);\n    _loader.throttleUp();\n    _thread.start();\n  }\n\n  @Override\n  public void run() {\n\n    Throwable previousException = null;\n    try {\n      for (int attempt = 0; attempt <= RETRY; attempt++) {\n\n        if (attempt == RETRY) {\n          if (previousException != null) {\n            _loader.abort(\n                new Loader.ConnectionError(\n                    String.format(\n                        \"File could not be uploaded to remote stage \"\n                            + \"after retrying %d times: %s\",\n                        RETRY, _file.getCanonicalPath()),\n                    Utils.getCause(previousException)));\n          } else {\n            _loader.abort(\n                new Loader.ConnectionError(\n                    String.format(\n                        \"File could not be uploaded to remote stage \"\n                            + \"after retrying %d times: %s\",\n                        RETRY, _file.getCanonicalPath())));\n          }\n          break;\n        }\n\n        if (attempt > 0) {\n          logger.debug(\"Will retry PUT after {} seconds\", Math.pow(2, attempt));\n          Thread.sleep(1000 * ((int) Math.pow(2, attempt)));\n        }\n\n        // In test mode force fail first file\n        if (_loader._testMode) {\n          // TEST MODE\n          if (attempt < 2) {\n            _loader\n                .getPutConnection()\n                .unwrap(SnowflakeConnectionImpl.class)\n                .setInjectFileUploadFailure(_file.getName());\n          } else {\n            // so that retry now succeeds.\n            _loader\n                .getPutConnection()\n                .unwrap(SnowflakeConnectionImpl.class)\n                .setInjectFileUploadFailure(null);\n          }\n        }\n\n        // Upload local files to a remote stage\n\n        // No double quote is added _loader.getRemoteStage(), since\n        // it is most likely \"~\". If not, we may need to double quote\n        // them.\n        String remoteStage = \"@\" + _loader.getRemoteStage() + \"/\" + remoteSeparator(_stage);\n\n        String putStatement =\n            \"PUT \"\n                + (attempt > 0 ? \"/* retry:\" + attempt + \" */ \" : \"\")\n                + \"'file://\"\n                + _file.getCanonicalPath().replaceAll(\"\\\\\\\\\", \"\\\\\\\\\\\\\\\\\")\n                + \"' '\"\n                + remoteStage\n                + \"' parallel=10\" // upload chunks in parallel\n                + \" overwrite=true\"; // skip file existence check\n        if (_loader._compressDataBeforePut) {\n          putStatement += \" auto_compress=false\" + \" SOURCE_COMPRESSION=gzip\";\n        } else if (_loader._compressFileByPut) {\n          putStatement += \" auto_compress=true\";\n        } else {\n          // don't compress file at all\n          putStatement += \" auto_compress=false\";\n        }\n\n        Statement statement = _loader.getPutConnection().createStatement();\n        try {\n          logger.debug(\"Put Statement start: {}\", putStatement);\n          statement.execute(putStatement);\n          logger.debug(\"Put Statement end: {}\", putStatement);\n          ResultSet putResult = statement.getResultSet();\n\n          putResult.next();\n\n          String file =\n              localSeparator(\n                  putResult.getString(SnowflakeFileTransferAgent.UploadColumns.source.name()));\n          String status =\n              putResult.getString(SnowflakeFileTransferAgent.UploadColumns.status.name());\n          String message =\n              putResult.getString(SnowflakeFileTransferAgent.UploadColumns.message.name());\n\n          if (status != null\n              && status.equals(SnowflakeFileTransferAgent.ResultStatus.UPLOADED.name())) {\n            // UPLOAD is success\n            _file.delete();\n            break;\n          } else {\n            // The log level should be WARNING for a single upload failure.\n            if (message.startsWith(\"Simulated upload failure\")) {\n              logger.debug(\n                  \"Failed to upload a file:\" + \" status={},\" + \" filename={},\" + \" message={}\",\n                  status,\n                  file,\n                  message);\n            } else {\n              logger.debug(\n                  \"Failed to upload a file:\" + \" status={},\" + \" filename={},\" + \" message={}\",\n                  status,\n                  file,\n                  message);\n            }\n          }\n        } catch (Throwable t) {\n          // The log level for unknown error is set to SEVERE\n          logger.error(\n              String.format(\n                  \"Failed to PUT on attempt: attempt=[%s], \" + \"Message=[%s]\",\n                  attempt, t.getMessage()),\n              t.getCause());\n          previousException = t;\n        }\n      }\n    } catch (Throwable t) {\n      logger.error(\"PUT exception\", t);\n      _loader.abort(new Loader.ConnectionError(t.getMessage(), t.getCause()));\n    } finally {\n      _loader.throttleDown();\n    }\n  }\n\n  public void join() {\n    logger.trace(\"Joining threads\", false);\n    try {\n      _thread.join(0);\n    } catch (InterruptedException ex) {\n      logger.error(ex.getMessage(), ex);\n    }\n  }\n\n  /**\n   * convert any back slashes to forward slashes if necessary when converting a local filename to a\n   * one suitable for S3\n   *\n   * @param fname a file name to PUT\n   * @return A fname string for S3\n   */\n  private String remoteSeparator(String fname) {\n    if (File.separatorChar == '\\\\') {\n      return fname.replace(\"\\\\\", \"/\");\n    } else {\n      return fname;\n    }\n  }\n\n  /**\n   * convert any forward slashes to back slashes if necessary when converting a S3 file name to a\n   * local file name\n   *\n   * @param fname a file name to PUT\n   * @return A fname string for the local FS (Windows/other Unix like OS)\n   */\n  private String localSeparator(String fname) {\n    if (File.separatorChar == '\\\\') {\n      return fname.replace(\"/\", \"\\\\\");\n    } else {\n      return fname;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/OnError.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport java.util.regex.Pattern;\n\n/** COPY ON_ERROR option */\nclass OnError {\n  private static final Pattern validPattern =\n      Pattern.compile(\"(?i)(?:ABORT_STATEMENT|CONTINUE|SKIP_FILE(?:_\\\\d+%?)?)\");\n\n  /** Default behavior for ON_ERROR for Loader API. */\n  static final String DEFAULT = \"CONTINUE\";\n\n  private OnError() {}\n\n  /**\n   * Validates ON_ERROR value and return true if valid otherwise false.\n   *\n   * @param value ON_ERROR value\n   * @return true if valid otherwise false.\n   */\n  static boolean validate(String value) {\n    return value != null && validPattern.matcher(value).matches();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/ProcessQueue.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static java.lang.Math.toIntExact;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.LinkedList;\nimport java.util.List;\nimport net.snowflake.client.api.loader.LoadResultListener;\nimport net.snowflake.client.api.loader.Loader;\nimport net.snowflake.client.api.loader.LoadingError;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * This class is responsible for processing a collection of uploaded data files represented by\n * BufferStage class\n */\npublic class ProcessQueue implements Runnable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ProcessQueue.class);\n\n  private final Thread _thread;\n\n  private final StreamLoader _loader;\n\n  public ProcessQueue(StreamLoader loader) {\n    logger.debug(\"\", false);\n\n    _loader = loader;\n    _thread = new Thread(this);\n    _thread.setName(\"ProcessQueueThread\");\n    _thread.start();\n  }\n\n  @Override\n  public void run() {\n\n    while (true) {\n\n      BufferStage stage = null;\n\n      Connection conn = _loader.getProcessConnection();\n      State currentState = State.INITIALIZE;\n      String currentCommand = null;\n      try {\n        stage = _loader.takeProcess();\n\n        if (stage.getRowCount() == 0) {\n          // Nothing was written to that stage\n          if (stage.isTerminate()) {\n            break;\n          } else {\n            continue;\n          }\n        }\n\n        // Place where the files are.\n        // No double quote is added _loader.getRemoteStage(), since\n        // it is mostly likely to be \"~\". If not, we may need to double quote\n        // them.\n        String remoteStage = \"@\" + _loader.getRemoteStage() + \"/\" + stage.getRemoteLocation();\n        // process uploaded files\n        // Loader.abort() and finish() are also synchronized on this\n        synchronized (_loader) {\n          String updateKeys = getOn(_loader.getKeys(), \"T\", \"S\");\n          if (stage.getOp() != Operation.INSERT && updateKeys.isEmpty()) {\n            _loader.abort(new RuntimeException(\"No update key column is specified for the job.\"));\n          }\n\n          if (_loader.isAborted()) {\n            if (!_loader._preserveStageFile) {\n              currentCommand = \"RM '\" + remoteStage + \"'\";\n              logger.debug(currentCommand, true);\n              conn.createStatement().execute(currentCommand);\n            } else {\n              logger.debug(\n                  \"Error occurred. The remote stage is preserved for \"\n                      + \"further investigation: {}\",\n                  remoteStage);\n            }\n            if (stage.isTerminate()) {\n              break;\n            } else {\n              continue;\n            }\n            // Do not do anything to this stage.\n            // Everything was rolled back upon abort() call\n          }\n          // Create a temporary table to hold all uploaded data\n\n          long loaded = 0;\n          long parsed = 0;\n          int errorCount = 0;\n          String lastErrorRow = \"\";\n\n          // Create temp table to load data (may have a subset of columns)\n          logger.debug(\"Creating Temporary Table: name={}\", stage.getId());\n          currentState = State.CREATE_TEMP_TABLE;\n          List<String> allColumns = getAllColumns(conn);\n\n          // use like to make sure columns in temporary table\n          // contains properties (e.g., NOT NULL) from the source table\n          currentCommand =\n              \"CREATE TEMPORARY TABLE \\\"\" + stage.getId() + \"\\\" LIKE \" + _loader.getFullTableName();\n          List<String> selectedColumns = _loader.getColumns();\n          conn.createStatement().execute(currentCommand);\n\n          // In case clustering key exists, drop it from the temporary table so that unused\n          // columns can be dropped from the table without errors.\n          String dropClusteringKey = \"alter table \\\"\" + stage.getId() + \"\\\" drop clustering key\";\n          conn.createStatement().execute(dropClusteringKey);\n\n          // the temp table can contain only a subset of columns\n          // so remove unselected columns\n          for (String col : allColumns) {\n            if (!selectedColumns.contains(col)) {\n              String dropUnSelectedColumn =\n                  \"alter table \\\"\" + stage.getId() + \"\\\" drop column \\\"\" + col + \"\\\"\";\n              conn.createStatement().execute(dropUnSelectedColumn);\n            }\n          }\n\n          // Load data there\n          logger.debug(\n              \"COPY data in the stage to table:\" + \" stage={},\" + \" name={}\",\n              remoteStage,\n              stage.getId());\n          currentState = State.COPY_INTO_TABLE;\n          currentCommand =\n              \"COPY INTO \\\"\"\n                  + stage.getId()\n                  + \"\\\" FROM '\"\n                  + remoteStage\n                  + \"' on_error='\"\n                  + _loader._onError\n                  + \"'\"\n                  + \" file_format=(\"\n                  + \" field_optionally_enclosed_by='\\\"'\"\n                  + \" empty_field_as_null=\"\n                  + Boolean.toString(!_loader._copyEmptyFieldAsEmpty)\n                  + \")\";\n          ResultSet rs = conn.createStatement().executeQuery(currentCommand);\n\n          while (rs.next()) {\n            // Get the number of rows actually loaded\n            loaded += rs.getLong(\"rows_loaded\");\n            // Get the number of rows parsed\n            parsed += rs.getLong(\"rows_parsed\");\n          }\n\n          int errorRecordCount = toIntExact(parsed - loaded);\n          logger.debug(\n              \"errorRecordCount=[{}],\" + \" parsed=[{}],\" + \" loaded=[{}]\",\n              errorRecordCount,\n              parsed,\n              loaded);\n\n          LoadResultListener listener = _loader.getListener();\n          listener.addErrorRecordCount(errorRecordCount);\n\n          if (loaded == stage.getRowCount()) {\n            // successfully loaded everything\n            logger.debug(\n                \"COPY command successfully finished:\" + \" stage={},\" + \" name={}\",\n                remoteStage,\n                stage.getId());\n            listener.addErrorCount(0);\n          } else {\n            logger.debug(\n                \"Found errors in COPY command:\" + \" stage={},\" + \" name={}\",\n                remoteStage,\n                stage.getId());\n            if (listener.needErrors()) {\n              currentState = State.COPY_INTO_TABLE_ERROR;\n              currentCommand =\n                  \"COPY INTO \\\"\"\n                      + stage.getId()\n                      + \"\\\" FROM '\"\n                      + remoteStage\n                      + \"' validation_mode='return_all_errors'\"\n                      + \" file_format=(\"\n                      + \"field_optionally_enclosed_by='\\\"'\"\n                      + \"empty_field_as_null=\"\n                      + Boolean.toString(!_loader._copyEmptyFieldAsEmpty)\n                      + \")\";\n              ResultSet errorsSet = conn.createStatement().executeQuery(currentCommand);\n\n              Loader.DataError dataError = null;\n\n              while (errorsSet.next()) {\n                errorCount++;\n                String rn = errorsSet.getString(LoadingError.ErrorProperty.ROW_NUMBER.name());\n                if (rn != null && !lastErrorRow.equals(rn)) {\n                  // de-duping records with multiple errors\n                  lastErrorRow = rn;\n                }\n                LoadingError loadError = new LoadingError(errorsSet, stage, _loader);\n\n                listener.addError(loadError);\n                if (dataError == null) {\n                  dataError = loadError.getException();\n                }\n              }\n              logger.debug(\"errorCount: {}\", errorCount);\n\n              listener.addErrorCount(errorCount);\n              if (listener.throwOnError()) {\n                // stop operation and raise the error\n                _loader.abort(dataError);\n\n                if (!_loader._preserveStageFile) {\n                  logger.debug(\"RM: {}\", remoteStage);\n                  conn.createStatement().execute(\"RM '\" + remoteStage + \"'\");\n                } else {\n                  logger.error(\n                      \"Error occurred. The remote stage is preserved for \"\n                          + \"further investigation: {}\",\n                      remoteStage);\n                }\n                if (stage.isTerminate()) {\n                  break;\n                } else {\n                  continue;\n                }\n              }\n            }\n          }\n\n          stage.setState(BufferStage.State.VALIDATED);\n\n          // Generate set and values statement\n          StringBuilder setStatement = null;\n          StringBuilder valueStatement = null;\n          if (stage.getOp() != Operation.INSERT && stage.getOp() != Operation.DELETE) {\n\n            setStatement = new StringBuilder(\" \");\n            valueStatement = new StringBuilder(\"(\");\n\n            for (int c = 0; c < _loader.getColumns().size(); ++c) {\n              String column = _loader.getColumns().get(c);\n              if (c > 0) {\n                setStatement.append(\", \");\n                valueStatement.append(\" , \");\n              }\n              setStatement\n                  .append(\"T.\\\"\")\n                  .append(column)\n                  .append(\"\\\"=\")\n                  .append(\"S.\\\"\")\n                  .append(column)\n                  .append(\"\\\"\");\n              valueStatement.append(\"S.\\\"\").append(column).append(\"\\\"\");\n            }\n            valueStatement.append(\")\");\n          }\n\n          // generate statement for processing\n          currentState = State.INGEST_DATA;\n          String loadStatement;\n          switch (stage.getOp()) {\n            case INSERT:\n              {\n                loadStatement =\n                    \"INSERT INTO \"\n                        + _loader.getFullTableName()\n                        + \"(\"\n                        + _loader.getColumnsAsString()\n                        + \")\"\n                        + \" SELECT \"\n                        + _loader.getStageColumnsAsString()\n                        + \" FROM \\\"\"\n                        + stage.getId()\n                        + \"\\\"\";\n                break;\n              }\n            case DELETE:\n              {\n                loadStatement =\n                    \"DELETE FROM \"\n                        + _loader.getFullTableName()\n                        + \" T USING \\\"\"\n                        + stage.getId()\n                        + \"\\\" AS S WHERE \"\n                        + updateKeys;\n                break;\n              }\n            case MODIFY:\n              {\n                loadStatement =\n                    \"MERGE INTO \"\n                        + _loader.getFullTableName()\n                        + \" T USING \\\"\"\n                        + stage.getId()\n                        + \"\\\" AS S ON \"\n                        + updateKeys\n                        + \" WHEN MATCHED THEN UPDATE SET \"\n                        + setStatement;\n                break;\n              }\n            case UPSERT:\n              {\n                loadStatement =\n                    \"MERGE INTO \"\n                        + _loader.getFullTableName()\n                        + \" T USING \\\"\"\n                        + stage.getId()\n                        + \"\\\" AS S ON \"\n                        + updateKeys\n                        + \" WHEN MATCHED THEN UPDATE SET \"\n                        + setStatement\n                        + \" WHEN NOT MATCHED THEN INSERT(\"\n                        + _loader.getColumnsAsString()\n                        + \") VALUES\"\n                        + valueStatement;\n                break;\n              }\n            default:\n              loadStatement = \"\";\n          }\n          currentCommand = loadStatement;\n\n          logger.debug(\"Load Statement: {}\", loadStatement);\n          Statement s = conn.createStatement();\n          s.execute(loadStatement);\n\n          stage.setState(BufferStage.State.PROCESSED);\n          currentState = State.FINISH;\n          currentCommand = null;\n          switch (stage.getOp()) {\n            case INSERT:\n            case UPSERT:\n              {\n                _loader.getListener().addProcessedRecordCount(stage.getOp(), stage.getRowCount());\n\n                _loader.getListener().addOperationRecordCount(stage.getOp(), s.getUpdateCount());\n                break;\n              }\n            case DELETE:\n            case MODIFY:\n              {\n                // the number of successful DELETE is the number\n                // of processed rows and not the number of given\n                // rows.\n                _loader.getListener().addProcessedRecordCount(stage.getOp(), s.getUpdateCount());\n\n                _loader.getListener().addOperationRecordCount(stage.getOp(), s.getUpdateCount());\n                break;\n              }\n          }\n\n          // delete stage file if all success\n          conn.createStatement().execute(\"RM '\" + remoteStage + \"'\");\n\n          if (stage.isTerminate()) {\n            break;\n          }\n        }\n      } catch (InterruptedException ex) {\n        logger.error(\"Interrupted\", ex);\n        break;\n      } catch (Exception ex) {\n        String msg =\n            String.format(\"State: %s, %s, %s\", currentState, currentCommand, ex.getMessage());\n        _loader.abort(new Loader.ConnectionError(msg, Utils.getCause(ex)));\n        logger.error(msg, true);\n        if (stage == null || stage.isTerminate()) {\n          break;\n        }\n      }\n    }\n  }\n\n  private List<String> getAllColumns(final Connection conn) throws SQLException {\n    List<String> columns = new LinkedList<>();\n    ResultSet result =\n        conn.createStatement()\n            .executeQuery(\"show \" + \"columns\" + \" in \" + _loader.getFullTableName());\n    while (result.next()) {\n      String col = result.getString(\"column_name\");\n      columns.add(col);\n    }\n    return columns;\n  }\n\n  private String getOn(List<String> keys, String L, String R) {\n    if (keys == null) {\n      return \"\";\n    }\n    // L and R don't need to be quoted.\n    StringBuilder sb = keys.size() > 1 ? new StringBuilder(64) : new StringBuilder();\n    for (int i = 0; i < keys.size(); i++) {\n      if (i > 0) {\n        sb.append(\"AND \");\n      }\n      sb.append(L);\n      sb.append(\".\\\"\");\n      sb.append(keys.get(i));\n      sb.append(\"\\\" = \");\n      sb.append(R);\n      sb.append(\".\\\"\");\n      sb.append(keys.get(i));\n      sb.append(\"\\\" \");\n    }\n    return sb.toString();\n  }\n\n  public void join() {\n    logger.trace(\"Joining threads\", false);\n    try {\n      _thread.join(0);\n    } catch (InterruptedException ex) {\n      logger.debug(\"Exception: \", ex);\n    }\n  }\n\n  private enum State {\n    INITIALIZE,\n    CREATE_TEMP_TABLE,\n    COPY_INTO_TABLE,\n    COPY_INTO_TABLE_ERROR,\n    INGEST_DATA,\n    FINISH\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/PutQueue.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport java.io.IOException;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/**\n * Queue that sequentially finalizes BufferStage uploads and schedules them for processing in\n * ProcessQueue.\n */\npublic class PutQueue implements Runnable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(PutQueue.class);\n\n  private final Thread _thread;\n\n  private final StreamLoader _loader;\n\n  public PutQueue(StreamLoader loader) {\n    logger.trace(\"Creating new PutQueue\", false);\n    _loader = loader;\n    _thread = new Thread(this);\n    _thread.setName(\"PutQueueThread\");\n    _thread.start();\n  }\n\n  @Override\n  public void run() {\n\n    while (true) {\n\n      BufferStage stage = null;\n\n      try {\n\n        stage = _loader.takePut();\n\n        if (stage.getRowCount() == 0) {\n          // Nothing was written to that stage\n          if (stage.isTerminate()) {\n            _loader.queueProcess(stage);\n            stage.completeUploading();\n            break;\n          } else {\n            continue;\n          }\n        }\n\n        // Uploads the stage\n        stage.completeUploading();\n\n        // Schedules it for processing\n        _loader.queueProcess(stage);\n\n        if (stage.isTerminate()) {\n          break;\n        }\n\n      } catch (InterruptedException | IOException ex) {\n        logger.error(\"Exception: \", ex);\n        break;\n      } finally {\n\n      }\n    }\n  }\n\n  public void join() {\n    try {\n      _thread.join(0);\n    } catch (InterruptedException ex) {\n      logger.error(\"Exception: \", ex);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/StreamLoader.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.GregorianCalendar;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.zip.Deflater;\nimport net.snowflake.client.api.loader.LoadResultListener;\nimport net.snowflake.client.api.loader.Loader;\nimport net.snowflake.client.api.loader.LoaderProperty;\nimport net.snowflake.client.api.loader.LoadingError;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Stream Loader */\npublic class StreamLoader implements Loader, Runnable {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(StreamLoader.class);\n\n  private static final String SYSTEM_PARAMETER_PREFIX = \"net.snowflake.client.loader.\";\n\n  // Temporary directory used for data cache\n  private static final String tmpdir = systemGetProperty(\"java.io.tmpdir\");\n\n  private static final String BASE =\n      tmpdir\n          + (!(tmpdir.endsWith(\"/\") || tmpdir.endsWith(\"\\\\\")) ? File.separatorChar : \"\")\n          + \"snowflake\"\n          + File.separatorChar\n          + \"stage\";\n\n  static final String FILE_PREFIX = \"stream_\";\n\n  static final String FILE_SUFFIX = \".gz\";\n\n  /** Default batch row size */\n  private static final long DEFAULT_BATCH_ROW_SIZE = -1L;\n\n  public static DatabaseMetaData metadata;\n\n  private BufferStage _stage = null;\n\n  private Operation _op = null;\n\n  private boolean _startTransaction = false;\n\n  // force not to truncate in case where loader.start is called in\n  // multiple times in a single job.\n  private boolean _is_first_start_call = true;\n\n  // force not to commit or rollback in case where loader.finish is called\n  // in multiple times in a single job.\n  private boolean _is_last_finish_call = true;\n\n  private boolean _oneBatch = false;\n\n  private boolean _truncate = false;\n\n  private String _before = null;\n\n  private String _after = null;\n\n  private ArrayBlockingQueue<byte[]> _queueData;\n\n  private Thread _thread;\n\n  private ArrayBlockingQueue<BufferStage> _queuePut;\n\n  private PutQueue _put;\n\n  private ArrayBlockingQueue<BufferStage> _queueProcess;\n\n  private ProcessQueue _process;\n\n  private String _remoteStage = \"~\";\n\n  private String _table;\n\n  private String _schema;\n\n  private String _database;\n\n  private List<String> _columns;\n\n  private Map<String, Integer> _vectorColumnsNameAndSize = new HashMap<String, Integer>();\n\n  // Vector type can be FLOAT  or INT\n  private String _vectorType;\n\n  private List<String> _keys;\n\n  private long _batchRowSize = DEFAULT_BATCH_ROW_SIZE;\n\n  private long _csvFileBucketSize = BufferStage.FILE_BUCKET_SIZE;\n\n  private long _csvFileSize = BufferStage.FILE_SIZE;\n\n  boolean _testRemoteBadCSV = false; // TEST: inject bad csv in remote stage\n\n  boolean _preserveStageFile = false; // reserve stage file\n\n  private boolean _useLocalTimezone = false; // use local timezone instead of UTC\n\n  private boolean _mapTimeToTimestamp =\n      false; // map TIME to TIMESTAMP. Informatica V1 connector behavior\n\n  boolean _compressDataBeforePut = true; // compress data before PUT\n\n  boolean _compressFileByPut = false; // compress file by PUT\n\n  long _compressLevel = Deflater.BEST_SPEED; // compression level used to compress data before PUT\n\n  String _onError = OnError.DEFAULT;\n\n  boolean _copyEmptyFieldAsEmpty = false; // COPY command option to set EMPTY_FIELD_AS_NULL = false\n\n  boolean _testMode = false;\n\n  private final Connection _putConn;\n  private final Connection _processConn;\n\n  // a per-instance bit of random noise to make filenames more unique\n  private final String _noise;\n\n  // Track fatal errors\n  private AtomicBoolean _active = new AtomicBoolean(false);\n  private AtomicBoolean _aborted = new AtomicBoolean(false);\n  private RuntimeException _abortCause = new ConnectionError(\"Unknown exception\");\n\n  private AtomicInteger _throttleCounter = new AtomicInteger(0);\n\n  private final GregorianCalendar _calendarUTC = new GregorianCalendar(TimeZone.getTimeZone(\"UTC\"));\n\n  private GregorianCalendar _calendarLocal;\n\n  /** Resets calendar when start the job. */\n  private void resetCalendar() {\n    _calendarUTC.clear();\n    _calendarLocal = new GregorianCalendar(TimeZone.getDefault());\n    _calendarLocal.clear();\n  }\n\n  private DateFormat _dateFormat;\n  private DateFormat _timeFormat;\n  private DateFormat _timestampFormat;\n  private DateFormat _timestampTzFormat;\n\n  public StreamLoader(\n      Map<LoaderProperty, Object> properties,\n      Connection putConnection,\n      Connection processConnection) {\n    _putConn = putConnection;\n    _processConn = processConnection;\n\n    // Sort properties by ordinal to ensure columns is processed after table, schema and db to\n    // execute more performant metadata queries\n    properties.entrySet().stream()\n        .sorted(Map.Entry.comparingByKey(Enum::compareTo))\n        .forEach(e -> setProperty(e.getKey(), e.getValue()));\n\n    _noise = SnowflakeUtil.randomAlphaNumeric(6);\n  }\n\n  @Override\n  public void setProperty(LoaderProperty property, Object value) {\n    switch (property) {\n      case tableName:\n        _table = (String) value;\n        break;\n      case schemaName:\n        _schema = (String) value;\n        break;\n      case databaseName:\n        _database = (String) value;\n        break;\n      case remoteStage:\n        _remoteStage = (String) value;\n        break;\n      case columns:\n        if (value == null) {\n          _columns = null;\n        } else {\n          final List<String> typeCheckedColumns = new ArrayList<>();\n          for (Object e : (List<?>) value) {\n            typeCheckedColumns.add((String) e);\n          }\n          _columns = typeCheckedColumns;\n          setVectorColumns();\n        }\n        break;\n      case keys:\n        if (value == null) {\n          _keys = null;\n        } else {\n          final List<String> typeCheckedKeys = new ArrayList<>();\n          for (Object e : (List<?>) value) {\n            typeCheckedKeys.add((String) e);\n          }\n          _keys = typeCheckedKeys;\n        }\n        break;\n      case operation:\n        _op = (Operation) value;\n        break;\n      case startTransaction:\n        _startTransaction = Boolean.valueOf(String.valueOf(value));\n        break;\n      case oneBatch:\n        _oneBatch = Boolean.valueOf(String.valueOf(value));\n        break;\n      case truncateTable:\n        _truncate = Boolean.valueOf(String.valueOf(value));\n        break;\n      case executeBefore:\n        _before = String.valueOf(value);\n        break;\n      case executeAfter:\n        _after = String.valueOf(value);\n        break;\n      case isFirstStartCall:\n        _is_first_start_call = Boolean.valueOf(String.valueOf(value));\n        break;\n      case isLastFinishCall:\n        _is_last_finish_call = Boolean.valueOf(String.valueOf(value));\n        break;\n      case batchRowSize:\n        _batchRowSize = parseLongValue(LoaderProperty.batchRowSize, value);\n        break;\n      case csvFileBucketSize:\n        _csvFileBucketSize = parseLongValue(LoaderProperty.csvFileBucketSize, value);\n        break;\n      case csvFileSize:\n        _csvFileSize = parseLongValue(LoaderProperty.csvFileSize, value);\n        break;\n      case preserveStageFile:\n        _preserveStageFile = Boolean.valueOf(String.valueOf(value));\n        break;\n      case useLocalTimezone:\n        _useLocalTimezone = Boolean.valueOf(String.valueOf(value));\n        break;\n      case copyEmptyFieldAsEmpty:\n        _copyEmptyFieldAsEmpty = Boolean.valueOf(String.valueOf(value));\n        break;\n      case mapTimeToTimestamp:\n        // NOTE: this is a special flag to change mapping\n        // from TIME. Informatica connector v1 maps to TIMESTAMP\n        // but a legitimate behavior is supposed to be to TIME.\n        _mapTimeToTimestamp = Boolean.valueOf(String.valueOf(value));\n        break;\n      case compressDataBeforePut:\n        _compressDataBeforePut = Boolean.valueOf(String.valueOf(value));\n        break;\n      case compressFileByPut:\n        _compressFileByPut = Boolean.valueOf(String.valueOf(value));\n        break;\n      case compressLevel:\n        _compressLevel = parseLongValue(LoaderProperty.compressLevel, value);\n        if ((_compressLevel < Deflater.BEST_SPEED || _compressLevel > Deflater.BEST_COMPRESSION)\n            && _compressLevel != Deflater.DEFAULT_COMPRESSION) {\n          throw new IllegalArgumentException(\"invalid compression level\");\n        }\n        break;\n      case onError:\n        String v = String.valueOf(value);\n        _onError = OnError.validate(v) ? v : OnError.DEFAULT;\n        break;\n      case testRemoteBadCSV:\n        _testRemoteBadCSV = Boolean.valueOf(String.valueOf(value));\n        break;\n      default:\n        // nop, this should ever happens\n    }\n  }\n\n  private long parseLongValue(LoaderProperty name, Object value) {\n    long ret;\n    if (value instanceof String) {\n      ret = Long.valueOf((String) value);\n    } else if (value instanceof Long) {\n      ret = (Long) value;\n    } else if (value instanceof Integer) {\n      ret = Long.valueOf((Integer) value);\n    } else {\n      throw new IllegalArgumentException(\n          String.format(\"'%s' Must be a LONG value\", name.toString()));\n    }\n    return ret;\n  }\n\n  private void setPropertyBySystemProperty() {\n    final String BATCH_ROW_SIZE_KEY = SYSTEM_PARAMETER_PREFIX + \"batchRowSize\";\n    final String CSV_FILE_BUCKET_SIZE = SYSTEM_PARAMETER_PREFIX + \"csvFileBucketSize\";\n    final String CSV_FILE_SIZE = SYSTEM_PARAMETER_PREFIX + \"csvFileSize\";\n    final String COMPRESS_DATA_BEFORE_PUT_KEY = SYSTEM_PARAMETER_PREFIX + \"compressDataBeforePut\";\n    final String COMPRESS_FILE_BY_PUT_KEY = SYSTEM_PARAMETER_PREFIX + \"compressFileByPut\";\n    final String COMPRESS_LEVEL = SYSTEM_PARAMETER_PREFIX + \"compressLevel\";\n\n    Properties props = System.getProperties();\n    for (String propKey : props.stringPropertyNames()) {\n      String value = props.getProperty(propKey);\n      if (BATCH_ROW_SIZE_KEY.equals(propKey)) {\n        _batchRowSize = parseLongValue(LoaderProperty.batchRowSize, value);\n      } else if (CSV_FILE_BUCKET_SIZE.equals(propKey)) {\n        _csvFileBucketSize = parseLongValue(LoaderProperty.csvFileBucketSize, value);\n      } else if (CSV_FILE_SIZE.equals(propKey)) {\n        _csvFileSize = parseLongValue(LoaderProperty.csvFileSize, value);\n      } else if (COMPRESS_DATA_BEFORE_PUT_KEY.equals(propKey)) {\n        _compressDataBeforePut = Boolean.valueOf(value);\n      } else if (COMPRESS_FILE_BY_PUT_KEY.equals(propKey)) {\n        _compressFileByPut = Boolean.valueOf(value);\n      } else if (COMPRESS_LEVEL.equals(propKey)) {\n        _compressLevel = Long.valueOf(value);\n      }\n    }\n  }\n\n  private void initDateFormats() {\n    resetCalendar();\n\n    _dateFormat = new SimpleDateFormat(SnowflakeTypeHelper.DATE_OR_TIME_FORMAT_PATTERN);\n    if (_mapTimeToTimestamp) {\n      // same format for TIME to TIMESTAMP\n      _timeFormat = _dateFormat;\n    } else {\n      _timeFormat = new SimpleDateFormat(SnowflakeTypeHelper.TIME_FORMAT_PATTERN);\n    }\n    _timestampFormat = new SimpleDateFormat(SnowflakeTypeHelper.TIMESTAMP_FORMAT_PATTERN);\n    _timestampTzFormat = new SimpleDateFormat(SnowflakeTypeHelper.TIMESTAMP_FORMAT_TZ_PATTERN);\n\n    Calendar cal = !_useLocalTimezone ? _calendarUTC : _calendarLocal;\n    _dateFormat.setCalendar(cal);\n    _timeFormat.setCalendar(cal);\n    _timestampFormat.setCalendar(cal);\n    _timestampTzFormat.setCalendar(cal);\n  }\n\n  /** Starts the loader */\n  @Override\n  public void start() {\n    logger.debug(\"Start Loading\", false);\n    // validate parameters\n    validateParameters();\n\n    if (_op == null) {\n      this.abort(new ConnectionError(\"Loader started with no operation\"));\n      return;\n    }\n\n    initDateFormats();\n\n    initQueues();\n\n    if (_is_first_start_call) {\n      // is this the first start call?\n\n      try {\n        if (_startTransaction) {\n          logger.debug(\"Begin Transaction\", false);\n          _processConn.createStatement().execute(\"begin transaction\");\n        } else {\n          logger.debug(\"No Transaction started\", false);\n        }\n      } catch (SQLException ex) {\n        abort(new Loader.ConnectionError(\"Failed to start Transaction\", Utils.getCause(ex)));\n      }\n\n      if (_truncate) {\n        truncateTargetTable();\n      }\n\n      try {\n        if (_before != null) {\n          logger.debug(\"Running Execute Before SQL\", false);\n          _processConn.createStatement().execute(_before);\n        }\n      } catch (SQLException ex) {\n        abort(\n            new Loader.ConnectionError(\n                String.format(\"Execute Before SQL failed to run: %s\", _before),\n                Utils.getCause(ex)));\n      }\n    }\n  }\n\n  private void validateParameters() {\n    logger.debug(\"Validate Parameters\", false);\n    if (Operation.INSERT != this._op) {\n      if (this._keys == null || this._keys.isEmpty()) {\n        throw new ConnectionError(\"Updating operations require keys\");\n      }\n    }\n    setPropertyBySystemProperty();\n    logger.debug(\n        \"Database Name: {}, Schema Name: {}, Table Name: {}, \"\n            + \"Remote Stage: {}, Columns: {}, Keys: {}, Operation: {}, \"\n            + \"Start Transaction: {}, OneBatch: {}, Truncate Table: {}, \"\n            + \"Execute Before: {}, Execute After: {}, Batch Row Size: {}, \"\n            + \"CSV File Bucket Size: {}, CSV File Size: {}, Preserve Stage File: {}, \"\n            + \"Use Local TimeZone: {}, Copy Empty Field As Empty: {}, \"\n            + \"MapTimeToTimestamp: {}, Compress Data before PUT: {}, \"\n            + \"Compress File By Put: {}, Compress Level: {}, OnError: {}\",\n        _database,\n        _schema,\n        _table,\n        _remoteStage,\n        _columns,\n        _keys,\n        _op,\n        _startTransaction,\n        _oneBatch,\n        _truncate,\n        _before,\n        _after,\n        _batchRowSize,\n        _csvFileBucketSize,\n        _csvFileSize,\n        _preserveStageFile,\n        _useLocalTimezone,\n        _copyEmptyFieldAsEmpty,\n        _mapTimeToTimestamp,\n        _compressDataBeforePut,\n        _compressFileByPut,\n        _compressLevel,\n        _onError);\n  }\n\n  String getNoise() {\n    return _noise;\n  }\n\n  public void abort(RuntimeException t) {\n    synchronized (this) {\n      // Abort once, keep first error.\n      logger.debug(\"Exception received. Aborting...\", t);\n\n      if (_aborted.getAndSet(true)) {\n        return;\n      }\n\n      if (t != null) {\n        _abortCause = t;\n      }\n\n      // Rollback and do not process anything else\n      rollback();\n    }\n  }\n\n  boolean isAborted() {\n    synchronized (this) {\n      // don't synchronize unless the caller does\n      return _aborted.get();\n    }\n  }\n\n  @Override\n  public void rollback() {\n    logger.debug(\"Rollback\", false);\n    try {\n      terminate();\n\n      logger.debug(\"Rollback\", false);\n      this._processConn.createStatement().execute(\"rollback\");\n    } catch (SQLException ex) {\n      logger.error(ex.getMessage(), ex);\n    }\n  }\n\n  @Override\n  public void submitRow(final Object[] row) {\n    try {\n      if (_aborted.get()) {\n        if (_listener.throwOnError()) {\n          throw _abortCause;\n        }\n        return;\n      }\n    } catch (Exception ex) {\n      abort(new Loader.ConnectionError(\"Throwing Error\", Utils.getCause(ex)));\n    }\n\n    byte[] data = null;\n    try {\n      if (!_active.get()) {\n        logger.debug(\"Inactive loader. Row ignored\", false);\n        return;\n      }\n\n      data = createCSVRecord(row);\n\n    } catch (Exception ex) {\n      abort(new Loader.ConnectionError(\"Creating data set for CSV\", Utils.getCause(ex)));\n    }\n\n    try {\n      writeBytes(data);\n      _listener.addSubmittedRowCount(1);\n\n      if (_listener.needSuccessRecords()) {\n        _listener.recordProvided(_op, row);\n      }\n    } catch (Exception ex) {\n      abort(new Loader.ConnectionError(\"Writing Bytes to CSV files\", Utils.getCause(ex)));\n    }\n\n    if (_batchRowSize > 0\n        && _listener.getSubmittedRowCount() > 0\n        && (_listener.getSubmittedRowCount() % _batchRowSize) == 0) {\n      logger.debug(\n          \"Flushing Queue: Submitted Row Count: {}, Batch Row Size: {}\",\n          _listener.getSubmittedRowCount(),\n          _batchRowSize);\n      // flush data loading\n      try {\n        flushQueues();\n      } catch (Exception ex) {\n        abort(new Loader.ConnectionError(\"Flush Queues\", Utils.getCause(ex)));\n      }\n      try {\n        initQueues();\n      } catch (Exception ex) {\n        abort(new Loader.ConnectionError(\"Init Queues\", Utils.getCause(ex)));\n      }\n    }\n  }\n\n  /** Initializes queues */\n  private void initQueues() {\n    logger.debug(\"Init Queues\", false);\n    if (_active.getAndSet(true)) {\n      // NOP if the loader is already active\n      return;\n    }\n    // start PUT and PROCESS queues\n    _queuePut = new ArrayBlockingQueue<>(48);\n    _queueProcess = new ArrayBlockingQueue<>(48);\n    _put = new PutQueue(this);\n    _process = new ProcessQueue(this);\n\n    // Start queue. NOTE: This is not actively used\n    _queueData = new ArrayBlockingQueue<>(1024);\n    _thread = new Thread(this);\n    _thread.setName(\"StreamLoaderThread\");\n    _thread.start();\n\n    // Create stage\n    _stage = new BufferStage(this, _op, _csvFileBucketSize, _csvFileSize);\n  }\n\n  /** Flushes data by joining PUT and PROCESS queues */\n  private void flushQueues() {\n    // Terminate data loading thread.\n    logger.debug(\"Flush Queues\", false);\n    try {\n      _queueData.put(new byte[0]);\n      _thread.join(10000);\n\n      if (_thread.isAlive()) {\n        _thread.interrupt();\n      }\n    } catch (Exception ex) {\n      String msg = \"Failed to join StreamLoader queue: \" + ex.getMessage();\n      logger.error(msg, ex);\n      throw new DataError(msg, Utils.getCause(ex));\n    }\n    // Put last stage on queue\n    terminate();\n\n    // wait for the processing to finish\n    _put.join();\n    _process.join();\n\n    if (_aborted.get()) {\n      // Loader was aborted due to an exception.\n      // It was rolled back at that time.\n\n      // LOGGER.log(Level.WARNING,\n      //            \"Loader had been previously aborted by error\", _abortCause);\n      throw _abortCause;\n    }\n  }\n\n  private void writeBytes(final byte[] data) throws IOException, InterruptedException {\n    // this loader was aborted\n    if (_aborted.get()) {\n      return;\n    }\n\n    boolean full = _stage.stageData(data);\n\n    if (full && !_oneBatch) {\n      // if Buffer stage is full and NOT one batch mode,\n      // queue PUT request.\n      queuePut(_stage);\n      _stage = new BufferStage(this, _op, _csvFileBucketSize, _csvFileSize);\n    }\n  }\n\n  private void truncateTargetTable() {\n    try {\n      // TODO: could be replaced with TRUNCATE?\n      _processConn.createStatement().execute(\"DELETE FROM \" + this.getFullTableName());\n    } catch (SQLException ex) {\n      logger.error(ex.getMessage(), ex);\n      abort(new Loader.ConnectionError(Utils.getCause(ex)));\n    }\n  }\n\n  public void setVectorColumnType(String vectorType) {\n    this._vectorType = vectorType;\n  }\n\n  public void setVectorColumns() {\n    try {\n      DatabaseMetaData dbmd = _processConn.getMetaData();\n      for (String col : _columns) {\n        try (ResultSet rs = dbmd.getColumns(_database, _schema, _table, col)) {\n          rs.next();\n          if (isColumnTypeVector(rs.getString(6))) {\n            _vectorColumnsNameAndSize.put(col, rs.getInt(7));\n          }\n        }\n      }\n    } catch (SQLException e) {\n      logger.error(e.getMessage(), e);\n      abort(new Loader.ConnectionError(Utils.getCause(e)));\n    }\n  }\n\n  private boolean isColumnTypeVector(String col) {\n    return col != null && col.equalsIgnoreCase(\"vector\");\n  }\n\n  @Override\n  public void run() {\n    try {\n      while (true) {\n        byte[] data = this._queueData.take();\n\n        if (data.length == 0) {\n          break;\n        }\n\n        this.writeBytes(data);\n      }\n    } catch (Exception ex) {\n      logger.error(ex.getMessage(), ex);\n      abort(new Loader.ConnectionError(Utils.getCause(ex)));\n    }\n  }\n\n  private byte[] createCSVRecord(final Object[] data) {\n    StringBuilder sb = new StringBuilder();\n\n    for (int i = 0; i < data.length; ++i) {\n      if (i > 0) {\n        sb.append(',');\n      }\n      sb.append(\n          SnowflakeTypeUtil.escapeForCSV(\n              SnowflakeTypeUtil.lexicalValue(\n                  data[i], _dateFormat, _timeFormat, _timestampFormat, _timestampTzFormat)));\n    }\n    return sb.toString().getBytes(UTF_8);\n  }\n\n  /**\n   * Finishes loader\n   *\n   * @throws Exception an exception raised in finishing loader.\n   */\n  @Override\n  public void finish() throws Exception {\n    logger.debug(\"Finish Loading\", false);\n    flushQueues();\n\n    if (_is_last_finish_call) {\n      try {\n        if (_after != null) {\n          logger.debug(\"Running Execute After SQL\", false);\n          _processConn.createStatement().execute(_after);\n        }\n        // Loader successfully completed. Commit and return.\n        _processConn.createStatement().execute(\"commit\");\n        logger.debug(\"Committed\", false);\n      } catch (SQLException ex) {\n        try {\n          _processConn.createStatement().execute(\"rollback\");\n        } catch (SQLException ex0) {\n          logger.debug(\"Failed to rollback\", false);\n        }\n        logger.debug(String.format(\"Execute After SQL failed to run: %s\", _after), ex);\n        throw new Loader.ConnectionError(Utils.getCause(ex));\n      }\n    }\n  }\n\n  @Override\n  public void close() throws Exception {\n    logger.debug(\"Close Loader\", false);\n    try {\n      this._processConn.close();\n      this._putConn.close();\n    } catch (SQLException ex) {\n      logger.error(ex.getMessage(), ex);\n      throw new ConnectionError(Utils.getCause(ex));\n    }\n  }\n\n  /** Set active to false (no-op if not active), add a stage with terminate flag onto the queue */\n  private void terminate() {\n    logger.debug(\"Terminate Loader\", false);\n\n    boolean active = _active.getAndSet(false);\n\n    if (!active) {\n      return; // No-op\n    }\n\n    if (_stage == null) {\n      _stage = new BufferStage(this, Operation.INSERT, _csvFileBucketSize, _csvFileSize);\n    }\n\n    _stage.setTerminate(true);\n\n    try {\n      queuePut(_stage);\n    } catch (InterruptedException ex) {\n      logger.error(\"Unknown Error\", ex);\n    }\n\n    logger.debug(\"Snowflake loader terminating\", false);\n  }\n\n  // If operation changes, existing stage needs to be scheduled for processing.\n  @Override\n  public void resetOperation(Operation op) {\n\n    if (op.equals(_op)) {\n      // no-op\n      return;\n    }\n\n    logger.debug(\"Operation is changing from {} to {}\", _op, op);\n    _op = op;\n\n    if (_stage != null) {\n      try {\n        queuePut(_stage);\n      } catch (InterruptedException ex) {\n        logger.error(_stage.getId(), ex);\n      }\n    }\n\n    _stage = new BufferStage(this, _op, _csvFileBucketSize, _csvFileSize);\n  }\n\n  public String getTable() {\n    return _table;\n  }\n\n  String getBase() {\n    return BASE;\n  }\n\n  Connection getPutConnection() {\n    return _putConn;\n  }\n\n  Connection getProcessConnection() {\n    return _processConn;\n  }\n\n  String getRemoteStage() {\n    return _remoteStage;\n  }\n\n  List<String> getKeys() {\n    return this._keys;\n  }\n\n  List<String> getColumns() {\n    return this._columns;\n  }\n\n  Map<String, Integer> getVectorColumns() {\n    return this._vectorColumnsNameAndSize;\n  }\n\n  String getColumnsAsString() {\n    // comma separate list of column names\n    StringBuilder sb = new StringBuilder(\"\\\"\");\n    for (int i = 0; i < _columns.size(); i++) {\n      if (i > 0) {\n        sb.append(\"\\\",\\\"\");\n      }\n      sb.append(_columns.get(i));\n    }\n    sb.append(\"\\\"\");\n    return sb.toString();\n  }\n\n  String getFullTableName() {\n    return (_database == null ? \"\" : (\"\\\"\" + _database + \"\\\".\"))\n        + (_schema == null ? \"\" : (\"\\\"\" + _schema + \"\\\".\"))\n        + \"\\\"\"\n        + _table\n        + \"\\\"\";\n  }\n\n  public LoadResultListener getListener() {\n    return _listener;\n  }\n\n  @Override\n  public void setListener(LoadResultListener _listener) {\n    this._listener = _listener;\n  }\n\n  private void queuePut(BufferStage stage) throws InterruptedException {\n    _queuePut.put(stage);\n  }\n\n  BufferStage takePut() throws InterruptedException {\n    return _queuePut.take();\n  }\n\n  void queueProcess(BufferStage stage) throws InterruptedException {\n    _queueProcess.put(stage);\n  }\n\n  BufferStage takeProcess() throws InterruptedException {\n    return _queueProcess.take();\n  }\n\n  void throttleUp() {\n    int open = this._throttleCounter.incrementAndGet();\n    logger.debug(\"PUT Throttle Up: {}\", open);\n    if (open > 8) {\n      logger.debug(\n          \"Will retry scheduling file for upload after {} seconds\", (Math.pow(2, open - 7)));\n      try {\n        Thread.sleep(1000 * ((int) Math.pow(2, open - 7)));\n      } catch (InterruptedException ex) {\n        logger.error(\"Exception occurs while waiting\", ex);\n      }\n    }\n  }\n\n  void throttleDown() {\n    int throttleLevel = this._throttleCounter.decrementAndGet();\n    logger.debug(\"PUT Throttle Down: {}\", throttleLevel);\n    if (throttleLevel < 0) {\n      logger.debug(\"Unbalanced throttle\", false);\n      _throttleCounter.set(0);\n    }\n\n    logger.debug(\"Connector throttle {}\", throttleLevel);\n  }\n\n  private LoadResultListener _listener =\n      new LoadResultListener() {\n\n        private final AtomicInteger errorCount = new AtomicInteger(0);\n        private final AtomicInteger errorRecordCount = new AtomicInteger(0);\n        private final AtomicInteger submittedRowCount = new AtomicInteger(0);\n\n        @Override\n        public boolean needErrors() {\n          return false;\n        }\n\n        @Override\n        public boolean needSuccessRecords() {\n          return false;\n        }\n\n        @Override\n        public void addError(LoadingError error) {}\n\n        @Override\n        public boolean throwOnError() {\n          return false;\n        }\n\n        @Override\n        public void recordProvided(Operation op, Object[] record) {}\n\n        @Override\n        public void addProcessedRecordCount(Operation op, int i) {}\n\n        @Override\n        public void addOperationRecordCount(Operation op, int i) {}\n\n        @Override\n        public int getErrorCount() {\n          return errorCount.get();\n        }\n\n        @Override\n        public int getErrorRecordCount() {\n          return errorRecordCount.get();\n        }\n\n        @Override\n        public void resetErrorCount() {\n          errorCount.set(0);\n        }\n\n        @Override\n        public void resetErrorRecordCount() {\n          errorRecordCount.set(0);\n        }\n\n        @Override\n        public void addErrorCount(int count) {\n          errorCount.addAndGet(count);\n        }\n\n        @Override\n        public void addErrorRecordCount(int count) {\n          errorRecordCount.addAndGet(count);\n        }\n\n        @Override\n        public void resetSubmittedRowCount() {\n          submittedRowCount.set(0);\n        }\n\n        @Override\n        public void addSubmittedRowCount(int count) {\n          submittedRowCount.addAndGet(count);\n        }\n\n        @Override\n        public int getSubmittedRowCount() {\n          return submittedRowCount.get();\n        }\n      };\n\n  void setTestMode(boolean mode) {\n    this._testMode = mode;\n  }\n\n  public String getStageColumnsAsString() {\n    // if there are no vector columns in the target table just select * is needed from the staging\n    // table.\n    if (_vectorColumnsNameAndSize.isEmpty()) {\n      return \"*\";\n    }\n    if (_vectorType == null) {\n      throw new IllegalArgumentException(\n          \"Target table with vector columns must use setVectorColumnType with \\\"INT\\\" or \\\"FLOAT\\\"\");\n    }\n\n    StringBuilder sb = new StringBuilder();\n    for (int i = 0; i < _columns.size(); i++) {\n      String colName = _columns.get(i);\n      if (_vectorColumnsNameAndSize.containsKey(colName)) {\n        sb.append(\n            colName\n                + \"::VECTOR(\"\n                + _vectorType\n                + \", \"\n                + _vectorColumnsNameAndSize.get(colName)\n                + \")\");\n      } else {\n        sb.append(\"\\\"\");\n        sb.append(colName);\n        sb.append(\"\\\"\");\n      }\n      if (i != _columns.size() - 1) {\n        sb.append(\", \");\n      }\n    }\n    return sb.toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/loader/Utils.java",
    "content": "package net.snowflake.client.internal.loader;\n\n/** Utils class for Loader API */\npublic class Utils {\n\n  /**\n   * Find the root cause of the exception\n   *\n   * @param e throwable object\n   * @return the throwable cause\n   */\n  public static Throwable getCause(Throwable e) {\n    Throwable cause = null;\n    Throwable result = e;\n\n    while (null != (cause = result.getCause()) && (result != cause)) {\n      result = cause;\n    }\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/ArgSupplier.java",
    "content": "package net.snowflake.client.internal.log;\n\n/**\n * An interface for representing lambda expressions that supply values to placeholders in message\n * formats.\n *\n * <p>E.g., {@code Logger.debug(\"Value: {}\", (ArgSupplier) () -> getValue());}\n */\n@FunctionalInterface\npublic interface ArgSupplier {\n  /**\n   * Get value\n   *\n   * @return Object value.\n   */\n  Object get();\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/CommonsLoggingWrapper.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport org.apache.commons.logging.Log;\n\n/**\n * This is a wrapper class of apache commons logging which uses SFLogger to use driver configuration\n * (via java.util.logging or SLF4J) and mask secrets. Wrapper does not hide trace and debug\n * messages.\n */\npublic class CommonsLoggingWrapper implements Log {\n  private final SFLogger logger;\n\n  public CommonsLoggingWrapper(String className) {\n    this.logger = SFLoggerFactory.getLogger(className);\n  }\n\n  public void debug(Object msg) {\n    logger.debug(String.valueOf(msg), true);\n  }\n\n  public void debug(Object msg, Throwable t) {\n    logger.debug(String.valueOf(msg), t);\n  }\n\n  public void error(Object msg) {\n    logger.error(String.valueOf(msg), true);\n  }\n\n  public void error(Object msg, Throwable t) {\n    logger.error(String.valueOf(msg), t);\n  }\n\n  public void fatal(Object msg) {\n    this.error(msg);\n  }\n\n  public void fatal(Object msg, Throwable t) {\n    this.error(msg, t);\n  }\n\n  public void info(Object msg) {\n    logger.info(String.valueOf(msg), true);\n  }\n\n  public void info(Object msg, Throwable t) {\n    logger.info(String.valueOf(msg), t);\n  }\n\n  public boolean isDebugEnabled() {\n    return logger.isDebugEnabled();\n  }\n\n  public boolean isErrorEnabled() {\n    return logger.isErrorEnabled();\n  }\n\n  public boolean isFatalEnabled() {\n    return logger.isErrorEnabled();\n  }\n\n  public boolean isInfoEnabled() {\n    return logger.isInfoEnabled();\n  }\n\n  public boolean isTraceEnabled() {\n    return logger.isTraceEnabled();\n  }\n\n  public boolean isWarnEnabled() {\n    return logger.isWarnEnabled();\n  }\n\n  public void trace(Object msg) {\n    logger.debug(String.valueOf(msg), true);\n  }\n\n  public void trace(Object msg, Throwable t) {\n    logger.trace(String.valueOf(msg), t);\n  }\n\n  public void warn(Object msg) {\n    logger.warn(String.valueOf(msg));\n  }\n\n  public void warn(Object msg, Throwable t) {\n    logger.warn(String.valueOf(msg), t);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/CommonsLoggingWrapperMode.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nenum CommonsLoggingWrapperMode {\n  /** All logs from commons logging are passed to SFLogger (check {@link CommonsLoggingWrapper}) */\n  ALL,\n  /**\n   * The default behaviour is forwarding all logs to java.util.logging from commons logging (check\n   * {@link JDK14JCLWrapper}), no logs are forwarded to SLF4J logger (check {@link SLF4JJCLWrapper})\n   */\n  DEFAULT,\n  /**\n   * Logs from commons logging are not forwarded and commons logging is not reconfigured - may be\n   * the option when if you need to replace commons logging with the SLF4J bridge when thin jar is\n   * used\n   */\n  OFF;\n\n  static final String JAVA_PROPERTY = \"net.snowflake.jdbc.commons_logging_wrapper\";\n\n  static CommonsLoggingWrapperMode detect() {\n    String value = systemGetProperty(JAVA_PROPERTY);\n    if (value == null) {\n      return DEFAULT;\n    }\n    try {\n      return CommonsLoggingWrapperMode.valueOf(value);\n    } catch (Exception e) {\n      throw new IllegalArgumentException(\n          \"Unknown commons logging wrapper value '\"\n              + value\n              + \"', expected one of: \"\n              + Stream.of(CommonsLoggingWrapperMode.values())\n                  .map(Enum::name)\n                  .collect(Collectors.joining(\", \")));\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/JDK14JCLWrapper.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport org.apache.commons.logging.Log;\n\n/* This is a wrapper class of snowflake JDK14Logger for apache Jakarta Commons Logging\n * (the logging framework used by apache httpclient4.5 package) to choose to give us\n * the ability to filter out sensitive data.\n */\npublic class JDK14JCLWrapper implements Log {\n  private final SFLogger logger;\n\n  public JDK14JCLWrapper(String className) {\n    this.logger = new JDK14Logger(className);\n  }\n\n  SFLogger getLogger() {\n    return logger;\n  }\n\n  public void debug(Object msg) {\n    // do nothing\n  }\n\n  public void debug(Object msg, Throwable t) {\n    // do nothing\n  }\n\n  public void error(Object msg) {\n    logger.error(String.valueOf(msg), true);\n  }\n\n  public void error(Object msg, Throwable t) {\n    logger.error(String.valueOf(msg), t);\n  }\n\n  public void fatal(Object msg) {\n    this.error(msg);\n  }\n\n  public void fatal(Object msg, Throwable t) {\n    this.error(msg, t);\n  }\n\n  public void info(Object msg) {\n    logger.info(String.valueOf(msg), true);\n  }\n\n  public void info(Object msg, Throwable t) {\n    logger.info(String.valueOf(msg), t);\n  }\n\n  public boolean isDebugEnabled() {\n    return false;\n  }\n\n  public boolean isErrorEnabled() {\n    return logger.isErrorEnabled();\n  }\n\n  public boolean isFatalEnabled() {\n    return logger.isErrorEnabled();\n  }\n\n  public boolean isInfoEnabled() {\n    return logger.isInfoEnabled();\n  }\n\n  public boolean isTraceEnabled() {\n    return false;\n  }\n\n  public boolean isWarnEnabled() {\n    return logger.isWarnEnabled();\n  }\n\n  public void trace(Object msg) {\n    // do nothing\n  }\n\n  public void trace(Object msg, Throwable t) {\n    // do nothing\n  }\n\n  public void warn(Object msg) {\n    logger.warn(String.valueOf(msg));\n  }\n\n  public void warn(Object msg, Throwable t) {\n    logger.warn(String.valueOf(msg), t);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/JDK14Logger.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport java.io.IOException;\nimport java.text.MessageFormat;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.FileHandler;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.logging.SimpleFormatter;\nimport net.snowflake.client.internal.core.EventHandler;\nimport net.snowflake.client.internal.core.EventUtil;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.util.MaskedException;\nimport net.snowflake.client.internal.util.SecretDetector;\n\n/**\n * Use java.util.logging to implements SFLogger.\n *\n * <p>Log Level mapping from SFLogger to java.util.logging: ERROR -- SEVERE WARN -- WARNING INFO --\n * INFO DEBUG -- FINE TRACE -- FINEST\n */\npublic class JDK14Logger implements SFLogger {\n  private Logger jdkLogger;\n\n  private Set<String> logMethods =\n      new HashSet<>(Arrays.asList(\"debug\", \"error\", \"info\", \"trace\", \"warn\", \"debugNoMask\"));\n\n  private static boolean isLoggerInit = false;\n\n  public static String STDOUT = \"STDOUT\";\n\n  private static final StdOutConsoleHandler STD_OUT_CONSOLE_HANDLER = new StdOutConsoleHandler();\n\n  public JDK14Logger(String name) {\n    this.jdkLogger = Logger.getLogger(name);\n  }\n\n  static {\n    String javaLoggingConsoleStdOut =\n        systemGetProperty(SFSessionProperty.JAVA_LOGGING_CONSOLE_STD_OUT.getPropertyKey());\n    if (\"true\".equalsIgnoreCase(javaLoggingConsoleStdOut)) {\n      String javaLoggingConsoleStdOutThreshold =\n          systemGetProperty(\n              SFSessionProperty.JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD.getPropertyKey());\n      useStdOutConsoleHandler(javaLoggingConsoleStdOutThreshold);\n    }\n  }\n\n  public static void useStdOutConsoleHandler(String threshold) {\n    Level thresholdLevel = threshold != null ? tryParse(threshold) : null;\n    Logger rootLogger = Logger.getLogger(\"\");\n    for (Handler handler : rootLogger.getHandlers()) {\n      if (handler instanceof ConsoleHandler) {\n        rootLogger.removeHandler(handler);\n        if (thresholdLevel != null) {\n          rootLogger.addHandler(new StdErrOutThresholdAwareConsoleHandler(thresholdLevel));\n        } else {\n          rootLogger.addHandler(new StdOutConsoleHandler());\n        }\n        break;\n      }\n    }\n  }\n\n  private static Level tryParse(String threshold) {\n    try {\n      return Level.parse(threshold);\n    } catch (Exception e) {\n      throw new UnknownJavaUtilLoggingLevelException(threshold);\n    }\n  }\n\n  static void resetToDefaultConsoleHandler() {\n    Logger rootLogger = Logger.getLogger(\"\");\n    for (Handler handler : rootLogger.getHandlers()) {\n      if (handler instanceof StdErrOutThresholdAwareConsoleHandler\n          || handler instanceof StdOutConsoleHandler) {\n        rootLogger.removeHandler(handler);\n        rootLogger.addHandler(new ConsoleHandler());\n        break;\n      }\n    }\n  }\n\n  public boolean isDebugEnabled() {\n    return this.jdkLogger.isLoggable(Level.FINE);\n  }\n\n  public boolean isErrorEnabled() {\n    return this.jdkLogger.isLoggable(Level.SEVERE);\n  }\n\n  public boolean isInfoEnabled() {\n    return this.jdkLogger.isLoggable(Level.INFO);\n  }\n\n  public boolean isTraceEnabled() {\n    return this.jdkLogger.isLoggable(Level.FINEST);\n  }\n\n  public boolean isWarnEnabled() {\n    return this.jdkLogger.isLoggable(Level.WARNING);\n  }\n\n  public void debug(String msg, boolean isMasked) {\n    logInternal(Level.FINE, msg, isMasked);\n  }\n\n  // This function is used to display unmasked, potentially sensitive log information for internal\n  // regression testing purposes. Do not use otherwise.\n  public void debugNoMask(String msg) {\n    logInternal(Level.FINE, msg, false);\n  }\n\n  public void debug(String msg, Object... arguments) {\n    logInternal(Level.FINE, msg, arguments);\n  }\n\n  public void debug(String msg, Throwable t) {\n    logInternal(Level.FINE, msg, t);\n  }\n\n  public void error(String msg, boolean isMasked) {\n    logInternal(Level.SEVERE, msg, isMasked);\n  }\n\n  public void error(String msg, Object... arguments) {\n    logInternal(Level.SEVERE, msg, arguments);\n  }\n\n  public void error(String msg, Throwable t) {\n    logInternal(Level.SEVERE, msg, t);\n  }\n\n  public void info(String msg, boolean isMasked) {\n    logInternal(Level.INFO, msg, isMasked);\n  }\n\n  public void info(String msg, Object... arguments) {\n    logInternal(Level.INFO, msg, arguments);\n  }\n\n  public void info(String msg, Throwable t) {\n    logInternal(Level.INFO, msg, t);\n  }\n\n  public void trace(String msg, boolean isMasked) {\n    logInternal(Level.FINEST, msg, isMasked);\n  }\n\n  public void trace(String msg, Object... arguments) {\n    logInternal(Level.FINEST, msg, arguments);\n  }\n\n  public void trace(String msg, Throwable t) {\n    logInternal(Level.FINEST, msg, t);\n  }\n\n  public void warn(String msg, boolean isMasked) {\n    logInternal(Level.WARNING, msg, isMasked);\n  }\n\n  public void warn(String msg, Object... arguments) {\n    logInternal(Level.WARNING, msg, arguments);\n  }\n\n  public void warn(String msg, Throwable t) {\n    logInternal(Level.WARNING, msg, t);\n  }\n\n  private void logInternal(Level level, String msg, boolean masked) {\n    if (jdkLogger.isLoggable(level)) {\n      String[] source = findSourceInStack();\n      jdkLogger.logp(\n          level, source[0], source[1], masked == true ? SecretDetector.maskSecrets(msg) : msg);\n    }\n  }\n\n  private void logInternal(Level level, String msg, Object... arguments) {\n    if (jdkLogger.isLoggable(level)) {\n      String[] source = findSourceInStack();\n      String message = \"\";\n      try {\n        message = MessageFormat.format(refactorString(msg), evaluateLambdaArgs(arguments));\n      } catch (IllegalArgumentException e) {\n        message = \"Unable to format msg: \" + msg;\n      }\n      jdkLogger.logp(level, source[0], source[1], SecretDetector.maskSecrets(message));\n    }\n  }\n\n  private void logInternal(Level level, String msg, Throwable t) {\n    // add logger message here\n    if (jdkLogger.isLoggable(level)) {\n      String[] source = findSourceInStack();\n      Throwable masked = (t == null) ? null : new MaskedException(t);\n      jdkLogger.logp(level, source[0], source[1], SecretDetector.maskSecrets(msg), masked);\n    }\n  }\n\n  public static void addHandler(Handler handler) {\n    Logger snowflakeLogger = Logger.getLogger(SFFormatter.CLASS_NAME_PREFIX);\n    snowflakeLogger.addHandler(handler);\n  }\n\n  public static void removeHandler(Handler handler) {\n    Logger snowflakeLogger = Logger.getLogger(SFFormatter.CLASS_NAME_PREFIX);\n    snowflakeLogger.removeHandler(handler);\n  }\n\n  public static void setUseParentHandlers(boolean value) {\n    Logger snowflakeLogger = Logger.getLogger(SFFormatter.CLASS_NAME_PREFIX);\n    snowflakeLogger.setUseParentHandlers(value);\n  }\n\n  public static void setLevel(Level level) {\n    Logger snowflakeLogger = Logger.getLogger(SFFormatter.CLASS_NAME_PREFIX);\n    snowflakeLogger.setLevel(level);\n  }\n\n  public static Level getLevel() {\n    Logger snowflakeLogger = Logger.getLogger(SFFormatter.CLASS_NAME_PREFIX);\n    return snowflakeLogger.getLevel();\n  }\n\n  /**\n   * This is way to enable logging in JDBC through TRACING parameter or sf client config file.\n   *\n   * @param level log level\n   * @param logPath log path\n   * @throws IOException if there is an error writing to the log\n   */\n  public static synchronized void instantiateLogger(Level level, String logPath)\n      throws IOException {\n    if (!isLoggerInit) {\n      loggerInit(level, logPath);\n      isLoggerInit = true;\n    }\n  }\n\n  /**\n   * Since we use SLF4J ways of formatting string we need to refactor message string if we have\n   * arguments. For example, in sl4j, this string can be formatted with 2 arguments\n   *\n   * <p>ex.1: Error happened in {} on {}\n   *\n   * <p>And if two arguments are provided, error message can be formatted.\n   *\n   * <p>However, in java.util.logging, to achieve formatted error message, Same string should be\n   * converted to\n   *\n   * <p>ex.2: Error happened in {0} on {1}\n   *\n   * <p>Which represented first arguments and second arguments will be replaced in the corresponding\n   * places.\n   *\n   * <p>This method will convert string in ex.1 to ex.2\n   *\n   * @param original original string\n   * @return refactored string\n   */\n  private String refactorString(String original) {\n    StringBuilder sb = new StringBuilder();\n    int argCount = 0;\n    for (int i = 0; i < original.length(); i++) {\n      if (original.charAt(i) == '{' && i < original.length() - 1 && original.charAt(i + 1) == '}') {\n        sb.append(String.format(\"{%d}\", argCount));\n        argCount++;\n        i++;\n      } else {\n        sb.append(original.charAt(i));\n      }\n    }\n    return sb.toString();\n  }\n\n  /**\n   * Used to find the index of the source class/method in current stack This method will locate the\n   * source as the first method after logMethods\n   *\n   * @return an array of size two, first element is className and second is methodName\n   */\n  private String[] findSourceInStack() {\n    StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();\n    String[] results = new String[2];\n    for (int i = 0; i < stackTraces.length; i++) {\n      if (logMethods.contains(stackTraces[i].getMethodName())) {\n        // since already find the highest logMethods, find the first method after this one\n        // and is not a logMethods. This is done to avoid multiple wrapper over log methods\n        for (int j = i; j < stackTraces.length; j++) {\n          if (!logMethods.contains(stackTraces[j].getMethodName())) {\n            results[0] = stackTraces[j].getClassName();\n            results[1] = stackTraces[j].getMethodName();\n            return results;\n          }\n        }\n      }\n    }\n    return results;\n  }\n\n  private static void loggerInit(Level level, String outputPath) throws IOException {\n    Logger snowflakeLoggerInformaticaV1 =\n        Logger.getLogger(SFFormatter.INFORMATICA_V1_CLASS_NAME_PREFIX);\n\n    // setup event handler\n    EventHandler eventHandler = EventUtil.getEventHandlerInstance();\n    eventHandler.setLevel(Level.INFO);\n    eventHandler.setFormatter(new SimpleFormatter());\n    JDK14Logger.addHandler(eventHandler);\n\n    snowflakeLoggerInformaticaV1.setLevel(level);\n    JDK14Logger.setLevel(level);\n    snowflakeLoggerInformaticaV1.addHandler(eventHandler);\n\n    if (STDOUT.equalsIgnoreCase(outputPath)) {\n      // Initialize console handler\n      ConsoleHandler consoleHandler = new ConsoleHandler();\n      consoleHandler.setLevel(level);\n      consoleHandler.setFormatter(new SFFormatter());\n      JDK14Logger.addHandler(consoleHandler);\n      snowflakeLoggerInformaticaV1.addHandler(consoleHandler);\n\n    } else {\n      // Initialize file handler.\n      // get log count and size\n      String defaultLogSizeVal = systemGetProperty(\"snowflake.jdbc.log.size\");\n      String defaultLogCountVal = systemGetProperty(\"snowflake.jdbc.log.count\");\n\n      // default log size to 1 GB\n      int logSize = 1000000000;\n\n      // default number of log files to rotate to 2\n      int logCount = 2;\n\n      if (defaultLogSizeVal != null) {\n        try {\n          logSize = Integer.parseInt(defaultLogSizeVal);\n        } catch (Exception ex) {\n          ;\n        }\n      }\n\n      if (defaultLogCountVal != null) {\n        try {\n          logCount = Integer.parseInt(defaultLogCountVal);\n        } catch (Exception ex) {\n          ;\n        }\n      }\n\n      // write log file to tmp directory\n      FileHandler fileHandler = new FileHandler(outputPath, logSize, logCount, true);\n      fileHandler.setFormatter(new SFFormatter());\n      fileHandler.setLevel(level);\n\n      JDK14Logger.addHandler(fileHandler);\n      snowflakeLoggerInformaticaV1.addHandler(fileHandler);\n    }\n  }\n\n  private static Object[] evaluateLambdaArgs(Object... args) {\n    final Object[] result = new Object[args.length];\n\n    for (int i = 0; i < args.length; i++) {\n      result[i] = args[i] instanceof ArgSupplier ? ((ArgSupplier) args[i]).get() : args[i];\n    }\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SFFormatter.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport java.text.DateFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.TimeZone;\nimport java.util.logging.Formatter;\nimport java.util.logging.Handler;\nimport java.util.logging.LogRecord;\n\n/** SFFormatter */\npublic class SFFormatter extends Formatter {\n  private static final DateFormat df = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n\n  static {\n    df.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  // Fixed to \"net.snowflake.client\" since SnowflakeDriver moved to api.driver package\n  // Previously calculated dynamically, but should always be the root client package\n  public static final String CLASS_NAME_PREFIX = \"net.snowflake.client\";\n\n  public static final String INFORMATICA_V1_CLASS_NAME_PREFIX = \"com.snowflake\";\n\n  @Override\n  public String format(LogRecord record) {\n    int lineNumber = -1;\n    String className = record.getSourceClassName();\n    final String methodName = record.getSourceMethodName();\n    StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();\n    for (StackTraceElement ste : stackTraces) {\n      if (className.equals(ste.getClassName()) && methodName.equals(ste.getMethodName())) {\n        lineNumber = ste.getLineNumber();\n        break;\n      }\n    }\n    if (className.startsWith(CLASS_NAME_PREFIX)) {\n      className = \"n.s.c\" + className.substring(CLASS_NAME_PREFIX.length());\n    } else if (className.startsWith(INFORMATICA_V1_CLASS_NAME_PREFIX)) {\n      className = \"c.s\" + className.substring(INFORMATICA_V1_CLASS_NAME_PREFIX.length());\n    }\n\n    StringBuilder builder = new StringBuilder(1000);\n    builder.append(df.format(new Date(record.getMillis()))).append(\" \");\n    builder.append(className).append(\" \");\n    builder.append(record.getLevel()).append(\" \");\n    builder.append(methodName).append(\":\");\n    builder.append(lineNumber).append(\" - \");\n    builder.append(formatMessage(record));\n    builder.append(\"\\n\");\n    return builder.toString();\n  }\n\n  @Override\n  public String getHead(Handler h) {\n    return super.getHead(h);\n  }\n\n  @Override\n  public String getTail(Handler h) {\n    return super.getTail(h);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SFLogLevel.java",
    "content": "package net.snowflake.client.internal.log;\n\n/** Extended log levels for snowflake package. */\npublic enum SFLogLevel {\n  // OFF is highest level, no logs will be shown at this level.\n  // We can extend this enum to add levels for perf instrumentation and network request/response.\n  OFF(50, \"OFF\"),\n  ERROR(40, \"ERROR\"),\n  WARN(30, \"WARN\"),\n  INFO(20, \"INFO\"),\n  DEBUG(10, \"DEBUG\"),\n  TRACE(0, \"TRACE\");\n\n  private final int levelInt;\n  private final String levelStr;\n\n  SFLogLevel(int levelInt, String levelStr) {\n    this.levelInt = levelInt;\n    this.levelStr = levelStr;\n  }\n\n  /**\n   * Method to parse the input loglevel string and returns corresponding loglevel. This method uses\n   * case in-sensitive matching.\n   *\n   * @param levelStr log level string\n   * @return SFLogLevel\n   */\n  public static SFLogLevel getLogLevel(String levelStr) {\n    for (SFLogLevel level : SFLogLevel.values()) {\n      if (level.levelStr.equalsIgnoreCase(levelStr)) {\n        return level;\n      }\n    }\n\n    // Default is off.\n    return SFLogLevel.OFF;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SFLogger.java",
    "content": "package net.snowflake.client.internal.log;\n\n/**\n * Interface used by JDBC driver to log information\n *\n * <p>Five levels are included in this interface, from high to low: ERROR WARN INFO DEBUG TRACE\n */\npublic interface SFLogger {\n  /**\n   * Is debug level enabled?\n   *\n   * @return true if the trace level is DEBUG\n   */\n  boolean isDebugEnabled();\n\n  /**\n   * Is error level enabled?\n   *\n   * @return true if the trace level is ERROR\n   */\n  boolean isErrorEnabled();\n\n  /**\n   * Is info level enabled?\n   *\n   * @return true if the trace level is INFO\n   */\n  boolean isInfoEnabled();\n\n  /**\n   * Is trace level enabled?\n   *\n   * @return true if the trace level is TRACE\n   */\n  boolean isTraceEnabled();\n\n  /**\n   * Is warn level enabled?\n   *\n   * @return true if the trace level is WARN\n   */\n  boolean isWarnEnabled();\n\n  void debug(String msg, boolean isMasked);\n\n  void debugNoMask(String msg);\n\n  /**\n   * Logs message at DEBUG level.\n   *\n   * @param msg Message or message format\n   * @param arguments objects that supply value to placeholders in the message format. Expensive\n   *     operations that supply these values can be specified using lambdas implementing {@link\n   *     ArgSupplier} so that they are run only if the message is going to be logged. E.g., {@code\n   *     Logger.debug(\"Value: {}\", (ArgSupplier) () -> expensiveOperation());}\n   */\n  void debug(String msg, Object... arguments);\n\n  void debug(String msg, Throwable t);\n\n  void error(String msg, boolean isMasked);\n\n  /**\n   * Logs message at ERROR level.\n   *\n   * @param msg Message or message format\n   * @param arguments objects that supply value to placeholders in the message format. Expensive\n   *     operations that supply these values can be specified using lambdas implementing {@link\n   *     ArgSupplier} so that they are run only if the message is going to be logged. E.g., {@code\n   *     Logger.warn(\"Value: {}\", (ArgSupplier) () -> expensiveOperation());}\n   */\n  void error(String msg, Object... arguments);\n\n  void error(String msg, Throwable t);\n\n  void info(String msg, boolean isMasked);\n\n  /**\n   * Logs message at INFO level.\n   *\n   * @param msg Message or message format\n   * @param arguments objects that supply value to placeholders in the message format. Expensive\n   *     operations that supply these values can be specified using lambdas implementing {@link\n   *     ArgSupplier} so that they are run only if the message is going to be logged. E.g., {@code\n   *     Logger.info(\"Value: {}\", (ArgSupplier) () -> expensiveOperation());}\n   */\n  void info(String msg, Object... arguments);\n\n  void info(String msg, Throwable t);\n\n  void trace(String msg, boolean isMasked);\n\n  /**\n   * Logs message at TRACE level.\n   *\n   * @param msg Message or message format\n   * @param arguments objects that supply value to placeholders in the message format. Expensive\n   *     operations that supply these values can be specified using lambdas implementing {@link\n   *     ArgSupplier} so that they are run only if the message is going to be logged. E.g., {@code\n   *     Logger.trace(\"Value: {}\", (ArgSupplier) () -> expensiveOperation());}\n   */\n  void trace(String msg, Object... arguments);\n\n  void trace(String msg, Throwable t);\n\n  void warn(String msg, boolean isMasked);\n\n  /**\n   * Logs message at WARN level.\n   *\n   * @param msg Message or message format\n   * @param arguments objects that supply value to placeholders in the message format. Expensive\n   *     operations that supply these values can be specified using lambdas implementing {@link\n   *     ArgSupplier} so that they are run only if the message is going to be logged. E.g., {@code\n   *     Logger.warn(\"Value: {}\", (ArgSupplier) () -> expensiveOperation());}\n   */\n  void warn(String msg, Object... arguments);\n\n  void warn(String msg, Throwable t);\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SFLoggerFactory.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\n/** Used to create SFLogger instance */\npublic class SFLoggerFactory {\n  private static LoggerImpl loggerImplementation;\n\n  enum LoggerImpl {\n    SLF4JLOGGER(\"net.snowflake.client.log.SLF4JLogger\"),\n    JDK14LOGGER(\"net.snowflake.client.log.JDK14Logger\");\n\n    private String loggerImplClassName;\n\n    LoggerImpl(String loggerClass) {\n      this.loggerImplClassName = loggerClass;\n    }\n\n    public String getLoggerImplClassName() {\n      return this.loggerImplClassName;\n    }\n\n    public static LoggerImpl fromString(String loggerImplClassName) {\n      if (loggerImplClassName != null) {\n        for (LoggerImpl imp : LoggerImpl.values()) {\n          if (loggerImplClassName.equalsIgnoreCase(imp.getLoggerImplClassName())) {\n            return imp;\n          }\n        }\n      }\n      return null;\n    }\n  }\n\n  /**\n   * @param clazz Class type that the logger is instantiated\n   * @return An SFLogger instance given the name of the class\n   */\n  public static SFLogger getLogger(Class<?> clazz) {\n    // only need to determine the logger implementation only once\n    if (loggerImplementation == null) {\n      String logger = systemGetProperty(\"net.snowflake.jdbc.loggerImpl\");\n\n      loggerImplementation = LoggerImpl.fromString(logger);\n\n      if (loggerImplementation == null) {\n        // default to use java util logging\n        loggerImplementation = LoggerImpl.JDK14LOGGER;\n      }\n    }\n\n    switch (loggerImplementation) {\n      case SLF4JLOGGER:\n        return new SLF4JLogger(clazz);\n      case JDK14LOGGER:\n      default:\n        return new JDK14Logger(clazz.getName());\n    }\n  }\n\n  public static String getLoggerImplementationName() {\n    // Ensure the implementation is initialized\n    if (loggerImplementation == null) {\n      getLogger(SFLoggerFactory.class);\n    }\n    switch (loggerImplementation) {\n      case SLF4JLOGGER:\n        return \"SLF4J\";\n      case JDK14LOGGER:\n      default:\n        return \"JUL\";\n    }\n  }\n\n  /**\n   * A replacement for getLogger function, whose parameter is Class&lt;?&gt;, when Class&lt;?&gt; is\n   * inaccessible. For example, the name we have is an alias name of a class, we can't get the\n   * correct Class&lt;?&gt; by the given name.\n   *\n   * @param name name to indicate the class (might be different with the class name) that the logger\n   *     is instantiated\n   * @return An SFLogger instance given the name\n   */\n  public static SFLogger getLogger(String name) {\n    if (loggerImplementation == null) {\n      String logger = systemGetProperty(\"net.snowflake.jdbc.loggerImpl\");\n\n      loggerImplementation = LoggerImpl.fromString(logger);\n\n      if (loggerImplementation == null) {\n        // default to use java util logging\n        loggerImplementation = LoggerImpl.JDK14LOGGER;\n      }\n    }\n\n    switch (loggerImplementation) {\n      case SLF4JLOGGER:\n        return new SLF4JLogger(name);\n      case JDK14LOGGER:\n      default:\n        return new JDK14Logger(name);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SFLoggerUtil.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isNullOrEmpty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\n\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.apache.commons.logging.LogFactory;\n\npublic class SFLoggerUtil {\n  private static final String NOT_PROVIDED_LOG = \"not provided\";\n  private static final String PROVIDED_LOG = \"provided\";\n\n  public static void initializeSnowflakeLogger() {\n    String logger = systemGetProperty(\"net.snowflake.jdbc.loggerImpl\");\n    SFLoggerFactory.LoggerImpl loggerImplementation = SFLoggerFactory.LoggerImpl.fromString(logger);\n    if (loggerImplementation == null) {\n      loggerImplementation = SFLoggerFactory.LoggerImpl.JDK14LOGGER;\n    }\n\n    CommonsLoggingWrapperMode commonsLoggingWrapperMode = CommonsLoggingWrapperMode.detect();\n    if (commonsLoggingWrapperMode == CommonsLoggingWrapperMode.OFF) {\n      return;\n    }\n\n    try {\n      SnowflakeUtil.systemSetProperty(\n          \"org.apache.commons.logging.LogFactory\",\n          \"org.apache.commons.logging.impl.LogFactoryImpl\");\n    } catch (SecurityException ex) {\n      // SecurityManager denied setProperty; logging still works with default backend\n    }\n    LogFactory logFactory = LogFactory.getFactory();\n    if (commonsLoggingWrapperMode == CommonsLoggingWrapperMode.ALL) {\n      logFactory.setAttribute(\n          \"org.apache.commons.logging.Log\", CommonsLoggingWrapper.class.getName());\n      return;\n    }\n    switch (loggerImplementation) {\n      case SLF4JLOGGER:\n        logFactory.setAttribute(\n            \"org.apache.commons.logging.Log\", \"net.snowflake.client.internal.log.SLF4JJCLWrapper\");\n        break;\n      case JDK14LOGGER:\n      default:\n        logFactory.setAttribute(\n            \"org.apache.commons.logging.Log\", \"net.snowflake.client.internal.log.JDK14JCLWrapper\");\n    }\n  }\n\n  public static <T> String isVariableProvided(T variable) {\n    if (variable instanceof String) {\n      return (isNullOrEmpty((String) variable)) ? NOT_PROVIDED_LOG : PROVIDED_LOG;\n    }\n    return variable == null ? NOT_PROVIDED_LOG : PROVIDED_LOG;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SFToJavaLogMapper.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport java.util.HashMap;\nimport java.util.logging.Level;\n\n/** Utility class to map SFLogLevels to java.util.logging.Level; */\npublic class SFToJavaLogMapper {\n  private static HashMap<SFLogLevel, Level> levelMap = new HashMap<>();\n\n  static {\n    levelMap.put(SFLogLevel.TRACE, java.util.logging.Level.FINEST);\n    levelMap.put(SFLogLevel.DEBUG, java.util.logging.Level.FINE);\n    levelMap.put(SFLogLevel.INFO, java.util.logging.Level.INFO);\n    levelMap.put(SFLogLevel.WARN, java.util.logging.Level.WARNING);\n    levelMap.put(SFLogLevel.ERROR, java.util.logging.Level.SEVERE);\n    levelMap.put(SFLogLevel.OFF, java.util.logging.Level.OFF);\n  }\n\n  public static java.util.logging.Level toJavaUtilLoggingLevel(SFLogLevel level) {\n    return levelMap.getOrDefault(level, java.util.logging.Level.OFF);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SLF4JJCLWrapper.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport org.apache.commons.logging.Log;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/* Although this class doesn't really include SLF4J, this class play the role of\n * a wrapper class of snowflake SLF4JLogger for apache Jakarta Commons\n * Logging (the logging framework used by apache httpclient4.5 package)\n * to choose to give us the ability to filter out sensitive data.\n *\n * The reason why we don't unify this class and JDK14JCLWrapper class is that the\n * way SLF4J gets the log functions caller prevents us to use an extra wrapper. If\n * we really wrap up SLF4J, the log will not catch correct log callers.\n */\npublic class SLF4JJCLWrapper implements Log {\n  private Logger slf4jLogger;\n\n  public SLF4JJCLWrapper(String name) {\n    slf4jLogger = LoggerFactory.getLogger(name);\n  }\n\n  Logger getLogger() {\n    return slf4jLogger;\n  }\n\n  public void debug(Object message) {\n    // do nothing\n  }\n\n  public void debug(Object msg, Throwable t) {}\n\n  public void error(Object msg) {}\n\n  public void error(Object msg, Throwable t) {}\n\n  public void fatal(Object msg) {}\n\n  public void fatal(Object msg, Throwable t) {}\n\n  public void info(Object msg) {}\n\n  public void info(Object msg, Throwable t) {}\n\n  public void trace(Object msg) {}\n\n  public void trace(Object msg, Throwable t) {}\n\n  public void warn(Object msg) {}\n\n  public void warn(Object msg, Throwable t) {}\n\n  public boolean isDebugEnabled() {\n    return false;\n  }\n\n  public boolean isErrorEnabled() {\n    return this.slf4jLogger.isErrorEnabled();\n  }\n\n  public boolean isFatalEnabled() {\n    return this.slf4jLogger.isErrorEnabled();\n  }\n\n  public boolean isInfoEnabled() {\n    return this.slf4jLogger.isInfoEnabled();\n  }\n\n  public boolean isTraceEnabled() {\n    return false;\n  }\n\n  public boolean isWarnEnabled() {\n    return this.slf4jLogger.isWarnEnabled();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/SLF4JLogger.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport net.snowflake.client.internal.util.MaskedException;\nimport net.snowflake.client.internal.util.SecretDetector;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.helpers.FormattingTuple;\nimport org.slf4j.helpers.MessageFormatter;\nimport org.slf4j.spi.LocationAwareLogger;\n\npublic class SLF4JLogger implements SFLogger {\n  private Logger slf4jLogger;\n\n  private boolean isLocationAwareLogger;\n\n  private static final String FQCN = SLF4JLogger.class.getName();\n\n  public SLF4JLogger(Class<?> clazz) {\n    slf4jLogger = LoggerFactory.getLogger(clazz);\n    isLocationAwareLogger = slf4jLogger instanceof LocationAwareLogger;\n  }\n\n  public SLF4JLogger(String name) {\n    slf4jLogger = LoggerFactory.getLogger(name);\n    isLocationAwareLogger = slf4jLogger instanceof LocationAwareLogger;\n  }\n\n  public boolean isDebugEnabled() {\n    return this.slf4jLogger.isDebugEnabled();\n  }\n\n  public boolean isErrorEnabled() {\n    return this.slf4jLogger.isErrorEnabled();\n  }\n\n  public boolean isInfoEnabled() {\n    return this.slf4jLogger.isInfoEnabled();\n  }\n\n  public boolean isTraceEnabled() {\n    return this.slf4jLogger.isTraceEnabled();\n  }\n\n  public boolean isWarnEnabled() {\n    return this.slf4jLogger.isWarnEnabled();\n  }\n\n  public void debug(String msg, boolean isMasked) {\n    msg = isMasked == true ? SecretDetector.maskSecrets(msg) : msg;\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.DEBUG_INT, msg, null, null);\n    } else {\n      slf4jLogger.debug(msg);\n    }\n  }\n\n  // This function is used to display unmasked, potentially sensitive log information for internal\n  // regression testing purposes. Do not use otherwise\n  public void debugNoMask(String msg) {\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.DEBUG_INT, msg, null, null);\n    } else {\n      slf4jLogger.debug(msg);\n    }\n  }\n\n  public void debug(String msg, Object... arguments) {\n    // use this as format example for JDK14Logger.\n    if (isDebugEnabled()) {\n      FormattingTuple ft = MessageFormatter.arrayFormat(msg, evaluateLambdaArgs(arguments));\n      this.debug(SecretDetector.maskSecrets(ft.getMessage()), false);\n    }\n  }\n\n  public void debug(String msg, Throwable t) {\n    msg = SecretDetector.maskSecrets(msg);\n    Throwable masked = (t == null) ? null : new MaskedException(t);\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.DEBUG_INT, msg, null, masked);\n    } else {\n      slf4jLogger.debug(msg, masked);\n    }\n  }\n\n  public void error(String msg, boolean isMasked) {\n    msg = isMasked == true ? SecretDetector.maskSecrets(msg) : msg;\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.ERROR_INT, msg, null, null);\n    } else {\n      slf4jLogger.error(msg);\n    }\n  }\n\n  public void error(String msg, Object... arguments) {\n    if (isErrorEnabled()) {\n      FormattingTuple ft = MessageFormatter.arrayFormat(msg, evaluateLambdaArgs(arguments));\n      this.error(SecretDetector.maskSecrets(ft.getMessage()), false);\n    }\n  }\n\n  public void error(String msg, Throwable t) {\n    msg = SecretDetector.maskSecrets(msg);\n    Throwable masked = (t == null) ? null : new MaskedException(t);\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.ERROR_INT, msg, null, masked);\n    } else {\n      slf4jLogger.error(msg, masked);\n    }\n  }\n\n  public void info(String msg, boolean isMasked) {\n    msg = isMasked == true ? SecretDetector.maskSecrets(msg) : msg;\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.INFO_INT, msg, null, null);\n    } else {\n      slf4jLogger.info(msg);\n    }\n  }\n\n  public void info(String msg, Object... arguments) {\n    if (isInfoEnabled()) {\n      FormattingTuple ft = MessageFormatter.arrayFormat(msg, evaluateLambdaArgs(arguments));\n      this.info(SecretDetector.maskSecrets(ft.getMessage()), false);\n    }\n  }\n\n  public void info(String msg, Throwable t) {\n    msg = SecretDetector.maskSecrets(msg);\n    Throwable masked = (t == null) ? null : new MaskedException(t);\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.INFO_INT, msg, null, masked);\n    } else {\n      slf4jLogger.error(msg, masked);\n    }\n  }\n\n  public void trace(String msg, boolean isMasked) {\n    msg = isMasked == true ? SecretDetector.maskSecrets(msg) : msg;\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.TRACE_INT, msg, null, null);\n    } else {\n      slf4jLogger.trace(msg);\n    }\n  }\n\n  public void trace(String msg, Object... arguments) {\n    if (isTraceEnabled()) {\n      FormattingTuple ft = MessageFormatter.arrayFormat(msg, evaluateLambdaArgs(arguments));\n      this.trace(SecretDetector.maskSecrets(ft.getMessage()), false);\n    }\n  }\n\n  public void trace(String msg, Throwable t) {\n    msg = SecretDetector.maskSecrets(msg);\n    Throwable masked = (t == null) ? null : new MaskedException(t);\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.TRACE_INT, msg, null, masked);\n    } else {\n      slf4jLogger.trace(msg, masked);\n    }\n  }\n\n  public void warn(String msg, boolean isMasked) {\n    msg = isMasked == true ? SecretDetector.maskSecrets(msg) : msg;\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.WARN_INT, msg, null, null);\n    } else {\n      slf4jLogger.error(msg);\n    }\n  }\n\n  public void warn(String msg, Object... arguments) {\n    if (isWarnEnabled()) {\n      FormattingTuple ft = MessageFormatter.arrayFormat(msg, evaluateLambdaArgs(arguments));\n      this.warn(SecretDetector.maskSecrets(ft.getMessage()), false);\n    }\n  }\n\n  public void warn(String msg, Throwable t) {\n    msg = SecretDetector.maskSecrets(msg);\n    Throwable masked = (t == null) ? null : new MaskedException(t);\n    if (isLocationAwareLogger) {\n      ((LocationAwareLogger) slf4jLogger)\n          .log(null, FQCN, LocationAwareLogger.WARN_INT, msg, null, masked);\n    } else {\n      slf4jLogger.error(msg, masked);\n    }\n  }\n\n  private static Object[] evaluateLambdaArgs(Object... args) {\n    final Object[] result = new Object[args.length];\n\n    for (int i = 0; i < args.length; i++) {\n      result[i] = args[i] instanceof ArgSupplier ? ((ArgSupplier) args[i]).get() : args[i];\n    }\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/StdErrOutThresholdAwareConsoleHandler.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport java.util.logging.SimpleFormatter;\nimport java.util.logging.StreamHandler;\n\nclass StdErrOutThresholdAwareConsoleHandler extends StreamHandler {\n  private final ConsoleHandler stdErrConsoleHandler = new ConsoleHandler();\n  private final Level threshold;\n\n  public StdErrOutThresholdAwareConsoleHandler(Level threshold) {\n    super(System.out, new SimpleFormatter());\n    this.threshold = threshold;\n  }\n\n  @Override\n  public void publish(LogRecord record) {\n    if (record.getLevel().intValue() > threshold.intValue()) {\n      stdErrConsoleHandler.publish(record);\n    } else {\n      super.publish(record);\n      flush();\n    }\n  }\n\n  @Override\n  public void close() {\n    flush();\n    stdErrConsoleHandler.close();\n  }\n\n  Level getThreshold() {\n    return threshold;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/StdOutConsoleHandler.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport java.util.logging.LogRecord;\nimport java.util.logging.SimpleFormatter;\nimport java.util.logging.StreamHandler;\n\nclass StdOutConsoleHandler extends StreamHandler {\n  public StdOutConsoleHandler() {\n    // configure with specific defaults for ConsoleHandler\n    super(System.out, new SimpleFormatter());\n  }\n\n  @Override\n  public void publish(LogRecord record) {\n    super.publish(record);\n    flush();\n  }\n\n  @Override\n  public void close() {\n    flush();\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/log/UnknownJavaUtilLoggingLevelException.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport java.util.logging.Level;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nclass UnknownJavaUtilLoggingLevelException extends RuntimeException {\n  private static final String AVAILABLE_LEVELS =\n      Stream.of(\n              Level.OFF,\n              Level.SEVERE,\n              Level.WARNING,\n              Level.INFO,\n              Level.CONFIG,\n              Level.FINE,\n              Level.FINER,\n              Level.FINEST,\n              Level.ALL)\n          .map(Level::getName)\n          .collect(Collectors.joining(\", \"));\n\n  UnknownJavaUtilLoggingLevelException(String threshold) {\n    super(\n        \"Unknown java util logging level: \" + threshold + \", expected one of: \" + AVAILABLE_LEVELS);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/Converter.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport net.snowflake.client.internal.core.SFException;\n\n/** Functional interface used to convert data to expected type */\n@FunctionalInterface\npublic interface Converter<T> {\n  T convert(Object object) throws SFException;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/DecorrelatedJitterBackoff.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport java.util.concurrent.ThreadLocalRandom;\n\n/**\n * Decorrelated Jitter backoff\n *\n * <p>https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n */\npublic class DecorrelatedJitterBackoff {\n  private final long base;\n  private final long cap;\n\n  public DecorrelatedJitterBackoff(long base, long cap) {\n    this.base = base;\n    this.cap = cap;\n  }\n\n  public long nextSleepTime(long sleep) {\n    return Math.min(cap, ThreadLocalRandom.current().nextLong(base, sleep * 3));\n  }\n\n  public long getJitterForLogin(long currentTime) {\n    double multiplicationFactor = chooseRandom(-1, 1);\n    long jitter = (long) (multiplicationFactor * currentTime * 0.5);\n    return jitter;\n  }\n\n  public double chooseRandom(double min, double max) {\n    return min + (Math.random() * (max - min));\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/EnvironmentProvider.java",
    "content": "package net.snowflake.client.internal.util;\n\n/**\n * Interface for providing environment variables to enable thread-safe testing. This abstraction\n * allows dependency injection of environment variable access, making code testable with instance\n * mocks (as opposed to static SnowflakeUtil) that work across threads.\n */\npublic interface EnvironmentProvider {\n\n  String getEnv(String name);\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/LibcDetails.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.concurrent.TimeUnit;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.commons.io.IOUtils;\n\n/**\n * Detects the libc family (glibc / musl) and version on Linux for telemetry and minicore platform\n * targeting purposes.\n */\npublic final class LibcDetails {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(LibcDetails.class);\n\n  public static final String GLIBC = \"glibc\";\n  public static final String MUSL = \"musl\";\n\n  static final String DEFAULT_LDD_PATH = \"/usr/bin/ldd\";\n\n  // e.g. \"GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.16) stable release version 2.31.\"\n  private static final Pattern RE_GLIBC_VERSION =\n      Pattern.compile(\"LIBC[-a-z0-9 ).]*?(\\\\d+\\\\.\\\\d+)\", Pattern.CASE_INSENSITIVE);\n  // e.g. \"Version 1.2.3\"\n  private static final Pattern RE_MUSL_VERSION =\n      Pattern.compile(\"Version\\\\s+(\\\\d+\\\\.\\\\d+[\\\\d.]*)\", Pattern.CASE_INSENSITIVE);\n\n  // Word-boundary family markers - guard against false positives like \"muslib\" or \"muscle\".\n  private static final Pattern RE_MUSL_MARKER = Pattern.compile(\"\\\\bmusl\\\\b\");\n  private static final Pattern RE_GLIBC_NAME_MARKER = Pattern.compile(\"\\\\bGNU C Library\\\\b\");\n  private static final Pattern RE_GLIBC_GETCONF_MARKER = Pattern.compile(\"\\\\bglibc\\\\b\");\n\n  private static final long EXEC_TIMEOUT_MS = 200;\n\n  private static LibcInfo cachedResult;\n\n  private LibcDetails() {}\n\n  /** Returns the cached libc details, performing detection on first call. */\n  public static synchronized LibcInfo load() {\n    if (cachedResult == null) {\n      cachedResult = detect();\n    }\n    return cachedResult;\n  }\n\n  private static LibcInfo detect() {\n    if (Constants.getOS() != Constants.OS.LINUX) {\n      logger.trace(\"Libc detection skipped: not running on Linux\");\n      return new LibcInfo(null, null);\n    }\n\n    LibcInfo fromFs = detectFromFilesystem(Paths.get(DEFAULT_LDD_PATH));\n    if (fromFs != null && fromFs.getFamily() != null && fromFs.getVersion() != null) {\n      return fromFs;\n    }\n\n    LibcInfo fromCmd = detectFromCommand();\n    if (fromCmd == null) {\n      return fromFs != null ? fromFs : new LibcInfo(null, null);\n    }\n    if (fromFs == null || fromFs.getFamily() == null) {\n      return fromCmd;\n    }\n    // Family already detected; only adopt the command-derived version if both strategies agree\n    // on the family. Mixing version from a different family would yield a corrupt result.\n    if (fromFs.getVersion() == null && fromFs.getFamily().equals(fromCmd.getFamily())) {\n      return new LibcInfo(fromFs.getFamily(), fromCmd.getVersion());\n    }\n    return fromFs;\n  }\n\n  static LibcInfo detectFromFilesystem(Path lddPath) {\n    try {\n      byte[] bytes = Files.readAllBytes(lddPath);\n      String content = new String(bytes, StandardCharsets.UTF_8);\n      return parseLddContent(content);\n    } catch (IOException e) {\n      logger.debug(\"Failed to read libc details from {}: {}\", lddPath, e.getMessage());\n      return null;\n    } catch (Exception e) {\n      logger.debug(\"Unexpected error reading libc details from {}: {}\", lddPath, e.getMessage());\n      return null;\n    }\n  }\n\n  static LibcInfo parseLddContent(String content) {\n    if (content == null || content.isEmpty()) {\n      return null;\n    }\n\n    String family;\n    Pattern versionRe;\n    if (RE_MUSL_MARKER.matcher(content).find()) {\n      family = MUSL;\n      versionRe = RE_MUSL_VERSION;\n    } else if (RE_GLIBC_NAME_MARKER.matcher(content).find()) {\n      family = GLIBC;\n      versionRe = RE_GLIBC_VERSION;\n    } else {\n      return null;\n    }\n\n    Matcher m = versionRe.matcher(content);\n    String version = m.find() ? m.group(1) : null;\n    return new LibcInfo(family, version);\n  }\n\n  static LibcInfo detectFromCommand() {\n    String output = runLibcVersionCommands();\n    if (output == null) {\n      return null;\n    }\n    return parseCommandOutput(output);\n  }\n\n  static LibcInfo parseCommandOutput(String output) {\n    if (output == null || output.isEmpty()) {\n      return null;\n    }\n\n    String[] lines = output.split(\"\\\\R+\");\n    String getconfLine = lines.length > 0 ? lines[0] : null;\n    String lddLine1 = lines.length > 1 ? lines[1] : null;\n    String lddLine2 = lines.length > 2 ? lines[2] : null;\n\n    if (getconfLine != null && RE_GLIBC_GETCONF_MARKER.matcher(getconfLine).find()) {\n      String[] parts = getconfLine.trim().split(\"\\\\s+\");\n      String version = parts.length > 1 ? parts[1] : null;\n      return new LibcInfo(GLIBC, version);\n    }\n    if (lddLine1 != null && RE_MUSL_MARKER.matcher(lddLine1).find()) {\n      String version = null;\n      if (lddLine2 != null) {\n        String[] parts = lddLine2.trim().split(\"\\\\s+\");\n        if (parts.length > 1) {\n          version = parts[1];\n        }\n      }\n      return new LibcInfo(MUSL, version);\n    }\n    return null;\n  }\n\n  /**\n   * Runs {@code getconf GNU_LIBC_VERSION} and {@code ldd --version} via {@code /bin/sh} and returns\n   * the combined stdout/stderr, or {@code null} on failure.\n   */\n  private static String runLibcVersionCommands() {\n    ProcessBuilder pb =\n        new ProcessBuilder(\n            \"/bin/sh\", \"-c\", \"getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true\");\n    pb.redirectErrorStream(true);\n    Process process = null;\n    try {\n      process = pb.start();\n      String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);\n      if (!process.waitFor(EXEC_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {\n        process.destroyForcibly();\n        logger.debug(\"Libc version command timed out after {}ms\", EXEC_TIMEOUT_MS);\n        return null;\n      }\n      return output;\n    } catch (IOException e) {\n      logger.debug(\"Failed to run libc version command: {}\", e.getMessage());\n      return null;\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      logger.debug(\"Interrupted while running libc version command: {}\", e.getMessage());\n      return null;\n    } catch (Exception e) {\n      logger.debug(\"Unexpected error running libc version command: {}\", e.getMessage());\n      return null;\n    } finally {\n      if (process != null && process.isAlive()) {\n        process.destroyForcibly();\n      }\n    }\n  }\n\n  /** Visible for testing. Resets the cache so that {@link #load()} re-detects on next call. */\n  static synchronized void resetCacheForTesting() {\n    cachedResult = null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/LibcInfo.java",
    "content": "package net.snowflake.client.internal.util;\n\n/**\n * Immutable libc detection result returned by {@link LibcDetails#load()}.\n *\n * <p>Both {@code family} and {@code version} may be {@code null} - e.g. on non-Linux platforms\n * (both null), or on musl when no version source could be parsed (family set, version null).\n */\npublic final class LibcInfo {\n\n  private final String family;\n  private final String version;\n\n  public LibcInfo(String family, String version) {\n    this.family = family;\n    this.version = version;\n  }\n\n  public String getFamily() {\n    return family;\n  }\n\n  public String getVersion() {\n    return version;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/MaskedException.java",
    "content": "package net.snowflake.client.internal.util;\n\n/** Wrapper exception that ensures any secret in log output is masked via {@link SecretDetector}. */\npublic class MaskedException extends RuntimeException {\n  private final Throwable inner;\n\n  public MaskedException(Throwable inner) {\n    // Avoid capturing an extra stack trace; we'll copy the inner frames below.\n    super(null, null, true, true);\n    this.inner = inner;\n    if (inner != null) {\n      setStackTrace(inner.getStackTrace());\n    }\n  }\n\n  @Override\n  public String getMessage() {\n    return SecretDetector.maskSecrets(inner == null ? null : inner.getMessage());\n  }\n\n  @Override\n  public String getLocalizedMessage() {\n    return SecretDetector.maskSecrets(inner == null ? null : inner.getLocalizedMessage());\n  }\n\n  @Override\n  public String toString() {\n    return SecretDetector.maskSecrets(inner == null ? null : inner.toString());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/OsReleaseDetails.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\n\n/** Parses Linux distribution details from /etc/os-release for telemetry purposes. */\npublic class OsReleaseDetails {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(OsReleaseDetails.class);\n\n  private static final String DEFAULT_OS_RELEASE_PATH = \"/etc/os-release\";\n\n  private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(\"^([A-Z0-9_]+)=(.*)$\");\n\n  private static final Set<String> ALLOWED_KEYS =\n      Collections.unmodifiableSet(\n          new HashSet<>(\n              Arrays.asList(\n                  \"NAME\",\n                  \"PRETTY_NAME\",\n                  \"ID\",\n                  \"IMAGE_ID\",\n                  \"IMAGE_VERSION\",\n                  \"BUILD_ID\",\n                  \"VERSION\",\n                  \"VERSION_ID\")));\n\n  private static Map<String, String> cachedOsDetails = null;\n\n  public static synchronized Map<String, String> load() {\n    if (cachedOsDetails != null) {\n      return cachedOsDetails;\n    }\n\n    if (Constants.getOS() != Constants.OS.LINUX) {\n      logger.trace(\"OS details collection skipped: not running on Linux\");\n      cachedOsDetails = Collections.emptyMap();\n      return cachedOsDetails;\n    }\n\n    cachedOsDetails = loadFromPath(Paths.get(DEFAULT_OS_RELEASE_PATH));\n    return cachedOsDetails;\n  }\n\n  static Map<String, String> loadFromPath(Path path) {\n    try {\n      byte[] bytes = Files.readAllBytes(path);\n      String content = new String(bytes, StandardCharsets.UTF_8);\n      return parse(content);\n    } catch (IOException e) {\n      logger.debug(\"Failed to read OS details from {}: {}\", path, e.getMessage());\n      return Collections.emptyMap();\n    } catch (Exception e) {\n      logger.debug(\"Unexpected error reading OS details from {}: {}\", path, e.getMessage());\n      return Collections.emptyMap();\n    }\n  }\n\n  static Map<String, String> parse(String content) {\n    Map<String, String> details = new HashMap<>();\n    if (content == null || content.isEmpty()) {\n      return details;\n    }\n\n    for (String line : content.split(\"\\n\")) {\n      line = line.trim();\n      if (line.isEmpty() || line.startsWith(\"#\")) {\n        continue;\n      }\n\n      Matcher matcher = KEY_VALUE_PATTERN.matcher(line);\n      if (matcher.matches()) {\n        String key = matcher.group(1).trim();\n        if (ALLOWED_KEYS.contains(key)) {\n          String value = parseValue(matcher.group(2));\n          details.put(key, value);\n        }\n      }\n    }\n\n    return Collections.unmodifiableMap(details);\n  }\n\n  private static String parseValue(String value) {\n    if (value == null) {\n      return null;\n    }\n\n    value = value.trim();\n    if (value.isEmpty()) {\n      return value;\n    }\n\n    char firstChar = value.charAt(0);\n\n    // Handle quoted values (single or double quotes)\n    if (firstChar == '\"' || firstChar == '\\'') {\n      int endQuote = value.indexOf(firstChar, 1);\n      return endQuote > 0 ? value.substring(1, endQuote) : value.substring(1).trim();\n    }\n\n    // Unquoted value - strip inline comment if present\n    int commentIndex = value.indexOf('#');\n    return commentIndex > 0 ? value.substring(0, commentIndex).trim() : value;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/Platform.java",
    "content": "package net.snowflake.client.internal.util;\n\n/** Enum representing all detectable cloud platforms and identity providers. */\npublic enum Platform {\n  IS_AWS_LAMBDA(\"is_aws_lambda\"),\n  IS_AZURE_FUNCTION(\"is_azure_function\"),\n  IS_GCE_CLOUD_RUN_SERVICE(\"is_gce_cloud_run_service\"),\n  IS_GCE_CLOUD_RUN_JOB(\"is_gce_cloud_run_job\"),\n  IS_GITHUB_ACTION(\"is_github_action\"),\n  IS_EC2_INSTANCE(\"is_ec2_instance\"),\n  HAS_AWS_IDENTITY(\"has_aws_identity\"),\n  IS_AZURE_VM(\"is_azure_vm\"),\n  HAS_AZURE_MANAGED_IDENTITY(\"has_azure_managed_identity\"),\n  IS_GCE_VM(\"is_gce_vm\"),\n  HAS_GCP_IDENTITY(\"has_gcp_identity\");\n\n  private final String value;\n\n  Platform(String value) {\n    this.value = value;\n  }\n\n  public String getValue() {\n    return value;\n  }\n\n  @Override\n  public String toString() {\n    return value;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/PlatformDetector.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport java.io.IOException;\nimport java.net.SocketTimeoutException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.auth.wif.AwsAttestationService;\nimport net.snowflake.client.internal.core.auth.wif.PlatformDetectionUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPut;\n\npublic class PlatformDetector {\n\n  private static final SFLogger logger = SFLoggerFactory.getLogger(PlatformDetector.class);\n\n  private static final int DEFAULT_DETECTION_TIMEOUT_MS = 200;\n\n  private static List<String> cachedDetectedPlatforms = null;\n\n  // AWS platform detection constants\n  private static final String AWS_LAMBDA_TASK_ROOT = \"LAMBDA_TASK_ROOT\";\n\n  // Azure platform detection constants\n  private static final String AZURE_FUNCTIONS_WORKER_RUNTIME = \"FUNCTIONS_WORKER_RUNTIME\";\n  private static final String AZURE_FUNCTIONS_EXTENSION_VERSION = \"FUNCTIONS_EXTENSION_VERSION\";\n  private static final String AZURE_WEBJOBS_STORAGE = \"AzureWebJobsStorage\";\n  private static final String AZURE_IDENTITY_HEADER = \"IDENTITY_HEADER\";\n\n  // GCP platform detection constants\n  private static final String GCP_K_SERVICE = \"K_SERVICE\";\n  private static final String GCP_K_REVISION = \"K_REVISION\";\n  private static final String GCP_K_CONFIGURATION = \"K_CONFIGURATION\";\n  private static final String GCP_CLOUD_RUN_JOB = \"CLOUD_RUN_JOB\";\n  private static final String GCP_CLOUD_RUN_EXECUTION = \"CLOUD_RUN_EXECUTION\";\n\n  // GitHub Actions detection constants\n  private static final String GITHUB_ACTIONS = \"GITHUB_ACTIONS\";\n\n  // Default cloud metadata service URLs\n  private static final String DEFAULT_METADATA_SERVICE_BASE_URL = \"http://169.254.169.254\";\n  // IPv6 fallback for EC2 IMDS on IPv6-only instances (IPv4 unreachable)\n  private static final String DEFAULT_AWS_METADATA_IPV6_BASE_URL = \"http://[fd00:ec2::254]\";\n  private static final String DEFAULT_GCP_METADATA_BASE_URL = \"http://metadata.google.internal\";\n\n  // Metadata service headers and values\n  private static final String AWS_METADATA_TOKEN_TTL_HEADER =\n      \"X-aws-ec2-metadata-token-ttl-seconds\";\n  private static final String AWS_METADATA_TOKEN_HEADER = \"X-aws-ec2-metadata-token\";\n  private static final String AWS_METADATA_TOKEN_TTL_VALUE = \"21600\";\n  private static final String AZURE_METADATA_HEADER = \"Metadata\";\n  private static final String AZURE_METADATA_VALUE = \"True\";\n  private static final String GCP_METADATA_FLAVOR_HEADER = \"Metadata-Flavor\";\n  private static final String GCP_METADATA_FLAVOR_VALUE = \"Google\";\n\n  // Metadata service endpoints paths\n  private static final String AWS_TOKEN_ENDPOINT_PATH = \"/latest/api/token\";\n  private static final String AWS_INSTANCE_IDENTITY_ENDPOINT_PATH =\n      \"/latest/dynamic/instance-identity/document\";\n  private static final String AZURE_INSTANCE_ENDPOINT_PATH =\n      \"/metadata/instance?api-version=2021-02-01\";\n  private static final String AZURE_IDENTITY_ENDPOINT_PATH =\n      \"/metadata/identity/oauth2/token?api-version=2018-02-01&resource=\";\n  private static final String GCP_SERVICE_ACCOUNT_ENDPOINT_PATH =\n      \"/computeMetadata/v1/instance/service-accounts/default/email\";\n\n  // Azure managed identity resource URL\n  private static final String AZURE_MANAGEMENT_RESOURCE_URL = \"https://management.azure.com\";\n\n  // Timeout suffix for platform names\n  private static final String TIMEOUT_SUFFIX = \"_timeout\";\n\n  // Instance fields for configurable URLs and environment provider (for testing)\n  private final String awsMetadataBaseUrl;\n  private final String awsMetadataIpv6BaseUrl;\n  private final String azureMetadataBaseUrl;\n  private final String gcpMetadataBaseUrl;\n  private final EnvironmentProvider environmentProvider;\n\n  // Default constructor for production use\n  public PlatformDetector() {\n    this.awsMetadataBaseUrl = DEFAULT_METADATA_SERVICE_BASE_URL;\n    this.awsMetadataIpv6BaseUrl = DEFAULT_AWS_METADATA_IPV6_BASE_URL;\n    this.azureMetadataBaseUrl = DEFAULT_METADATA_SERVICE_BASE_URL;\n    this.gcpMetadataBaseUrl = DEFAULT_GCP_METADATA_BASE_URL;\n    this.environmentProvider = new SnowflakeEnvironmentProvider();\n  }\n\n  /** Constructor for testing purposes - allows overriding both URLs and environment provider */\n  PlatformDetector(\n      String awsMetadataBaseUrl,\n      String awsMetadataIpv6BaseUrl,\n      String azureMetadataBaseUrl,\n      String gcpMetadataBaseUrl,\n      EnvironmentProvider environmentProvider) {\n    this.awsMetadataBaseUrl = awsMetadataBaseUrl;\n    this.awsMetadataIpv6BaseUrl = awsMetadataIpv6BaseUrl;\n    this.azureMetadataBaseUrl = azureMetadataBaseUrl;\n    this.gcpMetadataBaseUrl = gcpMetadataBaseUrl;\n    this.environmentProvider = environmentProvider;\n  }\n\n  private enum DetectionState {\n    DETECTED,\n    NOT_DETECTED,\n    TIMEOUT\n  }\n\n  /**\n   * Get cached platform detection results. If platform detection has not been performed yet,\n   * initializes the cache.\n   *\n   * @return list of detected platform strings\n   */\n  public static synchronized List<String> getCachedPlatformDetection() {\n    if (cachedDetectedPlatforms != null) {\n      return cachedDetectedPlatforms;\n    }\n\n    logger.debug(\n        \"Platform detection cache miss. Initializing with default timeout: {}ms\",\n        DEFAULT_DETECTION_TIMEOUT_MS);\n\n    PlatformDetector detector = new PlatformDetector();\n    AwsAttestationService attestationService = new AwsAttestationService();\n    List<String> result = detectPlatformsAndCache(detector, attestationService);\n\n    logger.debug(\"Platform detection cache initialized: {}\", result);\n    return result;\n  }\n\n  static synchronized List<String> detectPlatformsAndCache(\n      PlatformDetector detector, AwsAttestationService attestationService) {\n    List<String> detectedPlatforms =\n        detector.detectPlatforms(DEFAULT_DETECTION_TIMEOUT_MS, attestationService);\n\n    cachedDetectedPlatforms = Collections.unmodifiableList(detectedPlatforms);\n    return cachedDetectedPlatforms;\n  }\n\n  /**\n   * Detect all potential platforms that the current environment may be running on. Swallows all\n   * exceptions and returns an empty list if any exception occurs.\n   *\n   * @param platformDetectionTimeoutMs Timeout value for platform detection requests in\n   *     milliseconds. If null, defaults to DEFAULT_DETECTION_TIMEOUT_MS. If 0, skips\n   *     network-dependent checks.\n   * @return List of detected platform names. Platforms that timed out will have \"_timeout\" suffix\n   *     appended to their name. Returns empty list if any exception occurs during detection.\n   */\n  List<String> detectPlatforms(\n      Integer platformDetectionTimeoutMs, AwsAttestationService attestationService) {\n    try {\n      int timeoutMs =\n          platformDetectionTimeoutMs != null\n              ? platformDetectionTimeoutMs\n              : DEFAULT_DETECTION_TIMEOUT_MS;\n\n      // Run environment-only checks synchronously (no network calls)\n      Map<Platform, DetectionState> platforms = new HashMap<>();\n      platforms.put(Platform.IS_AWS_LAMBDA, isAwsLambda());\n      platforms.put(Platform.IS_AZURE_FUNCTION, isAzureFunction());\n      platforms.put(Platform.IS_GCE_CLOUD_RUN_SERVICE, isGcpCloudRunService());\n      platforms.put(Platform.IS_GCE_CLOUD_RUN_JOB, isGcpCloudRunJob());\n      platforms.put(Platform.IS_GITHUB_ACTION, isGithubAction());\n\n      if (timeoutMs != 0) {\n        ExecutorService executor = Executors.newFixedThreadPool(6);\n        try {\n          Map<Platform, CompletableFuture<DetectionState>> futures = new HashMap<>();\n\n          futures.put(\n              Platform.IS_EC2_INSTANCE,\n              CompletableFuture.supplyAsync(() -> isEc2Instance(timeoutMs), executor));\n          futures.put(\n              Platform.HAS_AWS_IDENTITY,\n              CompletableFuture.supplyAsync(\n                  () -> hasAwsIdentity(attestationService, timeoutMs), executor));\n          futures.put(\n              Platform.IS_AZURE_VM,\n              CompletableFuture.supplyAsync(() -> isAzureVm(timeoutMs), executor));\n          futures.put(\n              Platform.HAS_AZURE_MANAGED_IDENTITY,\n              CompletableFuture.supplyAsync(() -> hasAzureManagedIdentity(timeoutMs), executor));\n          futures.put(\n              Platform.IS_GCE_VM,\n              CompletableFuture.supplyAsync(() -> isGceVm(timeoutMs), executor));\n          futures.put(\n              Platform.HAS_GCP_IDENTITY,\n              CompletableFuture.supplyAsync(() -> hasGcpIdentity(timeoutMs), executor));\n\n          // Wait for all futures to complete with timeout\n          for (Map.Entry<Platform, CompletableFuture<DetectionState>> entry : futures.entrySet()) {\n            try {\n              DetectionState result = entry.getValue().get(timeoutMs, TimeUnit.MILLISECONDS);\n              platforms.put(entry.getKey(), result);\n            } catch (TimeoutException e) {\n              logger.debug(\"Platform detection timed out for: {}\", entry.getKey());\n              platforms.put(entry.getKey(), DetectionState.TIMEOUT);\n              entry.getValue().cancel(true);\n            } catch (Exception e) {\n              logger.debug(\"Platform detection failed for {}: {}\", entry.getKey(), e.getMessage());\n              platforms.put(entry.getKey(), DetectionState.NOT_DETECTED);\n              entry.getValue().cancel(true);\n            }\n          }\n\n        } finally {\n          executor.shutdown();\n          try {\n            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {\n              executor.shutdownNow();\n            }\n          } catch (InterruptedException e) {\n            executor.shutdownNow();\n            Thread.currentThread().interrupt();\n          }\n        }\n      }\n\n      List<String> detectedPlatforms = getDetectedPlatforms(platforms);\n\n      logger.debug(\"Platform detection completed. Detected platforms: {}\", detectedPlatforms);\n      return detectedPlatforms;\n\n    } catch (Exception e) {\n      logger.debug(\"Platform detection failed with exception: {}\", e.getMessage());\n      return new ArrayList<>();\n    }\n  }\n\n  private static List<String> getDetectedPlatforms(Map<Platform, DetectionState> platforms) {\n    List<String> detectedPlatforms = new ArrayList<>();\n    for (Map.Entry<Platform, DetectionState> entry : platforms.entrySet()) {\n      Platform platform = entry.getKey();\n      DetectionState state = entry.getValue();\n\n      if (state == DetectionState.DETECTED) {\n        detectedPlatforms.add(platform.getValue());\n      } else if (state == DetectionState.TIMEOUT) {\n        detectedPlatforms.add(platform.getValue() + TIMEOUT_SUFFIX);\n      }\n      // NOT_DETECTED platforms are not included in the result list\n    }\n    return detectedPlatforms;\n  }\n\n  private static boolean isTimeoutException(Exception e) {\n    if (e instanceof SocketTimeoutException) {\n      return true;\n    }\n    if (e instanceof TimeoutException) {\n      return true;\n    }\n    if (e instanceof SnowflakeSQLException) {\n      String message = e.getMessage();\n      return message != null\n          && (message.contains(\"timeout\")\n              || message.contains(\"timed out\")\n              || message.contains(\"elapsed time\")\n              || message.toLowerCase().contains(\"timeout\"));\n    }\n    // Check for nested timeout exceptions\n    Throwable cause = e.getCause();\n    if (cause instanceof SocketTimeoutException || cause instanceof TimeoutException) {\n      return true;\n    }\n    return false;\n  }\n\n  private static String executeHttpGet(String uri, Map<String, String> headers, int timeoutMs)\n      throws SnowflakeSQLException, IOException {\n    HttpGet request = new HttpGet(uri);\n\n    // Add headers in pairs (key, value)\n    for (Map.Entry<String, String> entry : headers.entrySet()) {\n      request.setHeader(entry.getKey(), entry.getValue());\n    }\n\n    return PlatformDetectionUtil.performPlatformDetectionRequest(request, timeoutMs);\n  }\n\n  private static String executeHttpPut(String uri, Map<String, String> headers, int timeoutMs)\n      throws SnowflakeSQLException, IOException {\n    HttpPut request = new HttpPut(uri);\n\n    // Add headers in pairs (key, value)\n    for (Map.Entry<String, String> entry : headers.entrySet()) {\n      request.setHeader(entry.getKey(), entry.getValue());\n    }\n\n    return PlatformDetectionUtil.performPlatformDetectionRequest(request, timeoutMs);\n  }\n\n  // Shared daemon executor for EC2 IMDS probes. Cached pool: threads live briefly\n  // during platform detection, daemon so they never block JVM shutdown, named for\n  // log/thread-dump visibility.\n  private static final ExecutorService EC2_PROBE_EXECUTOR =\n      Executors.newCachedThreadPool(\n          r -> {\n            Thread t = new Thread(r, \"snowflake-jdbc-ec2-imds-probe\");\n            t.setDaemon(true);\n            return t;\n          });\n\n  private DetectionState isEc2Instance(int timeoutMs) {\n    // Probe IPv4 and IPv6 IMDS endpoints concurrently and return as soon as either\n    // succeeds. On dual-stack hosts the IPv4 probe wins quickly; on IPv6-only EC2\n    // instances (where the IPv4 link-local address is unreachable) the IPv6 probe\n    // succeeds. We never wait longer than timeoutMs total.\n    long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);\n\n    CompletableFuture<DetectionState> ipv4Future =\n        CompletableFuture.supplyAsync(\n            () -> probeEc2Instance(awsMetadataBaseUrl, timeoutMs), EC2_PROBE_EXECUTOR);\n    CompletableFuture<DetectionState> ipv6Future =\n        CompletableFuture.supplyAsync(\n            () -> probeEc2Instance(awsMetadataIpv6BaseUrl, timeoutMs), EC2_PROBE_EXECUTOR);\n\n    try {\n      DetectionState firstResult =\n          awaitFirstDetectionOrDeadline(ipv4Future, ipv6Future, deadlineNanos);\n      if (firstResult == DetectionState.DETECTED) {\n        return DetectionState.DETECTED;\n      }\n      // First probe did not detect; wait for the other within the remaining budget.\n      DetectionState secondResult = awaitRemaining(ipv4Future, ipv6Future, deadlineNanos);\n      if (secondResult == DetectionState.DETECTED) {\n        return DetectionState.DETECTED;\n      }\n      if (firstResult == DetectionState.TIMEOUT || secondResult == DetectionState.TIMEOUT) {\n        return DetectionState.TIMEOUT;\n      }\n      return DetectionState.NOT_DETECTED;\n    } finally {\n      ipv4Future.cancel(true);\n      ipv6Future.cancel(true);\n    }\n  }\n\n  private static DetectionState awaitFirstDetectionOrDeadline(\n      CompletableFuture<DetectionState> a,\n      CompletableFuture<DetectionState> b,\n      long deadlineNanos) {\n    while (true) {\n      long remainingNanos = deadlineNanos - System.nanoTime();\n      if (remainingNanos <= 0) {\n        return DetectionState.TIMEOUT;\n      }\n      try {\n        DetectionState result =\n            (DetectionState)\n                CompletableFuture.anyOf(a, b).get(remainingNanos, TimeUnit.NANOSECONDS);\n        if (result == DetectionState.DETECTED) {\n          return result;\n        }\n        // One probe finished without detecting; return its state so the caller can\n        // wait on the other.\n        return result;\n      } catch (TimeoutException e) {\n        return DetectionState.TIMEOUT;\n      } catch (Exception e) {\n        // A probe threw; treat as NOT_DETECTED and let the caller consider the other.\n        return DetectionState.NOT_DETECTED;\n      }\n    }\n  }\n\n  private static DetectionState awaitRemaining(\n      CompletableFuture<DetectionState> a,\n      CompletableFuture<DetectionState> b,\n      long deadlineNanos) {\n    CompletableFuture<DetectionState> pending = a.isDone() ? b : a;\n    long remainingNanos = deadlineNanos - System.nanoTime();\n    if (remainingNanos <= 0) {\n      return DetectionState.TIMEOUT;\n    }\n    try {\n      return pending.get(remainingNanos, TimeUnit.NANOSECONDS);\n    } catch (TimeoutException e) {\n      return DetectionState.TIMEOUT;\n    } catch (Exception e) {\n      return DetectionState.NOT_DETECTED;\n    }\n  }\n\n  private DetectionState probeEc2Instance(String baseUrl, int timeoutMs) {\n    try {\n      // First try to get IMDSv2 token\n      String token = null;\n      try {\n        String tokenResponse =\n            executeHttpPut(\n                baseUrl + AWS_TOKEN_ENDPOINT_PATH,\n                Collections.singletonMap(\n                    AWS_METADATA_TOKEN_TTL_HEADER, AWS_METADATA_TOKEN_TTL_VALUE),\n                timeoutMs);\n        if (tokenResponse != null && !tokenResponse.trim().isEmpty()) {\n          token = tokenResponse.trim();\n          logger.debug(\"Successfully obtained IMDSv2 token from {}\", baseUrl);\n        }\n      } catch (Exception e) {\n        logger.debug(\n            \"Failed to get IMDSv2 token from {}, will try IMDSv1: {}\", baseUrl, e.getMessage());\n      }\n\n      // Try to get instance identity document\n      Map<String, String> headers = new HashMap<>();\n      if (token != null) {\n        headers.put(AWS_METADATA_TOKEN_HEADER, token);\n      }\n\n      String response =\n          executeHttpGet(baseUrl + AWS_INSTANCE_IDENTITY_ENDPOINT_PATH, headers, timeoutMs);\n      if (response != null && !response.trim().isEmpty()) {\n        logger.debug(\"Successfully detected EC2 instance via metadata service at {}\", baseUrl);\n        return DetectionState.DETECTED;\n      }\n    } catch (Exception e) {\n      logger.debug(\"EC2 instance detection failed at {}: {}\", baseUrl, e.getMessage());\n      if (isTimeoutException(e)) {\n        return DetectionState.TIMEOUT;\n      }\n    }\n    return DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isAwsLambda() {\n    return checkAllEnvironmentVariables(AWS_LAMBDA_TASK_ROOT)\n        ? DetectionState.DETECTED\n        : DetectionState.NOT_DETECTED;\n  }\n\n  private static DetectionState hasAwsIdentity(\n      AwsAttestationService attestationService, int timeoutMs) {\n    return PlatformDetectionUtil.hasValidAwsIdentityForWif(attestationService, timeoutMs)\n        ? DetectionState.DETECTED\n        : DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isAzureVm(int timeoutMs) {\n    try {\n      Map<String, String> headers =\n          Collections.singletonMap(AZURE_METADATA_HEADER, AZURE_METADATA_VALUE);\n      String response = executeHttpGet(getAzureMetadataInstanceEndpoint(), headers, timeoutMs);\n      if (response != null && !response.trim().isEmpty()) {\n        logger.debug(\"Successfully detected Azure VM via metadata service\");\n        return DetectionState.DETECTED;\n      }\n    } catch (Exception e) {\n      logger.debug(\"Azure VM detection failed: {}\", e.getMessage());\n      if (isTimeoutException(e)) {\n        return DetectionState.TIMEOUT;\n      }\n    }\n    return DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isAzureFunction() {\n    return checkAllEnvironmentVariables(\n            AZURE_FUNCTIONS_WORKER_RUNTIME,\n            AZURE_FUNCTIONS_EXTENSION_VERSION,\n            AZURE_WEBJOBS_STORAGE)\n        ? DetectionState.DETECTED\n        : DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isManagedIdentityAvailableOnAzureVm(int timeoutMs, String resource) {\n    try {\n      String endpoint = getAzureManagedIdentityEndpoint(resource);\n      Map<String, String> headers =\n          Collections.singletonMap(AZURE_METADATA_HEADER, AZURE_METADATA_VALUE);\n      String response = executeHttpGet(endpoint, headers, timeoutMs);\n      if (response != null && !response.trim().isEmpty()) {\n        logger.debug(\"Successfully detected Azure managed identity\");\n        return DetectionState.DETECTED;\n      }\n    } catch (Exception e) {\n      logger.debug(\"Azure managed identity detection failed: {}\", e.getMessage());\n      if (isTimeoutException(e)) {\n        return DetectionState.TIMEOUT;\n      }\n    }\n    return DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState hasAzureManagedIdentity(int timeoutMs) {\n    // Check environment variable first (for Azure Functions)\n    if (isAzureFunction() == DetectionState.DETECTED) {\n      if (checkAllEnvironmentVariables(AZURE_IDENTITY_HEADER)) {\n        logger.debug(\"Detected Azure managed identity via IDENTITY_HEADER environment variable\");\n        return DetectionState.DETECTED;\n      }\n    }\n\n    // Check via metadata service (for Azure VMs)\n    return isManagedIdentityAvailableOnAzureVm(timeoutMs, AZURE_MANAGEMENT_RESOURCE_URL);\n  }\n\n  private DetectionState isGceVm(int timeoutMs) {\n    try {\n      Map<String, String> headers =\n          Collections.singletonMap(GCP_METADATA_FLAVOR_HEADER, GCP_METADATA_FLAVOR_VALUE);\n      String response = executeHttpGet(getGcpMetadataBaseEndpoint(), headers, timeoutMs);\n      if (response != null) {\n        logger.debug(\"Successfully detected GCE VM via metadata service\");\n        return DetectionState.DETECTED;\n      }\n    } catch (Exception e) {\n      logger.debug(\"GCE VM detection failed: {}\", e.getMessage());\n    }\n    return DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isGcpCloudRunService() {\n    return checkAllEnvironmentVariables(GCP_K_SERVICE, GCP_K_REVISION, GCP_K_CONFIGURATION)\n        ? DetectionState.DETECTED\n        : DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isGcpCloudRunJob() {\n    return checkAllEnvironmentVariables(GCP_CLOUD_RUN_JOB, GCP_CLOUD_RUN_EXECUTION)\n        ? DetectionState.DETECTED\n        : DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState hasGcpIdentity(int timeoutMs) {\n    try {\n      Map<String, String> headers =\n          Collections.singletonMap(GCP_METADATA_FLAVOR_HEADER, GCP_METADATA_FLAVOR_VALUE);\n      String response = executeHttpGet(getGcpServiceAccountEndpoint(), headers, timeoutMs);\n      if (response != null) {\n        logger.debug(\"Successfully detected GCP identity via metadata service\");\n        return DetectionState.DETECTED;\n      }\n    } catch (Exception e) {\n      logger.debug(\"GCP identity detection failed: {}\", e.getMessage());\n      if (isTimeoutException(e)) {\n        return DetectionState.TIMEOUT;\n      }\n    }\n    return DetectionState.NOT_DETECTED;\n  }\n\n  private DetectionState isGithubAction() {\n    return checkAllEnvironmentVariables(GITHUB_ACTIONS)\n        ? DetectionState.DETECTED\n        : DetectionState.NOT_DETECTED;\n  }\n\n  private boolean checkAllEnvironmentVariables(String... variableNames) {\n    if (variableNames == null || variableNames.length == 0) {\n      return false;\n    }\n\n    for (String varName : variableNames) {\n      String value = environmentProvider.getEnv(varName);\n      if (value == null || value.trim().isEmpty()) {\n        logger.debug(\"Environment variable {} is not set or empty\", varName);\n        return false;\n      }\n    }\n\n    logger.debug(\n        \"All environment variables are present: {}\", java.util.Arrays.toString(variableNames));\n    return true;\n  }\n\n  private String getAzureMetadataInstanceEndpoint() {\n    return azureMetadataBaseUrl + AZURE_INSTANCE_ENDPOINT_PATH;\n  }\n\n  private String getAzureManagedIdentityEndpoint(String resource) {\n    return azureMetadataBaseUrl + AZURE_IDENTITY_ENDPOINT_PATH + resource;\n  }\n\n  private String getGcpMetadataBaseEndpoint() {\n    return gcpMetadataBaseUrl;\n  }\n\n  private String getGcpServiceAccountEndpoint() {\n    return gcpMetadataBaseUrl + GCP_SERVICE_ACCOUNT_ENDPOINT_PATH;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/SFPair.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport java.util.Objects;\n\npublic class SFPair<L, R> {\n  public L left;\n\n  public R right;\n\n  public static <L, R> SFPair<L, R> of(L l, R r) {\n    return new SFPair<>(l, r);\n  }\n\n  private SFPair(L left, R right) {\n    this.left = left;\n    this.right = right;\n  }\n\n  @Override\n  public boolean equals(Object other) {\n    if (other == null) {\n      return false;\n    }\n\n    if (other == this) {\n      return true;\n    }\n\n    if (!(SFPair.class.isInstance(other))) {\n      return false;\n    }\n\n    SFPair<?, ?> pair2 = (SFPair<?, ?>) other;\n    return Objects.equals(this.left, pair2.left) && Objects.equals(this.right, pair2.right);\n  }\n\n  @Override\n  public int hashCode() {\n    int result = 0;\n    if (left != null) {\n      result += 37 * left.hashCode();\n    }\n    if (right != null) {\n      result += right.hashCode();\n    }\n    return result;\n  }\n\n  @Override\n  public String toString() {\n    return \"[ \" + left + \", \" + right + \" ]\";\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/SFTimestamp.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.TimeZone;\n\npublic class SFTimestamp {\n  /**\n   * Get current time in UTC in the following format\n   *\n   * @return String representation in this format: yyyy-MM-dd HH:mm:ss\n   */\n  public static String getUTCNow() {\n    SimpleDateFormat dateFormatGmt = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    dateFormatGmt.setTimeZone(TimeZone.getTimeZone(\"GMT\"));\n\n    // Time in GMT\n    return dateFormatGmt.format(new Date());\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/SecretDetector.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.fasterxml.jackson.databind.node.TextNode;\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport net.minidev.json.JSONArray;\nimport net.minidev.json.JSONObject;\nimport net.minidev.json.JSONStyle;\n\n/** Search for credentials in sql and/or other text */\npublic class SecretDetector {\n  // \"\\\\s*\" refers to >= 0 spaces, \"[^']\" refers to chars other than `'`\n  private static final Pattern AWS_KEY_PATTERN =\n      Pattern.compile(\n          \"(aws_key_id|aws_secret_key|access_key_id|secret_access_key)(\\\\s*=\\\\s*)'([^']+)'\",\n          Pattern.CASE_INSENSITIVE);\n\n  // Used for detecting tokens in serialized JSON\n  private static final Pattern AWS_TOKEN_PATTERN =\n      Pattern.compile(\n          \"(accessToken|tempToken|keySecret)\\\"\\\\s*:\\\\s*\\\"([a-z0-9/+]{32,}={0,2})\\\"\",\n          Pattern.CASE_INSENSITIVE);\n\n  // Used for detecting OAuth tokens in serialized JSON\n  private static final Pattern OAUTH_JSON_PATTERN =\n      Pattern.compile(\n          \"(access_token|refresh_token)\\\"\"\n              + \"\\\\s*:\\\\s*\"\n              + \"\\\"([a-zA-Z0-9!\\\"#$%&'\\\\()*+,-./:;<=>?@\\\\[\\\\]^_`\\\\{|\\\\}~]{3,})\\\"\",\n          Pattern.CASE_INSENSITIVE);\n\n  // Signature added in the query string of a URL in SAS based authentication\n  // for S3 or Azure Storage requests\n  private static final Pattern SAS_TOKEN_PATTERN =\n      Pattern.compile(\n          \"(sig|signature|AWSAccessKeyId|password|passcode)=([a-z0-9%/+]{16,})\",\n          Pattern.CASE_INSENSITIVE);\n\n  // Search for password pattern\n  private static final Pattern PASSWORD_PATTERN =\n      Pattern.compile(\n          \"(password|passcode|pwd)\"\n              + \"([\\'\\\"\\\\s:=]+)\"\n              + \"([a-z0-9!\\\"#$%&'\\\\()*+,-./:;<=>?@\\\\[\\\\]^_`\\\\{|\\\\}~]{6,})\",\n          Pattern.CASE_INSENSITIVE);\n\n  // \"-----BEGIN PRIVATE KEY-----  // #pragma: allowlist secret\n  // [PRIVATE-KEY]\n  // -----END PRIVATE KEY-----\"\n  private static final Pattern PRIVATE_KEY_PATTERN =\n      Pattern.compile(\n          \"-----BEGIN PRIVATE KEY-----\" // #pragma: allowlist secret\n              + \"\\\\\\\\n([a-z0-9/+=\\\\\\\\n]{32,})\\\\\\\\n\"\n              + \"-----END PRIVATE KEY-----\",\n          Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);\n\n  // \"privateKeyData\": \"[PRIVATE-KEY]\"\n  private static final Pattern PRIVATE_KEY_DATA_PATTERN =\n      Pattern.compile(\n          \"\\\"privateKeyData\\\": \\\"([a-z0-9/+=\\\\\\\\n]{10,})\\\"\",\n          Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);\n\n  private static final Pattern CONNECTION_TOKEN_PATTERN =\n      Pattern.compile(\n          \"(token|assertion content)\" + \"(['\\\"\\\\s:=]+)\" + \"([a-z0-9=/_\\\\-+]{8,})\",\n          Pattern.CASE_INSENSITIVE);\n\n  private static final Pattern ENCRYPTION_MATERIAL_PATTERN =\n      Pattern.compile(\"\\\"encryptionMaterial\\\"\\\\s*:\\\\s*\\\\{.*?\\\\}\", Pattern.CASE_INSENSITIVE);\n\n  // only attempt to find secrets in its leading 100Kb SNOW-30961\n  private static final int MAX_LENGTH = 100 * 1000;\n\n  private static String[] SENSITIVE_NAMES = {\n    \"access_key_id\",\n    \"accesstoken\",\n    \"aws_key_id\",\n    \"aws_secret_key\",\n    \"awsaccesskeyid\",\n    \"keysecret\",\n    \"passcode\",\n    \"password\",\n    \"privatekey\",\n    \"privatekeydata\",\n    \"secret_access_key\",\n    \"sig\",\n    \"signature\",\n    \"temptoken\",\n    \"oauthClientId\",\n    \"oauthClientSecret\",\n    \"accessToken\",\n    \"refreshToken\"\n  };\n\n  private static Set<String> SENSITIVE_NAME_SET = new HashSet<>(Arrays.asList(SENSITIVE_NAMES));\n\n  /**\n   * Check whether the name is sensitive\n   *\n   * @param name the name\n   * @return true if the name is sensitive.\n   */\n  public static boolean isSensitive(String name) {\n    return SENSITIVE_NAME_SET.contains(name.toLowerCase());\n  }\n\n  /**\n   * Determine whether a connection parameter should be masked if parameter values are being\n   * printed. Sensitive parameters include passwords and token values. Helper function for\n   * maskParameterValue().\n   *\n   * @param name of the parameter\n   * @return true if the parameter should be masked\n   */\n  private static boolean isSensitiveParameter(String name) {\n    Pattern PASSWORD_IN_NAME =\n        Pattern.compile(\n            \".*?(password|pwd|token|proxyuser|privatekey|passcode|proxypassword|private_key_base|oauthClientSecret|oauthClientId).*?\",\n            Pattern.CASE_INSENSITIVE);\n    Matcher matcher = PASSWORD_IN_NAME.matcher(name);\n    return isSensitive(name) || matcher.matches();\n  }\n\n  /**\n   * Mask sensitive parameter values. Used currently for connection parameters whose values are to\n   * be recorded for each session.\n   *\n   * @param key parameter key\n   * @param value parameter value, which is sometimes masked\n   * @return the original value if the parameter key does not mark it as sensitive, or return a\n   *     masked text if the key is determined to be sensitive.\n   */\n  public static String maskParameterValue(String key, String value) {\n    if (isSensitiveParameter(key)) {\n      return \"****\";\n    }\n    return value;\n  }\n\n  private static String filterAWSKeys(String text) {\n    if (text == null) {\n      return null;\n    }\n    Matcher matcher =\n        AWS_KEY_PATTERN.matcher(text.length() <= MAX_LENGTH ? text : text.substring(0, MAX_LENGTH));\n\n    if (matcher.find()) {\n      return matcher.replaceAll(\"$1$2'****'\");\n    }\n    return text;\n  }\n\n  private static String filterSASTokens(String text) {\n    if (text == null) {\n      return null;\n    }\n    Matcher matcher =\n        SAS_TOKEN_PATTERN.matcher(\n            text.length() <= MAX_LENGTH ? text : text.substring(0, MAX_LENGTH));\n\n    if (matcher.find()) {\n      return matcher.replaceAll(\"$1=****\");\n    }\n    return text;\n  }\n\n  private static String filterPassword(String text) {\n    if (text == null) {\n      return null;\n    }\n    Matcher matcher =\n        PASSWORD_PATTERN.matcher(\n            text.length() <= MAX_LENGTH ? text : text.substring(0, MAX_LENGTH));\n\n    if (matcher.find()) {\n      return matcher.replaceAll(\"$1$2**** \");\n    }\n    return text;\n  }\n\n  private static String filterConnectionTokens(String text) {\n    if (text == null) {\n      return null;\n    }\n    Matcher matcher =\n        CONNECTION_TOKEN_PATTERN.matcher(\n            text.length() <= MAX_LENGTH ? text : text.substring(0, MAX_LENGTH));\n\n    if (matcher.find()) {\n      return matcher.replaceAll(\"$1$2****\");\n    }\n    return text;\n  }\n\n  /**\n   * mask AWS secret in the input string\n   *\n   * @param sql The sql text to mask\n   * @return masked string\n   */\n  public static String maskAWSSecret(String sql) {\n    return filterAWSKeys(sql);\n  }\n\n  /**\n   * Masks SAS token(s) in the input string\n   *\n   * @param text Text which may contain SAS token(s)\n   * @return Masked string\n   */\n  public static String maskSASToken(String text) {\n    return filterSASTokens(text);\n  }\n\n  /**\n   * Masks any secrets present in the input string. This currently checks for SAS tokens ({@link\n   * SecretDetector#maskSASToken(String)}) and AWS keys ({@link\n   * SecretDetector#maskAWSSecret(String)}.\n   *\n   * @param text Text which may contain secrets\n   * @return Masked string\n   */\n  public static String maskSecrets(String text) {\n    if (text == null) {\n      return null;\n    }\n    return filterAccessTokens(\n        filterConnectionTokens(\n            filterPassword(\n                filterSASTokens(\n                    filterAWSKeys(filterOAuthTokens(filterEncryptionMaterial(text)))))));\n  }\n\n  /**\n   * Masks any secrets present in the OAuth token request JSON response.\n   *\n   * @param text Text which may contain secrets\n   * @return Masked string\n   */\n  public static String filterOAuthTokens(String text) {\n    if (text == null) {\n      return null;\n    }\n    Matcher matcher =\n        OAUTH_JSON_PATTERN.matcher(\n            text.length() <= MAX_LENGTH ? text : text.substring(0, MAX_LENGTH));\n\n    if (matcher.find()) {\n      return matcher.replaceAll(\"$1\\\":\\\"****\\\"\");\n    }\n    return text;\n  }\n\n  /**\n   * Filter access tokens that might be buried in JSON. Currently only used to filter the\n   * scopedCreds passed for XP binary downloads\n   *\n   * @param message the message text which may contain secrets\n   * @return Return filtered message\n   */\n  public static String filterAccessTokens(String message) {\n    Matcher awsMatcher = AWS_TOKEN_PATTERN.matcher(message);\n\n    // aws\n    if (awsMatcher.find()) {\n      message = awsMatcher.replaceAll(\"$1\\\":\\\"XXXX\\\"\");\n    }\n\n    // azure\n    Matcher azureMatcher = SAS_TOKEN_PATTERN.matcher(message);\n\n    if (azureMatcher.find()) {\n      message = azureMatcher.replaceAll(\"$1=XXXX\");\n    }\n\n    // GCS\n    Matcher gcsMatcher = PRIVATE_KEY_PATTERN.matcher(message);\n    if (gcsMatcher.find()) {\n      message =\n          gcsMatcher.replaceAll(\n              \"-----BEGIN PRIVATE KEY-----\" // #pragma: allowlist secret\n                  + \"\\\\\\\\nXXXX\\\\\\\\n\"\n                  + \"-----END PRIVATE KEY-----\");\n    }\n\n    gcsMatcher = PRIVATE_KEY_DATA_PATTERN.matcher(message);\n    if (gcsMatcher.find()) {\n      message = gcsMatcher.replaceAll(\"\\\"privateKeyData\\\": \\\"XXXX\\\"\");\n    }\n\n    return message;\n  }\n\n  /**\n   * Filter encryption material that may be buried inside a JSON string.\n   *\n   * @param message the message text which may contain encryption material\n   * @return Return filtered message\n   */\n  public static String filterEncryptionMaterial(String message) {\n    if (message == null) {\n      return null;\n    }\n    Matcher matcher =\n        ENCRYPTION_MATERIAL_PATTERN.matcher(\n            message.length() <= MAX_LENGTH ? message : message.substring(0, MAX_LENGTH));\n\n    if (matcher.find()) {\n      return matcher.replaceAll(\"\\\"encryptionMaterial\\\" : ****\");\n    }\n    return message;\n  }\n\n  public static JSONObject maskJsonObject(JSONObject json) {\n    for (Map.Entry<String, Object> entry : json.entrySet()) {\n      if (entry.getValue() instanceof String) {\n        entry.setValue(maskSecrets((String) entry.getValue()));\n      } else if (entry.getValue() instanceof JSONArray) {\n        maskJsonArray((JSONArray) entry.getValue());\n      } else if (entry.getValue() instanceof JSONObject) {\n        maskJsonObject((JSONObject) entry.getValue());\n      }\n    }\n    return json;\n  }\n\n  public static JSONArray maskJsonArray(JSONArray array) {\n    for (int i = 0; i < array.size(); i++) {\n      Object node = array.get(i);\n      if (node instanceof JSONObject) {\n        maskJsonObject((JSONObject) node);\n      } else if (node instanceof JSONArray) {\n        maskJsonArray((JSONArray) node);\n      } else if (node instanceof String) {\n        array.set(i, SecretDetector.maskSecrets((String) node));\n      }\n      // for other types, we can just leave it untouched\n    }\n\n    return array;\n  }\n\n  public static JsonNode maskJacksonNode(JsonNode node) {\n    if (node.isTextual()) {\n      String maskedText = SecretDetector.maskSecrets(node.textValue());\n      if (!maskedText.equals(node.textValue())) {\n        return new TextNode(maskedText);\n      }\n    } else if (node.isObject()) {\n      ObjectNode objNode = (ObjectNode) node;\n      Iterator<String> fieldNames = objNode.fieldNames();\n\n      while (fieldNames.hasNext()) {\n        String fieldName = fieldNames.next();\n        JsonNode tmpNode = maskJacksonNode(objNode.get(fieldName));\n        if (objNode.get(fieldName).isTextual()) {\n          objNode.set(fieldName, tmpNode);\n        }\n      }\n    } else if (node.isArray()) {\n      ArrayNode arrayNode = (ArrayNode) node;\n      for (int i = 0; i < arrayNode.size(); i++) {\n        JsonNode tmpNode = maskJacksonNode(arrayNode.get(i));\n        if (arrayNode.get(i).isTextual()) {\n          arrayNode.set(i, tmpNode);\n        }\n      }\n    }\n    return node;\n  }\n\n  // This class aims to parse minidev.json's node better\n  public static class SecretDetectorJSONStyle extends JSONStyle {\n    public SecretDetectorJSONStyle() {\n      super();\n    }\n\n    public void objectNext(Appendable out) throws IOException {\n      out.append(\", \");\n    }\n\n    public void arrayStop(Appendable out) throws IOException {\n      out.append(\"] \");\n    }\n\n    public void arrayNextElm(Appendable out) throws IOException {\n      out.append(\", \");\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/SnowflakeEnvironmentProvider.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\n/**\n * Implementation of EnvironmentProvider that delegates to SnowflakeUtil. This wrapper enables\n * thread-safe testing while maintaining existing behavior.\n */\npublic class SnowflakeEnvironmentProvider implements EnvironmentProvider {\n\n  @Override\n  public String getEnv(String name) {\n    return SnowflakeUtil.systemGetEnv(name);\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/Stopwatch.java",
    "content": "package net.snowflake.client.internal.util;\n\n/** Stopwatch class used to calculate the time between start and stop. */\npublic class Stopwatch {\n  private boolean isStarted = false;\n  private long startTime;\n  private long elapsedTime;\n\n  /**\n   * Starts the Stopwatch.\n   *\n   * @throws IllegalStateException when Stopwatch is already running.\n   */\n  public void start() {\n    if (isStarted) {\n      throw new IllegalStateException(\"Stopwatch is already running\");\n    }\n\n    isStarted = true;\n    startTime = System.nanoTime();\n  }\n\n  /**\n   * Stops the Stopwatch.\n   *\n   * @throws IllegalStateException when Stopwatch was not yet started or is already stopped.\n   */\n  public void stop() {\n    if (!isStarted) {\n      if (startTime == 0) {\n        throw new IllegalStateException(\"Stopwatch has not been started\");\n      }\n      throw new IllegalStateException(\"Stopwatch is already stopped\");\n    }\n\n    isStarted = false;\n    elapsedTime = System.nanoTime() - startTime;\n  }\n\n  /** Resets the instance to it's initial state. */\n  public void reset() {\n    isStarted = false;\n    startTime = 0;\n    elapsedTime = 0;\n  }\n\n  /** Restarts the instance. */\n  public void restart() {\n    isStarted = true;\n    startTime = System.nanoTime();\n    elapsedTime = 0;\n  }\n\n  /**\n   * Get the elapsed time (in ms) between the stopTime and startTime.\n   *\n   * @return elapsed milliseconds between stopTime and startTime\n   * @throws IllegalStateException when Stopwatch has not been started yet\n   */\n  public long elapsedMillis() {\n    return elapsedNanos() / 1_000_000;\n  }\n\n  /**\n   * Get the elapsed time (in nanoseconds) between the stopTime and startTime.\n   *\n   * @return elapsed nanoseconds between stopTime and startTime\n   * @throws IllegalStateException when Stopwatch has not been started yet\n   */\n  public long elapsedNanos() {\n    if (isStarted) {\n      return (System.nanoTime() - startTime);\n    }\n    if (startTime == 0) {\n      throw new IllegalStateException(\"Stopwatch has not been started\");\n    }\n    return elapsedTime;\n  }\n\n  /**\n   * Get the instance status.\n   *\n   * @return true if the stopwatch is running, false otherwise\n   */\n  public boolean isStarted() {\n    return isStarted;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/ThrowingBiCallable.java",
    "content": "package net.snowflake.client.internal.util;\n\n@FunctionalInterface\npublic interface ThrowingBiCallable<A, B, T extends Throwable> {\n  void apply(A a, B b) throws T;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/ThrowingBiFunction.java",
    "content": "package net.snowflake.client.internal.util;\n\n@FunctionalInterface\npublic interface ThrowingBiFunction<A, B, R, T extends Throwable> {\n  R apply(A a, B b) throws T;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/ThrowingCallable.java",
    "content": "package net.snowflake.client.internal.util;\n\n@FunctionalInterface\npublic interface ThrowingCallable<A, T extends Throwable> {\n  A call() throws T;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/ThrowingFunction.java",
    "content": "package net.snowflake.client.internal.util;\n\n@FunctionalInterface\npublic interface ThrowingFunction<A, R, T extends Throwable> {\n  R apply(A a) throws T;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/ThrowingTriCallable.java",
    "content": "package net.snowflake.client.internal.util;\n\n@FunctionalInterface\npublic interface ThrowingTriCallable<A, B, C, T extends Throwable> {\n  void apply(A a, B b, C c) throws T;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/ThrowingTriFunction.java",
    "content": "package net.snowflake.client.internal.util;\n\n@FunctionalInterface\npublic interface ThrowingTriFunction<A, B, C, R, T extends Throwable> {\n  R apply(A a, B b, C c) throws T;\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/TimeMeasurement.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\n\n/** Class keeping the start and stop time in epoch microseconds. */\npublic class TimeMeasurement {\n  private long start;\n  private long end;\n\n  /**\n   * Get the start time as epoch time in microseconds.\n   *\n   * @return the start time as epoch time in microseconds.\n   */\n  public long getStart() {\n    return start;\n  }\n\n  /** Set the start time as current epoch time in microseconds. */\n  public void setStart() {\n    this.start = SnowflakeUtil.getEpochTimeInMicroSeconds();\n  }\n\n  /**\n   * Get the stop time as epoch time in microseconds.\n   *\n   * @return the stop time as epoch time in microseconds.\n   */\n  public long getEnd() {\n    return end;\n  }\n\n  /** Set the stop time as current epoch time in microseconds. */\n  public void setEnd() {\n    this.end = SnowflakeUtil.getEpochTimeInMicroSeconds();\n  }\n\n  /**\n   * Get the microseconds between the stop and start time.\n   *\n   * @return difference between stop and start in microseconds. If one of the variables is not\n   *     initialized, it returns -1\n   */\n  public long getTime() {\n    if (start == 0 || end == 0) {\n      return -1;\n    }\n\n    return end - start;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/internal/util/VariableTypeArray.java",
    "content": "package net.snowflake.client.internal.util;\n\npublic class VariableTypeArray {\n  public int[] intArr;\n  public long[] longArr;\n\n  public VariableTypeArray(int[] arr1, long[] arr2) {\n    intArr = arr1;\n    longArr = arr2;\n  }\n}\n"
  },
  {
    "path": "src/main/java/net/snowflake/client/jdbc/SnowflakeDriver.java",
    "content": "package net.snowflake.client.jdbc;\n\nimport java.sql.Driver;\n\n/**\n * This is left in to ensure backward compatibility for old customers that are still using the\n * legacy net.snowflake.client.jdbc.SnowflakeDriver. Ideally, we want to remove this class and have\n * all customers move to net.snowflake.client.api.driver.SnowflakeDriver.\n *\n * @deprecated Use {@link net.snowflake.client.api.driver.SnowflakeDriver} instead\n */\n@Deprecated\npublic class SnowflakeDriver extends net.snowflake.client.api.driver.SnowflakeDriver\n    implements Driver {}\n"
  },
  {
    "path": "src/main/java-fat-jar/net/snowflake/client/internal/log/SFBridgeLogger.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport org.slf4j.helpers.MarkerIgnoringBase;\n\n/**\n * SLF4J {@link org.slf4j.Logger} implementation that bridges to the Snowflake JDBC driver's {@link\n * SFLogger} abstraction.\n *\n * <p>This class is used by the shaded SLF4J inside the fat jar, so that internal dependency logging\n * (AWS SDK v2, Azure SDK, Google Cloud SDK) flows through {@link SFLoggerFactory} and ultimately to\n * JUL (default) or the user's SLF4J binding.\n *\n * <p>Extends {@link MarkerIgnoringBase} to handle all Marker-accepting overloads by delegating to\n * the non-Marker versions.\n */\npublic class SFBridgeLogger extends MarkerIgnoringBase {\n\n  private static final long serialVersionUID = 1L;\n\n  private final transient SFLogger delegate;\n\n  SFBridgeLogger(String name) {\n    this.name = name;\n    this.delegate = SFLoggerFactory.getLogger(name);\n  }\n\n  // --- Trace ---\n\n  @Override\n  public boolean isTraceEnabled() {\n    return delegate.isTraceEnabled();\n  }\n\n  @Override\n  public void trace(String msg) {\n    if (delegate.isTraceEnabled()) {\n      delegate.trace(msg, false);\n    }\n  }\n\n  @Override\n  public void trace(String format, Object arg) {\n    if (delegate.isTraceEnabled()) {\n      delegate.trace(format, arg);\n    }\n  }\n\n  @Override\n  public void trace(String format, Object arg1, Object arg2) {\n    if (delegate.isTraceEnabled()) {\n      delegate.trace(format, arg1, arg2);\n    }\n  }\n\n  @Override\n  public void trace(String format, Object... arguments) {\n    if (delegate.isTraceEnabled()) {\n      delegate.trace(format, arguments);\n    }\n  }\n\n  @Override\n  public void trace(String msg, Throwable t) {\n    if (delegate.isTraceEnabled()) {\n      delegate.trace(msg, t);\n    }\n  }\n\n  // --- Debug ---\n\n  @Override\n  public boolean isDebugEnabled() {\n    return delegate.isDebugEnabled();\n  }\n\n  @Override\n  public void debug(String msg) {\n    if (delegate.isDebugEnabled()) {\n      delegate.debug(msg, false);\n    }\n  }\n\n  @Override\n  public void debug(String format, Object arg) {\n    if (delegate.isDebugEnabled()) {\n      delegate.debug(format, arg);\n    }\n  }\n\n  @Override\n  public void debug(String format, Object arg1, Object arg2) {\n    if (delegate.isDebugEnabled()) {\n      delegate.debug(format, arg1, arg2);\n    }\n  }\n\n  @Override\n  public void debug(String format, Object... arguments) {\n    if (delegate.isDebugEnabled()) {\n      delegate.debug(format, arguments);\n    }\n  }\n\n  @Override\n  public void debug(String msg, Throwable t) {\n    if (delegate.isDebugEnabled()) {\n      delegate.debug(msg, t);\n    }\n  }\n\n  // --- Info ---\n\n  @Override\n  public boolean isInfoEnabled() {\n    return delegate.isInfoEnabled();\n  }\n\n  @Override\n  public void info(String msg) {\n    if (delegate.isInfoEnabled()) {\n      delegate.info(msg, false);\n    }\n  }\n\n  @Override\n  public void info(String format, Object arg) {\n    if (delegate.isInfoEnabled()) {\n      delegate.info(format, arg);\n    }\n  }\n\n  @Override\n  public void info(String format, Object arg1, Object arg2) {\n    if (delegate.isInfoEnabled()) {\n      delegate.info(format, arg1, arg2);\n    }\n  }\n\n  @Override\n  public void info(String format, Object... arguments) {\n    if (delegate.isInfoEnabled()) {\n      delegate.info(format, arguments);\n    }\n  }\n\n  @Override\n  public void info(String msg, Throwable t) {\n    if (delegate.isInfoEnabled()) {\n      delegate.info(msg, t);\n    }\n  }\n\n  // --- Warn ---\n\n  @Override\n  public boolean isWarnEnabled() {\n    return delegate.isWarnEnabled();\n  }\n\n  @Override\n  public void warn(String msg) {\n    if (delegate.isWarnEnabled()) {\n      delegate.warn(msg, false);\n    }\n  }\n\n  @Override\n  public void warn(String format, Object arg) {\n    if (delegate.isWarnEnabled()) {\n      delegate.warn(format, arg);\n    }\n  }\n\n  @Override\n  public void warn(String format, Object arg1, Object arg2) {\n    if (delegate.isWarnEnabled()) {\n      delegate.warn(format, arg1, arg2);\n    }\n  }\n\n  @Override\n  public void warn(String format, Object... arguments) {\n    if (delegate.isWarnEnabled()) {\n      delegate.warn(format, arguments);\n    }\n  }\n\n  @Override\n  public void warn(String msg, Throwable t) {\n    if (delegate.isWarnEnabled()) {\n      delegate.warn(msg, t);\n    }\n  }\n\n  // --- Error ---\n\n  @Override\n  public boolean isErrorEnabled() {\n    return delegate.isErrorEnabled();\n  }\n\n  @Override\n  public void error(String msg) {\n    if (delegate.isErrorEnabled()) {\n      delegate.error(msg, false);\n    }\n  }\n\n  @Override\n  public void error(String format, Object arg) {\n    if (delegate.isErrorEnabled()) {\n      delegate.error(format, arg);\n    }\n  }\n\n  @Override\n  public void error(String format, Object arg1, Object arg2) {\n    if (delegate.isErrorEnabled()) {\n      delegate.error(format, arg1, arg2);\n    }\n  }\n\n  @Override\n  public void error(String format, Object... arguments) {\n    if (delegate.isErrorEnabled()) {\n      delegate.error(format, arguments);\n    }\n  }\n\n  @Override\n  public void error(String msg, Throwable t) {\n    if (delegate.isErrorEnabled()) {\n      delegate.error(msg, t);\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java-fat-jar/net/snowflake/client/internal/log/SFBridgeLoggerFactory.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport org.slf4j.ILoggerFactory;\nimport org.slf4j.Logger;\n\n/**\n * Logger factory for the shaded SLF4J bridge. Returns {@link SFBridgeLogger} instances that\n * delegate to {@link SFLoggerFactory}.\n */\npublic class SFBridgeLoggerFactory implements ILoggerFactory {\n\n  private final ConcurrentMap<String, SFBridgeLogger> loggerMap = new ConcurrentHashMap<>();\n\n  @Override\n  public Logger getLogger(String name) {\n    return loggerMap.computeIfAbsent(name, SFBridgeLogger::new);\n  }\n}\n"
  },
  {
    "path": "src/main/java-fat-jar/net/snowflake/client/internal/log/SFBridgeServiceProvider.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport net.snowflake.client.internal.driver.DriverVersionProperties;\nimport org.slf4j.ILoggerFactory;\nimport org.slf4j.IMarkerFactory;\nimport org.slf4j.helpers.BasicMarkerFactory;\nimport org.slf4j.helpers.NOPMDCAdapter;\nimport org.slf4j.spi.MDCAdapter;\nimport org.slf4j.spi.SLF4JServiceProvider;\n\n/**\n * SLF4J service provider that bridges the shaded SLF4J (used internally by AWS SDK v2, Azure SDK,\n * Google Cloud SDK) to the Snowflake JDBC driver's own logging abstraction ({@link\n * SFLoggerFactory}).\n *\n * <p>This class is discovered by the shaded SLF4J's {@link java.util.ServiceLoader} mechanism. The\n * shade plugin rewrites the SPI registration file and all {@code org.slf4j} references in this\n * class to the shaded namespace, so it integrates with the shaded SLF4J — not the user's real\n * SLF4J.\n *\n * <p>This allows internal dependency logging to flow through the driver's {@link SFLoggerFactory},\n * which routes to either JUL (default) or the user's SLF4J binding (when opted in).\n */\npublic class SFBridgeServiceProvider implements SLF4JServiceProvider {\n\n  private static final String REQUESTED_API_VERSION =\n      DriverVersionProperties.get(\"slf4j.version\");\n\n  private ILoggerFactory loggerFactory;\n  private IMarkerFactory markerFactory;\n  private MDCAdapter mdcAdapter;\n\n  @Override\n  public ILoggerFactory getLoggerFactory() {\n    return loggerFactory;\n  }\n\n  @Override\n  public IMarkerFactory getMarkerFactory() {\n    return markerFactory;\n  }\n\n  @Override\n  public MDCAdapter getMDCAdapter() {\n    return mdcAdapter;\n  }\n\n  @Override\n  public String getRequestedApiVersion() {\n    return REQUESTED_API_VERSION;\n  }\n\n  @Override\n  public void initialize() {\n    loggerFactory = new SFBridgeLoggerFactory();\n    markerFactory = new BasicMarkerFactory();\n    mdcAdapter = new NOPMDCAdapter();\n  }\n}\n"
  },
  {
    "path": "src/main/javadoc/licenses.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Open Source License Disclosure - Snowflake JDBC</title>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"stylesheet.css\" title=\"Style\">\n  </head>\n  <body>\n  <div class=\"contentContainer\">\n    <hr>\n    <h1> Open Source License Disclosure </h1>\n    <p> The following notices are required by licensors of software used in the Snowflake JDBC. </p>\n    <h2> Table Of Contents </h2>\n    <p class=\"indexContainer\"><a href=\"#all_licenses\">All Licenses</a></p>\n    <p class=\"indexContainer\"><a href=\"#common_licenses\">Common Licenses</a></p>\n    <br>\n\n    <a id=\"all_licenses\"></a>\n    <h2> All Licenses </h2>\n    <ol>\n      <!-- template\n      <li>???\n        <p><a href=\"?\" target=\"_blank\"> ? </a> <br>\n          Copyright ???(year) <br>\n          This project is licensed under ????, which is included below.\n      </li>\n      -->\n      <li>AWS SDK for Java\n        <p><a href=\"https://github.com/aws/aws-sdk-java\" target=\"_blank\"> https://github.com/aws/aws-sdk-java </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li> FasterXML jackson-core\n        <p><a href=\"https://github.com/FasterXML/jackson-core\", target=\"_blank\"> https://github.com/FasterXML/jackson-core </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li> FasterXML jackson-databind\n        <p><a href=\"https://github.com/FasterXML/jackson-databind\", target=\"_blank\"> https://github.com/FasterXML/jackson-databind </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p> \n      </li>\n      <li>Google APIs Client Library for Java\n        <p><a href=\"https://github.com/googleapis/google-api-java-client\" target=\"_blank\"> https://github.com/googleapis/google-api-java-client </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Google Auth Library\n        <p><a href=\"https://github.com/googleapis/google-auth-library-java\" target=\"_blank\"> https://github.com/googleapis/google-auth-library-java </a> <br>\n          Copyright 2014, Google Inc. All rights reserved. <br>\n          This project is licensed under BSD New license, which is included below.\n        </p>\n      </li>\n      <li>Google Cloud Storage Client for Java\n        <p><a href=\"https://github.com/googleapis/java-storage\" target=\"_blank\"> https://github.com/googleapis/java-storage </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Guava: Google Core Libraries for Java\n        <p><a href=\"https://github.com/google/guava\" target=\"_blank\"> https://github.com/google/guava </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Google HTTP Client Library for Java\n        <p><a href=\"https://github.com/googleapis/google-http-java-client\" target=\"_blank\"> https://github.com/googleapis/google-http-java-client </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Microsoft Azure Storage SDK for Java\n        <p><a href=\"https://github.com/Azure/azure-storage-java\" target=\"_blank\"> https://github.com/Azure/azure-storage-java </a> <br>\n          Copyright Microsoft Corporation <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Nimbus JOSE+JWT\n        <p><a href=\"https://bitbucket.org/connect2id/nimbus-jose-jwt/wiki/Home\" target=\"_blank\"> https://bitbucket.org/connect2id/nimbus-jose-jwt/wiki/Home </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Apache Commons IO\n        <p><a href=\"https://github.com/apache/commons-io\" target=\"_blank\"> https://github.com/apache/commons-io </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Java Servlet\n        <p><a href=\"https://javaee.github.io/servlet-spec/\" target=\"_blank\"> https://javaee.github.io/servlet-spec/ </a> <br>\n          Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved. <br>\n          Copyright 2004 The Apache Software Foundation <br>\n\n          This project is licensed under CDDL + GPLv2 with classpath exception, which are included below.\n        </p>\n      </li>\n      <li>Apache Arrow\n        <p><a href=\"https://github.com/apache/arrow\" target=\"_blank\"> https://github.com/apache/arrow </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Apache HttpComponents Client\n        <p><a href=\"https://github.com/apache/httpcomponents-client\" target=\"_blank\"> https://github.com/apache/httpcomponents-client </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Apache Tika\n        <p><a href=\"https://github.com/apache/tika\" target=\"_blank\"> https://github.com/apache/tika </a> <br>\n          This project is licensed under Apache License 2.0, which is included below.\n        </p>\n      </li>\n      <li>Bouncy Castle Crypto\n        <p><a href=\"https://www.bouncycastle.org/java.html\" target=\"_blank\"> https://www.bouncycastle.org/java.html </a> <br>\n          Copyright (c) 2000 - 2020 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) <br><br>\n\n          Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:<br><br>\n\n          The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.<br><br>\n\n          THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n        </p>\n      </li>\n      <li>jsoup: Java HTML Parser\n        <p><a href=\"https://github.com/jhy/jsoup/\" target=\"_blank\"> https://github.com/jhy/jsoup/ </a> <br>\n          Copyright (c) 2009-2020 Jonathan Hedley <https://jsoup.org/> <br>\n          This project is licensed under MIT License, which is included below.\n      </li>      \n      <li>Java Native Access (JNA)\n        <p><a href=\"https://github.com/java-native-access/jna\" target=\"_blank\"> https://github.com/java-native-access/jna </a> <br>\n          This project (JNA) is licensed under Apache License 2.0. (starting with JNA version 4.0.0), which is included below.\n        </p>\n      </li>\n    </ol>\n\n    <a id=\"common_licenses\"></a>\n    <h2> Common Licenses </h2>\n\n    <a id=\"apache_license\"></a>\n    <h3> Apache License </h3>\n    <p>Version 2.0, January 2004<br>http://www.apache.org/licenses/</p>\n    <div class=\"contentContainer\" id=\"apache_license_div\">\n      <div id=\"main_details_of_Apache_License\">\n        <dl>\n          <dt class=\"serializedFormContainer\"><b>TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION</b></dt>\n        </dl>\n        <ol start=\"1\">\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Definitions </dt>\n              <dd class=\"serializedFormContainer\"> &quot;License&quot; shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Licensor&quot; shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Legal Entity&quot; shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, &quot;control&quot; means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;You&quot; (or &quot;Your&quot;) shall mean an individual or Legal Entity exercising permissions granted by this License. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Source&quot; form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Object&quot; form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Work&quot; shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Derivative Works&quot; shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Contribution&quot; shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, &quot;submitted&quot; means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as &quot;Not a Contribution.&quot; </dd>\n              <dd class=\"serializedFormContainer\"> &quot;Contributor&quot; shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. </dd>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. </dt>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. </dt>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: </dt>\n              <dd class=\"serializedFormContainer\"> (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and </dd>\n              <dd class=\"serializedFormContainer\"> (b) You must cause any modified files to carry prominent notices stating that You changed the files; and </dd>\n              <dd class=\"serializedFormContainer\"> (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and </dd>\n              <dd class=\"serializedFormContainer\"> (d) If the Work includes a &quot;NOTICE&quot; text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. </dd>\n              <dt class=\"serializedFormContainer\"> You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. </dt>\n            </dl>\n          </li>\n          <li>      \n            <dl>\n              <dt class=\"serializedFormContainer\"> Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. </dt>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. </dt>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. </dt>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. </dt>\n            </dl>\n          </li>\n          <li>\n            <dl>\n              <dt class=\"serializedFormContainer\"> Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. </dt>\n            </dl>\n          </li>\n        </ol>\n        <dl>\n          <dt class=\"serializedFormContainer\"><b> END OF TERMS AND CONDITIONS </b></dt>\n        </dl>\n      </div>\n    </div>\n\n    <a id=\"new_BSD_license\"></a>\n    <h3> THE 3-CLAUSE BSD LICENSE </h3>\n    <div class=\"contentContainer\" id=\"new_BSD_license_div\">\n      <p> Note: This license has also been called the \"New BSD License\" or \"Modified BSD License\". See also the <a href=\"https://opensource.org/licenses/BSD-2-Clause\">2-clause BSD License</a>. </p>\n      <p>Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:</p>\n      <ol>\n        <li>Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.</li>\n        <li>Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.</li>\n        <li>Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.</li>\n      </ol>\n      <p> </p>\n      <p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>\n    </div>\n\n    <a id=\"MIT_License\"></a>\n    <h3> MIT License </h3>\n    <div class=\"contentContainer\" id=\"MIT_License_div\">\n      <p> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: </p>\n      <p> </p>\n      <p> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. </p>\n      <p> </p>\n      <p> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </p>\n    </div>\n\n    <a id=\"LGPL_2.1_License\"></a>\n    <h3> GNU LESSER GENERAL PUBLIC LICENSE </h3>\n    <p>Version 2.1, February 1999<br>https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html</p>\n    <pre>\n    Copyright (C) 1991, 1999 Free Software Foundation, Inc.\n    51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n    Everyone is permitted to copy and distribute verbatim copies\n    of this license document, but changing it is not allowed.\n\n    [This is the first released version of the Lesser GPL.  It also counts\n     as the successor of the GNU Library Public License, version 2, hence\n     the version number 2.1.]\n    </pre>\n\n    <div class=\"contentContainer\" id=\"LGPL_2.1_License_div\">\n      <h3><a id=\"SEC2\">Preamble</a></h3>\n\n      <p>\n        The licenses for most software are designed to take away your\n      freedom to share and change it.  By contrast, the GNU General Public\n      Licenses are intended to guarantee your freedom to share and change\n      free software--to make sure the software is free for all its users.\n      </p>\n      <p>\n        This license, the Lesser General Public License, applies to some\n      specially designated software packages--typically libraries--of the\n      Free Software Foundation and other authors who decide to use it.  You\n      can use it too, but we suggest you first think carefully about whether\n      this license or the ordinary General Public License is the better\n      strategy to use in any particular case, based on the explanations below.\n      </p>\n      <p>\n        When we speak of free software, we are referring to freedom of use,\n      not price.  Our General Public Licenses are designed to make sure that\n      you have the freedom to distribute copies of free software (and charge\n      for this service if you wish); that you receive source code or can get\n      it if you want it; that you can change the software and use pieces of\n      it in new free programs; and that you are informed that you can do\n      these things.\n      </p>\n      <p>\n        To protect your rights, we need to make restrictions that forbid\n      distributors to deny you these rights or to ask you to surrender these\n      rights.  These restrictions translate to certain responsibilities for\n      you if you distribute copies of the library or if you modify it.\n      </p>\n      <p>\n        For example, if you distribute copies of the library, whether gratis\n      or for a fee, you must give the recipients all the rights that we gave\n      you.  You must make sure that they, too, receive or can get the source\n      code.  If you link other code with the library, you must provide\n      complete object files to the recipients, so that they can relink them\n      with the library after making changes to the library and recompiling\n      it.  And you must show them these terms so they know their rights.\n      </p>\n      <p>\n        We protect your rights with a two-step method: (1) we copyright the\n      library, and (2) we offer you this license, which gives you legal\n      permission to copy, distribute and/or modify the library.\n      </p>\n      <p>\n        To protect each distributor, we want to make it very clear that\n      there is no warranty for the free library.  Also, if the library is\n      modified by someone else and passed on, the recipients should know\n      that what they have is not the original version, so that the original\n      author's reputation will not be affected by problems that might be\n      introduced by others.\n      </p>\n      <p>\n        Finally, software patents pose a constant threat to the existence of\n      any free program.  We wish to make sure that a company cannot\n      effectively restrict the users of a free program by obtaining a\n      restrictive license from a patent holder.  Therefore, we insist that\n      any patent license obtained for a version of the library must be\n      consistent with the full freedom of use specified in this license.\n      </p>\n      <p>\n        Most GNU software, including some libraries, is covered by the\n      ordinary GNU General Public License.  This license, the GNU Lesser\n      General Public License, applies to certain designated libraries, and\n      is quite different from the ordinary General Public License.  We use\n      this license for certain libraries in order to permit linking those\n      libraries into non-free programs.\n      </p>\n      <p>\n        When a program is linked with a library, whether statically or using\n      a shared library, the combination of the two is legally speaking a\n      combined work, a derivative of the original library.  The ordinary\n      General Public License therefore permits such linking only if the\n      entire combination fits its criteria of freedom.  The Lesser General\n      Public License permits more lax criteria for linking other code with\n      the library.\n      </p>\n      <p>\n        We call this license the \"Lesser\" General Public License because it\n      does Less to protect the user's freedom than the ordinary General\n      Public License.  It also provides other free software developers Less\n      of an advantage over competing non-free programs.  These disadvantages\n      are the reason we use the ordinary General Public License for many\n      libraries.  However, the Lesser license provides advantages in certain\n      special circumstances.\n      </p>\n      <p>\n        For example, on rare occasions, there may be a special need to\n      encourage the widest possible use of a certain library, so that it becomes\n      a de-facto standard.  To achieve this, non-free programs must be\n      allowed to use the library.  A more frequent case is that a free\n      library does the same job as widely used non-free libraries.  In this\n      case, there is little to gain by limiting the free library to free\n      software only, so we use the Lesser General Public License.\n      </p>\n      <p>\n        In other cases, permission to use a particular library in non-free\n      programs enables a greater number of people to use a large body of\n      free software.  For example, permission to use the GNU C Library in\n      non-free programs enables many more people to use the whole GNU\n      operating system, as well as its variant, the GNU/Linux operating\n      system.\n      </p>\n      <p>\n        Although the Lesser General Public License is Less protective of the\n      users' freedom, it does ensure that the user of a program that is\n      linked with the Library has the freedom and the wherewithal to run\n      that program using a modified version of the Library.\n      </p>\n      <p>\n        The precise terms and conditions for copying, distribution and\n      modification follow.  Pay close attention to the difference between a\n      \"work based on the library\" and a \"work that uses the library\".  The\n      former contains code derived from the library, whereas the latter must\n      be combined with the library in order to run.\n      </p>\n\n      <h3><a id=\"SEC3\">TERMS AND CONDITIONS FOR COPYING,\n      DISTRIBUTION AND MODIFICATION</a></h3>\n\n\n      <p>\n      <strong>0.</strong>\n      This License Agreement applies to any software library or other\n      program which contains a notice placed by the copyright holder or\n      other authorized party saying it may be distributed under the terms of\n      this Lesser General Public License (also called \"this License\").\n      Each licensee is addressed as \"you\".\n      </p>\n      <p>\n        A \"library\" means a collection of software functions and/or data\n      prepared so as to be conveniently linked with application programs\n      (which use some of those functions and data) to form executables.\n      </p>\n      <p>\n        The \"Library\", below, refers to any such software library or work\n      which has been distributed under these terms.  A \"work based on the\n      Library\" means either the Library or any derivative work under\n      copyright law: that is to say, a work containing the Library or a\n      portion of it, either verbatim or with modifications and/or translated\n      straightforwardly into another language.  (Hereinafter, translation is\n      included without limitation in the term \"modification\".)\n      </p>\n      <p>\n        \"Source code\" for a work means the preferred form of the work for\n      making modifications to it.  For a library, complete source code means\n      all the source code for all modules it contains, plus any associated\n      interface definition files, plus the scripts used to control compilation\n      and installation of the library.\n      </p>\n      <p>\n        Activities other than copying, distribution and modification are not\n      covered by this License; they are outside its scope.  The act of\n      running a program using the Library is not restricted, and output from\n      such a program is covered only if its contents constitute a work based\n      on the Library (independent of the use of the Library in a tool for\n      writing it).  Whether that is true depends on what the Library does\n      and what the program that uses the Library does.\n      </p>\n      <p>\n      <strong>1.</strong>\n      You may copy and distribute verbatim copies of the Library's\n      complete source code as you receive it, in any medium, provided that\n      you conspicuously and appropriately publish on each copy an\n      appropriate copyright notice and disclaimer of warranty; keep intact\n      all the notices that refer to this License and to the absence of any\n      warranty; and distribute a copy of this License along with the\n      Library.\n      </p>\n      <p>\n        You may charge a fee for the physical act of transferring a copy,\n      and you may at your option offer warranty protection in exchange for a\n      fee.\n      </p>\n      <p>\n      <strong>2.</strong>\n      You may modify your copy or copies of the Library or any portion\n      of it, thus forming a work based on the Library, and copy and\n      distribute such modifications or work under the terms of Section 1\n      above, provided that you also meet all of these conditions:\n      </p>\n\n      <ul>\n        <li><strong>a)</strong>\n             The modified work must itself be a software library.</li>\n        <li><strong>b)</strong>\n             You must cause the files modified to carry prominent notices\n             stating that you changed the files and the date of any change.</li>\n\n        <li><strong>c)</strong>\n             You must cause the whole of the work to be licensed at no\n             charge to all third parties under the terms of this License.</li>\n\n        <li><strong>d)</strong>\n             If a facility in the modified Library refers to a function or a\n             table of data to be supplied by an application program that uses\n             the facility, other than as an argument passed when the facility\n             is invoked, then you must make a good faith effort to ensure that,\n             in the event an application does not supply such function or\n             table, the facility still operates, and performs whatever part of\n             its purpose remains meaningful.\n             <p>\n             (For example, a function in a library to compute square roots has\n             a purpose that is entirely well-defined independent of the\n             application.  Therefore, Subsection 2d requires that any\n             application-supplied function or table used by this function must\n             be optional: if the application does not supply it, the square\n             root function must still compute square roots.)</p></li>\n      </ul>\n\n      <p>\n      These requirements apply to the modified work as a whole.  If identifiable\n      sections of that work are not derived from the Library, and can be\n      reasonably considered independent and separate works in themselves, then\n      this License, and its terms, do not apply to those sections when you\n      distribute them as separate works.  But when you distribute the same\n      sections as part of a whole which is a work based on the Library, the\n      distribution of the whole must be on the terms of this License, whose\n      permissions for other licensees extend to the entire whole, and thus to\n      each and every part regardless of who wrote it.\n      </p>\n      <p>\n      Thus, it is not the intent of this section to claim rights or contest your\n      rights to work written entirely by you; rather, the intent is to exercise\n      the right to control the distribution of derivative or collective works\n      based on the Library.\n      </p>\n      <p>\n      In addition, mere aggregation of another work not based on the Library with\n      the Library (or with a work based on the Library) on a volume of a storage\n      or distribution medium does not bring the other work under the scope of\n      this License.\n      </p>\n      <p>\n      <strong>3.</strong>\n      You may opt to apply the terms of the ordinary GNU General Public\n      License instead of this License to a given copy of the Library.  To do\n      this, you must alter all the notices that refer to this License, so\n      that they refer to the ordinary GNU General Public License, version 2,\n      instead of to this License.  (If a newer version than version 2 of the\n      ordinary GNU General Public License has appeared, then you can specify\n      that version instead if you wish.)  Do not make any other change in\n      these notices.\n      </p>\n      <p>\n        Once this change is made in a given copy, it is irreversible for\n      that copy, so the ordinary GNU General Public License applies to all\n      subsequent copies and derivative works made from that copy.\n      </p>\n      <p>\n        This option is useful when you wish to copy part of the code of\n      the Library into a program that is not a library.\n      </p>\n      <p>\n      <strong>4.</strong>\n      You may copy and distribute the Library (or a portion or\n      derivative of it, under Section 2) in object code or executable form\n      under the terms of Sections 1 and 2 above provided that you accompany\n      it with the complete corresponding machine-readable source code, which\n      must be distributed under the terms of Sections 1 and 2 above on a\n      medium customarily used for software interchange.\n      </p>\n      <p>\n        If distribution of object code is made by offering access to copy\n      from a designated place, then offering equivalent access to copy the\n      source code from the same place satisfies the requirement to\n      distribute the source code, even though third parties are not\n      compelled to copy the source along with the object code.\n      </p>\n      <p>\n      <strong>5.</strong>\n      A program that contains no derivative of any portion of the\n      Library, but is designed to work with the Library by being compiled or\n      linked with it, is called a \"work that uses the Library\".  Such a\n      work, in isolation, is not a derivative work of the Library, and\n      therefore falls outside the scope of this License.\n      </p>\n      <p>\n        However, linking a \"work that uses the Library\" with the Library\n      creates an executable that is a derivative of the Library (because it\n      contains portions of the Library), rather than a \"work that uses the\n      library\".  The executable is therefore covered by this License.\n      Section 6 states terms for distribution of such executables.\n      </p>\n      <p>\n        When a \"work that uses the Library\" uses material from a header file\n      that is part of the Library, the object code for the work may be a\n      derivative work of the Library even though the source code is not.\n      Whether this is true is especially significant if the work can be\n      linked without the Library, or if the work is itself a library.  The\n      threshold for this to be true is not precisely defined by law.\n      </p>\n      <p>\n        If such an object file uses only numerical parameters, data\n      structure layouts and accessors, and small macros and small inline\n      functions (ten lines or less in length), then the use of the object\n      file is unrestricted, regardless of whether it is legally a derivative\n      work.  (Executables containing this object code plus portions of the\n      Library will still fall under Section 6.)\n      </p>\n      <p>\n        Otherwise, if the work is a derivative of the Library, you may\n      distribute the object code for the work under the terms of Section 6.\n      Any executables containing that work also fall under Section 6,\n      whether or not they are linked directly with the Library itself.\n      </p>\n      <p>\n      <strong>6.</strong>\n      As an exception to the Sections above, you may also combine or\n      link a \"work that uses the Library\" with the Library to produce a\n      work containing portions of the Library, and distribute that work\n      under terms of your choice, provided that the terms permit\n      modification of the work for the customer's own use and reverse\n      engineering for debugging such modifications.\n      </p>\n      <p>\n        You must give prominent notice with each copy of the work that the\n      Library is used in it and that the Library and its use are covered by\n      this License.  You must supply a copy of this License.  If the work\n      during execution displays copyright notices, you must include the\n      copyright notice for the Library among them, as well as a reference\n      directing the user to the copy of this License.  Also, you must do one\n      of these things:\n      </p>\n\n      <ul>\n          <li><strong>a)</strong> Accompany the work with the complete\n          corresponding machine-readable source code for the Library\n          including whatever changes were used in the work (which must be\n          distributed under Sections 1 and 2 above); and, if the work is an\n          executable linked with the Library, with the complete\n          machine-readable \"work that uses the Library\", as object code\n          and/or source code, so that the user can modify the Library and\n          then relink to produce a modified executable containing the\n          modified Library.  (It is understood that the user who changes the\n          contents of definitions files in the Library will not necessarily\n          be able to recompile the application to use the modified\n          definitions.)</li>\n\n          <li><strong>b)</strong> Use a suitable shared library mechanism\n          for linking with the Library.  A suitable mechanism is one that\n          (1) uses at run time a copy of the library already present on the\n          user's computer system, rather than copying library functions into\n          the executable, and (2) will operate properly with a modified\n          version of the library, if the user installs one, as long as the\n          modified version is interface-compatible with the version that the\n          work was made with.</li>\n\n          <li><strong>c)</strong> Accompany the work with a written offer,\n          valid for at least three years, to give the same user the\n          materials specified in Subsection 6a, above, for a charge no more\n          than the cost of performing this distribution.</li>\n\n          <li><strong>d)</strong> If distribution of the work is made by\n          offering access to copy from a designated place, offer equivalent\n          access to copy the above specified materials from the same\n          place.</li>\n\n          <li><strong>e)</strong> Verify that the user has already received\n          a copy of these materials or that you have already sent this user\n          a copy.</li>\n      </ul>\n\n      <p>\n        For an executable, the required form of the \"work that uses the\n      Library\" must include any data and utility programs needed for\n      reproducing the executable from it.  However, as a special exception,\n      the materials to be distributed need not include anything that is\n      normally distributed (in either source or binary form) with the major\n      components (compiler, kernel, and so on) of the operating system on\n      which the executable runs, unless that component itself accompanies\n      the executable.\n      </p>\n      <p>\n        It may happen that this requirement contradicts the license\n      restrictions of other proprietary libraries that do not normally\n      accompany the operating system.  Such a contradiction means you cannot\n      use both them and the Library together in an executable that you\n      distribute.\n      </p>\n      <p>\n      <strong>7.</strong> You may place library facilities that are a work\n      based on the Library side-by-side in a single library together with\n      other library facilities not covered by this License, and distribute\n      such a combined library, provided that the separate distribution of\n      the work based on the Library and of the other library facilities is\n      otherwise permitted, and provided that you do these two things:\n      </p>\n\n      <ul>\n          <li><strong>a)</strong> Accompany the combined library with a copy\n          of the same work based on the Library, uncombined with any other\n          library facilities.  This must be distributed under the terms of\n          the Sections above.</li>\n\n          <li><strong>b)</strong> Give prominent notice with the combined\n          library of the fact that part of it is a work based on the\n          Library, and explaining where to find the accompanying uncombined\n          form of the same work.</li>\n      </ul>\n\n      <p>\n      <strong>8.</strong> You may not copy, modify, sublicense, link with,\n      or distribute the Library except as expressly provided under this\n      License.  Any attempt otherwise to copy, modify, sublicense, link\n      with, or distribute the Library is void, and will automatically\n      terminate your rights under this License.  However, parties who have\n      received copies, or rights, from you under this License will not have\n      their licenses terminated so long as such parties remain in full\n      compliance.\n      </p>\n      <p>\n      <strong>9.</strong>\n      You are not required to accept this License, since you have not\n      signed it.  However, nothing else grants you permission to modify or\n      distribute the Library or its derivative works.  These actions are\n      prohibited by law if you do not accept this License.  Therefore, by\n      modifying or distributing the Library (or any work based on the\n      Library), you indicate your acceptance of this License to do so, and\n      all its terms and conditions for copying, distributing or modifying\n      the Library or works based on it.\n      </p>\n      <p>\n      <strong>10.</strong>\n      Each time you redistribute the Library (or any work based on the\n      Library), the recipient automatically receives a license from the\n      original licensor to copy, distribute, link with or modify the Library\n      subject to these terms and conditions.  You may not impose any further\n      restrictions on the recipients' exercise of the rights granted herein.\n      You are not responsible for enforcing compliance by third parties with\n      this License.\n      </p>\n      <p>\n      <strong>11.</strong>\n      If, as a consequence of a court judgment or allegation of patent\n      infringement or for any other reason (not limited to patent issues),\n      conditions are imposed on you (whether by court order, agreement or\n      otherwise) that contradict the conditions of this License, they do not\n      excuse you from the conditions of this License.  If you cannot\n      distribute so as to satisfy simultaneously your obligations under this\n      License and any other pertinent obligations, then as a consequence you\n      may not distribute the Library at all.  For example, if a patent\n      license would not permit royalty-free redistribution of the Library by\n      all those who receive copies directly or indirectly through you, then\n      the only way you could satisfy both it and this License would be to\n      refrain entirely from distribution of the Library.\n      </p>\n      <p>\n      If any portion of this section is held invalid or unenforceable under any\n      particular circumstance, the balance of the section is intended to apply,\n      and the section as a whole is intended to apply in other circumstances.\n      </p>\n      <p>\n      It is not the purpose of this section to induce you to infringe any\n      patents or other property right claims or to contest validity of any\n      such claims; this section has the sole purpose of protecting the\n      integrity of the free software distribution system which is\n      implemented by public license practices.  Many people have made\n      generous contributions to the wide range of software distributed\n      through that system in reliance on consistent application of that\n      system; it is up to the author/donor to decide if he or she is willing\n      to distribute software through any other system and a licensee cannot\n      impose that choice.\n      </p>\n      <p>\n      This section is intended to make thoroughly clear what is believed to\n      be a consequence of the rest of this License.\n      </p>\n      <p>\n      <strong>12.</strong>\n      If the distribution and/or use of the Library is restricted in\n      certain countries either by patents or by copyrighted interfaces, the\n      original copyright holder who places the Library under this License may add\n      an explicit geographical distribution limitation excluding those countries,\n      so that distribution is permitted only in or among countries not thus\n      excluded.  In such case, this License incorporates the limitation as if\n      written in the body of this License.\n      </p>\n      <p>\n      <strong>13.</strong>\n      The Free Software Foundation may publish revised and/or new\n      versions of the Lesser General Public License from time to time.\n      Such new versions will be similar in spirit to the present version,\n      but may differ in detail to address new problems or concerns.\n      </p>\n      <p>\n      Each version is given a distinguishing version number.  If the Library\n      specifies a version number of this License which applies to it and\n      \"any later version\", you have the option of following the terms and\n      conditions either of that version or of any later version published by\n      the Free Software Foundation.  If the Library does not specify a\n      license version number, you may choose any version ever published by\n      the Free Software Foundation.\n      </p>\n      <p>\n      <strong>14.</strong>\n      If you wish to incorporate parts of the Library into other free\n      programs whose distribution conditions are incompatible with these,\n      write to the author to ask for permission.  For software which is\n      copyrighted by the Free Software Foundation, write to the Free\n      Software Foundation; we sometimes make exceptions for this.  Our\n      decision will be guided by the two goals of preserving the free status\n      of all derivatives of our free software and of promoting the sharing\n      and reuse of software generally.\n      </p>\n      <p>\n      <strong>NO WARRANTY</strong>\n      </p>\n      <p>\n      <strong>15.</strong>\n      BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO\n      WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\n      EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\n      OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY\n      KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\n      IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n      PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\n      LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME\n      THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n      </p>\n      <p>\n      <strong>16.</strong>\n      IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\n      WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\n      AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU\n      FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\n      CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\n      LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\n      RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\n      FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF\n      SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\n      DAMAGES.\n      </p>\n\n      <h3>END OF TERMS AND CONDITIONS</h3>\n    </div>\n\n    <a id=\"CDDL_1.1_License\"></a>\n    <h2> COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1 </h2>\n\n    <div class=\"contentContainer\" id=\"CDDL_1.1_License_div\">\n      <ol start=\"1\">\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\">\n              Definitions.\n            </dt>\n            <dd class=\"serializedFormContainer\">\n              1.1. \"Contributor\" means each individual or entity that creates or\n              contributes to the creation of Modifications.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.2. \"Contributor Version\" means the combination of the Original\n              Software, prior Modifications used by a Contributor (if any), and\n              the Modifications made by that particular Contributor.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.3. \"Covered Software\" means (a) the Original Software, or (b)\n              Modifications, or (c) the combination of files containing Original\n              Software with files containing Modifications, in each case including\n              portions thereof.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.4. \"Executable\" means the Covered Software in any form other than\n              Source Code.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.5. \"Initial Developer\" means the individual or entity that first\n              makes Original Software available under this License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.6. \"Larger Work\" means a work which combines Covered Software or\n              portions thereof with code not governed by the terms of this License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.7. \"License\" means this document.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.8. \"Licensable\" means having the right to grant, to the maximum\n              extent possible, whether at the time of the initial grant or\n              subsequently acquired, any and all of the rights conveyed herein.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.9. \"Modifications\" means the Source Code and Executable form of\n              any of the following:\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              A. Any file that results from an addition to, deletion from or\n              modification of the contents of a file containing Original Software\n              or previous Modifications;\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              B. Any new file that contains any part of the Original Software or\n              previous Modification; or\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              C. Any new file that is contributed or otherwise made available\n              under the terms of this License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.10. \"Original Software\" means the Source Code and Executable form\n              of computer software code that is originally released under this\n              License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.11. \"Patent Claims\" means any patent claim(s), now owned or\n              hereafter acquired, including without limitation, method, process,\n              and apparatus claims, in any patent Licensable by grantor.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.12. \"Source Code\" means (a) the common form of computer software\n              code in which modifications are made and (b) associated\n              documentation included in or with such code.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              1.13. \"You\" (or \"Your\") means an individual or a legal entity\n              exercising rights under, and complying with all of the terms of,\n              this License. For legal entities, \"You\" includes any entity which\n              controls, is controlled by, or is under common control with You. For\n              purposes of this definition, \"control\" means (a) the power, direct\n              or indirect, to cause the direction or management of such entity,\n              whether by contract or otherwise, or (b) ownership of more than\n              fifty percent (50%) of the outstanding shares or beneficial\n              ownership of such entity.\n            </dd>\n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\">\n              License Grants.\n            </dt>\n            <dd class=\"serializedFormContainer\">\n              2.1. The Initial Developer Grant.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              Conditioned upon Your compliance with Section 3.1 below and subject\n              to third party intellectual property claims, the Initial Developer\n              hereby grants You a world-wide, royalty-free, non-exclusive license:\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              (a) under intellectual property rights (other than patent or\n              trademark) Licensable by Initial Developer, to use, reproduce,\n              modify, display, perform, sublicense and distribute the Original\n              Software (or portions thereof), with or without Modifications,\n              and/or as part of a Larger Work; and\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              (b) under Patent Claims infringed by the making, using or selling of\n              Original Software, to make, have made, use, practice, sell, and\n              offer for sale, and/or otherwise dispose of the Original Software\n              (or portions thereof).\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              (c) The licenses granted in Sections 2.1(a) and (b) are effective on\n              the date Initial Developer first distributes or otherwise makes the\n              Original Software available to a third party under the terms of this\n              License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              (d) Notwithstanding Section 2.1(b) above, no patent license is\n              granted: (1) for code that You delete from the Original Software, or\n              (2) for infringements caused by: (i) the modification of the\n              Original Software, or (ii) the combination of the Original Software\n              with other software or devices.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              2.2. Contributor Grant.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              Conditioned upon Your compliance with Section 3.1 below and subject\n              to third party intellectual property claims, each Contributor hereby\n              grants You a world-wide, royalty-free, non-exclusive license:\n            </dd>\n            <dd class=\"serializedFormContainer\"> \n              (a) under intellectual property rights (other than patent or\n              trademark) Licensable by Contributor to use, reproduce, modify,\n              display, perform, sublicense and distribute the Modifications\n              created by such Contributor (or portions thereof), either on an\n              unmodified basis, with other Modifications, as Covered Software\n              and/or as part of a Larger Work; and\n            </dd>\n            <dd class=\"serializedFormContainer\"> \n              (b) under Patent Claims infringed by the making, using, or selling\n              of Modifications made by that Contributor either alone and/or in\n              combination with its Contributor Version (or portions of such\n              combination), to make, use, sell, offer for sale, have made, and/or\n              otherwise dispose of: (1) Modifications made by that Contributor (or\n              portions thereof); and (2) the combination of Modifications made by\n              that Contributor with its Contributor Version (or portions of such\n              combination).\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective\n              on the date Contributor first distributes or otherwise makes the\n              Modifications available to a third party.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              (d) Notwithstanding Section 2.2(b) above, no patent license is\n              granted: (1) for any code that Contributor has deleted from the\n              Contributor Version; (2) for infringements caused by: (i) third\n              party modifications of Contributor Version, or (ii) the combination\n              of Modifications made by that Contributor with other software\n              (except as part of the Contributor Version) or other devices; or (3)\n              under Patent Claims infringed by Covered Software in the absence of\n              Modifications made by that Contributor.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n            </dd>\n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> Distribution Obligations. </dt>\n            <dd class=\"serializedFormContainer\">\n              3.1. Availability of Source Code.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              Any Covered Software that You distribute or otherwise make available\n              in Executable form must also be made available in Source Code form\n              and that Source Code form must be distributed only under the terms\n              of this License. You must include a copy of this License with every\n              copy of the Source Code form of the Covered Software You distribute\n              or otherwise make available. You must inform recipients of any such\n              Covered Software in Executable form as to how they can obtain such\n              Covered Software in Source Code form in a reasonable manner on or\n              through a medium customarily used for software exchange.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              3.2. Modifications.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              The Modifications that You create or to which You contribute are\n              governed by the terms of this License. You represent that You\n              believe Your Modifications are Your original creation(s) and/or You\n              have sufficient rights to grant the rights conveyed by this License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              3.3. Required Notices.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              You must include a notice in each of Your Modifications that\n              identifies You as the Contributor of the Modification. You may not\n              remove or alter any copyright, patent or trademark notices contained\n              within the Covered Software, or any notices of licensing or any\n              descriptive text giving attribution to any Contributor or the\n              Initial Developer.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              3.4. Application of Additional Terms.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              You may not offer or impose any terms on any Covered Software in\n              Source Code form that alters or restricts the applicable version of\n              this License or the recipients' rights hereunder. You may choose to\n              offer, and to charge a fee for, warranty, support, indemnity or\n              liability obligations to one or more recipients of Covered Software.\n              However, you may do so only on Your own behalf, and not on behalf of\n              the Initial Developer or any Contributor. You must make it\n              absolutely clear that any such warranty, support, indemnity or\n              liability obligation is offered by You alone, and You hereby agree\n              to indemnify the Initial Developer and every Contributor for any\n              liability incurred by the Initial Developer or such Contributor as a\n              result of warranty, support, indemnity or liability terms You offer.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              3.5. Distribution of Executable Versions.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              You may distribute the Executable form of the Covered Software under\n              the terms of this License or under the terms of a license of Your\n              choice, which may contain terms different from this License,\n              provided that You are in compliance with the terms of this License\n              and that the license for the Executable form does not attempt to\n              limit or alter the recipient's rights in the Source Code form from\n              the rights set forth in this License. If You distribute the Covered\n              Software in Executable form under a different license, You must make\n              it absolutely clear that any terms which differ from this License\n              are offered by You alone, not by the Initial Developer or\n              Contributor. You hereby agree to indemnify the Initial Developer and\n              every Contributor for any liability incurred by the Initial\n              Developer or such Contributor as a result of any such terms You offer.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              3.6. Larger Works.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              You may create a Larger Work by combining Covered Software with\n              other code not governed by the terms of this License and distribute\n              the Larger Work as a single product. In such a case, You must make\n              sure the requirements of this License are fulfilled for the Covered\n              Software.\n            </dd>\n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> Versions of the License. </dt>\n            <dd class=\"serializedFormContainer\">\n              4.1. New Versions\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              Oracle is the initial license steward and may publish revised and/or\n              new versions of this License from time to time. Each version will be\n              given a distinguishing version number. Except as provided in Section\n              4.3, no one other than the license steward has the right to modify\n              this License.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              4.2. Effect of New Versions.\n            </dd>\n            <dd class=\"serializedFormContainer\">\n              You may always continue to use, distribute or otherwise make the\n              Covered Software available under the terms of the version of the\n              License under which You originally received the Covered Software. If\n              the Initial Developer includes a notice in the Original Software\n              prohibiting it from being distributed or otherwise made available\n              under any subsequent version of the License, You must distribute and\n              make the Covered Software available under the terms of the version\n              of the License under which You originally received the Covered\n              Software. Otherwise, You may also choose to use, distribute or\n              otherwise make the Covered Software available under the terms of any\n              subsequent version of the License published by the license steward.\n            </dd> \n            <dd class=\"serializedFormContainer\">\n              4.3. Modified Versions.\n            </dd> \n            <dd class=\"serializedFormContainer\">\n              When You are an Initial Developer and You want to create a new\n              license for Your Original Software, You may create and use a\n              modified version of this License if You: (a) rename the license and\n              remove any references to the name of the license steward (except to\n              note that the license differs from this License); and (b) otherwise\n              make it clear that the license contains terms which differ from this\n              License.\n            </dd> \n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> DISCLAIMER OF WARRANTY. </dt>  \n            <dd class=\"serializedFormContainer\">\n              COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN \"AS IS\" BASIS,\n              WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,\n              INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE\n              IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR\n              NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF\n              THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE\n              DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY\n              OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING,\n              REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN\n              ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS\n              AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.\n            </dd>  \n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> TERMINATION. </dt> \n            <dd class=\"serializedFormContainer\">\n              6.1. This License and the rights granted hereunder will terminate\n              automatically if You fail to comply with terms herein and fail to\n              cure such breach within 30 days of becoming aware of the breach.\n              Provisions which, by their nature, must remain in effect beyond the\n              termination of this License shall survive.\n            </dd>  \n            <dd class=\"serializedFormContainer\">\n              6.2. If You assert a patent infringement claim (excluding\n              declaratory judgment actions) against Initial Developer or a\n              Contributor (the Initial Developer or Contributor against whom You\n              assert such claim is referred to as \"Participant\") alleging that the\n              Participant Software (meaning the Contributor Version where the\n              Participant is a Contributor or the Original Software where the\n              Participant is the Initial Developer) directly or indirectly\n              infringes any patent, then any and all rights granted directly or\n              indirectly to You by such Participant, the Initial Developer (if the\n              Initial Developer is not the Participant) and all Contributors under\n              Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice\n              from Participant terminate prospectively and automatically at the\n              expiration of such 60 day notice period, unless if within such 60\n              day period You withdraw Your claim with respect to the Participant\n              Software against such Participant either unilaterally or pursuant to\n              a written agreement with Participant.\n            </dd>  \n            <dd class=\"serializedFormContainer\">\n              6.3. If You assert a patent infringement claim against Participant\n              alleging that the Participant Software directly or indirectly\n              infringes any patent where such claim is resolved (such as by\n              license or settlement) prior to the initiation of patent\n              infringement litigation, then the reasonable value of the licenses\n              granted by such Participant under Sections 2.1 or 2.2 shall be taken\n              into account in determining the amount or value of any payment or\n              license.\n            </dd>  \n            <dd class=\"serializedFormContainer\">\n              6.4. In the event of termination under Sections 6.1 or 6.2 above,\n              all end user licenses that have been validly granted by You or any\n              distributor hereunder prior to termination (excluding licenses\n              granted to You by any distributor) shall survive termination. \n            </dd>  \n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> LIMITATION OF LIABILITY. </dt>  \n            <dd class=\"serializedFormContainer\">\n              UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT\n              (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE\n              INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF\n              COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE\n              TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR\n              CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT\n              LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER\n              FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR\n              LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE\n              POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT\n              APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH\n              PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH\n              LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR\n              LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION\n              AND LIMITATION MAY NOT APPLY TO YOU.\n            </dd>\n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> U.S. GOVERNMENT END USERS. </dt>  \n            <dd class=\"serializedFormContainer\">\n              The Covered Software is a \"commercial item,\" as that term is defined\n              in 48 C.F.R. 2.101 (Oct. 1995), consisting of \"commercial computer\n              software\" (as that term is defined at 48 C.F.R. §\n              252.227-7014(a)(1)) and \"commercial computer software documentation\"\n              as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent\n              with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4\n              (June 1995), all U.S. Government End Users acquire Covered Software\n              with only those rights set forth herein. This U.S. Government Rights\n              clause is in lieu of, and supersedes, any other FAR, DFAR, or other\n              clause or provision that addresses Government rights in computer\n              software under this License.\n            </dd>\n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> MISCELLANEOUS. </dt>  \n            <dd class=\"serializedFormContainer\">\n              This License represents the complete agreement concerning subject\n              matter hereof. If any provision of this License is held to be\n              unenforceable, such provision shall be reformed only to the extent\n              necessary to make it enforceable. This License shall be governed by\n              the law of the jurisdiction specified in a notice contained within\n              the Original Software (except to the extent applicable law, if any,\n              provides otherwise), excluding such jurisdiction's conflict-of-law\n              provisions. Any litigation relating to this License shall be subject\n              to the jurisdiction of the courts located in the jurisdiction and\n              venue specified in a notice contained within the Original Software,\n              with the losing party responsible for costs, including, without\n              limitation, court costs and reasonable attorneys' fees and expenses.\n              The application of the United Nations Convention on Contracts for\n              the International Sale of Goods is expressly excluded. Any law or\n              regulation which provides that the language of a contract shall be\n              construed against the drafter shall not apply to this License. You\n              agree that You alone are responsible for compliance with the United\n              States export administration regulations (and the export control\n              laws and regulation of any other countries) when You use, distribute\n              or otherwise make available any Covered Software.\n            </dd>\n          </dl>\n        </li>\n        <li>\n          <dl>\n            <dt class=\"serializedFormContainer\"> RESPONSIBILITY FOR CLAIMS. </dt>  \n            <dd class=\"serializedFormContainer\">\n              As between Initial Developer and the Contributors, each party is\n              responsible for claims and damages arising, directly or indirectly,\n              out of its utilization of rights under this License and You agree to\n              work with Initial Developer and Contributors to distribute such\n              responsibility on an equitable basis. Nothing herein is intended or\n              shall be deemed to constitute any admission of liability.\n            </dd>\n          </dl>\n        </li>\n      </ol>\n      <p>------------------------------------------------------------------------</p>\n      <p> </p>\n      <p>\n        NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION\n        LICENSE (CDDL)\n      </p>\n      <p>\n        The code released under the CDDL shall be governed by the laws of the\n        State of California (excluding conflict-of-law provisions). Any\n        litigation relating to this License shall be subject to the jurisdiction\n        of the Federal Courts of the Northern District of California and the\n        state courts of the State of California, with venue lying in Santa Clara\n        County, California.\n      </p>\n    </div>\n    \n    <a id=\"GPL_2_License\"></a>\n    <div>\n      <h2><a id=\"GPL_SEC1\">GNU GENERAL PUBLIC LICENSE</a></h2>\n      <p>\n      Version 2, June 1991<br>\n      https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\n      </p>\n\n      <pre>\n      Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\n\n      Everyone is permitted to copy and distribute verbatim copies\n      of this license document, but changing it is not allowed.\n      </pre>\n\n      <h3 id=\"GPL_preamble\"><a id=\"GPL_SEC2\">Preamble</a></h3>\n\n      <p>\n        The licenses for most software are designed to take away your\n      freedom to share and change it.  By contrast, the GNU General Public\n      License is intended to guarantee your freedom to share and change free\n      software--to make sure the software is free for all its users.  This\n      General Public License applies to most of the Free Software\n      Foundation's software and to any other program whose authors commit to\n      using it.  (Some other Free Software Foundation software is covered by\n      the GNU Lesser General Public License instead.)  You can apply it to\n      your programs, too.\n      </p>\n\n      <p>\n        When we speak of free software, we are referring to freedom, not\n      price.  Our General Public Licenses are designed to make sure that you\n      have the freedom to distribute copies of free software (and charge for\n      this service if you wish), that you receive source code or can get it\n      if you want it, that you can change the software or use pieces of it\n      in new free programs; and that you know you can do these things.\n      </p>\n\n      <p>\n        To protect your rights, we need to make restrictions that forbid\n      anyone to deny you these rights or to ask you to surrender the rights.\n      These restrictions translate to certain responsibilities for you if you\n      distribute copies of the software, or if you modify it.\n      </p>\n\n      <p>\n        For example, if you distribute copies of such a program, whether\n      gratis or for a fee, you must give the recipients all the rights that\n      you have.  You must make sure that they, too, receive or can get the\n      source code.  And you must show them these terms so they know their\n      rights.\n      </p>\n\n      <p>\n        We protect your rights with two steps: (1) copyright the software, and\n      (2) offer you this license which gives you legal permission to copy,\n      distribute and/or modify the software.\n      </p>\n\n      <p>\n        Also, for each author's protection and ours, we want to make certain\n      that everyone understands that there is no warranty for this free\n      software.  If the software is modified by someone else and passed on, we\n      want its recipients to know that what they have is not the original, so\n      that any problems introduced by others will not reflect on the original\n      authors' reputations.\n      </p>\n\n      <p>\n        Finally, any free program is threatened constantly by software\n      patents.  We wish to avoid the danger that redistributors of a free\n      program will individually obtain patent licenses, in effect making the\n      program proprietary.  To prevent this, we have made it clear that any\n      patent must be licensed for everyone's free use or not licensed at all.\n      </p>\n\n      <p>\n        The precise terms and conditions for copying, distribution and\n      modification follow.\n      </p>\n\n\n      <h3 id=\"GPL_terms\"><a id=\"GPL_SEC3\">TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</a></h3>\n\n\n      <p id=\"GPL_section0\">\n      <strong>0.</strong>\n       This License applies to any program or other work which contains\n      a notice placed by the copyright holder saying it may be distributed\n      under the terms of this General Public License.  The \"Program\", below,\n      refers to any such program or work, and a \"work based on the Program\"\n      means either the Program or any derivative work under copyright law:\n      that is to say, a work containing the Program or a portion of it,\n      either verbatim or with modifications and/or translated into another\n      language.  (Hereinafter, translation is included without limitation in\n      the term \"modification\".)  Each licensee is addressed as \"you\".\n      </p>\n\n      <p>\n      Activities other than copying, distribution and modification are not\n      covered by this License; they are outside its scope.  The act of\n      running the Program is not restricted, and the output from the Program\n      is covered only if its contents constitute a work based on the\n      Program (independent of having been made by running the Program).\n      Whether that is true depends on what the Program does.\n      </p>\n\n      <p id=\"GPL_section1\">\n      <strong>1.</strong>\n       You may copy and distribute verbatim copies of the Program's\n      source code as you receive it, in any medium, provided that you\n      conspicuously and appropriately publish on each copy an appropriate\n      copyright notice and disclaimer of warranty; keep intact all the\n      notices that refer to this License and to the absence of any warranty;\n      and give any other recipients of the Program a copy of this License\n      along with the Program.\n      </p>\n\n      <p>\n      You may charge a fee for the physical act of transferring a copy, and\n      you may at your option offer warranty protection in exchange for a fee.\n      </p>\n\n      <p id=\"PGL_section2\">\n      <strong>2.</strong>\n       You may modify your copy or copies of the Program or any portion\n      of it, thus forming a work based on the Program, and copy and\n      distribute such modifications or work under the terms of Section 1\n      above, provided that you also meet all of these conditions:\n      </p>\n\n      <dl>\n        <dt></dt>\n          <dd>\n            <strong>a)</strong>\n            You must cause the modified files to carry prominent notices\n            stating that you changed the files and the date of any change.\n          </dd>\n        <dt></dt>\n          <dd>\n            <strong>b)</strong>\n            You must cause any work that you distribute or publish, that in\n            whole or in part contains or is derived from the Program or any\n            part thereof, to be licensed as a whole at no charge to all third\n            parties under the terms of this License.\n          </dd>\n        <dt></dt>\n          <dd>\n            <strong>c)</strong>\n            If the modified program normally reads commands interactively\n            when run, you must cause it, when started running for such\n            interactive use in the most ordinary way, to print or display an\n            announcement including an appropriate copyright notice and a\n            notice that there is no warranty (or else, saying that you provide\n            a warranty) and that users may redistribute the program under\n            these conditions, and telling the user how to view a copy of this\n            License.  (Exception: if the Program itself is interactive but\n            does not normally print such an announcement, your work based on\n            the Program is not required to print an announcement.)\n          </dd>\n      </dl>\n\n      <p>\n      These requirements apply to the modified work as a whole.  If\n      identifiable sections of that work are not derived from the Program,\n      and can be reasonably considered independent and separate works in\n      themselves, then this License, and its terms, do not apply to those\n      sections when you distribute them as separate works.  But when you\n      distribute the same sections as part of a whole which is a work based\n      on the Program, the distribution of the whole must be on the terms of\n      this License, whose permissions for other licensees extend to the\n      entire whole, and thus to each and every part regardless of who wrote it.\n      </p>\n\n      <p>\n      Thus, it is not the intent of this section to claim rights or contest\n      your rights to work written entirely by you; rather, the intent is to\n      exercise the right to control the distribution of derivative or\n      collective works based on the Program.\n      </p>\n\n      <p>\n      In addition, mere aggregation of another work not based on the Program\n      with the Program (or with a work based on the Program) on a volume of\n      a storage or distribution medium does not bring the other work under\n      the scope of this License.\n      </p>\n\n      <p id=\"GPL_section3\">\n      <strong>3.</strong>\n       You may copy and distribute the Program (or a work based on it,\n      under Section 2) in object code or executable form under the terms of\n      Sections 1 and 2 above provided that you also do one of the following:\n      </p>\n\n      <!-- we use this doubled UL to get the sub-sections indented, -->\n      <!-- while making the bullets as unobvious as possible. -->\n\n      <dl>\n        <dt></dt>\n          <dd>\n            <strong>a)</strong>\n            Accompany it with the complete corresponding machine-readable\n            source code, which must be distributed under the terms of Sections\n            1 and 2 above on a medium customarily used for software interchange; or,\n          </dd>\n        <dt></dt>\n          <dd>\n            <strong>b)</strong>\n            Accompany it with a written offer, valid for at least three\n            years, to give any third party, for a charge no more than your\n            cost of physically performing source distribution, a complete\n            machine-readable copy of the corresponding source code, to be\n            distributed under the terms of Sections 1 and 2 above on a medium\n            customarily used for software interchange; or,\n          </dd>\n        <dt></dt>\n          <dd>\n            <strong>c)</strong>\n            Accompany it with the information you received as to the offer\n            to distribute corresponding source code.  (This alternative is\n            allowed only for noncommercial distribution and only if you\n            received the program in object code or executable form with such\n            an offer, in accord with Subsection b above.)\n          </dd>\n      </dl>\n\n      <p>\n      The source code for a work means the preferred form of the work for\n      making modifications to it.  For an executable work, complete source\n      code means all the source code for all modules it contains, plus any\n      associated interface definition files, plus the scripts used to\n      control compilation and installation of the executable.  However, as a\n      special exception, the source code distributed need not include\n      anything that is normally distributed (in either source or binary\n      form) with the major components (compiler, kernel, and so on) of the\n      operating system on which the executable runs, unless that component\n      itself accompanies the executable.\n      </p>\n\n      <p>\n      If distribution of executable or object code is made by offering\n      access to copy from a designated place, then offering equivalent\n      access to copy the source code from the same place counts as\n      distribution of the source code, even though third parties are not\n      compelled to copy the source along with the object code.\n      </p>\n\n      <p id=\"GPL_section4\">\n      <strong>4.</strong>\n       You may not copy, modify, sublicense, or distribute the Program\n      except as expressly provided under this License.  Any attempt\n      otherwise to copy, modify, sublicense or distribute the Program is\n      void, and will automatically terminate your rights under this License.\n      However, parties who have received copies, or rights, from you under\n      this License will not have their licenses terminated so long as such\n      parties remain in full compliance.\n      </p>\n\n      <p id=\"GPL_section5\">\n      <strong>5.</strong>\n       You are not required to accept this License, since you have not\n      signed it.  However, nothing else grants you permission to modify or\n      distribute the Program or its derivative works.  These actions are\n      prohibited by law if you do not accept this License.  Therefore, by\n      modifying or distributing the Program (or any work based on the\n      Program), you indicate your acceptance of this License to do so, and\n      all its terms and conditions for copying, distributing or modifying\n      the Program or works based on it.\n      </p>\n\n      <p id=\"GPL_section6\">\n      <strong>6.</strong>\n       Each time you redistribute the Program (or any work based on the\n      Program), the recipient automatically receives a license from the\n      original licensor to copy, distribute or modify the Program subject to\n      these terms and conditions.  You may not impose any further\n      restrictions on the recipients' exercise of the rights granted herein.\n      You are not responsible for enforcing compliance by third parties to\n      this License.\n      </p>\n\n      <p id=\"GPL_section7\">\n      <strong>7.</strong>\n       If, as a consequence of a court judgment or allegation of patent\n      infringement or for any other reason (not limited to patent issues),\n      conditions are imposed on you (whether by court order, agreement or\n      otherwise) that contradict the conditions of this License, they do not\n      excuse you from the conditions of this License.  If you cannot\n      distribute so as to satisfy simultaneously your obligations under this\n      License and any other pertinent obligations, then as a consequence you\n      may not distribute the Program at all.  For example, if a patent\n      license would not permit royalty-free redistribution of the Program by\n      all those who receive copies directly or indirectly through you, then\n      the only way you could satisfy both it and this License would be to\n      refrain entirely from distribution of the Program.\n      </p>\n\n      <p>\n      If any portion of this section is held invalid or unenforceable under\n      any particular circumstance, the balance of the section is intended to\n      apply and the section as a whole is intended to apply in other\n      circumstances.\n      </p>\n\n      <p>\n      It is not the purpose of this section to induce you to infringe any\n      patents or other property right claims or to contest validity of any\n      such claims; this section has the sole purpose of protecting the\n      integrity of the free software distribution system, which is\n      implemented by public license practices.  Many people have made\n      generous contributions to the wide range of software distributed\n      through that system in reliance on consistent application of that\n      system; it is up to the author/donor to decide if he or she is willing\n      to distribute software through any other system and a licensee cannot\n      impose that choice.\n      </p>\n\n      <p>\n      This section is intended to make thoroughly clear what is believed to\n      be a consequence of the rest of this License.\n      </p>\n\n      <p id=\"GPL_section8\">\n      <strong>8.</strong>\n       If the distribution and/or use of the Program is restricted in\n      certain countries either by patents or by copyrighted interfaces, the\n      original copyright holder who places the Program under this License\n      may add an explicit geographical distribution limitation excluding\n      those countries, so that distribution is permitted only in or among\n      countries not thus excluded.  In such case, this License incorporates\n      the limitation as if written in the body of this License.\n      </p>\n\n      <p id=\"GPL_section9\">\n      <strong>9.</strong>\n       The Free Software Foundation may publish revised and/or new versions\n      of the General Public License from time to time.  Such new versions will\n      be similar in spirit to the present version, but may differ in detail to\n      address new problems or concerns.\n      </p>\n\n      <p>\n      Each version is given a distinguishing version number.  If the Program\n      specifies a version number of this License which applies to it and \"any\n      later version\", you have the option of following the terms and conditions\n      either of that version or of any later version published by the Free\n      Software Foundation.  If the Program does not specify a version number of\n      this License, you may choose any version ever published by the Free Software\n      Foundation.\n      </p>\n\n      <p id=\"GPL_section10\">\n      <strong>10.</strong>\n       If you wish to incorporate parts of the Program into other free\n      programs whose distribution conditions are different, write to the author\n      to ask for permission.  For software which is copyrighted by the Free\n      Software Foundation, write to the Free Software Foundation; we sometimes\n      make exceptions for this.  Our decision will be guided by the two goals\n      of preserving the free status of all derivatives of our free software and\n      of promoting the sharing and reuse of software generally.\n      </p>\n\n      <p id=\"GPL_section11\"><strong>NO WARRANTY</strong></p>\n\n      <p>\n      <strong>11.</strong>\n       BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\n      FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\n      OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\n      PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\n      OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n      MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\n      TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\n      PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\n      REPAIR OR CORRECTION.\n      </p>\n\n      <p id=\"GPL_section12\">\n      <strong>12.</strong>\n       IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n      WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\n      REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\n      INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\n      OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\n      TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n      YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n      PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n      POSSIBILITY OF SUCH DAMAGES.\n      </p>\n\n      <h3>END OF TERMS AND CONDITIONS</h3>\n    </div><!-- for GPL content -->\n  </div>\n  </body>\n</html>\n"
  },
  {
    "path": "src/main/javadoc/overview.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<html>\n  <head>\n    <title>Open Source License Disclosure - Snowflake JDBC</title>\n  </head>\n  <body>\n    <hr>\n    <a href=\"licenses.html\">Open Source License Disclosure - Snowflake JDBC</a>\n  </body>\n</html>\n"
  },
  {
    "path": "src/main/resources/META-INF/com.boomi.Dependencies",
    "content": "extended_security\n"
  },
  {
    "path": "src/main/resources/META-INF/services/java.nio.file.spi.FileTypeDetector",
    "content": "net.snowflake.client.internal.core.FileTypeDetector\n"
  },
  {
    "path": "src/main/resources/META-INF/services/java.sql.Driver",
    "content": "net.snowflake.client.api.driver.SnowflakeDriver"
  },
  {
    "path": "src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages.properties",
    "content": "#\n# Exception messages.\n# Those error code are associated to symbols in ErrorCode.java\n#\n\n200001=JDBC driver internal error: {0}.\n200002=JDBC driver not able to connect to Snowflake. Error code: {0}, Message: {1}.\n200003=JDBC driver Interrupt exception encountered.\n200004=Copy command does not support compression type {0}.\n200005=JDBC driver: query has been canceled by user.\n200006=Copy command does not recognize compression type {0}.\n200007=Error encountered when listing file: {0}.\n200008=File not found.\\nCause: specified file does not exist: {0}\\nUsage: to \\\nspecify files, use \"file://\" prefix followed by path to files. A path can be \\\nrelative or absolute. Example: put file:///tmp/bla* @table.\n200009=File not found.\\nCause: specified file is a directory: {0}\\nUsage: to \\\nspecify files, use \"file://\" prefix followed by path to files. A path can be \\\nrelative or absolute. Example: put file:///tmp/bla* @table.\n200010=Connection property specified more than once: {0}\n200011=Missing user name.\n200012=Missing password.\n200013=S3 operation failed: Operation={0}, Error type={1}, Error code={2}, \\\nError message={3}, Request id={4}, Extended request id={5}\n200014=Maximum size for a query result has been exceeded.\n200015=JDBC driver encountered communication error. Message: {0}.\n200016=JDBC driver encountered IO error. Message: {0}.\n200017=Download location is not a directory: {0}.\n200018=Data type not supported for binding: {0}.\n200019=Client side sorting is not supported when the result is split into \\\n  multiple chunks.\n200020=AWS operation failed: Operation={0}, Error message={1}\n200021=Invalid SQL text: {0}\n200022=Bad response received from server. Response body: {0}. Possible causes \\\n  are network issues or server side internal errors.\n200023=Array bind for values with mixed types not supported. Previous type: {0}, \\\n  Current type: {1} at Column: {2}, Row: {3}.\n200024=Statement is closed.\n200025=Statement running a query already.\n200026=Missing server URL.\n200027=Number of session parameters has exceeded the supported limit ({0}).\n200028=Missing required connection property: {0}.\n200029=Invalid connection URL: {0}.\n200030=Statement parameter specified more than once: {0}.\n200031=Number of statement parameters has exceeded the supported limit: ({0}).\n200032=Invalid column index: {0}\n200033=Invalid parameter value type: {0}, expected type: {1}.\n200034=Row not found.\n200035=Feature unsupported: {0}.\n200036=Invalid state: {0}. A potential cause is closing of a connection when a \\\nquery is still running.\n200037=Result set has been closed.\n200038=Cannot convert value in the driver from type:{0} to type:{1}, value={2}.\n200039=The specified authenticator is not accepted by your Snowflake account \\\n  configuration.  Please contact your local system administrator to get the \\\n  correct URL to use.\n200040=Identity provider configuration for the specified authenticator does \\\n  not match with your Snowflake account configuration (destination URL \\\n  mismatch).  Please contact your local system administrator.\n200041=Connection property value {0} is invalid. Value specified by user: {1}, \\\n  returned by server: {2}.\n200042=Statement ''{0}'' cannot be executed using current API.\n200043=Statement ''{0}'' prepare failed. Result set metadata is missing.\n200044=Azure storage operation failed: Operation={0}, Error code={1}, Status code={2}, \\\nError message={3}, Extended error info={4}\n200045=Private key provided is invalid or not supported: {0}\n200046=Failed to generate jwt token.\n200047=Invalid parameter value {0} for parameter type {1}.\n200048=Query executed successfully, but the first statement returned an update \\\n  count (result set required).\n200049=Update executed successfully, but the first statement returned a result \\\n  set (update count required).\n200050=The number of child result ID's received ({0}) was different from the \\\n  number of child statement types ({1}).\n200052=Connection has been closed.\n200053=Non-Fatal incident: {0}.\n200054=Index value of {0} is out of bounds. Acceptable range: 1 to {1}.\n200055=No valid metadata available for binding parameters.\n200056=Invalid application name of {1}. Name must start with a letter and contain no special characters.\n200057=Only allowable value is application name.\n200058=Value is too large to be stored as integer at batch index {0}. Use executeLargeBatch() instead.\n200059=Invalid Connect String: {0}.\n200061=GCS operation failed: Operation={0}, Error code={1}, Message={2}, Reason={3}\n200062=Authentication timed out.\n200063=Invalid data - Cannot be parsed and converted to structured type.\n200064=The values for 'disableOCSPChecks' and 'insecureMode' must be identical.\n200065=Too many files to download as stream\n200066=JDBC driver file operation error while performing stage upload.\n200067=JDBC driver file operation error while performing stage download.\n200068=Error during OAuth Authorization Code authentication: {0}\n200069=Error during OAuth Client Credentials authentication: {0}\n200070=Error during obtaining OAuth access token using refresh token: {0}\n200071=Error during Workload Identity authentication: {0}\n200072=MFA enabled in Okta is not supported with this authenticator type. \\\n  Please use 'externalbrowser' instead or a different authentication method.\n200073=Invalid certificate revocation mode: {0}.\n200074=OCSP and certificate revocation mode checks cannot be enabled at the same time.\n253000=Error during file transfer: {0}\n253003=Error during file upload to stage: {0}\n253002=Error during file download to stage: {0}\n254000=Error during OCSP validation: {0}\n290000=Error during HTTP request: {0}\n"
  },
  {
    "path": "src/main/resources/net/snowflake/client/jdbc/jdbc_error_messages_fr.properties",
    "content": "#\n# Exception messages\n#\n\n"
  },
  {
    "path": "src/main/resources/net/snowflake/client/jdbc/version.properties",
    "content": "version=${project.version}\nslf4j.version=${slf4j.version}"
  },
  {
    "path": "src/main/resources-fat-jar/META-INF/services/org.slf4j.spi.SLF4JServiceProvider",
    "content": "net.snowflake.client.internal.log.SFBridgeServiceProvider\n"
  },
  {
    "path": "src/test/java/com/snowflake/client/jdbc/SnowflakeDriverIT.java",
    "content": "package com.snowflake.client.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class SnowflakeDriverIT extends AbstractDriverIT {\n\n  @Test\n  public void testConnection() throws SQLException {\n    Connection con = getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, false, true);\n    con.close();\n    assertTrue(con.isClosed());\n    con.close(); // ensure no exception\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/.gitignore",
    "content": "ingest\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/AbstractDriverIT.java",
    "content": "package net.snowflake.client;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.google.common.base.Strings;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Paths;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Timestamp;\nimport java.util.Calendar;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport javax.annotation.Nullable;\n\n/** Base test class with common constants, data structures and methods */\npublic class AbstractDriverIT {\n\n  public static final String DRIVER_CLASS = \"net.snowflake.client.api.driver.SnowflakeDriver\";\n  public static final String DRIVER_CLASS_COM = \"com.snowflake.client.jdbc.SnowflakeDriver\";\n  public static final int DONT_INJECT_SOCKET_TIMEOUT = 0;\n\n  // data files\n  protected static final String TEST_DATA_FILE = \"orders_100.csv\";\n  protected static final String TEST_DATA_FILE_2 = \"orders_101.csv\";\n\n  protected static final String[] fileNames = {TEST_DATA_FILE, TEST_DATA_FILE_2};\n\n  private static Logger logger = Logger.getLogger(AbstractDriverIT.class.getName());\n\n  protected final int ERROR_CODE_BIND_VARIABLE_NOT_ALLOWED_IN_VIEW_OR_UDF_DEF = 2210;\n\n  private static String getConnPropValueFromEnv(String connectionType, String propKey) {\n    String envKey = String.format(\"SNOWFLAKE_%s_%s\", connectionType, propKey);\n    return TestUtil.systemGetEnv(envKey);\n  }\n\n  public static Map<String, String> getConnectionParameters(String accountName) {\n    return getConnectionParameters(accountName, \"TEST\");\n  }\n\n  /**\n   * getConnectionParameters is to obtain connection params from Env\n   *\n   * @param accountName the connection could be different with different accounts\n   * @param connectionType use connectionType is either \"TEST\"(default) or \"ORG\"\n   * @return properties' key-value map -- In the connection json files the parameters' format is\n   *     like below and these key/values have been flattened to a bunch of env variables of these\n   *     junit tests.\n   *     <p>\"testconnection\": { \"SNOWFLAKE_TEST_ACCOUNT\": \"...\", ... \"SNOWFLAKE_TEST_ROLE\": \"..\" },\n   *     }\n   */\n  public static Map<String, String> getConnectionParameters(\n      String accountName, String connectionType) {\n    Map<String, String> params = new HashMap<>();\n    String account;\n    String host;\n\n    if (accountName == null) {\n      account = getConnPropValueFromEnv(connectionType, \"ACCOUNT\");\n      host = getConnPropValueFromEnv(connectionType, \"HOST\");\n    } else {\n      account = accountName;\n      // By default, the test will run against reg deployment.\n      // If developer needs to run in IntelliJ, you can set this env as \".dev.local\"\n      String deployment = getConnPropValueFromEnv(connectionType, \"DEPLOYMENT\");\n      if (Strings.isNullOrEmpty(deployment)) {\n        deployment = \".reg.local\";\n      }\n      host = accountName.trim() + deployment;\n    }\n    assertThat(\n        \"set SNOWFLAKE_TEST_ACCOUNT environment variable to the account name.\",\n        !Strings.isNullOrEmpty(account));\n    params.put(\"account\", account);\n\n    if (Strings.isNullOrEmpty(host)) {\n      host = account + \".snowflakecomputing.com\";\n    }\n\n    assertThat(\n        \"set SNOWFLAKE_TEST_HOST environment variable to the host name.\",\n        !Strings.isNullOrEmpty(host));\n    params.put(\"host\", host);\n\n    String protocol = getConnPropValueFromEnv(connectionType, \"PROTOCOL\");\n    String ssl;\n    if (\"http\".equals(protocol)) {\n      ssl = \"off\";\n    } else {\n      ssl = \"on\";\n    }\n    params.put(\"ssl\", ssl);\n\n    String user = getConnPropValueFromEnv(connectionType, \"USER\");\n    assertThat(\"set SNOWFLAKE_TEST_USER environment variable.\", !Strings.isNullOrEmpty(user));\n    params.put(\"user\", user);\n\n    String privateKeyFile = getConnPropValueFromEnv(connectionType, \"PRIVATE_KEY_FILE\");\n    if (!Strings.isNullOrEmpty(privateKeyFile)) {\n      String workspace = System.getenv(\"WORKSPACE\");\n      if (workspace != null) {\n        params.put(\n            \"private_key_file\", java.nio.file.Paths.get(workspace, privateKeyFile).toString());\n      } else {\n        params.put(\"private_key_file\", privateKeyFile);\n      }\n      params.put(\"authenticator\", \"SNOWFLAKE_JWT\");\n\n      String privateKeyPwd = getConnPropValueFromEnv(connectionType, \"PRIVATE_KEY_PWD\");\n      if (!Strings.isNullOrEmpty(privateKeyPwd)) {\n        params.put(\"private_key_pwd\", privateKeyPwd);\n      }\n\n    } else {\n      String password = getConnPropValueFromEnv(connectionType, \"PASSWORD\");\n      if (!Strings.isNullOrEmpty(password)) {\n        params.put(\"password\", password);\n      } else {\n        throw new IllegalStateException(\n            \"Neither SNOWFLAKE_TEST_PRIVATE_KEY_FILE nor SNOWFLAKE_TEST_PASSWORD environment variable is set. Please configure one of them for authentication.\");\n      }\n    }\n\n    String port = getConnPropValueFromEnv(connectionType, \"PORT\");\n    if (Strings.isNullOrEmpty(port)) {\n      if (\"on\".equals(ssl)) {\n        port = \"443\";\n      } else {\n        port = \"80\";\n      }\n    }\n    assertThat(\"set SNOWFLAKE_TEST_PORT environment variable.\", !Strings.isNullOrEmpty(port));\n    params.put(\"port\", port);\n\n    String database = getConnPropValueFromEnv(connectionType, \"DATABASE\");\n    assertThat(\n        \"set SNOWFLAKE_TEST_DATABASE environment variable.\", !Strings.isNullOrEmpty(database));\n    params.put(\"database\", database);\n\n    String schema = getConnPropValueFromEnv(connectionType, \"SCHEMA\");\n    assertThat(\"set SNOWFLAKE_TEST_SCHEMA environment variable.\", !Strings.isNullOrEmpty(schema));\n    params.put(\"schema\", schema);\n\n    String role = getConnPropValueFromEnv(connectionType, \"ROLE\");\n    assertThat(\"set SNOWFLAKE_TEST_ROLE environment variable.\", !Strings.isNullOrEmpty(role));\n    params.put(\"role\", role);\n\n    String warehouse = getConnPropValueFromEnv(connectionType, \"WAREHOUSE\");\n    assertThat(\n        \"set SNOWFLAKE_TEST_WAREHOUSE environment variable.\", !Strings.isNullOrEmpty(warehouse));\n    params.put(\"warehouse\", warehouse);\n\n    params.put(\"uri\", String.format(\"jdbc:snowflake://%s:%s\", host, port));\n\n    String adminUser = getConnPropValueFromEnv(connectionType, \"ADMIN_USER\");\n    params.put(\"adminUser\", adminUser);\n\n    String adminPassword = getConnPropValueFromEnv(connectionType, \"ADMIN_PASSWORD\");\n    params.put(\"adminPassword\", adminPassword);\n\n    String ssoUser = getConnPropValueFromEnv(connectionType, \"SSO_USER\");\n    params.put(\"ssoUser\", ssoUser);\n\n    String ssoPassword = getConnPropValueFromEnv(connectionType, \"SSO_PASSWORD\");\n    params.put(\"ssoPassword\", ssoPassword);\n\n    return params;\n  }\n\n  public static Map<String, String> getConnectionParameters() {\n    return getConnectionParameters(null);\n  }\n\n  /**\n   * Gets a connection with default session parameter settings, but tunable query api version and\n   * socket timeout setting\n   *\n   * @param paramProperties connection properties\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(Properties paramProperties) throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, paramProperties, false, false);\n  }\n\n  /**\n   * Gets a connection with custom account name, but otherwise default settings\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(String accountName) throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, false, false, accountName);\n  }\n\n  /**\n   * Gets a connection with custom account name and some property set, useful for testing property\n   * on specific account\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(String accountName, Properties paramProperties)\n      throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, paramProperties, false, false, accountName);\n  }\n\n  /**\n   * Gets a connection with default settings\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection() throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, false, false);\n  }\n\n  /**\n   * Gets a connection with default session parameter settings, but tunable query api version and\n   * socket timeout setting\n   *\n   * @param injectSocketTimeout number of seconds to inject in connection\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(int injectSocketTimeout) throws SQLException {\n    return getConnection(injectSocketTimeout, null, false, false);\n  }\n\n  /**\n   * Gets a connection with Snowflake admin\n   *\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  protected static Connection getSnowflakeAdminConnection() throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, null, true, false);\n  }\n\n  /**\n   * Gets a connection with Snowflake admin\n   *\n   * @param paramProperties connection properties\n   * @return Connection a database connection\n   * @throws SQLException raised if any error occurs\n   */\n  protected static Connection getSnowflakeAdminConnection(Properties paramProperties)\n      throws SQLException {\n    return getConnection(DONT_INJECT_SOCKET_TIMEOUT, paramProperties, true, false);\n  }\n\n  /**\n   * Gets a connection in same way as function below but with default account (gotten from\n   * environment variables)\n   *\n   * @param injectSocketTimeout\n   * @param paramProperties\n   * @param isAdmin\n   * @param usesCom\n   * @return\n   * @throws SQLException\n   */\n  public static Connection getConnection(\n      int injectSocketTimeout, Properties paramProperties, boolean isAdmin, boolean usesCom)\n      throws SQLException {\n    return getConnection(injectSocketTimeout, paramProperties, isAdmin, usesCom, null);\n  }\n\n  /**\n   * Gets a connection for the custom session parameter settings and tunable query api version and\n   * socket timeout setting\n   *\n   * @param injectSocketTimeout number of seconds to inject in connection\n   * @param paramProperties connection properties\n   * @param isAdmin is Snowflake admin user?\n   * @param usesCom uses com.snowflake instead of net.snowflake?\n   * @return Connection database connection\n   * @throws SQLException raised if any error occurs\n   */\n  public static Connection getConnection(\n      int injectSocketTimeout,\n      @Nullable Properties paramProperties,\n      boolean isAdmin,\n      boolean usesCom,\n      String accountName)\n      throws SQLException {\n    // Load Snowflake JDBC class\n    String driverClass = DRIVER_CLASS;\n    if (usesCom) {\n      driverClass = DRIVER_CLASS_COM;\n    }\n    try {\n      Class.forName(driverClass);\n    } catch (Exception e) {\n      logger.log(Level.SEVERE, \"Cannot find Driver\", e);\n      throw new RuntimeException(e.getCause());\n    }\n    Map<String, String> params = getConnectionParameters(accountName);\n\n    // build connection properties\n    Properties properties = new Properties();\n    if (isAdmin) {\n      assertThat(\n          \"set SNOWFLAKE_TEST_ADMIN_USER environment variable.\",\n          !Strings.isNullOrEmpty(params.get(\"adminUser\")));\n      assertThat(\n          \"set SNOWFLAKE_TEST_ADMIN_PASSWORD environment variable.\",\n          !Strings.isNullOrEmpty(params.get(\"adminPassword\")));\n\n      properties.put(\"user\", params.get(\"adminUser\"));\n      properties.put(\"password\", params.get(\"adminPassword\"));\n      properties.put(\"role\", \"accountadmin\");\n      properties.put(\"account\", \"snowflake\");\n    } else {\n      properties.put(\"user\", params.get(\"user\"));\n      properties.put(\"role\", params.get(\"role\"));\n      properties.put(\"account\", params.get(\"account\"));\n\n      if (!Strings.isNullOrEmpty(params.get(\"private_key_file\"))) {\n        properties.put(\"private_key_file\", params.get(\"private_key_file\"));\n        properties.put(\"authenticator\", params.get(\"authenticator\"));\n        if (!Strings.isNullOrEmpty(params.get(\"private_key_pwd\"))) {\n          properties.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n        }\n      } else if (!Strings.isNullOrEmpty(params.get(\"password\"))) {\n        properties.put(\"password\", params.get(\"password\"));\n      }\n    }\n    properties.put(\"db\", params.get(\"database\"));\n    properties.put(\"schema\", params.get(\"schema\"));\n    properties.put(\"warehouse\", params.get(\"warehouse\"));\n    properties.put(\"ssl\", params.get(\"ssl\"));\n\n    properties.put(\"internal\", Boolean.TRUE.toString()); // TODO: do we need this?\n    properties.put(\"insecureMode\", false); // use OCSP for all tests.\n\n    if (injectSocketTimeout > 0) {\n      properties.put(\"injectSocketTimeout\", String.valueOf(injectSocketTimeout));\n    }\n\n    // Set the session parameter properties\n    if (paramProperties != null) {\n      for (Map.Entry<?, ?> entry : paramProperties.entrySet()) {\n        properties.put(entry.getKey(), entry.getValue());\n      }\n    }\n    String uri =\n        properties.getProperty(\"host\") != null && properties.getProperty(\"port\") != null\n            ? String.format(\n                \"jdbc:snowflake://%s%s:%s\",\n                properties.getProperty(\"protocol\"),\n                properties.getProperty(\"host\"),\n                properties.getProperty(\"port\"))\n            : params.get(\"uri\");\n    return DriverManager.getConnection(uri, properties);\n  }\n\n  /**\n   * Close SQL Objects\n   *\n   * @param resultSet a result set object\n   * @param statement a statement object\n   * @param connection a connection\n   * @throws SQLException raised if any error occurs\n   */\n  public void closeSQLObjects(ResultSet resultSet, Statement statement, Connection connection)\n      throws SQLException {\n    if (resultSet != null) {\n      resultSet.close();\n    }\n    if (statement != null) {\n      statement.close();\n    }\n    if (connection != null) {\n      connection.close();\n    }\n  }\n\n  /**\n   * Close SQL Objects\n   *\n   * @param statement a statement object\n   * @param connection a connection\n   * @throws SQLException raised if any error occurs\n   */\n  public void closeSQLObjects(Statement statement, Connection connection) throws SQLException {\n    if (statement != null) {\n      statement.close();\n    }\n    if (connection != null) {\n      connection.close();\n    }\n  }\n\n  /**\n   * Get a full path of the file in Resource\n   *\n   * @param fileName a file name\n   * @return a full path name of the file\n   */\n  public static String getFullPathFileInResource(String fileName) {\n    ClassLoader classLoader = AbstractDriverIT.class.getClassLoader();\n    URL url = classLoader.getResource(fileName);\n    if (url != null) {\n      try {\n        return Paths.get(url.toURI()).toAbsolutePath().toString();\n      } catch (URISyntaxException ex) {\n        throw new RuntimeException(\"Unable to get absolute path: \" + fileName);\n      }\n    } else {\n      throw new RuntimeException(\"No file is found: \" + fileName);\n    }\n  }\n\n  public static void connectAndVerifySimpleQuery(Properties props) throws SQLException {\n    try (Connection con =\n            DriverManager.getConnection(\n                String.format(\"jdbc:snowflake://%s:%s\", props.get(\"host\"), props.get(\"port\")),\n                props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n  }\n\n  protected static Timestamp buildTimestamp(\n      int year, int month, int day, int hour, int minute, int second, int fractionInNanoseconds) {\n    Calendar cal = Calendar.getInstance();\n    cal.set(year, month, day, hour, minute, second);\n    Timestamp ts = new Timestamp(cal.getTime().getTime());\n    ts.setNanos(fractionInNanoseconds);\n    return ts;\n  }\n\n  protected static Date buildDate(int year, int month, int day) {\n    Calendar cal = Calendar.getInstance();\n    cal.set(year, month, day, 0, 0, 0);\n    cal.set(Calendar.MILLISECOND, 0);\n    return new Date(cal.getTime().getTime());\n  }\n\n  protected static Date buildDateWithTZ(int year, int month, int day, TimeZone tz) {\n    Calendar cal = Calendar.getInstance();\n    cal.setTimeZone(tz);\n    cal.set(year, month, day, 0, 0, 0);\n    cal.set(Calendar.MILLISECOND, 0);\n    return new Date(cal.getTime().getTime());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/AssumptionUtils.java",
    "content": "package net.snowflake.client;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assumptions.assumeFalse;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\nimport net.snowflake.client.internal.core.Constants;\n\npublic class AssumptionUtils {\n  public static void assumeNotRunningOnGithubActionsMac() {\n    assumeFalse(isRunningOnGithubActions() && Constants.getOS() == Constants.OS.MAC);\n  }\n\n  public static void assumeNotRunningOnJava8() {\n    assumeFalse(systemGetProperty(\"java.version\").startsWith(\"1.8.0\"));\n  }\n\n  public static void assumeNotRunningOnJava21() {\n    assumeFalse(systemGetProperty(\"java.version\").startsWith(\"21.\"));\n  }\n\n  public static void assumeRunningOnGithubActions() {\n    assumeTrue(isRunningOnGithubActions());\n  }\n\n  public static boolean isRunningOnGithubActions() {\n    return TestUtil.systemGetEnv(\"GITHUB_ACTIONS\") != null;\n  }\n\n  public static void assumeRunningOnLinuxMac() {\n    assumeTrue(Constants.getOS() == Constants.OS.LINUX || Constants.getOS() == Constants.OS.MAC);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/SystemPropertyOverrider.java",
    "content": "package net.snowflake.client;\n\npublic class SystemPropertyOverrider {\n  private final String propertyName;\n  private final String oldValue;\n\n  public SystemPropertyOverrider(String propertyName, String newValue) {\n    this.propertyName = propertyName;\n    this.oldValue = System.getProperty(propertyName);\n    System.setProperty(propertyName, newValue);\n  }\n\n  public void rollback() {\n    if (oldValue != null) {\n      System.setProperty(propertyName, oldValue);\n    } else {\n      System.clearProperty(propertyName);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/TestUtil.java",
    "content": "package net.snowflake.client;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.matchesPattern;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.UUID;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.function.Consumer;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.hamcrest.MatcherAssert;\n\npublic class TestUtil {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(TestUtil.class);\n\n  private static final Pattern QUERY_ID_REGEX =\n      Pattern.compile(\"[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}\");\n\n  public static final String GENERATED_SCHEMA_PREFIX = \"GENERATED_\";\n  public static final String ESCAPED_GENERATED_SCHEMA_PREFIX =\n      GENERATED_SCHEMA_PREFIX.replaceAll(\"_\", \"\\\\\\\\_\");\n\n  private static final List<String> schemaGeneratedInTestsPrefixes =\n      Arrays.asList(\n          GENERATED_SCHEMA_PREFIX,\n          \"GITHUB_\", // created by JDBC CI jobs before tests\n          \"GH_JOB_\", // created by other drivers tests e.g. Python\n          \"JDBCPERF\", // created in JDBC perf tests\n          \"SCHEMA_\" // created by other drivers tests e.g. Scala\n          );\n\n  public static boolean isSchemaGeneratedInTests(String schema) {\n    return schemaGeneratedInTestsPrefixes.stream().anyMatch(prefix -> schema.startsWith(prefix));\n  }\n\n  /**\n   * Util function to assert a piece will throw exception and assert on the error code\n   *\n   * @param errorCode expected error code\n   * @param testCode the code that will run and throws exception\n   */\n  public static void assertSFException(int errorCode, TestRunInterface testCode) {\n    SFException e = assertThrows(SFException.class, testCode::run);\n    assertThat(e.getVendorCode(), is(errorCode));\n  }\n\n  /** Functional interface used to run a piece of code which throws SFException */\n  @FunctionalInterface\n  public interface TestRunInterface {\n    void run() throws SFException;\n  }\n\n  /**\n   * System.getenv wrapper. If System.getenv raises a SecurityException, it is ignored and returns\n   * null.\n   *\n   * @deprecated This method should be replaced by SnowflakeUtil.systemGetEnv.\n   *     <p>This is replicated from SnowflakeUtil.systemGetEnv, because the old driver doesn't have\n   *     that function for the tests to use it. Replace this function call with\n   *     SnowflakeUtil.systemGetEnv when it is available.\n   * @param env the environment variable name.\n   * @return the environment variable value if set, otherwise null.\n   */\n  @Deprecated\n  public static String systemGetEnv(String env) {\n    try {\n      return System.getenv(env);\n    } catch (SecurityException ex) {\n      logger.debug(\n          \"Failed to get environment variable {}. Security exception raised: {}\",\n          env,\n          ex.getMessage());\n    }\n    return null;\n  }\n\n  public static void assertValidQueryId(String queryId) {\n    assertNotNull(queryId);\n    MatcherAssert.assertThat(\n        \"Expecting \" + queryId + \" is a valid UUID\", queryId, matchesPattern(QUERY_ID_REGEX));\n  }\n\n  /**\n   * Creates schema and deletes it at the end of the passed function execution\n   *\n   * @param statement statement\n   * @param schemaName name of schema to create and delete after lambda execution\n   * @param action action to execute when schema was created\n   * @throws Exception when any error occurred\n   */\n  public static void withSchema(Statement statement, String schemaName, ThrowingRunnable action)\n      throws Exception {\n    try {\n      statement.execute(\"CREATE OR REPLACE SCHEMA \" + schemaName);\n      action.run();\n    } finally {\n      statement.execute(\"DROP SCHEMA \" + schemaName);\n    }\n  }\n\n  /**\n   * Creates schema and deletes it at the end of the passed function execution\n   *\n   * @param statement statement\n   * @param action action to execute when schema was created\n   * @throws Exception when any error occurred\n   */\n  public static void withRandomSchema(\n      Statement statement, ThrowingConsumer<String, Exception> action) throws Exception {\n    String customSchema =\n        GENERATED_SCHEMA_PREFIX + SnowflakeUtil.randomAlphaNumeric(5).toUpperCase();\n    try {\n      statement.execute(\"CREATE OR REPLACE SCHEMA \" + customSchema);\n      action.accept(customSchema);\n    } finally {\n      statement.execute(\"DROP SCHEMA \" + customSchema);\n    }\n  }\n\n  public interface MethodRaisesSQLException {\n    void run() throws SQLException;\n  }\n\n  public static void expectSnowflakeLoggedFeatureNotSupportedException(MethodRaisesSQLException f) {\n    SQLException ex = assertThrows(SQLException.class, f::run);\n    assertEquals(ex.getClass().getSimpleName(), \"SnowflakeLoggedFeatureNotSupportedException\");\n  }\n\n  /**\n   * Compares two string values both values are cleaned of whitespaces\n   *\n   * @param expected expected value\n   * @param actual actual value\n   */\n  public static void assertEqualsIgnoringWhitespace(String expected, String actual) {\n    assertEquals(expected.replaceAll(\"\\\\s+\", \"\"), actual.replaceAll(\"\\\\s+\", \"\"));\n  }\n\n  public static String randomTableName(String jiraId) {\n    return (\"TEST_\" + (jiraId != null ? jiraId : \"\") + \"_\" + UUID.randomUUID())\n        .replaceAll(\"-\", \"_\");\n  }\n\n  public static List<Integer> randomIntList(int length, int modulo) {\n    return new Random()\n        .ints()\n        .limit(length)\n        .mapToObj(i -> Math.abs(i) % modulo)\n        .collect(Collectors.toList());\n  }\n\n  public static CompletableFuture<Void> asyncAssert(\n      ExecutorService executor, Callable<Integer> supplier, Consumer<Integer> assertion) {\n    return CompletableFuture.supplyAsync(\n            () -> {\n              try {\n                return supplier.call();\n              } catch (Exception e) {\n                throw new RuntimeException(e);\n              }\n            },\n            executor)\n        .thenAccept(assertion);\n  }\n\n  // this allows exact metadata searches instead of pattern matching\n  public static String escapeUnderscore(String input) {\n    return input.replace(\"_\", \"\\\\_\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/ThrowingConsumer.java",
    "content": "package net.snowflake.client;\n\n@FunctionalInterface\npublic interface ThrowingConsumer<A, T extends Throwable> {\n  void accept(A parameter) throws T;\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/ThrowingRunnable.java",
    "content": "package net.snowflake.client;\n\n@FunctionalInterface\npublic interface ThrowingRunnable {\n  void run() throws Exception;\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnGithubActions.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledIfEnvironmentVariable(named = \"GITHUB_ACTIONS\", matches = \".*\")\npublic @interface DontRunOnGithubActions {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnJava21.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledOnJre;\nimport org.junit.jupiter.api.condition.JRE;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledOnJre(JRE.JAVA_21)\npublic @interface DontRunOnJava21 {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnJava8.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledOnJre;\nimport org.junit.jupiter.api.condition.JRE;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledOnJre(JRE.JAVA_8)\npublic @interface DontRunOnJava8 {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnJenkins.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledIfEnvironmentVariable(named = \"SNOWFLAKE_TEST_HOST\", matches = \".*.reg.local\")\npublic @interface DontRunOnJenkins {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnTestaccount.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledIfEnvironmentVariable(named = \"SNOWFLAKE_TEST_ACCOUNT\", matches = \"testaccount\")\npublic @interface DontRunOnTestaccount {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnThinJar.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledIfEnvironmentVariable(named = \"ADDITIONAL_MAVEN_PROFILE\", matches = \"-Dthin-jar\")\npublic @interface DontRunOnThinJar {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/DontRunOnWindows.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@DisabledOnOs(OS.WINDOWS)\npublic @interface DontRunOnWindows {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnAWS.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledIfEnvironmentVariable(named = \"CLOUD_PROVIDER\", matches = \"(?i)AWS(?-i)\")\npublic @interface RunOnAWS {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnAzure.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledIfEnvironmentVariable(named = \"CLOUD_PROVIDER\", matches = \"(?i)Azure(?-i)\")\npublic @interface RunOnAzure {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnGCP.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\n@Target({ElementType.METHOD, ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledIfEnvironmentVariable(named = \"CLOUD_PROVIDER\", matches = \"(?i)GCP(?-i)\")\npublic @interface RunOnGCP {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnGithubActionsNotMac.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledOnOs;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledIfEnvironmentVariable(named = \"GITHUB_ACTIONS\", matches = \".*\")\n@DisabledOnOs(OS.MAC)\npublic @interface RunOnGithubActionsNotMac {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnLinux.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledOnOs({OS.LINUX, OS.AIX})\npublic @interface RunOnLinux {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnLinuxOrMac.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledOnOs({OS.MAC, OS.LINUX, OS.AIX})\npublic @interface RunOnLinuxOrMac {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnMac.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledOnOs(OS.MAC)\npublic @interface RunOnMac {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnTestaccountNotOnGithubActions.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledIfEnvironmentVariable(named = \"SNOWFLAKE_TEST_ACCOUNT\", matches = \"testaccount\")\n@DisabledIfEnvironmentVariable(named = \"GITHUB_ACTIONS\", matches = \".*\")\npublic @interface RunOnTestaccountNotOnGithubActions {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnWindows.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledOnOs(OS.WINDOWS)\npublic @interface RunOnWindows {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/annotations/RunOnWindowsOrMac.java",
    "content": "package net.snowflake.client.annotations;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@EnabledOnOs({OS.WINDOWS, OS.MAC})\npublic @interface RunOnWindowsOrMac {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/api/driver/SnowflakeDriverTest.java",
    "content": "package net.snowflake.client.api.driver;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport org.junit.jupiter.api.Test;\n\n/** Test class for SnowflakeDriver methods. */\npublic class SnowflakeDriverTest {\n\n  @Test\n  public void testStaticVersionMatchesManifest() {\n    String manifestVersion =\n        SnowflakeDriver.versionResourceBundleManager.getLocalizedMessage(\"version\");\n    assertNotNull(manifestVersion, \"Manifest version should not be null\");\n\n    // Remove -SNAPSHOT suffix if present for comparison\n    String normalizedManifestVersion = manifestVersion.replace(\"-SNAPSHOT\", \"\");\n    assertEquals(\n        SnowflakeDriver.getImplementationVersion(),\n        normalizedManifestVersion,\n        \"Static version should match manifest version\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/api/exception/SqlFeatureNotSupportedTelemetryTest.java",
    "content": "package net.snowflake.client.api.exception;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.internal.jdbc.telemetry.SqlExceptionTelemetryHandler;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport org.junit.jupiter.api.Test;\n\npublic class SqlFeatureNotSupportedTelemetryTest {\n\n  String queryId = \"test-query-idfake\";\n  String SQLState = \"00000\";\n  int vendorCode = 27;\n  String driverVersion = SnowflakeDriver.getImplementationVersion();\n\n  String comparison =\n      \"{\\\"type\\\":\\\"client_sql_exception\\\",\\\"DriverType\\\":\\\"JDBC\\\",\\\"DriverVersion\\\":\\\"\"\n          + driverVersion\n          + \"\\\",\"\n          + \"\\\"QueryID\\\":\\\"\"\n          + queryId\n          + \"\\\",\\\"SQLState\\\":\\\"\"\n          + SQLState\n          + \"\\\",\\\"ErrorNumber\\\":\"\n          + vendorCode\n          + \"}\";\n\n  /** Test that creating in-band objectNode looks as expected */\n  @Test\n  public void testCreateIBValue() {\n    ObjectNode ibValue =\n        TelemetryUtil.createIBValue(\n            queryId, SQLState, vendorCode, TelemetryField.SQL_EXCEPTION, null, null);\n    assertEquals(comparison, ibValue.toString());\n  }\n\n  /** Test that creating out-of-band JSONObject contains all attributes it needs */\n  @Test\n  public void testCreateOOBValue() {\n    JSONObject oobValue =\n        SqlExceptionTelemetryHandler.createOOBValue(queryId, SQLState, vendorCode);\n    assertEquals(\"client_sql_exception\", oobValue.get(\"type\").toString());\n    assertEquals(\"JDBC\", oobValue.get(\"DriverType\").toString());\n    assertEquals(driverVersion, oobValue.get(\"DriverVersion\").toString());\n    assertEquals(queryId, oobValue.get(\"QueryID\").toString());\n    assertEquals(SQLState, oobValue.get(\"SQLState\").toString());\n    assertEquals(vendorCode, oobValue.get(\"ErrorNumber\"));\n  }\n\n  @Test\n  public void testMaskStacktrace() {\n    // Unmasked stacktrace containing reason for failure after the exception type\n    String snowflakeSQLStacktrace =\n        \"net.snowflake.client.internal.exception.SnowflakeSQLLoggedException: This is a test exception.\\n\"\n            + \"\\tat net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryServiceIT.generateDummyException(TelemetryServiceIT.java:211)\\n\";\n    // Masked stacktrace with reason removed\n    String maskedSnowflakeSQLStacktrace =\n        \"net.snowflake.client.internal.exception.SnowflakeSQLLoggedException\\n\"\n            + \"\\tat net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryServiceIT.generateDummyException(TelemetryServiceIT.java:211)\\n\";\n\n    // Sometimes reason can be multiple lines\n    String multipleLineReasonMessage =\n        \"net.snowflake.client.api.exception.SnowflakeSQLException: Error parsing JSON: {\\\"dsadas\\n\"\n            + \"adsa\\\":12311}\\n\"\n            + \"  File 'VvCSoHWHrB/0.CSV.gz', line 1, character 0\\n\"\n            + \"  Row 1, column \\\"SPARK_TEST_TABLE_8417843441957284451\\\"[\\\"VAR\\\":1]\\n\"\n            + \"  If you would like to continue loading when an error is encountered, use other values such as \"\n            + \"'SKIP_FILE' or 'CONTINUE' for the ON_ERROR option. For more information on loading options, please \"\n            + \"run 'info loading_data' in a SQL client.\\n\"\n            + \"\\tat net.snowflake.client.internal.jdbc.SnowflakeUtil.checkErrorAndThrowExceptionSub(SnowflakeUtil.java:124)\\n\";\n\n    String maskedMultipleLineReasonMessage =\n        \"net.snowflake.client.api.exception.SnowflakeSQLException\\n\"\n            + \"\\tat net.snowflake.client.internal.jdbc.SnowflakeUtil.checkErrorAndThrowExceptionSub(SnowflakeUtil.java:124)\\n\";\n\n    assertEquals(\n        maskedSnowflakeSQLStacktrace,\n        SqlExceptionTelemetryHandler.maskStacktrace(snowflakeSQLStacktrace));\n\n    // Unmasked stacktrace for SQLFeatureNotSupportedException. Contains reason as well\n    String featureNotSupportedStacktrace =\n        \"net.snowflake.client.api.exception.SnowflakeLoggedFeatureNotSupportedException: Not supported!\\n\"\n            + \"\\tat net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl.execute(SnowflakeStatementImpl.java:344)\\n\";\n\n    // Masked stacktrace\n    String maskedFeatureNotSupportedStacktrace =\n        \"net.snowflake.client.api.exception.SnowflakeLoggedFeatureNotSupportedException\\n\"\n            + \"\\tat net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl.execute(SnowflakeStatementImpl.java:344)\\n\";\n\n    assertEquals(\n        maskedFeatureNotSupportedStacktrace,\n        SqlExceptionTelemetryHandler.maskStacktrace(featureNotSupportedStacktrace));\n\n    assertEquals(\n        maskedMultipleLineReasonMessage,\n        SqlExceptionTelemetryHandler.maskStacktrace(multipleLineReasonMessage));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/api/pooling/LogicalConnectionAlreadyClosedLatestIT.java",
    "content": "package net.snowflake.client.api.pooling;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Map;\nimport javax.sql.PooledConnection;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class LogicalConnectionAlreadyClosedLatestIT extends BaseJDBCTest {\n\n  @Test\n  public void testLogicalConnectionAlreadyClosed() throws SQLException {\n    Map<String, String> properties = getConnectionParameters();\n\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n\n    poolDataSource.setUrl(properties.get(\"uri\"));\n    poolDataSource.setPortNumber(Integer.parseInt(properties.get(\"port\")));\n    poolDataSource.setSsl(\"on\".equals(properties.get(\"ssl\")));\n    poolDataSource.setAccount(properties.get(\"account\"));\n    poolDataSource.setUser(properties.get(\"user\"));\n\n    // Use private key authentication if available, otherwise password\n    if (properties.get(\"private_key_file\") != null\n        && !properties.get(\"private_key_file\").isEmpty()) {\n      poolDataSource.setPrivateKeyFile(\n          properties.get(\"private_key_file\"), properties.get(\"private_key_pwd\"));\n    } else {\n      poolDataSource.setPassword(properties.get(\"password\"));\n    }\n\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    Connection logicalConnection = pooledConnection.getConnection();\n    logicalConnection.close();\n\n    expectConnectionAlreadyClosedException(logicalConnection::getMetaData);\n    expectConnectionAlreadyClosedException(logicalConnection::getAutoCommit);\n    expectConnectionAlreadyClosedException(logicalConnection::commit);\n    expectConnectionAlreadyClosedException(logicalConnection::rollback);\n    expectConnectionAlreadyClosedException(logicalConnection::isReadOnly);\n    expectConnectionAlreadyClosedException(logicalConnection::getCatalog);\n    expectConnectionAlreadyClosedException(logicalConnection::getSchema);\n    expectConnectionAlreadyClosedException(logicalConnection::getTransactionIsolation);\n    expectConnectionAlreadyClosedException(logicalConnection::getWarnings);\n    expectConnectionAlreadyClosedException(logicalConnection::clearWarnings);\n    expectConnectionAlreadyClosedException(() -> logicalConnection.nativeSQL(\"select 1\"));\n    expectConnectionAlreadyClosedException(() -> logicalConnection.setAutoCommit(false));\n    expectConnectionAlreadyClosedException(() -> logicalConnection.setReadOnly(false));\n    expectConnectionAlreadyClosedException(() -> logicalConnection.setCatalog(\"fakedb\"));\n    expectConnectionAlreadyClosedException(() -> logicalConnection.setSchema(\"fakedb\"));\n    expectConnectionAlreadyClosedException(\n        () -> logicalConnection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED));\n    expectConnectionAlreadyClosedException(() -> logicalConnection.createArrayOf(\"faketype\", null));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/api/pooling/LogicalConnectionFeatureNotSupportedLatestIT.java",
    "content": "package net.snowflake.client.api.pooling;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Savepoint;\nimport java.sql.Statement;\nimport java.util.HashMap;\nimport java.util.Map;\nimport javax.sql.PooledConnection;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class LogicalConnectionFeatureNotSupportedLatestIT extends BaseJDBCTest {\n\n  @Test\n  public void testLogicalConnectionFeatureNotSupported() throws SQLException {\n    Map<String, String> properties = getConnectionParameters();\n\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n\n    poolDataSource.setUrl(properties.get(\"uri\"));\n    poolDataSource.setPortNumber(Integer.parseInt(properties.get(\"port\")));\n    poolDataSource.setSsl(\"on\".equals(properties.get(\"ssl\")));\n    poolDataSource.setAccount(properties.get(\"account\"));\n    poolDataSource.setUser(properties.get(\"user\"));\n\n    // Use private key authentication if available, otherwise password\n    if (properties.get(\"private_key_file\") != null\n        && !properties.get(\"private_key_file\").isEmpty()) {\n      poolDataSource.setPrivateKeyFile(\n          properties.get(\"private_key_file\"), properties.get(\"private_key_pwd\"));\n    } else {\n      poolDataSource.setPassword(properties.get(\"password\"));\n    }\n\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    Connection logicalConnection = pooledConnection.getConnection();\n    expectFeatureNotSupportedException(() -> logicalConnection.rollback(new FakeSavepoint()));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.prepareStatement(\"select 1\", new int[] {1, 2}));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.prepareStatement(\"select 1\", new String[] {\"c1\", \"c2\"}));\n    expectFeatureNotSupportedException(\n        () ->\n            logicalConnection.prepareStatement(\n                \"select 1\", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY));\n    expectFeatureNotSupportedException(\n        () ->\n            logicalConnection.prepareStatement(\n                \"select 1\",\n                ResultSet.TYPE_SCROLL_SENSITIVE,\n                ResultSet.CONCUR_READ_ONLY,\n                ResultSet.HOLD_CURSORS_OVER_COMMIT));\n    expectFeatureNotSupportedException(\n        () ->\n            logicalConnection.createStatement(\n                ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY));\n    expectFeatureNotSupportedException(() -> logicalConnection.setTypeMap(new HashMap<>()));\n    expectFeatureNotSupportedException(logicalConnection::setSavepoint);\n    expectFeatureNotSupportedException(() -> logicalConnection.setSavepoint(\"fake\"));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.releaseSavepoint(new FakeSavepoint()));\n    expectFeatureNotSupportedException(logicalConnection::createBlob);\n    expectFeatureNotSupportedException(logicalConnection::createNClob);\n    expectFeatureNotSupportedException(logicalConnection::createSQLXML);\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.createStruct(\"fakeType\", new Object[] {}));\n    expectFeatureNotSupportedException(\n        () -> logicalConnection.prepareStatement(\"select 1\", Statement.RETURN_GENERATED_KEYS));\n  }\n\n  class FakeSavepoint implements Savepoint {\n    @Override\n    public int getSavepointId() throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public String getSavepointName() throws SQLException {\n      return \"\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/AuthConnectionParameters.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetEnv;\n\nimport java.util.Properties;\n\npublic class AuthConnectionParameters {\n\n  static final String SSO_USER = systemGetEnv(\"SNOWFLAKE_AUTH_TEST_BROWSER_USER\");\n  static final String SNOWFLAKE_USER = systemGetEnv(\"SNOWFLAKE_AUTH_TEST_SNOWFLAKE_USER\");\n  static final String HOST = systemGetEnv(\"SNOWFLAKE_AUTH_TEST_HOST\");\n  static final String SSO_PASSWORD = systemGetEnv(\"SNOWFLAKE_AUTH_TEST_OKTA_PASS\");\n  static final String OKTA = systemGetEnv(\"SNOWFLAKE_AUTH_TEST_OKTA_NAME\");\n  static final String OAUTH_PASSWORD =\n      systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_USER_PASSWORD\");\n  static final String SNOWFLAKE_INTERNAL_ROLE =\n      systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_ROLE\");\n\n  static Properties getBaseConnectionParameters() {\n    Properties properties = new Properties();\n    properties.put(\"host\", HOST);\n    properties.put(\"port\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_PORT\"));\n    properties.put(\"role\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_ROLE\"));\n    properties.put(\"account\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_ACCOUNT\"));\n    properties.put(\"db\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_DATABASE\"));\n    properties.put(\"schema\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_SCHEMA\"));\n    properties.put(\"warehouse\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_WAREHOUSE\"));\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", false);\n    return properties;\n  }\n\n  static Properties getExternalBrowserConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"user\", SSO_USER);\n    properties.put(\"authenticator\", \"externalbrowser\");\n    return properties;\n  }\n\n  static Properties getStoreIDTokenConnectionParameters() {\n    Properties properties = getExternalBrowserConnectionParameters();\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n    return properties;\n  }\n\n  static Properties getOktaConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"user\", SSO_USER);\n    properties.put(\"password\", SSO_PASSWORD);\n    properties.put(\"authenticator\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_OAUTH_URL\"));\n    return properties;\n  }\n\n  static Properties getOauthConnectionParameters(String token) {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"user\", SSO_USER);\n    properties.put(\"authenticator\", \"OAUTH\");\n    properties.put(\"token\", token);\n    return properties;\n  }\n\n  static Properties getMfaConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"user\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_MFA_USER\"));\n    properties.put(\"password\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_MFA_PASSWORD\"));\n    properties.put(\"authenticator\", \"USERNAME_PASSWORD_MFA\");\n    return properties;\n  }\n\n  static Properties getOAuthExternalAuthorizationCodeConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"authenticator\", \"OAUTH_AUTHORIZATION_CODE\");\n    properties.put(\n        \"oauthClientId\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_ID\"));\n    properties.put(\n        \"oauthClientSecret\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_SECRET\"));\n    properties.put(\n        \"oauthRedirectURI\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_REDIRECT_URI\"));\n    properties.put(\n        \"oauthAuthorizationUrl\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_AUTH_URL\"));\n    properties.put(\n        \"oauthTokenRequestUrl\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_TOKEN\"));\n    properties.put(\"user\", SSO_USER);\n\n    return properties;\n  }\n\n  static Properties getOAuthSnowflakeAuthorizationCodeConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"authenticator\", \"OAUTH_AUTHORIZATION_CODE\");\n    properties.put(\n        \"oauthClientId\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_CLIENT_ID\"));\n    properties.put(\n        \"oauthClientSecret\",\n        systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_CLIENT_SECRET\"));\n    properties.put(\n        \"oauthRedirectURI\",\n        systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_REDIRECT_URI\"));\n    properties.put(\"role\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_ROLE\"));\n    properties.put(\"user\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_ID\"));\n    return properties;\n  }\n\n  static Properties getOAuthSnowflakeWildcardsAuthorizationCodeConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"authenticator\", \"OAUTH_AUTHORIZATION_CODE\");\n    properties.put(\n        \"oauthClientId\",\n        systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_WILDCARDS_CLIENT_ID\"));\n    properties.put(\n        \"oauthClientSecret\",\n        systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_WILDCARDS_CLIENT_SECRET\"));\n    properties.put(\"role\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_INTERNAL_OAUTH_SNOWFLAKE_ROLE\"));\n    properties.put(\"user\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_ID\"));\n    return properties;\n  }\n\n  static Properties getOAuthOktaClientCredentialParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"authenticator\", \"OAUTH_CLIENT_CREDENTIALS\");\n    properties.put(\n        \"oauthClientId\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_ID\"));\n    properties.put(\n        \"oauthClientSecret\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_SECRET\"));\n    properties.put(\n        \"oauthTokenRequestUrl\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_TOKEN\"));\n    properties.put(\"user\", systemGetEnv(\"SNOWFLAKE_AUTH_TEST_EXTERNAL_OAUTH_OKTA_CLIENT_ID\"));\n    return properties;\n  }\n\n  static Properties getPATConnectionParameters() {\n    Properties properties = getBaseConnectionParameters();\n    properties.put(\"user\", SSO_USER);\n    properties.put(\"authenticator\", \"PROGRAMMATIC_ACCESS_TOKEN\");\n    return properties;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/AuthTestHelper.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.SessionUtil;\n\npublic class AuthTestHelper {\n\n  private Exception exception;\n  private String idToken;\n  private String accessToken;\n  private final boolean runAuthTestsManually;\n\n  public AuthTestHelper() {\n    this.runAuthTestsManually = Boolean.parseBoolean(System.getenv(\"RUN_AUTH_TESTS_MANUALLY\"));\n  }\n\n  public Thread getConnectAndExecuteSimpleQueryThread(Properties props, String sessionParameters) {\n    return new Thread(() -> connectAndExecuteSimpleQuery(props, sessionParameters));\n  }\n\n  public Thread getConnectAndExecuteSimpleQueryThread(Properties props) {\n    return new Thread(() -> connectAndExecuteSimpleQuery(props, null));\n  }\n\n  public void verifyExceptionIsThrown(String message) {\n    assertThat(\"Expected exception not thrown\", this.exception.getMessage(), is(message));\n  }\n\n  public void verifyExceptionIsNotThrown() {\n    assertThat(\"Unexpected exception thrown\", this.exception, nullValue());\n  }\n\n  public void connectAndProvideCredentials(Thread provideCredentialsThread, Thread connectThread)\n      throws InterruptedException {\n    if (runAuthTestsManually) {\n      connectThread.start();\n      connectThread.join();\n    } else {\n      provideCredentialsThread.start();\n      connectThread.start();\n      provideCredentialsThread.join();\n      connectThread.join();\n    }\n  }\n\n  public void provideCredentials(String scenario, String login, String password) {\n    try {\n      String provideBrowserCredentialsPath = \"/externalbrowser/provideBrowserCredentials.js\";\n      ProcessBuilder processBuilder =\n          new ProcessBuilder(\"node\", provideBrowserCredentialsPath, scenario, login, password);\n      Process process = processBuilder.start();\n      process.waitFor(15, TimeUnit.SECONDS);\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public void cleanBrowserProcesses() {\n    if (!runAuthTestsManually) {\n      String cleanBrowserProcessesPath = \"/externalbrowser/cleanBrowserProcesses.js\";\n      ProcessBuilder processBuilder = new ProcessBuilder(\"node\", cleanBrowserProcessesPath);\n      try {\n        Process process = processBuilder.start();\n        process.waitFor(15, TimeUnit.SECONDS);\n      } catch (InterruptedException | IOException e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }\n\n  public List<String> getTotp(String seed) {\n    if (runAuthTestsManually) {\n      System.err.println(\n          \"ERROR: TOTP code needs to be setup manually when running auth tests manually\");\n      return Collections.emptyList();\n    }\n\n    try {\n      String totpGeneratorPath = \"/externalbrowser/totpGenerator.js\";\n      ProcessBuilder processBuilder = new ProcessBuilder(\"node\", totpGeneratorPath, seed);\n      Process process = processBuilder.start();\n\n      try (BufferedReader reader =\n          new BufferedReader(new InputStreamReader(process.getInputStream()))) {\n        String output = reader.readLine();\n        process.waitFor(40, TimeUnit.SECONDS);\n        return Arrays.asList(output.trim().split(\"\\\\s+\"));\n      }\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public List<String> getTotp() {\n    return getTotp(\"\");\n  }\n\n  public boolean connectAndExecuteSimpleQueryWithMfaToken(\n      Properties props, List<String> totpCodes) {\n    for (int i = 0; i < totpCodes.size(); i++) {\n      String totpCode = totpCodes.get(i);\n      props.put(\"passcode\", totpCode);\n      this.exception = null;\n\n      connectAndExecuteSimpleQuery(props, null);\n\n      if (this.exception == null) {\n        return true;\n      } else {\n        String errorMsg = this.exception.getMessage();\n        System.out.println(\"TOTP code \" + (i + 1) + \" failed: \" + errorMsg);\n        if (errorMsg.contains(\"TOTP Invalid\")) {\n          System.out.println(\"TOTP/MFA error detected.\");\n        } else {\n          System.out.println(\"Non-TOTP error detected: \" + errorMsg);\n          break;\n        }\n      }\n    }\n    return false;\n  }\n\n  public static void deleteIdToken() {\n    SessionUtil.deleteIdTokenCache(\n        AuthConnectionParameters.HOST, AuthConnectionParameters.SSO_USER);\n  }\n\n  public static void deleteIdToken(String host, String user) {\n    SessionUtil.deleteIdTokenCache(host, user);\n  }\n\n  public static void deleteOauthToken() {\n    SessionUtil.deleteOAuthAccessTokenCache(\n        AuthConnectionParameters.OKTA, AuthConnectionParameters.SSO_USER);\n  }\n\n  public static void deleteOauthToken(String host, String user) {\n    SessionUtil.deleteOAuthAccessTokenCache(host, user);\n  }\n\n  public static void deleteOauthRefreshToken(String host, String user) {\n    SessionUtil.deleteOAuthRefreshTokenCache(host, user);\n  }\n\n  public void connectAndExecuteSimpleQuery(Properties props, String sessionParameters) {\n    String url = String.format(\"jdbc:snowflake://%s\", props.get(\"host\"));\n    if (sessionParameters != null) {\n      url += \"?\" + sessionParameters;\n    }\n    try (Connection con = DriverManager.getConnection(url, props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      int value = rs.getInt(1);\n      assertEquals(1, value);\n      saveToken(con);\n      saveAccessToken(con);\n    } catch (SQLException e) {\n      this.exception = e;\n    }\n  }\n\n  private void saveToken(Connection con) throws SnowflakeSQLException {\n    SnowflakeConnectionImpl sfcon = (SnowflakeConnectionImpl) con;\n    this.idToken = sfcon.getSfSession().getIdToken();\n  }\n\n  private void saveAccessToken(Connection con) throws SnowflakeSQLException {\n    SnowflakeConnectionImpl sfcon = (SnowflakeConnectionImpl) con;\n    this.accessToken = sfcon.getSfSession().getAccessToken();\n  }\n\n  public String getIdToken() {\n    return idToken;\n  }\n\n  public String getAccessToken() {\n    return accessToken;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/ExternalBrowserLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getExternalBrowserConnectionParameters;\n\nimport java.io.IOException;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\nclass ExternalBrowserLatestIT {\n\n  String login = AuthConnectionParameters.SSO_USER;\n  String password = AuthConnectionParameters.SSO_PASSWORD;\n  AuthTestHelper authTestHelper = new AuthTestHelper();\n  Properties properties;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    AuthTestHelper.deleteIdToken();\n    properties = getExternalBrowserConnectionParameters();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    authTestHelper.cleanBrowserProcesses();\n    AuthTestHelper.deleteIdToken();\n  }\n\n  @Test\n  void shouldAuthenticateUsingExternalBrowser() throws InterruptedException {\n    Thread provideCredentialsThread =\n        new Thread(() -> authTestHelper.provideCredentials(\"success\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForMismatchedUsername() throws InterruptedException {\n    properties.put(\"user\", \"differentUsername\");\n    Thread provideCredentialsThread =\n        new Thread(() -> authTestHelper.provideCredentials(\"success\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The user you were trying to authenticate as differs from the user currently logged in at the IDP.\");\n  }\n\n  @Test\n  void shouldThrowErrorForWrongCredentials() throws InterruptedException {\n    String login = \"itsnotanaccount.com\";\n    String password = \"fakepassword\";\n    Thread provideCredentialsThread =\n        new Thread(() -> authTestHelper.provideCredentials(\"fail\", login, password));\n    Thread connectThread =\n        authTestHelper.getConnectAndExecuteSimpleQueryThread(\n            properties, \"BROWSER_RESPONSE_TIMEOUT=10\");\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsThrown(\n        \"JDBC driver encountered communication error. Message: External browser authentication failed within timeout of 10000 milliseconds.\");\n  }\n\n  @Test\n  void shouldThrowErrorForBrowserTimeout() throws InterruptedException {\n    Thread provideCredentialsThread =\n        new Thread(() -> authTestHelper.provideCredentials(\"timeout\", login, password));\n    Thread connectThread =\n        authTestHelper.getConnectAndExecuteSimpleQueryThread(\n            properties, \"BROWSER_RESPONSE_TIMEOUT=1\");\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsThrown(\n        \"JDBC driver encountered communication error. Message: External browser authentication failed within timeout of 1000 milliseconds.\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/IdTokenLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getStoreIDTokenConnectionParameters;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.Matchers.notNullValue;\nimport static org.junit.jupiter.api.Assumptions.assumeTrue;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.MethodOrderer;\nimport org.junit.jupiter.api.Order;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestMethodOrder;\n\n@Tag(TestTags.AUTHENTICATION)\n@TestMethodOrder(MethodOrderer.OrderAnnotation.class)\nclass IdTokenLatestIT {\n\n  String login = AuthConnectionParameters.SSO_USER;\n  String password = AuthConnectionParameters.SSO_PASSWORD;\n  AuthTestHelper authTestHelper = new AuthTestHelper();\n  private static String firstToken;\n\n  @BeforeAll\n  public static void globalSetUp() {\n    AuthTestHelper.deleteIdToken();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    authTestHelper.cleanBrowserProcesses();\n  }\n\n  @Test\n  @Order(1)\n  void shouldAuthenticateUsingExternalBrowserAndSaveToken() throws InterruptedException {\n    Thread provideCredentialsThread =\n        new Thread(() -> authTestHelper.provideCredentials(\"success\", login, password));\n    Thread connectThread =\n        authTestHelper.getConnectAndExecuteSimpleQueryThread(getStoreIDTokenConnectionParameters());\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n    firstToken = authTestHelper.getIdToken();\n    assertThat(\"Id token was not saved\", firstToken, notNullValue());\n  }\n\n  @Test\n  @Order(2)\n  void shouldAuthenticateUsingTokenWithoutBrowser() {\n    verifyFirstTokenWasSaved();\n    authTestHelper.connectAndExecuteSimpleQuery(getStoreIDTokenConnectionParameters(), null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  @Order(3)\n  void shouldOpenBrowserAgainWhenTokenIsDeleted() throws InterruptedException {\n    verifyFirstTokenWasSaved();\n    AuthTestHelper.deleteIdToken();\n    Thread provideCredentialsThread =\n        new Thread(() -> authTestHelper.provideCredentials(\"success\", login, password));\n    Thread connectThread =\n        authTestHelper.getConnectAndExecuteSimpleQueryThread(getStoreIDTokenConnectionParameters());\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n    String secondToken = authTestHelper.getIdToken();\n    assertThat(\"Id token was not saved\", secondToken, notNullValue());\n    assertThat(\"Id token was not updated\", secondToken, not(firstToken));\n  }\n\n  private void verifyFirstTokenWasSaved() {\n    assumeTrue(firstToken != null, \"token was not saved, skipping test\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/MFALatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getMfaConnectionParameters;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class MFALatestIT {\n\n  AuthTestHelper authTestHelper;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    authTestHelper = new AuthTestHelper();\n  }\n\n  @Test\n  void testMfaSuccessful() {\n    Properties connectionParameters = getMfaConnectionParameters();\n    connectionParameters.put(\"CLIENT_REQUEST_MFA_TOKEN\", true);\n\n    List<String> totpCodes = authTestHelper.getTotp();\n    assertTrue(totpCodes.size() > 0, \"Expected to get TOTP codes but got none\");\n\n    boolean connectionSuccess =\n        authTestHelper.connectAndExecuteSimpleQueryWithMfaToken(connectionParameters, totpCodes);\n\n    assertTrue(\n        connectionSuccess, \"Failed to connect with any of the \" + totpCodes.size() + \" TOTP codes\");\n\n    authTestHelper.verifyExceptionIsNotThrown();\n\n    connectionParameters.remove(\"passcode\");\n    AuthTestHelper cacheTestHelper = new AuthTestHelper();\n    cacheTestHelper.connectAndExecuteSimpleQuery(connectionParameters, null);\n\n    cacheTestHelper.verifyExceptionIsNotThrown();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/OauthLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOauthConnectionParameters;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class OauthLatestIT {\n\n  AuthTestHelper authTestHelper;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    authTestHelper = new AuthTestHelper();\n  }\n\n  @Test\n  void shouldAuthenticateUsingOauth() throws IOException {\n    authTestHelper.connectAndExecuteSimpleQuery(getOauthConnectionParameters(getToken()), null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForInvalidToken() {\n    authTestHelper.connectAndExecuteSimpleQuery(getOauthConnectionParameters(\"invalidToken\"), null);\n    authTestHelper.verifyExceptionIsThrown(\"Invalid OAuth access token. \");\n  }\n\n  @Test\n  void shouldThrowErrorForMismatchedOauthUsername() throws IOException {\n    Properties properties = getOauthConnectionParameters(getToken());\n    properties.put(\"user\", \"differentUsername\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The user you were trying to authenticate as differs from the user tied to the access token.\");\n  }\n\n  private String getToken() throws IOException {\n    List<String> data =\n        Stream.of(\n                \"username=\" + System.getenv(\"SNOWFLAKE_AUTH_TEST_OKTA_USER\"),\n                \"password=\" + System.getenv(\"SNOWFLAKE_AUTH_TEST_OKTA_PASS\"),\n                \"grant_type=password\",\n                \"scope=session:role:\" + System.getenv(\"SNOWFLAKE_AUTH_TEST_ROLE\").toLowerCase())\n            .collect(Collectors.toList());\n\n    String auth =\n        System.getenv(\"SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_ID\")\n            + \":\"\n            + System.getenv(\"SNOWFLAKE_AUTH_TEST_OAUTH_CLIENT_SECRET\");\n    String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));\n\n    URL url = new URL(System.getenv(\"SNOWFLAKE_AUTH_TEST_OAUTH_URL\"));\n    HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n    connection.setRequestMethod(\"POST\");\n    connection.setRequestProperty(\n        \"Content-Type\", \"application/x-www-form-urlencoded;charset=UTF-8\");\n    connection.setRequestProperty(\"Authorization\", \"Basic \" + encodedAuth);\n    connection.setDoOutput(true);\n\n    try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) {\n      out.writeBytes(String.join(\"&\", data));\n      out.flush();\n    }\n\n    int responseCode = connection.getResponseCode();\n    assertThat(\"Failed to get access token, response code: \" + responseCode, responseCode, is(200));\n\n    ObjectMapper mapper = new ObjectMapper();\n    JsonNode jsonNode;\n    try (InputStream inputStream = connection.getInputStream()) {\n      jsonNode = mapper.readTree(inputStream);\n    }\n    return jsonNode.get(\"access_token\").asText();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/OauthOktaAuthorizationCodeLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOAuthExternalAuthorizationCodeConnectionParameters;\n\nimport java.io.IOException;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class OauthOktaAuthorizationCodeLatestIT {\n  String login = AuthConnectionParameters.SSO_USER;\n  String password = AuthConnectionParameters.SSO_PASSWORD;\n  AuthTestHelper authTestHelper = new AuthTestHelper();\n  Properties properties;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    AuthTestHelper.deleteIdToken();\n    AuthTestHelper.deleteOauthToken();\n    properties = getOAuthExternalAuthorizationCodeConnectionParameters();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    authTestHelper.cleanBrowserProcesses();\n  }\n\n  @AfterAll\n  public static void tearDown() {\n    AuthTestHelper.deleteIdToken();\n    AuthTestHelper.deleteOauthToken();\n  }\n\n  @Test\n  void shouldAuthenticateUsingExternalOauthOktaAuthorizationCode() throws InterruptedException {\n    Thread provideCredentialsThread =\n        new Thread(\n            () -> authTestHelper.provideCredentials(\"externalOauthOktaSuccess\", login, password));\n    Thread connectThread =\n        new Thread(() -> authTestHelper.connectAndExecuteSimpleQuery(properties, null));\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForMismatchedOauthOktaUsername() throws InterruptedException {\n    properties.setProperty(\"user\", \"invalidUser@snowflake.com\");\n    Thread provideCredentialsThread =\n        new Thread(\n            () -> authTestHelper.provideCredentials(\"externalOauthOktaSuccess\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties, null);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The user you were trying to authenticate as differs from the user tied to the access token.\");\n  }\n\n  @Test\n  void shouldThrowErrorForOauthOktaTimeout() throws InterruptedException {\n    properties.put(\"BROWSER_RESPONSE_TIMEOUT\", \"0\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"Error during OAuth Authorization Code authentication: Authorization request timed out. \"\n            + \"Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.\");\n  }\n\n  @Test\n  void shouldAuthenticateUsingTokenCacheForOauthOkta() throws InterruptedException {\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n\n    Thread provideCredentialsThread =\n        new Thread(\n            () -> authTestHelper.provideCredentials(\"externalOauthOktaSuccess\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties, null);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/OauthOktaClientCredentialsLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.OKTA;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOAuthOktaClientCredentialParameters;\n\nimport java.io.IOException;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class OauthOktaClientCredentialsLatestIT {\n  Properties properties = getOAuthOktaClientCredentialParameters();\n  String login = properties.getProperty(\"user\");\n  AuthTestHelper authTestHelper = new AuthTestHelper();\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    AuthTestHelper.deleteIdToken(AuthConnectionParameters.HOST, login);\n    AuthTestHelper.deleteOauthToken(OKTA, login);\n    properties = getOAuthOktaClientCredentialParameters();\n  }\n\n  @AfterAll\n  public static void tearDown() {\n    Properties properties = getOAuthOktaClientCredentialParameters();\n    AuthTestHelper.deleteIdToken(AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n    AuthTestHelper.deleteOauthToken(OKTA, properties.getProperty(\"user\"));\n  }\n\n  @Test\n  void shouldAuthenticateUsingSnowflakeOauthClientCredentials() {\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForClientCredentialsMismatchedUsername() throws InterruptedException {\n    properties.put(\"user\", \"invalidUser@snowflake.com\");\n\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The user you were trying to authenticate as differs from the user tied to the access token.\");\n  }\n\n  @Test\n  void shouldThrowErrorForUnauthorizedClientCredentials()\n      throws InterruptedException, SnowflakeSQLException {\n    properties.put(\"oauthClientId\", \"invalidClientId\");\n\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"Error during OAuth Client Credentials authentication: JDBC driver encountered communication error. Message: HTTP status=401.\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/OauthSnowflakeAuthorizationCodeLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.OAUTH_PASSWORD;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOAuthSnowflakeAuthorizationCodeConnectionParameters;\n\nimport java.io.IOException;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class OauthSnowflakeAuthorizationCodeLatestIT {\n\n  Properties properties = getOAuthSnowflakeAuthorizationCodeConnectionParameters();\n\n  String login = properties.getProperty(\"user\");\n  String password = OAUTH_PASSWORD;\n\n  AuthTestHelper authTestHelper = new AuthTestHelper();\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    authTestHelper.cleanBrowserProcesses();\n    AuthTestHelper.deleteIdToken(AuthConnectionParameters.HOST, login);\n    AuthTestHelper.deleteOauthToken(AuthConnectionParameters.HOST, login);\n    AuthTestHelper.deleteOauthRefreshToken(AuthConnectionParameters.HOST, login);\n    properties = getOAuthSnowflakeAuthorizationCodeConnectionParameters();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    authTestHelper.cleanBrowserProcesses();\n  }\n\n  @AfterAll\n  public static void tearDown() {\n    Properties properties = getOAuthSnowflakeAuthorizationCodeConnectionParameters();\n    AuthTestHelper.deleteIdToken(AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n    AuthTestHelper.deleteOauthToken(AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n    AuthTestHelper.deleteOauthRefreshToken(\n        AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n  }\n\n  @Test\n  void shouldAuthenticateUsingSnowflakeOauthAuthorizationCode() throws InterruptedException {\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread =\n        new Thread(() -> authTestHelper.connectAndExecuteSimpleQuery(properties, null));\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForMismatchedOauthSnowflakeUsername() throws InterruptedException {\n    properties.setProperty(\"user\", \"invalidUser@snowflake.com\");\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread =\n        new Thread(() -> authTestHelper.connectAndExecuteSimpleQuery(properties, null));\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The user you were trying to authenticate as differs from the user tied to the access token.\");\n  }\n\n  @Test\n  void shouldThrowErrorForOauthSnowflakeTimeout() throws InterruptedException {\n    properties.put(\"BROWSER_RESPONSE_TIMEOUT\", \"0\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"Error during OAuth Authorization Code authentication: Authorization request timed out. \"\n            + \"Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.\");\n  }\n\n  @Test\n  void shouldAuthenticateUsingTokenCacheOauthSnowflake() throws InterruptedException {\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties, null);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldNotAuthenticateUsingTokenCacheOauthSnowflake() throws InterruptedException {\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n    properties.remove(\"user\");\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties, null);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n\n    properties.put(\"BROWSER_RESPONSE_TIMEOUT\", \"5\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"Error during OAuth Authorization Code authentication: Authorization request timed out. \"\n            + \"Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/OauthSnowflakeAuthorizationCodeWildcardsLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.OAUTH_PASSWORD;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOAuthSnowflakeWildcardsAuthorizationCodeConnectionParameters;\n\nimport java.io.IOException;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class OauthSnowflakeAuthorizationCodeWildcardsLatestIT {\n\n  Properties properties = getOAuthSnowflakeWildcardsAuthorizationCodeConnectionParameters();\n\n  String login = properties.getProperty(\"user\");\n  String password = OAUTH_PASSWORD;\n\n  AuthTestHelper authTestHelper = new AuthTestHelper();\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    authTestHelper.cleanBrowserProcesses();\n    AuthTestHelper.deleteIdToken(AuthConnectionParameters.HOST, login);\n    AuthTestHelper.deleteOauthToken(AuthConnectionParameters.HOST, login);\n    AuthTestHelper.deleteOauthRefreshToken(AuthConnectionParameters.HOST, login);\n    properties = getOAuthSnowflakeWildcardsAuthorizationCodeConnectionParameters();\n  }\n\n  @AfterEach\n  public void cleanUp() {\n    authTestHelper.cleanBrowserProcesses();\n  }\n\n  @AfterAll\n  public static void tearDown() {\n    Properties properties = getOAuthSnowflakeWildcardsAuthorizationCodeConnectionParameters();\n    AuthTestHelper.deleteIdToken(AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n    AuthTestHelper.deleteOauthToken(AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n    AuthTestHelper.deleteOauthRefreshToken(\n        AuthConnectionParameters.HOST, properties.getProperty(\"user\"));\n  }\n\n  @Test\n  void shouldAuthenticateUsingSnowflakeOauthAuthorizationCode() throws InterruptedException {\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread =\n        new Thread(() -> authTestHelper.connectAndExecuteSimpleQuery(properties, null));\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForMismatchedOauthSnowflakeUsername() throws InterruptedException {\n    properties.setProperty(\"user\", \"invalidUser@snowflake.com\");\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread =\n        new Thread(() -> authTestHelper.connectAndExecuteSimpleQuery(properties, null));\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The user you were trying to authenticate as differs from the user tied to the access token.\");\n  }\n\n  @Test\n  void shouldThrowErrorForOauthSnowflakeTimeout() throws InterruptedException {\n    properties.put(\"BROWSER_RESPONSE_TIMEOUT\", \"0\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"Error during OAuth Authorization Code authentication: Authorization request timed out. \"\n            + \"Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.\");\n  }\n\n  @Test\n  void shouldAuthenticateUsingTokenCacheOauthSnowflake() throws InterruptedException {\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n    Thread provideCredentialsThread =\n        new Thread(\n            () ->\n                authTestHelper.provideCredentials(\n                    \"internalOauthSnowflakeSuccess\", login, password));\n    Thread connectThread = authTestHelper.getConnectAndExecuteSimpleQueryThread(properties, null);\n\n    authTestHelper.connectAndProvideCredentials(provideCredentialsThread, connectThread);\n    authTestHelper.verifyExceptionIsNotThrown();\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/OktaAuthLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.SSO_USER;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOktaConnectionParameters;\n\nimport java.io.IOException;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\nclass OktaAuthLatestIT {\n\n  AuthTestHelper authTestHelper;\n  Properties properties;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    authTestHelper = new AuthTestHelper();\n    properties = getOktaConnectionParameters();\n  }\n\n  @Test\n  void shouldAuthenticateUsingOkta() {\n    authTestHelper.connectAndExecuteSimpleQuery(getOktaConnectionParameters(), null);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldAuthenticateUsingOktaWithOktaUsernameParam() {\n    properties.replace(\"user\", \"differentUsername\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, \"oktausername=\" + SSO_USER);\n    authTestHelper.verifyExceptionIsNotThrown();\n  }\n\n  @Test\n  void shouldThrowErrorForWrongOktaCredentials() {\n    properties.put(\"user\", \"invalidUsername\");\n    properties.put(\"password\", \"fakepassword\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"JDBC driver encountered communication error. Message: HTTP status=401.\");\n  }\n\n  @Test\n  void shouldThrowErrorForWrongOktaCredentialsInOktaUsernameParam() {\n    properties.replace(\"user\", \"differentUsername\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, \"oktausername=invalidUser\");\n    authTestHelper.verifyExceptionIsThrown(\n        \"JDBC driver encountered communication error. Message: HTTP status=401.\");\n  }\n\n  @Test\n  void shouldThrowErrorForWrongOktaUrl() {\n    properties.put(\"authenticator\", \"https://invalid.okta.com/\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\n        \"The specified authenticator is not accepted by your Snowflake account configuration.  Please contact your local system administrator to get the correct URL to use.\");\n  }\n\n  @Test\n  @Disabled // todo SNOW-1852279 implement error handling for invalid URL\n  void shouldThrowErrorForWrongUrlWithoutOktaPath() {\n    properties.put(\"authenticator\", \"https://invalid.abc.com/\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\"todo\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/authentication/PATLatestIT.java",
    "content": "package net.snowflake.client.authentication;\n\nimport static net.snowflake.client.authentication.AuthConnectionParameters.HOST;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.SNOWFLAKE_INTERNAL_ROLE;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.SNOWFLAKE_USER;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getOktaConnectionParameters;\nimport static net.snowflake.client.authentication.AuthConnectionParameters.getPATConnectionParameters;\n\nimport java.io.IOException;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class PATLatestIT {\n\n  AuthTestHelper authTestHelper;\n  String patName;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    authTestHelper = new AuthTestHelper();\n  }\n\n  @Test\n  void shouldAuthenticateUsingPAT() {\n    Properties properties = getPATConnectionParameters();\n    properties.put(\"token\", getPAT());\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsNotThrown();\n    removePAT();\n  }\n\n  @Test\n  void shouldThrowErrorForInvalidPAT() {\n    Properties properties = getPATConnectionParameters();\n    properties.put(\"token\", \"invalidToken\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\"Programmatic access token is invalid.\");\n  }\n\n  @Test\n  void shouldThrowErrorForMismatchedPATUsername() throws IOException {\n    Properties properties = getPATConnectionParameters();\n    properties.put(\"token\", getPAT());\n    properties.put(\"user\", \"differentUsername\");\n    authTestHelper.connectAndExecuteSimpleQuery(properties, null);\n    authTestHelper.verifyExceptionIsThrown(\"Programmatic access token is invalid.\");\n    removePAT();\n  }\n\n  private String getPAT() {\n    patName = \"PAT_JDBC_\" + generateRandomSuffix();\n    String command =\n        String.format(\n            \"alter user %s add programmatic access token %s ROLE_RESTRICTION = '%s'\",\n            SNOWFLAKE_USER, patName, SNOWFLAKE_INTERNAL_ROLE);\n    return connectUsingDifferentAuthMethodAndExecuteCommand(command, true);\n  }\n\n  private void removePAT() {\n    String command =\n        String.format(\n            \"alter user %s remove programmatic access token %s;\", SNOWFLAKE_USER, patName);\n    connectUsingDifferentAuthMethodAndExecuteCommand(command, false);\n  }\n\n  private String connectUsingDifferentAuthMethodAndExecuteCommand(\n      String command, boolean shouldReturnToken) {\n    Properties properties = getOktaConnectionParameters();\n    String url = String.format(\"jdbc:snowflake://%s\", HOST);\n\n    try (Connection con = DriverManager.getConnection(url, properties);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(command)) {\n      if (shouldReturnToken && rs.next()) {\n        return rs.getString(\"token_secret\");\n      }\n      return null;\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private String generateRandomSuffix() {\n    SimpleDateFormat sdf = new SimpleDateFormat(\"yyyyMMddHHmmssSSS\");\n    return sdf.format(new Date());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/category/TestTags.java",
    "content": "package net.snowflake.client.category;\n\npublic class TestTags {\n  private TestTags() {}\n\n  public static final String ARROW = \"arrow\";\n  public static final String CONNECTION = \"connection\";\n  public static final String CORE = \"core\";\n  public static final String DIAGNOSTIC = \"diagnostic\";\n  public static final String LOADER = \"loader\";\n  public static final String DATABASE_META_DATA = \"databaseMetaData\";\n  public static final String OTHERS = \"others\";\n  public static final String RESULT_SET = \"resultSet\";\n  public static final String STATEMENT = \"statement\";\n  public static final String AUTHENTICATION = \"authentication\";\n  public static final String WIF = \"wif\";\n  public static final String TESTING = \"testing\";\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/api/implementation/metadata/SnowflakeDatabaseMetaDataImplColumnSizeTest.java",
    "content": "package net.snowflake.client.internal.api.implementation.metadata;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.sql.Types;\nimport java.util.stream.Stream;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumnMetadata;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\npublic class SnowflakeDatabaseMetaDataImplColumnSizeTest {\n\n  static Stream<Arguments> lengthBasedTypes() {\n    return Stream.of(\n        Arguments.of(Types.VARCHAR, 100),\n        Arguments.of(Types.CHAR, 50),\n        Arguments.of(Types.BINARY, 200),\n        Arguments.of(Types.VARBINARY, 250));\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"lengthBasedTypes\")\n  public void testGetColumnSizeByLength(int type, int length) {\n    SnowflakeColumnMetadata metadata = mock(SnowflakeColumnMetadata.class);\n    when(metadata.getType()).thenReturn(type);\n    when(metadata.getLength()).thenReturn(length);\n\n    assertEquals(length, SnowflakeDatabaseMetaDataImpl.getColumnSize(metadata));\n  }\n\n  static Stream<Integer> precisionBasedTypes() {\n    return Stream.of(\n        Types.DECIMAL,\n        Types.NUMERIC,\n        Types.BIGINT,\n        Types.INTEGER,\n        Types.SMALLINT,\n        Types.TINYINT,\n        Types.FLOAT,\n        Types.DOUBLE,\n        Types.REAL,\n        SnowflakeType.EXTRA_TYPES_DECFLOAT,\n        Types.DATE,\n        Types.TIME,\n        Types.TIMESTAMP,\n        Types.TIMESTAMP_WITH_TIMEZONE,\n        SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n        SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n        SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ);\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"precisionBasedTypes\")\n  public void testGetColumnSizeByPrecision(int type) {\n    int precision = 38;\n    SnowflakeColumnMetadata metadata = mock(SnowflakeColumnMetadata.class);\n    when(metadata.getType()).thenReturn(type);\n    when(metadata.getPrecision()).thenReturn(precision);\n\n    assertEquals(precision, SnowflakeDatabaseMetaDataImpl.getColumnSize(metadata));\n  }\n\n  @Test\n  public void testGetColumnSizeVector() {\n    SnowflakeColumnMetadata metadata = mock(SnowflakeColumnMetadata.class);\n    when(metadata.getType()).thenReturn(SnowflakeType.EXTRA_TYPES_VECTOR);\n    when(metadata.getDimension()).thenReturn(128);\n\n    assertEquals(128, SnowflakeDatabaseMetaDataImpl.getColumnSize(metadata));\n  }\n\n  @ParameterizedTest\n  @ValueSource(ints = {Types.BOOLEAN, Types.ARRAY, Types.STRUCT})\n  public void testGetColumnSizeOther(int type) {\n    SnowflakeColumnMetadata metadata = mock(SnowflakeColumnMetadata.class);\n    when(metadata.getType()).thenReturn(type);\n    assertNull(SnowflakeDatabaseMetaDataImpl.getColumnSize(metadata));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/api/implementation/pooling/ConnectionPoolingDataSourceIT.java",
    "content": "package net.snowflake.client.internal.api.implementation.pooling;\n\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.CoreMatchers.sameInstance;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport javax.sql.ConnectionEvent;\nimport javax.sql.ConnectionEventListener;\nimport javax.sql.PooledConnection;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.pooling.SnowflakeConnectionPoolDataSource;\nimport net.snowflake.client.api.pooling.SnowflakeConnectionPoolDataSourceFactory;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class ConnectionPoolingDataSourceIT extends AbstractDriverIT {\n  @Test\n  public void testPooledConnection() throws SQLException {\n    Map<String, String> properties = getConnectionParameters();\n\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n\n    poolDataSource.setUrl(properties.get(\"uri\"));\n    poolDataSource.setPortNumber(Integer.parseInt(properties.get(\"port\")));\n    poolDataSource.setSsl(\"on\".equals(properties.get(\"ssl\")));\n    poolDataSource.setAccount(properties.get(\"account\"));\n    poolDataSource.setUser(properties.get(\"user\"));\n\n    // Use private key authentication if available, otherwise password\n    if (properties.get(\"private_key_file\") != null\n        && !properties.get(\"private_key_file\").isEmpty()) {\n      poolDataSource.setPrivateKeyFile(\n          properties.get(\"private_key_file\"), properties.get(\"private_key_pwd\"));\n    } else {\n      poolDataSource.setPassword(properties.get(\"password\"));\n    }\n\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    TestingConnectionListener listener = new TestingConnectionListener();\n    pooledConnection.addConnectionEventListener(listener);\n\n    int thrownErrorCode;\n    try (Connection connection = pooledConnection.getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"select 1\");\n\n      SQLException e =\n          assertThrows(SQLException.class, () -> connection.setCatalog(\"nonexistent_database\"));\n      thrownErrorCode = e.getErrorCode();\n      // should not close underlying physical connection\n      // and fire connection closed events\n    }\n\n    List<ConnectionEvent> connectionClosedEvents = listener.getConnectionClosedEvents();\n    List<ConnectionEvent> connectionErrorEvents = listener.getConnectionErrorEvents();\n\n    // assert connection close event\n    assertThat(connectionClosedEvents.size(), is(1));\n    ConnectionEvent closedEvent = connectionClosedEvents.get(0);\n    assertThat(closedEvent.getSQLException(), is(nullValue()));\n    assertThat(closedEvent.getSource(), instanceOf(SnowflakePooledConnection.class));\n    assertThat((PooledConnection) closedEvent.getSource(), sameInstance(pooledConnection));\n\n    // assert connection error event\n    assertThat(connectionErrorEvents.size(), is(1));\n    ConnectionEvent errorEvent = connectionErrorEvents.get(0);\n\n    assertThat(errorEvent.getSource(), instanceOf(SnowflakePooledConnection.class));\n    assertThat((PooledConnection) errorEvent.getSource(), sameInstance(pooledConnection));\n    // error event error code match thrown error code\n    assertThat(errorEvent.getSQLException().getErrorCode(), is(thrownErrorCode));\n\n    // assert physical connection is not closed\n    Connection physicalConnection =\n        ((SnowflakePooledConnection) pooledConnection).getPhysicalConnection();\n\n    assertThat(physicalConnection.isClosed(), is(false));\n\n    pooledConnection.removeConnectionEventListener(listener);\n\n    // will close physical connection\n    pooledConnection.close();\n    assertThat(physicalConnection.isClosed(), is(true));\n  }\n\n  @Test\n  public void testPooledConnectionUsernamePassword() throws SQLException {\n    Map<String, String> properties = getConnectionParameters();\n\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n\n    poolDataSource.setUrl(properties.get(\"uri\"));\n    poolDataSource.setPortNumber(Integer.parseInt(properties.get(\"port\")));\n    poolDataSource.setSsl(\"on\".equals(properties.get(\"ssl\")));\n    poolDataSource.setAccount(properties.get(\"account\"));\n\n    PooledConnection pooledConnection;\n    // Use private key authentication if available, otherwise username/password method\n    if (properties.get(\"private_key_file\") != null\n        && !properties.get(\"private_key_file\").isEmpty()) {\n      poolDataSource.setUser(properties.get(\"user\"));\n      poolDataSource.setPrivateKeyFile(\n          properties.get(\"private_key_file\"), properties.get(\"private_key_pwd\"));\n      pooledConnection = poolDataSource.getPooledConnection();\n    } else {\n      pooledConnection =\n          poolDataSource.getPooledConnection(properties.get(\"user\"), properties.get(\"password\"));\n    }\n    TestingConnectionListener listener = new TestingConnectionListener();\n    pooledConnection.addConnectionEventListener(listener);\n\n    try (Connection connection = pooledConnection.getConnection()) {\n      connection.createStatement().execute(\"select 1\");\n    }\n    pooledConnection.close();\n  }\n\n  private static class TestingConnectionListener implements ConnectionEventListener {\n    private List<ConnectionEvent> connectionClosedEvents;\n\n    private List<ConnectionEvent> connectionErrorEvents;\n\n    TestingConnectionListener() {\n      connectionClosedEvents = new ArrayList<>();\n      connectionErrorEvents = new ArrayList<>();\n    }\n\n    @Override\n    public void connectionClosed(ConnectionEvent event) {\n      connectionClosedEvents.add(event);\n    }\n\n    @Override\n    public void connectionErrorOccurred(ConnectionEvent event) {\n      connectionErrorEvents.add(event);\n    }\n\n    public List<ConnectionEvent> getConnectionClosedEvents() {\n      return connectionClosedEvents;\n    }\n\n    public List<ConnectionEvent> getConnectionErrorEvents() {\n      return connectionErrorEvents;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/api/implementation/pooling/LogicalConnectionLatestIT.java",
    "content": "package net.snowflake.client.internal.api.implementation.pooling;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.sql.CallableStatement;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Properties;\nimport javax.sql.PooledConnection;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.pooling.SnowflakeConnectionPoolDataSource;\nimport net.snowflake.client.api.pooling.SnowflakeConnectionPoolDataSourceFactory;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class LogicalConnectionLatestIT extends BaseJDBCTest {\n  Map<String, String> properties = getConnectionParameters();\n\n  @Test\n  public void testLogicalConnection() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    Connection logicalConnection = pooledConnection.getConnection();\n    try (Statement statement =\n        logicalConnection.createStatement(\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT)) {\n      try (ResultSet resultSet = statement.executeQuery(\"show parameters\")) {\n        assertTrue(resultSet.next());\n        assertFalse(logicalConnection.isClosed());\n        assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, logicalConnection.getHoldability());\n      }\n    }\n    logicalConnection.close();\n    assertTrue(logicalConnection.isClosed());\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testNetworkTimeout() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      int millis = logicalConnection.getNetworkTimeout();\n      assertEquals(0, millis);\n      logicalConnection.setNetworkTimeout(null, 200);\n      assertEquals(200, logicalConnection.getNetworkTimeout());\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testIsValid() throws Throwable {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      assertTrue(logicalConnection.isValid(10));\n      assertThrows(SQLException.class, () -> logicalConnection.isValid(-10));\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testConnectionClientInfo() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      Properties property = logicalConnection.getClientInfo();\n      assertEquals(0, property.size());\n      Properties clientInfo = new Properties();\n      clientInfo.setProperty(\"name\", \"Peter\");\n      clientInfo.setProperty(\"description\", \"SNOWFLAKE JDBC\");\n\n      expectSQLClientInfoException(() -> logicalConnection.setClientInfo(clientInfo));\n      expectSQLClientInfoException(\n          () -> logicalConnection.setClientInfo(\"ApplicationName\", \"valueA\"));\n      assertNull(logicalConnection.getClientInfo(\"Peter\"));\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testAbort() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    Connection logicalConnection = pooledConnection.getConnection();\n    Connection physicalConnection =\n        ((SnowflakePooledConnection) pooledConnection).getPhysicalConnection();\n    assertTrue(!physicalConnection.isClosed());\n    logicalConnection.abort(null);\n    assertTrue(physicalConnection.isClosed());\n  }\n\n  @Test\n  public void testNativeSQL() throws Throwable {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      // today returning the source SQL.\n      assertEquals(\"select 1\", logicalConnection.nativeSQL(\"select 1\"));\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testUnwrapper() throws Throwable {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      boolean canUnwrap = logicalConnection.isWrapperFor(SnowflakeConnectionImpl.class);\n      assertTrue(canUnwrap);\n      SnowflakeConnectionImpl sfconnection =\n          logicalConnection.unwrap(SnowflakeConnectionImpl.class);\n      sfconnection.createStatement();\n\n      assertThrows(SQLException.class, () -> logicalConnection.unwrap(SnowflakeDriver.class));\n    }\n  }\n\n  @Test\n  public void testTransactionStatement() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      logicalConnection.setAutoCommit(false);\n      assertFalse(logicalConnection.getAutoCommit());\n      logicalConnection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);\n      assertEquals(2, logicalConnection.getTransactionIsolation());\n\n      try (Statement statement = logicalConnection.createStatement()) {\n        statement.executeUpdate(\"create or replace table test_transaction (colA int, colB string)\");\n        // start a transaction\n        statement.executeUpdate(\"insert into test_transaction values (1, 'abc')\");\n\n        // commit\n        logicalConnection.commit();\n        try (ResultSet resultSet =\n            statement.executeQuery(\"select count(*) from test_transaction\")) {\n          assertTrue(resultSet.next());\n          assertEquals(1, resultSet.getInt(1));\n        }\n\n        // rollback\n        statement.executeUpdate(\"delete from test_transaction\");\n        logicalConnection.rollback();\n        try (ResultSet resultSet =\n            statement.executeQuery(\"select count(*) from test_transaction\")) {\n          assertTrue(resultSet.next());\n          assertEquals(1, resultSet.getInt(1));\n        }\n      } finally {\n        try (Statement statement = logicalConnection.createStatement()) {\n          statement.execute(\"drop table if exists test_transaction\");\n        }\n      }\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testReadOnly() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      // read only is not supported - will always be false\n      assertEquals(false, logicalConnection.isReadOnly());\n      logicalConnection.setReadOnly(true);\n      assertEquals(false, logicalConnection.isReadOnly());\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testGetTypeMap() throws Throwable {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      // return an empty type map. setTypeMap is not supported.\n      assertEquals(Collections.emptyMap(), logicalConnection.getTypeMap());\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testPreparedStatement() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      try (Statement statement = logicalConnection.createStatement()) {\n        statement.execute(\"create or replace table test_prep (colA int, colB varchar)\");\n        try (PreparedStatement preparedStatement =\n            logicalConnection.prepareStatement(\"insert into test_prep values (?, ?)\")) {\n          preparedStatement.setInt(1, 25);\n          preparedStatement.setString(2, \"hello world\");\n          preparedStatement.execute();\n          int count = 0;\n          try (ResultSet resultSet = statement.executeQuery(\"select * from test_prep\")) {\n            while (resultSet.next()) {\n              count++;\n            }\n          }\n          assertEquals(1, count);\n        } finally {\n          statement.execute(\"drop table if exists test_prep\");\n        }\n      }\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testSetSchema() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      String schema = logicalConnection.getSchema();\n      // get the current schema\n      try (ResultSet rst =\n          logicalConnection.createStatement().executeQuery(\"select current_schema()\")) {\n        assertTrue(rst.next());\n        assertEquals(schema, rst.getString(1));\n      }\n\n      logicalConnection.setSchema(\"PUBLIC\");\n\n      // get the current schema\n      try (ResultSet rst =\n          logicalConnection.createStatement().executeQuery(\"select current_schema()\")) {\n        assertTrue(rst.next());\n        assertEquals(\"PUBLIC\", rst.getString(1));\n      }\n    }\n    pooledConnection.close();\n  }\n\n  @Test\n  public void testPrepareCall() throws SQLException {\n    String procedure =\n        \"CREATE OR REPLACE PROCEDURE output_message(message VARCHAR)\\n\"\n            + \"RETURNS VARCHAR NOT NULL\\n\"\n            + \"LANGUAGE SQL\\n\"\n            + \"AS\\n\"\n            + \"BEGIN\\n\"\n            + \"  RETURN message;\\n\"\n            + \"END;\";\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      try (Statement statement = logicalConnection.createStatement()) {\n        statement.execute(procedure);\n\n        try (CallableStatement callableStatement =\n            logicalConnection.prepareCall(\"call output_message(?)\")) {\n          callableStatement.setString(1, \"hello world\");\n          try (ResultSet resultSet = callableStatement.executeQuery()) {\n            resultSet.next();\n            assertEquals(\"hello world\", resultSet.getString(1));\n          }\n        }\n\n        try (CallableStatement callableStatement =\n            logicalConnection.prepareCall(\n                \"call output_message('hello world')\",\n                ResultSet.TYPE_FORWARD_ONLY,\n                ResultSet.CONCUR_READ_ONLY)) {\n          try (ResultSet resultSet = callableStatement.executeQuery()) {\n            resultSet.next();\n            assertEquals(\"hello world\", resultSet.getString(1));\n            assertEquals(1003, callableStatement.getResultSetType());\n            assertEquals(1007, callableStatement.getResultSetConcurrency());\n          }\n        }\n\n        try (CallableStatement callableStatement =\n            logicalConnection.prepareCall(\n                \"call output_message('hello world')\",\n                ResultSet.TYPE_FORWARD_ONLY,\n                ResultSet.CONCUR_READ_ONLY,\n                ResultSet.CLOSE_CURSORS_AT_COMMIT)) {\n          try (ResultSet resultSet = callableStatement.executeQuery()) {\n            resultSet.next();\n            assertEquals(2, callableStatement.getResultSetHoldability());\n          }\n        }\n        statement.execute(\"drop procedure if exists output_message(varchar)\");\n      }\n    }\n  }\n\n  @Test\n  public void testClob() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      try (Statement statement = logicalConnection.createStatement()) {\n        statement.execute(\"create or replace table test_clob (colA text)\");\n      }\n\n      try (PreparedStatement preparedStatement =\n          logicalConnection.prepareStatement(\"insert into test_clob values (?)\")) {\n        Clob clob = logicalConnection.createClob();\n        clob.setString(1, \"hello world\");\n        preparedStatement.setClob(1, clob);\n        preparedStatement.execute();\n      }\n\n      try (Statement statement = logicalConnection.createStatement()) {\n        statement.execute(\"select * from test_clob\");\n        try (ResultSet resultSet = statement.getResultSet()) {\n          resultSet.next();\n          assertEquals(\"hello world\", resultSet.getString(\"COLA\"));\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testDatabaseMetaData() throws SQLException {\n    SnowflakeConnectionPoolDataSource poolDataSource =\n        SnowflakeConnectionPoolDataSourceFactory.createConnectionPoolDataSource();\n    poolDataSource = setProperties(poolDataSource);\n    PooledConnection pooledConnection = poolDataSource.getPooledConnection();\n    try (Connection logicalConnection = pooledConnection.getConnection()) {\n      DatabaseMetaData databaseMetaData = logicalConnection.getMetaData();\n      assertEquals(\"Snowflake\", databaseMetaData.getDatabaseProductName());\n      assertEquals(properties.get(\"user\"), databaseMetaData.getUserName());\n    }\n  }\n\n  @Test\n  public void testLogicalConnectionWhenPhysicalConnectionThrowsErrors() throws SQLException {\n    Connection connection = mock(Connection.class);\n    SnowflakePooledConnection snowflakePooledConnection = mock(SnowflakePooledConnection.class);\n    when(snowflakePooledConnection.getPhysicalConnection()).thenReturn(connection);\n    SQLException sqlException = new SQLException(\"mocking error\");\n    when(connection.createStatement()).thenThrow(sqlException);\n    when(connection.createStatement(1, 2, 3)).thenThrow(sqlException);\n\n    when(connection.prepareStatement(\"mocksql\")).thenThrow(sqlException);\n    when(connection.prepareCall(\"mocksql\")).thenThrow(sqlException);\n    when(connection.prepareCall(\"mocksql\", 1, 2, 3)).thenThrow(sqlException);\n    when(connection.nativeSQL(\"mocksql\")).thenThrow(sqlException);\n    when(connection.getAutoCommit()).thenThrow(sqlException);\n    when(connection.getMetaData()).thenThrow(sqlException);\n    when(connection.isReadOnly()).thenThrow(sqlException);\n    when(connection.getCatalog()).thenThrow(sqlException);\n    when(connection.getTransactionIsolation()).thenThrow(sqlException);\n    when(connection.getWarnings()).thenThrow(sqlException);\n    when(connection.prepareCall(\"mocksql\", 1, 2)).thenThrow(sqlException);\n    when(connection.getTypeMap()).thenThrow(sqlException);\n    when(connection.getHoldability()).thenThrow(sqlException);\n    when(connection.createClob()).thenThrow(sqlException);\n    when(connection.getClientInfo(\"mocksql\")).thenThrow(sqlException);\n    when(connection.getClientInfo()).thenThrow(sqlException);\n    when(connection.createArrayOf(\"mock\", null)).thenThrow(sqlException);\n    when(connection.getSchema()).thenThrow(sqlException);\n    when(connection.getNetworkTimeout()).thenThrow(sqlException);\n    when(connection.isWrapperFor(Connection.class)).thenThrow(sqlException);\n\n    doThrow(sqlException).when(connection).setAutoCommit(false);\n    doThrow(sqlException).when(connection).commit();\n    doThrow(sqlException).when(connection).rollback();\n    doThrow(sqlException).when(connection).setReadOnly(false);\n    doThrow(sqlException).when(connection).clearWarnings();\n    doThrow(sqlException).when(connection).setSchema(null);\n    doThrow(sqlException).when(connection).abort(null);\n    doThrow(sqlException).when(connection).setNetworkTimeout(null, 1);\n\n    LogicalConnection logicalConnection = new LogicalConnection(snowflakePooledConnection);\n\n    assertThrows(SQLException.class, logicalConnection::createStatement);\n    assertThrows(SQLException.class, () -> logicalConnection.createStatement(1, 2, 3));\n    assertThrows(SQLException.class, () -> logicalConnection.nativeSQL(\"mocksql\"));\n    assertThrows(SQLException.class, logicalConnection::getAutoCommit);\n    assertThrows(SQLException.class, logicalConnection::getMetaData);\n    assertThrows(SQLException.class, logicalConnection::isReadOnly);\n    assertThrows(SQLException.class, logicalConnection::getCatalog);\n    assertThrows(SQLException.class, logicalConnection::getTransactionIsolation);\n    assertThrows(SQLException.class, logicalConnection::getWarnings);\n    assertThrows(SQLException.class, () -> logicalConnection.prepareCall(\"mocksql\"));\n    assertThrows(SQLException.class, logicalConnection::getTypeMap);\n    assertThrows(SQLException.class, logicalConnection::getHoldability);\n    assertThrows(SQLException.class, logicalConnection::createClob);\n    assertThrows(SQLException.class, () -> logicalConnection.getClientInfo(\"mocksql\"));\n    assertThrows(SQLException.class, logicalConnection::getClientInfo);\n    assertThrows(SQLException.class, () -> logicalConnection.createArrayOf(\"mock\", null));\n    assertThrows(SQLException.class, logicalConnection::getSchema);\n    assertThrows(SQLException.class, logicalConnection::getNetworkTimeout);\n    assertThrows(SQLException.class, () -> logicalConnection.isWrapperFor(Connection.class));\n    assertThrows(SQLException.class, () -> logicalConnection.setAutoCommit(false));\n    assertThrows(SQLException.class, logicalConnection::rollback);\n    assertThrows(SQLException.class, () -> logicalConnection.setReadOnly(false));\n    assertThrows(SQLException.class, logicalConnection::clearWarnings);\n    assertThrows(SQLException.class, () -> logicalConnection.setSchema(null));\n    assertThrows(SQLException.class, () -> logicalConnection.abort(null));\n    assertThrows(SQLException.class, () -> logicalConnection.setNetworkTimeout(null, 1));\n\n    verify(snowflakePooledConnection, times(26)).fireConnectionErrorEvent(sqlException);\n  }\n\n  private SnowflakeConnectionPoolDataSource setProperties(\n      SnowflakeConnectionPoolDataSource poolDataSource) {\n    poolDataSource.setUrl(properties.get(\"uri\"));\n    poolDataSource.setPortNumber(Integer.parseInt(properties.get(\"port\")));\n    poolDataSource.setSsl(\"on\".equals(properties.get(\"ssl\")));\n    poolDataSource.setAccount(properties.get(\"account\"));\n    poolDataSource.setUser(properties.get(\"user\"));\n\n    // Use private key authentication if available, otherwise password\n    if (properties.get(\"private_key_file\") != null\n        && !properties.get(\"private_key_file\").isEmpty()) {\n      poolDataSource.setPrivateKeyFile(\n          properties.get(\"private_key_file\"), properties.get(\"private_key_pwd\"));\n    } else {\n      poolDataSource.setPassword(properties.get(\"password\"));\n    }\n\n    poolDataSource.setDatabaseName(properties.get(\"database\"));\n    poolDataSource.setSchema(properties.get(\"schema\"));\n    poolDataSource.setWarehouse(properties.get(\"warehouse\"));\n    return poolDataSource;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/api/implementation/statement/SnowflakeStatementImplCopyResultSetTest.java",
    "content": "package net.snowflake.client.internal.api.implementation.statement;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFStatementType;\nimport net.snowflake.client.internal.jdbc.SFConnectionHandler;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass SnowflakeStatementImplCopyResultSetTest {\n\n  private SnowflakeConnectionImpl mockConnection;\n  private SFConnectionHandler mockHandler;\n  private SFBaseStatement mockSFStatement;\n  private SFBaseResultSet mockSFResultSet;\n  private SFBaseSession mockSession;\n  private SnowflakeBaseResultSet mockJdbcResultSet;\n  private SnowflakeStatementImpl statement;\n\n  @BeforeEach\n  void setUp() throws SQLException, SFException {\n    mockConnection = mock(SnowflakeConnectionImpl.class);\n    mockHandler = mock(SFConnectionHandler.class);\n    mockSFStatement = mock(SFBaseStatement.class);\n    mockSFResultSet = mock(SFBaseResultSet.class);\n    mockSession = mock(SFBaseSession.class);\n    mockJdbcResultSet = mock(SnowflakeBaseResultSet.class);\n\n    when(mockConnection.getHandler(any())).thenReturn(mockHandler);\n    when(mockHandler.getSFStatement()).thenReturn(mockSFStatement);\n    when(mockConnection.getSFBaseSession(any())).thenReturn(mockSession);\n    when(mockConnection.getSessionID()).thenReturn(\"test-session-id\");\n    when(mockConnection.isClosed()).thenReturn(false);\n\n    when(mockSFStatement.execute(any(), any(), any(), any())).thenReturn(mockSFResultSet);\n    when(mockSFStatement.hasChildren()).thenReturn(false);\n\n    when(mockSFResultSet.getQueryId()).thenReturn(\"query-id\");\n    when(mockSFResultSet.isClosed()).thenReturn(false);\n    when(mockSFResultSet.next()).thenReturn(false);\n\n    when(mockHandler.createResultSet(any(SFBaseResultSet.class), any()))\n        .thenReturn(mockJdbcResultSet);\n    when(mockJdbcResultSet.isClosed()).thenReturn(false);\n\n    statement =\n        new SnowflakeStatementImpl(\n            mockConnection,\n            ResultSet.TYPE_FORWARD_ONLY,\n            ResultSet.CONCUR_READ_ONLY,\n            ResultSet.CLOSE_CURSORS_AT_COMMIT);\n  }\n\n  @Test\n  void copyWithFlagDisabled_executeReturnsFalseAndResultSetIsNull() throws SQLException {\n    when(mockSFResultSet.getStatementType()).thenReturn(SFStatementType.COPY);\n    when(mockSession.isEnableCopyResultSet()).thenReturn(false);\n    when(mockSession.isSfSQLMode()).thenReturn(false);\n\n    boolean result = statement.execute(\"COPY INTO ...\");\n\n    assertFalse(result, \"execute() should return false for COPY when flag is disabled\");\n    assertNull(\n        statement.getResultSet(), \"getResultSet() should be null when execute() returns false\");\n  }\n\n  @Test\n  void copyWithFlagEnabled_executeReturnsTrueAndResultSetIsNonNull() throws SQLException {\n    when(mockSFResultSet.getStatementType()).thenReturn(SFStatementType.COPY);\n    when(mockSession.isEnableCopyResultSet()).thenReturn(true);\n\n    boolean result = statement.execute(\"COPY INTO ...\");\n\n    assertTrue(result, \"execute() should return true for COPY when flag is enabled\");\n    assertNotNull(\n        statement.getResultSet(), \"getResultSet() should be non-null when execute() returns true\");\n  }\n\n  @Test\n  void insertWithFlagEnabled_executeReturnsFalse() throws SQLException {\n    when(mockSFResultSet.getStatementType()).thenReturn(SFStatementType.INSERT);\n    when(mockSession.isEnableCopyResultSet()).thenReturn(true);\n    when(mockSession.isSfSQLMode()).thenReturn(false);\n\n    boolean result = statement.execute(\"INSERT INTO t VALUES (1)\");\n\n    assertFalse(\n        result, \"execute() should return false for INSERT regardless of enableCopyResultSet flag\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/config/ConnectionAutoUrlParserTest.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\n// Unit tests for getConnectionNameFromUrl() function.\npublic class ConnectionAutoUrlParserTest {\n  @Test\n  void testValidConnection() {\n    String url = \"jdbc:snowflake:auto?connectionName=readonly\";\n    String value = SFConnectionConfigParser.getConnectionNameFromUrl(url);\n    assertEquals(\"readonly\", value);\n  }\n\n  @Test\n  void testNoParameters() {\n    String url = \"jdbc:snowflake:auto\";\n    String value = SFConnectionConfigParser.getConnectionNameFromUrl(url);\n    assertEquals(\"\", value);\n  }\n\n  @Test\n  void testUTFCharParameterKey() {\n    String url =\n        \"jdbc:snowflake://account.region.snowflakecomputing.com/?\"\n            + \"user=vikram&password=secret&connectionName=myConfig&note=%E2%9C%93\";\n\n    assertEquals(\"myConfig\", SFConnectionConfigParser.getConnectionNameFromUrl(url));\n  }\n\n  @Test\n  void testMissingValueForConnection() {\n    String url = \"jdbc:snowflake:auto?connectionName=\";\n    assertEquals(\"\", SFConnectionConfigParser.getConnectionNameFromUrl(url));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/config/SFClientConfigParserTest.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static net.snowflake.client.internal.config.SFClientConfigParser.SF_CLIENT_CONFIG_ENV_NAME;\nimport static net.snowflake.client.internal.config.SFClientConfigParser.SF_CLIENT_CONFIG_FILE_NAME;\nimport static net.snowflake.client.internal.config.SFClientConfigParser.convertToWindowsPath;\nimport static net.snowflake.client.internal.config.SFClientConfigParser.getConfigFilePathFromJDBCJarLocation;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemSetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemUnsetEnv;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mockStatic;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\n\npublic class SFClientConfigParserTest {\n  private static final String CONFIG_JSON =\n      \"{\\\"common\\\":{\\\"log_level\\\":\\\"info\\\",\\\"log_path\\\":\\\"/jdbc.log\\\"}}\";\n  private static final String CONFIG_JSON_WITH_UNKNOWN_PROPS =\n      \"{\\\"common\\\":{\\\"log_level\\\":\\\"info\\\",\\\"log_path\\\":\\\"/jdbc.log\\\",\\\"unknown_inside\\\":\\\"/unknown\\\"},\\\"unknown_outside\\\":\\\"/unknown\\\"}\";\n\n  private Path configFilePath;\n\n  @AfterEach\n  public void cleanup() throws IOException {\n    if (configFilePath != null) {\n      Files.deleteIfExists(configFilePath);\n    }\n\n    systemUnsetEnv(SF_CLIENT_CONFIG_ENV_NAME);\n  }\n\n  @Test\n  public void testLoadSFClientConfigValidPath() throws IOException {\n    configFilePath = Paths.get(\"config.json\");\n    Files.write(configFilePath, CONFIG_JSON.getBytes());\n    SFClientConfig actualConfig =\n        SFClientConfigParser.loadSFClientConfig(configFilePath.toString());\n    assertEquals(\"info\", actualConfig.getCommonProps().getLogLevel());\n    assertEquals(\"/jdbc.log\", actualConfig.getCommonProps().getLogPath());\n    assertEquals(\"config.json\", actualConfig.getConfigFilePath());\n  }\n\n  @Test\n  public void testLoadSFClientConfigValidPathWithUnknownProperties() throws IOException {\n    configFilePath = Paths.get(\"config.json\");\n    Files.write(configFilePath, CONFIG_JSON_WITH_UNKNOWN_PROPS.getBytes());\n    SFClientConfig actualConfig =\n        SFClientConfigParser.loadSFClientConfig(configFilePath.toString());\n    assertEquals(\"info\", actualConfig.getCommonProps().getLogLevel());\n    assertEquals(\"/jdbc.log\", actualConfig.getCommonProps().getLogPath());\n  }\n\n  @Test\n  public void testLoadSFClientConfigInValidPath() {\n    String configFilePath = \"InvalidPath\";\n    assertThrows(IOException.class, () -> SFClientConfigParser.loadSFClientConfig(configFilePath));\n  }\n\n  @Test\n  public void testLoadSFClientConfigInValidJson() {\n    assertThrows(\n        IOException.class,\n        () -> {\n          String invalidJson = \"invalidJson\";\n          configFilePath = Paths.get(\"config.json\");\n          Files.write(configFilePath, invalidJson.getBytes());\n          SFClientConfigParser.loadSFClientConfig(configFilePath.toString());\n        });\n  }\n\n  @Test\n  public void testLoadSFClientConfigWithEnvVar() throws IOException {\n    configFilePath = Paths.get(\"config.json\");\n    Files.write(configFilePath, CONFIG_JSON.getBytes());\n    systemSetEnv(SF_CLIENT_CONFIG_ENV_NAME, \"config.json\");\n    SFClientConfig actualConfig = SFClientConfigParser.loadSFClientConfig(null);\n    assertEquals(\"info\", actualConfig.getCommonProps().getLogLevel());\n    assertEquals(\"/jdbc.log\", actualConfig.getCommonProps().getLogPath());\n  }\n\n  @Test\n  public void testLoadSFClientConfigWithDriverLocation() throws IOException {\n    String configLocation =\n        Paths.get(getConfigFilePathFromJDBCJarLocation(), SF_CLIENT_CONFIG_FILE_NAME).toString();\n    configFilePath = Paths.get(configLocation);\n    Files.write(configFilePath, CONFIG_JSON.getBytes());\n    SFClientConfig actualConfig = SFClientConfigParser.loadSFClientConfig(null);\n    assertEquals(\"info\", actualConfig.getCommonProps().getLogLevel());\n    assertEquals(\"/jdbc.log\", actualConfig.getCommonProps().getLogPath());\n  }\n\n  @Test\n  public void testLoadSFClientConfigWithUserHome() throws IOException {\n    String tmpDirectory = systemGetProperty(\"java.io.tmpdir\");\n    try (MockedStatic<SnowflakeUtil> mockedSnowflakeUtil = mockStatic(SnowflakeUtil.class)) {\n      // mocking this as Jenkins/GH Action doesn't have write permissions on user.home directory.\n      mockedSnowflakeUtil.when(() -> systemGetProperty(\"user.home\")).thenReturn(tmpDirectory);\n\n      configFilePath = Paths.get(systemGetProperty(\"user.home\"), SF_CLIENT_CONFIG_FILE_NAME);\n      Files.write(configFilePath, CONFIG_JSON.getBytes());\n      SFClientConfig actualConfig = SFClientConfigParser.loadSFClientConfig(null);\n      assertEquals(\"info\", actualConfig.getCommonProps().getLogLevel());\n      assertEquals(\"/jdbc.log\", actualConfig.getCommonProps().getLogPath());\n    }\n  }\n\n  @Test\n  public void testLoadSFClientNoConditionsMatch() throws IOException {\n    SFClientConfig actualConfig = SFClientConfigParser.loadSFClientConfig(null);\n    assertNull(actualConfig);\n  }\n\n  @Test\n  public void testGetConfigFileNameFromJDBCJarLocation() {\n    String jdbcDirectoryPath = getConfigFilePathFromJDBCJarLocation();\n    assertTrue(jdbcDirectoryPath != null && !jdbcDirectoryPath.isEmpty());\n  }\n\n  @Test\n  public void testConvertToWindowsPath() {\n    String mockWindowsPath = \"C:/Program Files/example.txt\";\n    String resultWindowsPath = \"C:\\\\Program Files\\\\example.txt\";\n    String[] testCases = new String[] {\"\", \"file:\\\\\", \"\\\\\\\\\", \"/\", \"nested:\\\\\"};\n    String mockCloudPrefix = \"cloud://\";\n\n    for (String testcase : testCases) {\n      assertEquals(resultWindowsPath, convertToWindowsPath(testcase + mockWindowsPath));\n    }\n\n    assertEquals(\n        mockCloudPrefix + resultWindowsPath,\n        convertToWindowsPath(mockCloudPrefix + mockWindowsPath));\n  }\n\n  @Test\n  void testSFClientConfigConstructorAndAccessors() {\n    SFClientConfig.CommonProps props = new SFClientConfig.CommonProps();\n    props.setLogLevel(\"DEBUG\");\n    props.setLogPath(\"/tmp/logs\");\n    SFClientConfig config = new SFClientConfig(props);\n    config.setConfigFilePath(\"/etc/snowflake/config.json\");\n\n    assertEquals(props, config.getCommonProps());\n    assertEquals(\"/etc/snowflake/config.json\", config.getConfigFilePath());\n  }\n\n  @Test\n  void testCommonPropsConstructorAndAccessors() {\n    SFClientConfig.CommonProps props = new SFClientConfig.CommonProps();\n    props.setLogLevel(\"DEBUG\");\n    props.setLogPath(\"/var/logs/snowflake.log\");\n\n    assertEquals(\"DEBUG\", props.getLogLevel());\n    assertEquals(\"/var/logs/snowflake.log\", props.getLogPath());\n  }\n\n  @Test\n  void testSFClientConfigEqualsAndHashCode() {\n    SFClientConfig.CommonProps props1 = new SFClientConfig.CommonProps();\n    props1.setLogLevel(\"INFO\");\n    props1.setLogPath(\"/tmp\");\n\n    SFClientConfig.CommonProps props2 = new SFClientConfig.CommonProps();\n    props2.setLogLevel(\"INFO\");\n    props2.setLogPath(\"/tmp\");\n    SFClientConfig config1 = new SFClientConfig(props1);\n    SFClientConfig config2 = new SFClientConfig(props2);\n\n    assertEquals(config1, config2);\n    assertEquals(config1.hashCode(), config2.hashCode());\n\n    // Negative test\n    props2.setLogLevel(\"DEBUG\");\n    assertNotEquals(config1, new SFClientConfig(props2));\n  }\n\n  @Test\n  void testCommonPropsEqualsAndHashCode() {\n    SFClientConfig.CommonProps props1 = new SFClientConfig.CommonProps();\n    props1.setLogLevel(\"WARN\");\n    props1.setLogPath(\"/opt/logs\");\n    SFClientConfig.CommonProps props2 = new SFClientConfig.CommonProps();\n    props2.setLogLevel(\"WARN\");\n    props2.setLogPath(\"/opt/logs\");\n\n    assertEquals(props1, props2);\n    assertEquals(props1.hashCode(), props2.hashCode());\n\n    // Negative test\n    props2.setLogLevel(\"ERROR\");\n    assertNotEquals(props1, props2);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/config/SFConnectionConfigParserPermissionTest.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nclass SFConnectionConfigParserPermissionTest {\n\n  private Path createTempFileWithPermissions(Set<PosixFilePermission> perms) throws Exception {\n    // Create a unique temporary directory\n    Path tempDir = Files.createTempDirectory(\"snowflake\");\n\n    // Inside it, create a file named \"connections.toml\"\n    Path tomlFile = tempDir.resolve(\"connections.toml\");\n    Files.createFile(tomlFile);\n\n    // Apply the given POSIX permissions\n    Files.setPosixFilePermissions(tomlFile, perms);\n\n    // Mark both the file and the directory for deletion on JVM exit\n    tomlFile.toFile().deleteOnExit();\n    tempDir.toFile().deleteOnExit();\n\n    return tomlFile;\n  }\n\n  static List<Object[]> permissionTestCases() {\n    return Arrays.asList(\n        new Object[][] {\n          { // Group write\n            new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.GROUP_WRITE)),\n            true,\n            \"writable by group or others\"\n          },\n          { // Others write\n            new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.OTHERS_WRITE)),\n            true,\n            \"writable by group or others\"\n          },\n          { // Owner execute\n            new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.OWNER_EXECUTE)),\n            true,\n            \"executable\"\n          },\n          { // Group execute\n            new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.GROUP_EXECUTE)),\n            true,\n            \"executable\"\n          },\n          { // Others execute\n            new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.OTHERS_EXECUTE)),\n            true,\n            \"executable\"\n          },\n          { // Owner read/write only\n            new HashSet<>(\n                Arrays.asList(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)),\n            false,\n            null\n          }\n        });\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"permissionTestCases\")\n  @DontRunOnWindows\n  void testFilePermissionScenarios(\n      Set<PosixFilePermission> perms, boolean shouldThrow, String expectedMsg) throws Exception {\n    Path tempFile = createTempFileWithPermissions(perms);\n    try {\n      if (shouldThrow) {\n        Exception ex =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () -> SFConnectionConfigParser.verifyFilePermissionSecure(tempFile));\n        assertTrue(ex.getMessage().contains(expectedMsg));\n      } else {\n        assertDoesNotThrow(() -> SFConnectionConfigParser.verifyFilePermissionSecure(tempFile));\n      }\n    } finally {\n      Files.deleteIfExists(tempFile);\n    }\n  }\n\n  static List<Object[]> skipReadWarningTestCases() {\n    return Arrays.asList(\n        new Object[][] {\n          {\n            new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.GROUP_READ,\n                    PosixFilePermission.OTHERS_READ))\n          }\n        });\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"skipReadWarningTestCases\")\n  @DontRunOnWindows\n  void testSkipWarningForReadPermissionsEnvVar(Set<PosixFilePermission> perms) throws Exception {\n    Path tempFile = createTempFileWithPermissions(perms);\n    SnowflakeUtil.systemSetEnv(\"SF_SKIP_WARNING_FOR_READ_PERMISSIONS_ON_CONFIG_FILE\", \"true\");\n    try {\n      assertDoesNotThrow(() -> SFConnectionConfigParser.verifyFilePermissionSecure(tempFile));\n    } finally {\n      Files.deleteIfExists(tempFile);\n      SnowflakeUtil.systemSetEnv(\"SF_SKIP_WARNING_FOR_READ_PERMISSIONS_ON_CONFIG_FILE\", null);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/config/SFConnectionConfigParserTest.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static net.snowflake.client.AssumptionUtils.assumeRunningOnLinuxMac;\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION;\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY;\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SNOWFLAKE_HOME_KEY;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport com.fasterxml.jackson.dataformat.toml.TomlMapper;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileAttribute;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class SFConnectionConfigParserTest {\n\n  private static final List<String> ENV_VARIABLES_KEYS =\n      new ArrayList<>(\n          Arrays.asList(\n              SNOWFLAKE_HOME_KEY,\n              SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY,\n              SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION));\n  private Path tempPath = null;\n  private TomlMapper tomlMapper = new TomlMapper();\n  private Map<String, String> envVariables = new HashMap<>();\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    tempPath = Files.createTempDirectory(\".snowflake\");\n    ENV_VARIABLES_KEYS.stream()\n        .forEach(\n            key -> {\n              if (SnowflakeUtil.systemGetEnv(key) != null) {\n                envVariables.put(key, SnowflakeUtil.systemGetEnv(key));\n              }\n            });\n  }\n\n  @AfterEach\n  public void close() throws IOException {\n    SnowflakeUtil.systemUnsetEnv(SNOWFLAKE_HOME_KEY);\n    SnowflakeUtil.systemUnsetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY);\n    SnowflakeUtil.systemUnsetEnv(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION);\n    Files.walk(tempPath).map(Path::toFile).forEach(File::delete);\n    Files.delete(tempPath);\n    envVariables.forEach((key, value) -> SnowflakeUtil.systemSetEnv(key, value));\n  }\n\n  @Test\n  public void testLoadSFConnectionConfigWrongConfigurationName()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"unknown\");\n    prepareConnectionConfigurationTomlFile();\n    assertThrows(\n        SnowflakeSQLException.class, () -> SFConnectionConfigParser.buildConnectionParameters(\"\"));\n  }\n\n  @Test\n  public void testLoadSFConnectionConfigInValidPath() throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, Paths.get(\"unknownPath\").toString());\n    prepareConnectionConfigurationTomlFile();\n    assertNull(SFConnectionConfigParser.buildConnectionParameters(\"\"));\n  }\n\n  @Test\n  public void testLoadSFConnectionConfigWithTokenFromFile()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    File tokenFile = new File(Paths.get(tempPath.toString(), \"token\").toUri());\n    prepareConnectionConfigurationTomlFile(\n        Collections.singletonMap(\"token_file_path\", tokenFile.toString()));\n\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(tokenFile.toString(), data.getParams().get(\"token_file_path\"));\n  }\n\n  @Test\n  public void testThrowErrorWhenWrongPermissionsForConnectionConfigurationFile()\n      throws IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    File tokenFile = new File(Paths.get(tempPath.toString(), \"token\").toUri());\n    prepareConnectionConfigurationTomlFile(\n        Collections.singletonMap(\"token_file_path\", tokenFile.toString()), false, false);\n    assumeRunningOnLinuxMac();\n    assertThrows(\n        SnowflakeSQLException.class, () -> SFConnectionConfigParser.buildConnectionParameters(\"\"));\n  }\n\n  @Test\n  public void testThrowErrorWhenWrongPermissionsForTokenFile() throws IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    File tokenFile = new File(Paths.get(tempPath.toString(), \"token\").toUri());\n    prepareConnectionConfigurationTomlFile(\n        Collections.singletonMap(\"token_file_path\", tokenFile.toString()), true, false);\n    assumeRunningOnLinuxMac();\n    assertThrows(\n        SnowflakeSQLException.class, () -> SFConnectionConfigParser.buildConnectionParameters(\"\"));\n  }\n\n  @Test\n  public void testNoThrowErrorWhenWrongPermissionsForTokenFileButSkippingFlagIsEnabled()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    SnowflakeUtil.systemSetEnv(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION, \"true\");\n    File tokenFile = new File(Paths.get(tempPath.toString(), \"token\").toUri());\n    prepareConnectionConfigurationTomlFile(\n        Collections.singletonMap(\"token_file_path\", tokenFile.toString()), true, false);\n\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(tokenFile.toString(), data.getParams().get(\"token_file_path\"));\n  }\n\n  @Test\n  public void testNoThrowErrorWhenWrongPermissionsForConnectionConfigButSkippingFlagIsEnabled()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    SnowflakeUtil.systemSetEnv(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION, \"true\");\n    File tokenFile = new File(Paths.get(tempPath.toString(), \"token\").toUri());\n    prepareConnectionConfigurationTomlFile(\n        Collections.singletonMap(\"token_file_path\", tokenFile.toString()), false, false);\n    assumeRunningOnLinuxMac();\n\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(tokenFile.toString(), data.getParams().get(\"token_file_path\"));\n  }\n\n  @Test\n  public void testLoadSFConnectionConfigWithHostConfigured()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    Map<String, String> extraparams = new HashMap<>();\n    extraparams.put(\"host\", \"snowflake.reg.local\");\n    extraparams.put(\"account\", null);\n    extraparams.put(\"port\", \"8082\");\n    extraparams.put(\"token\", \"testToken\");\n    prepareConnectionConfigurationTomlFile(extraparams);\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://snowflake.reg.local:8082\", data.getUrl());\n    assertEquals(\"oauth\", data.getParams().get(\"authenticator\"));\n    assertEquals(\"testToken\", data.getParams().get(\"token\"));\n  }\n\n  @Test\n  public void testDefaultPortIs443WhenNeitherPortNorProtocolIsSet()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(null, null);\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://myorg-myaccount.snowflakecomputing.com:443\", data.getUrl());\n  }\n\n  @Test\n  public void testDefaultPortIs443WhenProtocolIsHttps() throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(null, \"https\");\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://myorg-myaccount.snowflakecomputing.com:443\", data.getUrl());\n  }\n\n  @Test\n  public void testDefaultPortIs80WhenProtocolIsHttp() throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(null, \"http\");\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\n        \"jdbc:snowflake://http://myorg-myaccount.snowflakecomputing.com:80\", data.getUrl());\n  }\n\n  @Test\n  public void testExplicitPortIsPreservedRegardlessOfProtocol()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(\"8082\", \"http\");\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\n        \"jdbc:snowflake://http://myorg-myaccount.snowflakecomputing.com:8082\", data.getUrl());\n  }\n\n  @Test\n  public void testUrlParametersAreMergedIntoTomlConfiguration()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(null, null);\n    ConnectionParameters data =\n        SFConnectionConfigParser.buildConnectionParameters(\n            \"jdbc:snowflake:auto?connectionName=default&tracing=ALL&disablePlatformDetection=true\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://myorg-myaccount.snowflakecomputing.com:443\", data.getUrl());\n    assertEquals(\"ALL\", data.getParams().get(\"tracing\"));\n    assertEquals(\"true\", data.getParams().get(\"disablePlatformDetection\"));\n    assertEquals(\"user1\", data.getParams().get(\"user\"));\n    assertEquals(\"pass1\", data.getParams().get(\"password\"));\n    assertEquals(\"MY_WH\", data.getParams().get(\"warehouse\"));\n  }\n\n  @Test\n  public void testUrlParameterOverridesTomlValueForSameKey()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(\"8082\", \"http\");\n    ConnectionParameters data =\n        SFConnectionConfigParser.buildConnectionParameters(\n            \"jdbc:snowflake:auto?connectionName=default&port=443&protocol=https&warehouse=OTHER_WH&tracing=ALL\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://myorg-myaccount.snowflakecomputing.com:443\", data.getUrl());\n    assertEquals(\"443\", data.getParams().get(\"port\"));\n    assertEquals(\"https\", data.getParams().get(\"protocol\"));\n    assertEquals(\"OTHER_WH\", data.getParams().get(\"warehouse\"));\n    assertEquals(\"ALL\", data.getParams().get(\"tracing\"));\n    assertEquals(\"user1\", data.getParams().get(\"user\"));\n    assertEquals(\"pass1\", data.getParams().get(\"password\"));\n    assertEquals(\"myorg-myaccount\", data.getParams().get(\"account\"));\n    Set<String> expectedKeys =\n        new HashSet<>(\n            Arrays.asList(\n                \"account\", \"user\", \"password\", \"warehouse\", \"port\", \"protocol\", \"tracing\"));\n    Set<String> actualKeys = data.getParams().stringPropertyNames();\n    assertEquals(expectedKeys, actualKeys);\n  }\n\n  @Test\n  public void testUrlParameterOverridesTomlUser() throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(null, null);\n    ConnectionParameters data =\n        SFConnectionConfigParser.buildConnectionParameters(\n            \"jdbc:snowflake:auto?connectionName=default&user=overridden_user\");\n    assertNotNull(data);\n    assertEquals(\"overridden_user\", data.getParams().get(\"user\"));\n  }\n\n  @Test\n  public void testUrlParametersWithNoExtraParamsKeepsTomlValues()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    prepareTomlWithPortAndProtocol(\"8082\", \"http\");\n    ConnectionParameters data =\n        SFConnectionConfigParser.buildConnectionParameters(\n            \"jdbc:snowflake:auto?connectionName=default\");\n    assertNotNull(data);\n    assertEquals(\n        \"jdbc:snowflake://http://myorg-myaccount.snowflakecomputing.com:8082\", data.getUrl());\n    assertEquals(\"user1\", data.getParams().get(\"user\"));\n    assertEquals(\"pass1\", data.getParams().get(\"password\"));\n    assertEquals(\"MY_WH\", data.getParams().get(\"warehouse\"));\n  }\n\n  @Test\n  public void testHttpProtocolFromTomlIsEmbeddedInUrl() throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    Map<String, String> extraparams = new HashMap<>();\n    extraparams.put(\"host\", \"snowflake.reg.local\");\n    extraparams.put(\"account\", null);\n    extraparams.put(\"port\", \"8082\");\n    extraparams.put(\"token\", \"testToken\");\n    extraparams.put(\"protocol\", \"http\");\n    prepareConnectionConfigurationTomlFile(extraparams);\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://http://snowflake.reg.local:8082\", data.getUrl());\n  }\n\n  @Test\n  public void testHttpsProtocolFromTomlProducesStandardUrl()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    Map<String, String> extraparams = new HashMap<>();\n    extraparams.put(\"host\", \"snowflake.reg.local\");\n    extraparams.put(\"account\", null);\n    extraparams.put(\"port\", \"8082\");\n    extraparams.put(\"token\", \"testToken\");\n    extraparams.put(\"protocol\", \"https\");\n    prepareConnectionConfigurationTomlFile(extraparams);\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://snowflake.reg.local:8082\", data.getUrl());\n  }\n\n  @Test\n  public void testDefaultPortIs443WhenProtocolIsEmptyString()\n      throws SnowflakeSQLException, IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    Map<String, String> extraparams = new HashMap<>();\n    extraparams.put(\"host\", \"snowflake.reg.local\");\n    extraparams.put(\"account\", null);\n    extraparams.put(\"port\", null);\n    extraparams.put(\"token\", \"testToken\");\n    extraparams.put(\"protocol\", \"\");\n    prepareConnectionConfigurationTomlFile(extraparams);\n    ConnectionParameters data = SFConnectionConfigParser.buildConnectionParameters(\"\");\n    assertNotNull(data);\n    assertEquals(\"jdbc:snowflake://snowflake.reg.local:443\", data.getUrl());\n  }\n\n  @Test\n  public void shouldThrowExceptionIfNoneOfHostAndAccountIsSet() throws IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    Map<String, String> extraparams = new HashMap<>();\n    extraparams.put(\"host\", null);\n    extraparams.put(\"account\", null);\n    prepareConnectionConfigurationTomlFile(extraparams);\n    assertThrows(\n        SnowflakeSQLException.class, () -> SFConnectionConfigParser.buildConnectionParameters(\"\"));\n  }\n\n  @Test\n  public void shouldThrowExceptionIfTokenIsNotSetForOauth() throws IOException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, \"default\");\n    SnowflakeUtil.systemSetEnv(SKIP_TOKEN_FILE_PERMISSIONS_VERIFICATION, \"true\");\n    File tokenFile = new File(Paths.get(tempPath.toString(), \"token\").toUri());\n    prepareConnectionConfigurationTomlFile(\n        Collections.singletonMap(\"token_file_path\", tokenFile.toString()), true, false, \"\");\n\n    assertThrows(\n        SnowflakeSQLException.class, () -> SFConnectionConfigParser.buildConnectionParameters(\"\"));\n  }\n\n  private void prepareConnectionConfigurationTomlFile() throws IOException {\n    prepareConnectionConfigurationTomlFile(null, true, true);\n  }\n\n  private void prepareConnectionConfigurationTomlFile(Map<String, String> moreParameters)\n      throws IOException {\n    prepareConnectionConfigurationTomlFile(moreParameters, true, true);\n  }\n\n  private void prepareConnectionConfigurationTomlFile(\n      Map<String, String> moreParameters,\n      boolean onlyUserPermissionConnection,\n      boolean onlyUserPermissionToken)\n      throws IOException {\n    prepareConnectionConfigurationTomlFile(\n        moreParameters, onlyUserPermissionConnection, onlyUserPermissionToken, \"token_from_file\");\n  }\n\n  private void prepareConnectionConfigurationTomlFile(\n      Map<String, String> moreParameters,\n      boolean onlyUserPermissionConnection,\n      boolean onlyUserPermissionToken,\n      String token)\n      throws IOException {\n    Path path = Paths.get(tempPath.toString(), \"connections.toml\");\n    Path filePath = createFilePathWithPermission(path, onlyUserPermissionConnection);\n    File file = filePath.toFile();\n\n    Map<String, Object> configuration = new HashMap<>();\n    Map<String, Object> configurationParams = new HashMap<>();\n    configurationParams.put(\"account\", \"snowaccount.us-west-2.aws\");\n    configurationParams.put(\"user\", \"user1\");\n    configurationParams.put(\"port\", \"443\");\n    configurationParams.put(\"authenticator\", \"oauth\");\n\n    if (moreParameters != null) {\n      moreParameters.forEach((k, v) -> configurationParams.put(k, v));\n    }\n    configuration.put(\"default\", configurationParams);\n    tomlMapper.writeValue(file, configuration);\n\n    if (configurationParams.containsKey(\"token_file_path\")) {\n      Path tokenFilePath =\n          createFilePathWithPermission(\n              Paths.get(configurationParams.get(\"token_file_path\").toString()),\n              onlyUserPermissionToken);\n      Files.write(tokenFilePath, token.getBytes());\n      Path emptyTokenFilePath =\n          createFilePathWithPermission(\n              Paths.get(\n                  configurationParams\n                      .get(\"token_file_path\")\n                      .toString()\n                      .replaceAll(\"token\", \"emptytoken\")),\n              onlyUserPermissionToken);\n      Files.write(emptyTokenFilePath, \"\".getBytes());\n    }\n  }\n\n  private void prepareTomlWithPortAndProtocol(String port, String protocol) throws IOException {\n    Path path = Paths.get(tempPath.toString(), \"connections.toml\");\n    Path filePath = createFilePathWithPermission(path, true);\n    File file = filePath.toFile();\n\n    Map<String, Object> configurationParams = new HashMap<>();\n    configurationParams.put(\"account\", \"myorg-myaccount\");\n    configurationParams.put(\"user\", \"user1\");\n    configurationParams.put(\"password\", \"pass1\");\n    configurationParams.put(\"warehouse\", \"MY_WH\");\n    if (port != null) {\n      configurationParams.put(\"port\", port);\n    }\n    if (protocol != null) {\n      configurationParams.put(\"protocol\", protocol);\n    }\n\n    Map<String, Object> configuration = new HashMap<>();\n    configuration.put(\"default\", configurationParams);\n    tomlMapper.writeValue(file, configuration);\n  }\n\n  private Path createFilePathWithPermission(Path path, boolean onlyUserPermission)\n      throws IOException {\n    if (!isWindows()) {\n      FileAttribute<Set<PosixFilePermission>> fileAttribute =\n          onlyUserPermission\n              ? PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rw-------\"))\n              : PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rwxrw----\"));\n      return Files.createFile(path, fileAttribute);\n    } else {\n      return Files.createFile(path);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/config/SFPermissionsTest.java",
    "content": "package net.snowflake.client.internal.config;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\npublic class SFPermissionsTest {\n  Path configFilePath = Paths.get(\"config.json\");\n  String configJson = \"{\\\"common\\\":{\\\"log_level\\\":\\\"debug\\\",\\\"log_path\\\":\\\"logs\\\"}}\";\n\n  @BeforeEach\n  public void createConfigFile() throws IOException {\n    Files.write(configFilePath, configJson.getBytes());\n  }\n\n  @AfterEach\n  public void cleanupConfigFile() throws IOException {\n    Files.deleteIfExists(configFilePath);\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n    \"rwx------,false\",\n    \"rw-------,false\",\n    \"r-x------,false\",\n    \"r--------,false\",\n    \"rwxrwx---,true\",\n    \"rwxrw----,true\",\n    \"rwxr-x---,false\",\n    \"rwxr-----,false\",\n    \"rwx-wx---,true\",\n    \"rwx-w----,true\",\n    \"rwx--x---,false\",\n    \"rwx---rwx,true\",\n    \"rwx---rw-,true\",\n    \"rwx---r-x,false\",\n    \"rwx---r--,false\",\n    \"rwx----wx,true\",\n    \"rwx----w-,true\",\n    \"rwx-----x,false\"\n  })\n  @DontRunOnWindows\n  public void testLogDirectoryPermissions(String permission, boolean isSucceed) throws IOException {\n    // TODO: SNOW-1503722 Change to check for thrown exceptions\n    // Don't run on Windows\n    Files.setPosixFilePermissions(configFilePath, PosixFilePermissions.fromString(permission));\n    Boolean result =\n        SFClientConfigParser.checkGroupOthersWritePermissions(configFilePath.toString());\n    assertEquals(isSucceed, result);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/AttributeEnhancingHttpRequestRetryHandlerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.io.IOException;\nimport org.apache.http.protocol.BasicHttpContext;\nimport org.apache.http.protocol.HttpContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nclass AttributeEnhancingHttpRequestRetryHandlerTest {\n  private final IOException dummyException = new IOException(\"Test exception\");\n\n  @ParameterizedTest\n  @ValueSource(ints = {0, 3, 5, 10})\n  void testAttributeSet(int executionCount) {\n    HttpContext context = new BasicHttpContext();\n    AttributeEnhancingHttpRequestRetryHandler handler =\n        new AttributeEnhancingHttpRequestRetryHandler();\n\n    handler.retryRequest(dummyException, executionCount, context);\n\n    assertEquals(\n        executionCount,\n        context.getAttribute(AttributeEnhancingHttpRequestRetryHandler.EXECUTION_COUNT_ATTRIBUTE),\n        \"Attribute should be set to the provided executionCount: \" + executionCount);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/CertificateChainTrustValidationTestLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.security.KeyStore;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\n/**\n * This test validates the behavior of the SFTrustManager (which should use PKIX validation) when\n * validating a specific cross-signed certificate chain. It also compares this with the behavior of\n * the SunX509 X509TrustManager The test ensures that: - The SFTrustManager can validate the chain\n * successfully. - The SunX509 X509TrustManager fails validation due to its trust store\n * configuration.\n *\n * <p>Prerequisites for this test: - The certificates used in this test must be generated by the\n * script located at ssl-tests/generate_certs.sh - The test dynamically sets the JVM properties:\n * -Djavax.net.ssl.trustStore=path/to/test/resources/ssl-tests/certs/truststore.jks\n * -Djavax.net.ssl.trustStorePassword=changeit These properties are reset after all tests are\n * finished.\n */\n@org.junit.jupiter.api.Tag(\"CORE\")\npublic class CertificateChainTrustValidationTestLatestIT {\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(CertificateChainTrustValidationTestLatestIT.class);\n\n  private static final String CERT_RESOURCE_PATH = \"ssl-tests/certs/\";\n  private static final String TRUST_STORE_FILE_NAME = \"truststore.jks\";\n  private static final String TRUST_STORE_PASSWORD = \"changeit\";\n\n  // Original JVM properties to restore after tests\n  private static String originalTrustStore;\n  private static String originalTrustStorePassword;\n\n  // Certificates for the exact chain generated by the script\n  private static X509Certificate leafCert; // Cert 0\n  private static X509Certificate amzRsaM02IntermediateCert; // Cert 1\n  private static X509Certificate amzRootCa1ChainCert; // Cert 2 (issued by St G2)\n  private static X509Certificate stG2RootCert; // Cert 3 (issued by St Class 2)\n  private static X509Certificate stClass2RootCert; // Ultimate Root of the chain (self-signed)\n\n  // The specific self-signed Amz Root CA 1 from the trust store\n  private static X509Certificate amzRootCa1SelfSignedForTrustStoreCert;\n\n  private static SFTrustManager sfTrustManager;\n  private static X509TrustManager sunX509TrustManager;\n\n  @BeforeAll\n  static void setUpAll() throws Exception {\n    logger.debug(\"--- Test Setup Started ---\");\n\n    // Store original JVM properties\n    originalTrustStore = System.getProperty(\"javax.net.ssl.trustStore\");\n    originalTrustStorePassword = System.getProperty(\"javax.net.ssl.trustStorePassword\");\n\n    // Set JVM properties for the test\n    Path trustStorePath =\n        Paths.get(\"src\", \"test\", \"resources\", CERT_RESOURCE_PATH, TRUST_STORE_FILE_NAME);\n    System.setProperty(\"javax.net.ssl.trustStore\", trustStorePath.toAbsolutePath().toString());\n    System.setProperty(\"javax.net.ssl.trustStorePassword\", TRUST_STORE_PASSWORD);\n\n    logger.debug(\n        \"Set JVM property javax.net.ssl.trustStore to: \"\n            + System.getProperty(\"javax.net.ssl.trustStore\"));\n    logger.debug(\"Set JVM property javax.net.ssl.trustStorePassword.\");\n\n    logger.debug(\"Verifying chain certificates exist in resources...\");\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"leaf.crt\")) {\n      if (is == null) {\n        throw new IllegalStateException(\n            \"Leaf certificate not found in resources. \"\n                + \"Please ensure the 'test/resources/ssl-tests/generate_certs.sh' script was run successfully.\");\n      }\n    } catch (IOException e) {\n      throw new IllegalStateException(\"Error accessing test resources: \" + e.getMessage(), e);\n    }\n\n    CertificateFactory cf = CertificateFactory.getInstance(\"X.509\");\n\n    logger.debug(\"Loading chain certificates from classpath: \" + CERT_RESOURCE_PATH + \"...\");\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"leaf.crt\")) {\n      leafCert = (X509Certificate) cf.generateCertificate(is);\n    }\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"amz_rsa_m02_intermediate.crt\")) {\n      amzRsaM02IntermediateCert = (X509Certificate) cf.generateCertificate(is);\n    }\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"amz_root_ca1_chain.crt\")) {\n      amzRootCa1ChainCert = (X509Certificate) cf.generateCertificate(is);\n    }\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"st_g2_root.crt\")) {\n      stG2RootCert = (X509Certificate) cf.generateCertificate(is);\n    }\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"st_class2_root.crt\")) {\n      stClass2RootCert = (X509Certificate) cf.generateCertificate(is);\n    }\n    logger.debug(\"Chain certificates loaded successfully.\");\n\n    logger.debug(\"Loading the specific self-signed Amz Root CA 1 for the trust store...\");\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + \"amz_root_ca1_trust_store.crt\")) {\n      amzRootCa1SelfSignedForTrustStoreCert = (X509Certificate) cf.generateCertificate(is);\n    }\n    logger.debug(\"Self-signed Amz Root CA 1 for Trust Store loaded.\");\n\n    logger.debug(\n        \"Loading trust store from classpath: \"\n            + CERT_RESOURCE_PATH\n            + TRUST_STORE_FILE_NAME\n            + \"...\");\n    KeyStore trustStore = KeyStore.getInstance(\"JKS\");\n    // Load trust store using the dynamically set property, or directly from classpath if that fails\n    // (for robustness)\n    // Note: When javax.net.ssl.trustStore is set, default TrustManagerFactory uses it\n    // automatically.\n    // We load it here explicitly to ensure it exists and for direct checks.\n    try (InputStream is = getResourceStream(CERT_RESOURCE_PATH + TRUST_STORE_FILE_NAME)) {\n      trustStore.load(is, TRUST_STORE_PASSWORD.toCharArray());\n    }\n    logger.debug(\"Trust store loaded.\");\n\n    assertTrue(\n        trustStore.containsAlias(\"rootca1_self_signed_for_ts\"),\n        \"Trust store MUST contain 'rootca1_self_signed_for_ts' alias.\");\n    logger.debug(\n        \"Trust store content verified: only rootca1_self_signed_for_ts is present as a trust anchor.\");\n\n    logger.debug(\"Initializing PKIX X509TrustManager...\");\n    sfTrustManager = new SFTrustManager(new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED), null);\n    assertNotNull(sfTrustManager, \"PKIX X509TrustManager should be initialized.\");\n    logger.debug(\"PKIX X509TrustManager initialized successfully.\");\n\n    logger.debug(\"Initializing SunX509 X509TrustManager...\");\n    TrustManagerFactory sunX509Tmf = TrustManagerFactory.getInstance(\"SunX509\");\n    sunX509Tmf.init(trustStore); // Initialize with our explicitly loaded trustStore\n    for (TrustManager tm : sunX509Tmf.getTrustManagers()) {\n      if (tm instanceof X509TrustManager) {\n        sunX509TrustManager = (X509TrustManager) tm;\n        break;\n      }\n    }\n    assertNotNull(sunX509TrustManager, \"SunX509 X509TrustManager should be initialized.\");\n    logger.debug(\"SunX509 X509TrustManager initialized successfully.\");\n\n    logger.debug(\"--- Test Setup Complete ---\");\n  }\n\n  @AfterAll\n  static void tearDownAll() {\n    logger.debug(\"--- Test Teardown Started ---\");\n    // Restore original JVM properties\n    if (originalTrustStore != null) {\n      System.setProperty(\"javax.net.ssl.trustStore\", originalTrustStore);\n      logger.debug(\"Restored javax.net.ssl.trustStore to: \" + originalTrustStore);\n    } else {\n      System.clearProperty(\"javax.net.ssl.trustStore\");\n      logger.debug(\"Cleared javax.net.ssl.trustStore property.\");\n    }\n\n    if (originalTrustStorePassword != null) {\n      System.setProperty(\"javax.net.ssl.trustStorePassword\", originalTrustStorePassword);\n      logger.debug(\"Restored javax.net.ssl.trustStorePassword.\");\n    } else {\n      System.clearProperty(\"javax.net.ssl.trustStorePassword\");\n      logger.debug(\"Cleared javax.net.ssl.trustStorePassword property.\");\n    }\n    logger.debug(\"--- Test Teardown Complete ---\");\n  }\n\n  private static InputStream getResourceStream(String resourceName) {\n    InputStream is =\n        CertificateChainTrustValidationTestLatestIT.class\n            .getClassLoader()\n            .getResourceAsStream(resourceName);\n    if (is == null) {\n      System.err.println(\n          \"ERROR: Resource not found: \"\n              + resourceName\n              + \". Ensure 'recreate_all_certs.sh' placed it correctly.\");\n    }\n    return is;\n  }\n\n  /**\n   * Scenario: - Chain: Leaf -> Amz RSA 2048 M02 -> Amz Root CA 1 (issued by St G2) -> St G2 (issued\n   * by St Class 2) - Trust Store: Contains ONLY a *self-signed* Amz Root CA 1 (which shares Subject\n   * DN and Public Key with the chain's Amz Root CA 1).\n   */\n  @Test\n  void shouldProperlyValidateCrossSignedChain() {\n    // The chain provided will be the full 4-certificate chain.\n    // PKIX should find the trusted public key at amzRootCa1ChainCert and terminate successfully\n    // there.\n    X509Certificate[] chain =\n        new X509Certificate[] {\n          leafCert,\n          amzRsaM02IntermediateCert,\n          amzRootCa1ChainCert, // This is the Amz Root CA 1 issued by St G2.\n          stG2RootCert // This is St G2, its issuer.\n        };\n    String authType = \"RSA\";\n\n    // Test with SfTrustManager (PKIX Validation)\n    assertDoesNotThrow(\n        () -> {\n          sfTrustManager.checkServerTrusted(chain, authType);\n        },\n        \"PKIX should pass because the chain's Amz Root CA 1 public key matches a trusted anchor.\");\n\n    // Test with SunX509 Validation\n    Exception e =\n        assertThrows(\n            Exception.class, () -> sunX509TrustManager.checkServerTrusted(chain, authType));\n    assertTrue(\n        e.getMessage().contains(\"No trusted certificate found\"),\n        \"SunX509 should fail because it does match Amz Root CA 1 as a trusted anchor in the trust store, \"\n            + \"even though the public key matches. This is expected behavior for SunX509.\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/CoreUtilsMiscellaneousTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.azure.core.http.ProxyOptions;\nimport com.google.auth.http.HttpTransportFactory;\nimport java.net.InetSocketAddress;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Properties;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.jdbc.cloud.storage.CloudStorageProxyFactory;\nimport org.apache.http.HttpHost;\nimport org.junit.jupiter.api.Test;\nimport software.amazon.awssdk.http.nio.netty.ProxyConfiguration;\n\npublic class CoreUtilsMiscellaneousTest {\n\n  /**\n   * Assert that the AssertUtil.AssertTrue statement issues the correct SFException when conditions\n   * are not met\n   */\n  @Test\n  public void testSnowflakeAssertTrue() {\n    SFException e =\n        assertThrows(\n            SFException.class,\n            () -> {\n              AssertUtil.assertTrue(1 == 0, \"Numbers do not match\");\n            });\n    assertEquals(\"JDBC driver internal error: Numbers do not match.\", e.getMessage());\n  }\n\n  /** Test that Constants.getOS function is working as expected */\n  @Test\n  @DontRunOnGithubActions\n  public void testgetOS() {\n    Constants.clearOSForTesting();\n    String originalOS = systemGetProperty(\"os.name\");\n    System.setProperty(\"os.name\", \"Windows\");\n    assertEquals(Constants.OS.WINDOWS, Constants.getOS());\n    Constants.clearOSForTesting();\n    System.setProperty(\"os.name\", \"Linux\");\n    assertEquals(Constants.OS.LINUX, Constants.getOS());\n    Constants.clearOSForTesting();\n    System.setProperty(\"os.name\", \"Macintosh\");\n    assertEquals(Constants.OS.MAC, Constants.getOS());\n    Constants.clearOSForTesting();\n    System.setProperty(\"os.name\", \"Sunos\");\n    assertEquals(Constants.OS.SOLARIS, Constants.getOS());\n    // Set back to initial value at end of test\n    Constants.clearOSForTesting();\n    System.setProperty(\"os.name\", originalOS);\n  }\n\n  @Test\n  public void testHttpClientSettingsKey() {\n    // Create 2 identical HTTPClientKeys with different nonProxyHost settings\n    HttpClientSettingsKey testKey1 =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"snowflakecomputing.com\",\n            443,\n            \"*.foo.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    HttpClientSettingsKey testKey2 =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"snowflakecomputing.com\",\n            443,\n            \"*.baz.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    // Create an HTTPClientKey with all default settings\n    HttpClientSettingsKey testKey3 = new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED, \"jdbc\", false);\n    // Assert the first 2 test keys are equal\n    assertTrue(testKey1.equals(testKey2));\n    // Assert that testKey2 has its non proxy hosts updated by the equals function\n    assertEquals(\"*.foo.com\", testKey2.getNonProxyHosts());\n    // Assert that the test key with the default options is different from the others\n    assertFalse(testKey1.equals(testKey3));\n  }\n\n  @Test\n  public void testSetProxyForS3() {\n    HttpClientSettingsKey testKey =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"snowflakecomputing.com\",\n            443,\n            \"*.foo.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    ProxyConfiguration proxyConfiguration =\n        CloudStorageProxyFactory.createProxyConfigurationForS3(testKey);\n    assertEquals(HttpProtocol.HTTPS.toString(), proxyConfiguration.scheme().toUpperCase());\n    assertEquals(\"snowflakecomputing.com\", proxyConfiguration.host());\n    assertEquals(443, proxyConfiguration.port());\n    assertEquals(\n        new HashSet<>(Collections.singletonList(\"\\\\Q\\\\E.*\\\\Q.foo.com\\\\E\")),\n        proxyConfiguration.nonProxyHosts());\n    assertEquals(\"pw\", proxyConfiguration.password());\n    assertEquals(\"testuser\", proxyConfiguration.username());\n  }\n\n  @Test\n  public void testSetSessionlessProxyForS3() throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8084\");\n    props.put(\"proxyUser\", \"testuser\");\n    props.put(\"proxyPassword\", \"pw\");\n    props.put(\"nonProxyHosts\", \"baz.com | foo.com\");\n    props.put(\"proxyProtocol\", \"http\");\n    ProxyConfiguration proxyConfiguration =\n        CloudStorageProxyFactory.createSessionlessProxyConfigurationForS3(props);\n    assertEquals(HttpProtocol.HTTP.toString(), proxyConfiguration.scheme().toUpperCase());\n    assertEquals(\"localhost\", proxyConfiguration.host());\n    assertEquals(8084, proxyConfiguration.port());\n    assertEquals(\n        new HashSet<>(Arrays.asList(\"\\\\Qbaz.com\\\\E\", \"\\\\Qfoo.com\\\\E\")),\n        proxyConfiguration.nonProxyHosts());\n    assertEquals(\"pw\", proxyConfiguration.password());\n    assertEquals(\"testuser\", proxyConfiguration.username());\n    // Test that exception is thrown when port number is invalid\n    props.put(\"proxyPort\", \"invalidnumber\");\n    SnowflakeSQLException e =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> {\n              CloudStorageProxyFactory.createSessionlessProxyConfigurationForS3(props);\n            });\n    assertEquals((int) ErrorCode.INVALID_PROXY_PROPERTIES.getMessageCode(), e.getErrorCode());\n  }\n\n  @Test\n  public void testSetProxyForGCS() {\n    HttpClientSettingsKey testKey =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"snowflakecomputing.com\",\n            443,\n            \"*.foo.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    HttpTransportFactory transportFactory =\n        CloudStorageProxyFactory.createHttpTransportForGCS(testKey);\n    assertNotNull(transportFactory);\n    assertNotNull(transportFactory.create());\n    // Verify null is returned when no proxy is configured\n    HttpClientSettingsKey noProxyKey = new HttpClientSettingsKey(OCSPMode.FAIL_OPEN);\n    assertNull(CloudStorageProxyFactory.createHttpTransportForGCS(noProxyKey));\n    assertNull(CloudStorageProxyFactory.createHttpTransportForGCS(null));\n  }\n\n  @Test\n  public void testSetSessionlessProxyForGCS() throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8084\");\n    props.put(\"proxyUser\", \"testuser\");\n    props.put(\"proxyPassword\", \"pw\");\n    props.put(\"nonProxyHosts\", \"baz.com | foo.com\");\n    props.put(\"proxyProtocol\", \"http\");\n    HttpTransportFactory transportFactory =\n        CloudStorageProxyFactory.createSessionlessHttpTransportForGCS(props);\n    assertNotNull(transportFactory);\n    assertNotNull(transportFactory.create());\n    // Verify null is returned when proxy is disabled\n    Properties noProxyProps = new Properties();\n    noProxyProps.put(\"useProxy\", \"false\");\n    assertNull(CloudStorageProxyFactory.createSessionlessHttpTransportForGCS(noProxyProps));\n    assertNull(CloudStorageProxyFactory.createSessionlessHttpTransportForGCS(null));\n    // Test that exception is thrown when port number is invalid\n    props.put(\"proxyPort\", \"invalidnumber\");\n    SnowflakeSQLException e =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> {\n              CloudStorageProxyFactory.createSessionlessHttpTransportForGCS(props);\n            });\n    assertEquals((int) ErrorCode.INVALID_PROXY_PROPERTIES.getMessageCode(), e.getErrorCode());\n  }\n\n  @Test\n  public void testSetProxyForAzure() {\n    HttpClientSettingsKey testKey =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"snowflakecomputing.com\",\n            443,\n            \"*.foo.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    ProxyOptions proxyOptions = CloudStorageProxyFactory.createProxyOptionsForAzure(testKey);\n    assertEquals(ProxyOptions.Type.HTTP, proxyOptions.getType());\n    assertEquals(new InetSocketAddress(\"snowflakecomputing.com\", 443), proxyOptions.getAddress());\n    assertEquals(\"testuser\", proxyOptions.getUsername());\n    assertEquals(\"pw\", proxyOptions.getPassword());\n    assertEquals(\"(.*?\\\\.foo\\\\.com)\", proxyOptions.getNonProxyHosts());\n  }\n\n  @Test\n  public void testSetSessionlessProxyForAzure() throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8084\");\n    props.put(\"proxyUser\", \"testuser\");\n    props.put(\"proxyPassword\", \"pw\");\n    props.put(\"nonProxyHosts\", \"*\");\n    ProxyOptions proxyOptions =\n        CloudStorageProxyFactory.createSessionlessProxyOptionsForAzure(props);\n    assertEquals(ProxyOptions.Type.HTTP, proxyOptions.getType());\n    assertEquals(new InetSocketAddress(\"localhost\", 8084), proxyOptions.getAddress());\n    assertEquals(\"testuser\", proxyOptions.getUsername());\n    assertEquals(\"pw\", proxyOptions.getPassword());\n    assertEquals(\"(.*?)\", proxyOptions.getNonProxyHosts());\n    // Test that exception is thrown when port number is invalid\n    props.put(\"proxyPort\", \"invalidnumber\");\n    SnowflakeSQLException e =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> {\n              CloudStorageProxyFactory.createSessionlessProxyOptionsForAzure(props);\n            });\n    assertEquals((int) ErrorCode.INVALID_PROXY_PROPERTIES.getMessageCode(), e.getErrorCode());\n  }\n\n  @Test\n  public void testSizeOfHttpClientMapWithVariableNonProxyHosts() {\n    // clear httpClient hashmap before test\n    HttpUtil.httpClient = new HashMap<>();\n    // Clear client route planner hashmap before test\n    HttpUtil.httpClientRoutePlanner = new HashMap<>();\n    HttpClientSettingsKey key1 =\n        new HttpClientSettingsKey(\n            null,\n            \"localhost\",\n            8080,\n            \"google.com | baz.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    // Assert there is 1 entry in the hashmap now\n    HttpUtil.getHttpClient(key1);\n    assertEquals(1, HttpUtil.httpClient.size());\n    HttpClientSettingsKey key2 =\n        new HttpClientSettingsKey(\n            null, \"localhost\", 8080, \"snowflake.com\", \"testuser\", \"pw\", \"https\", \"jdbc\", false);\n    HttpUtil.getHttpClient(key2);\n    // Assert there is still 1 entry because key is re-used when only proxy difference is\n    // nonProxyHosts\n    assertEquals(1, HttpUtil.httpClient.size());\n    // Assert previous key has updated non-proxy hosts\n    assertEquals(\"snowflake.com\", key1.getNonProxyHosts());\n    HttpClientSettingsKey key3 =\n        new HttpClientSettingsKey(\n            null,\n            \"differenthost.com\",\n            8080,\n            \"snowflake.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    // Assert proxy with different host generates new entry in httpClient map\n    HttpUtil.getHttpClient(key3);\n    assertEquals(2, HttpUtil.httpClient.size());\n  }\n\n  @Test\n  public void testSizeOfHttpClientMapWithGzipAndUserAgentSuffix() {\n    // clear httpClient hashmap before test\n    HttpUtil.httpClient = new HashMap<>();\n    HttpClientSettingsKey key1 =\n        new HttpClientSettingsKey(\n            null,\n            \"localhost\",\n            8080,\n            \"google.com | baz.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            false);\n    // Assert there is 1 entry in the hashmap now\n    HttpUtil.getHttpClient(key1);\n    assertEquals(1, HttpUtil.httpClient.size());\n    HttpClientSettingsKey key2 =\n        new HttpClientSettingsKey(\n            null,\n            \"localhost\",\n            8080,\n            \"google.com | baz.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"jdbc\",\n            true);\n    HttpUtil.getHttpClient(key2);\n    // Assert there are 2 entries because gzip has changed\n    assertEquals(2, HttpUtil.httpClient.size());\n    HttpClientSettingsKey key3 =\n        new HttpClientSettingsKey(\n            null,\n            \"localhost\",\n            8080,\n            \"google.com | baz.com\",\n            \"testuser\",\n            \"pw\",\n            \"https\",\n            \"odbc\",\n            true);\n    HttpUtil.getHttpClient(key3);\n    // Assert there are 3 entries because userAgentSuffix has changed\n    assertEquals(3, HttpUtil.httpClient.size());\n  }\n\n  @Test\n  public void testSdkProxyRoutePlannerNonProxyHostsBypassesProxy() throws Exception {\n    SdkProxyRoutePlanner planner =\n        new SdkProxyRoutePlanner(\n            \"proxy.example.com\", 8080, HttpProtocol.HTTP, \"*.internal.com|localhost\");\n    // Hosts matching nonProxyHosts should bypass proxy (determineProxy returns null)\n    HttpHost internalHost = new HttpHost(\"app.internal.com\");\n    HttpHost localhostHost = new HttpHost(\"localhost\");\n    HttpHost externalHost = new HttpHost(\"external.com\");\n\n    assertNull(planner.determineProxy(internalHost, null, null));\n    assertNull(planner.determineProxy(localhostHost, null, null));\n    assertNotNull(planner.determineProxy(externalHost, null, null));\n  }\n\n  @Test\n  public void testSdkProxyRoutePlannerReDoSPatternDoesNotHang() throws Exception {\n    // The exact ReDoS payload from the vulnerability report\n    SdkProxyRoutePlanner planner =\n        new SdkProxyRoutePlanner(\"proxy.example.com\", 8080, HttpProtocol.HTTP, \"(a+)+\");\n    String maliciousHost =\n        \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac\";\n    HttpHost target = new HttpHost(maliciousHost);\n\n    long start = System.currentTimeMillis();\n    assertNotNull(planner.determineProxy(target, null, null));\n    long elapsed = System.currentTimeMillis() - start;\n    assertTrue(\n        elapsed < 1000,\n        \"Non-proxy host matching should complete nearly instantly, took \" + elapsed + \"ms\");\n  }\n\n  @Test\n  public void testConvertProxyPropertiesToHttpClientKey() throws SnowflakeSQLException {\n    OCSPMode mode = OCSPMode.FAIL_OPEN;\n    Properties props = new Properties();\n    HttpClientSettingsKey expectedNoProxy = new HttpClientSettingsKey(mode);\n\n    // Test for null proxy properties\n    HttpClientSettingsKey settingsKey =\n        SnowflakeUtil.convertProxyPropertiesToHttpClientKey(mode, props);\n    assertTrue(expectedNoProxy.equals(settingsKey));\n\n    // Set useProxy to false so proxy properties will not be set\n    props.put(\"useProxy\", \"false\");\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8084\");\n    settingsKey = SnowflakeUtil.convertProxyPropertiesToHttpClientKey(mode, props);\n    assertTrue(expectedNoProxy.equals(settingsKey));\n\n    // Test without gzip_disabled\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8084\");\n    props.put(\"proxyUser\", \"testuser\");\n    props.put(\"proxyPassword\", \"pw\");\n    props.put(\"nonProxyHosts\", \"baz.com | foo.com\");\n    props.put(\"proxyProtocol\", \"http\");\n    props.put(\"user_agent_suffix\", \"jdbc\");\n    settingsKey = SnowflakeUtil.convertProxyPropertiesToHttpClientKey(mode, props);\n    HttpClientSettingsKey expectedWithProxy =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"localhost\",\n            8084,\n            \"baz.com | foo.com\",\n            \"testuser\",\n            \"pw\",\n            \"http\",\n            \"jdbc\",\n            Boolean.valueOf(false));\n    assertTrue(expectedWithProxy.equals(settingsKey));\n\n    // Test with gzip_disabled\n    props.put(\"gzipDisabled\", \"true\");\n    settingsKey = SnowflakeUtil.convertProxyPropertiesToHttpClientKey(mode, props);\n    expectedWithProxy =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"localhost\",\n            8084,\n            \"baz.com | foo.com\",\n            \"testuser\",\n            \"pw\",\n            \"http\",\n            \"jdbc\",\n            Boolean.valueOf(true));\n    assertTrue(expectedWithProxy.equals(settingsKey));\n\n    // Test that exception is thrown when port number is invalid\n    props.put(\"proxyPort\", \"invalidnumber\");\n    SnowflakeSQLException e =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> {\n              SnowflakeUtil.convertProxyPropertiesToHttpClientKey(mode, props);\n            });\n    assertEquals((int) ErrorCode.INVALID_PROXY_PROPERTIES.getMessageCode(), e.getErrorCode());\n  }\n\n  @Test\n  public void testNullAndEmptyProxySettingsForS3() {\n    HttpClientSettingsKey testKey =\n        new HttpClientSettingsKey(OCSPMode.FAIL_OPEN, null, 443, null, null, null, \"\", \"\", false);\n    ProxyConfiguration proxyConfiguration =\n        CloudStorageProxyFactory.createProxyConfigurationForS3(testKey);\n    assertEquals(HttpProtocol.HTTP.toString(), proxyConfiguration.scheme().toUpperCase());\n    assertEquals(\"\", proxyConfiguration.host());\n    assertEquals(443, proxyConfiguration.port());\n    assertEquals(Collections.emptySet(), proxyConfiguration.nonProxyHosts());\n    assertNull(proxyConfiguration.username());\n    assertNull(proxyConfiguration.password());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/CredentialManagerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nclass CredentialManagerTest {\n\n  public static final String SNOWFLAKE_HOST = \"some-account.us-west-2.aws.snowflakecomputing.com\";\n  public static final String EXTERNAL_OAUTH_HOST = \"some-external-oauth-host.com\";\n  public static final String SOME_ACCESS_TOKEN = \"some-oauth-access-token\";\n  public static final String SOME_REFRESH_TOKEN = \"some-refresh-token\";\n  public static final String SOME_ID_TOKEN_FROM_CACHE = \"some-id-token\";\n  public static final String SOME_MFA_TOKEN_FROM_CACHE = \"some-mfa-token\";\n  public static final String SOME_DPOP_PUBLIC_KEY =\n      // pragma: allowlist nextline secret\n      \"{\\\"kty\\\":\\\"EC\\\",\\\"d\\\":\\\"j5-J-nLE4J1I8ZWtArP8eQbxUbYMPmRvaEjEkHFlHds\\\",\\\"crv\\\":\\\"P-256\\\",\\\"x\\\":\\\"RL5cE-TC4Jr6CxtT4lEI2Yu6wT6LbwojPQsgHUg01F0\\\",\\\"y\\\":\\\"UAdLUSWTJ6czXaS3SfEFUZzKPcVVq4OZAD8e7Rp75y4\\\"}\";\n  public static final String SOME_USER = \"some-user\";\n\n  public static final String ACCESS_TOKEN_FROM_CACHE = \"access-token-from-cache\";\n  public static final String REFRESH_TOKEN_FROM_CACHE = \"refresh-token-from-cache\";\n  public static final String EXTERNAL_ACCESS_TOKEN_FROM_CACHE = \"external-access-token-from-cache\";\n  public static final String EXTERNAL_REFRESH_TOKEN_FROM_CACHE =\n      \"external-refresh-token-from-cache\";\n\n  private static final SecureStorageManager mockSecureStorageManager =\n      mock(SecureStorageManager.class);\n\n  @BeforeAll\n  public static void setUp() {\n    CredentialManager.injectSecureStorageManager(mockSecureStorageManager);\n  }\n\n  @AfterAll\n  public static void tearDown() {\n    CredentialManager.resetSecureStorageManager();\n  }\n\n  @Test\n  public void shouldCreateHostBasedOnExternalIdpUrl() throws SFException {\n    SFLoginInput loginInput = createLoginInputWithExternalOAuth();\n    String host = CredentialManager.getHostForOAuthCacheKey(loginInput);\n    assertEquals(EXTERNAL_OAUTH_HOST, host);\n  }\n\n  @Test\n  public void shouldCreateHostBasedOnSnowflakeServerUrl() throws SFException {\n    SFLoginInput loginInput = createLoginInputWithSnowflakeServer();\n    String host = CredentialManager.getHostForOAuthCacheKey(loginInput);\n    assertEquals(SNOWFLAKE_HOST, host);\n  }\n\n  @Test\n  public void shouldProperlyWriteTokensToCache() throws SFException {\n    Base64.Encoder encoder = Base64.getEncoder();\n    SFLoginInput loginInputSnowflakeOAuth = createLoginInputWithSnowflakeServer();\n    CredentialManager.writeIdToken(loginInputSnowflakeOAuth, SOME_ID_TOKEN_FROM_CACHE);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            SNOWFLAKE_HOST,\n            SOME_USER,\n            CachedCredentialType.ID_TOKEN.getValue(),\n            encoder.encodeToString(SOME_ID_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.writeMfaToken(loginInputSnowflakeOAuth, SOME_MFA_TOKEN_FROM_CACHE);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            SNOWFLAKE_HOST,\n            SOME_USER,\n            CachedCredentialType.MFA_TOKEN.getValue(),\n            encoder.encodeToString(SOME_MFA_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n\n    CredentialManager.writeOAuthAccessToken(loginInputSnowflakeOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            SNOWFLAKE_HOST,\n            SOME_USER,\n            CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue(),\n            encoder.encodeToString(SOME_ACCESS_TOKEN.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.writeOAuthRefreshToken(loginInputSnowflakeOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            SNOWFLAKE_HOST,\n            SOME_USER,\n            CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue(),\n            encoder.encodeToString(SOME_REFRESH_TOKEN.getBytes(StandardCharsets.UTF_8)));\n\n    SFLoginInput loginInputExternalOAuth = createLoginInputWithExternalOAuth();\n    CredentialManager.writeOAuthAccessToken(loginInputExternalOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            EXTERNAL_OAUTH_HOST,\n            SOME_USER,\n            CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue(),\n            encoder.encodeToString(SOME_ACCESS_TOKEN.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.writeOAuthRefreshToken(loginInputExternalOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            EXTERNAL_OAUTH_HOST,\n            SOME_USER,\n            CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue(),\n            encoder.encodeToString(SOME_REFRESH_TOKEN.getBytes(StandardCharsets.UTF_8)));\n\n    SFLoginInput loginInputDPoP = createLoginInputWithDPoPPublicKey();\n    String dpopBundledToken =\n        encoder.encodeToString(SOME_ACCESS_TOKEN.getBytes(StandardCharsets.UTF_8))\n            + \".\"\n            + encoder.encodeToString(SOME_DPOP_PUBLIC_KEY.getBytes(StandardCharsets.UTF_8));\n    CredentialManager.writeDPoPBundledAccessToken(loginInputDPoP);\n    verify(mockSecureStorageManager, times(1))\n        .setCredential(\n            SNOWFLAKE_HOST,\n            SOME_USER,\n            CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN.getValue(),\n            dpopBundledToken);\n  }\n\n  @Test\n  public void shouldProperlyDeleteTokensFromCache() throws SFException {\n    SFLoginInput loginInputSnowflakeOAuth = createLoginInputWithSnowflakeServer();\n    CredentialManager.deleteIdTokenCacheEntry(\n        loginInputSnowflakeOAuth.getHostFromServerUrl(), loginInputSnowflakeOAuth.getUserName());\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.ID_TOKEN.getValue());\n    CredentialManager.deleteMfaTokenCacheEntry(\n        loginInputSnowflakeOAuth.getHostFromServerUrl(), loginInputSnowflakeOAuth.getUserName());\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.MFA_TOKEN.getValue());\n\n    CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInputSnowflakeOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue());\n    CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInputSnowflakeOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue());\n\n    SFLoginInput loginInputExternalOAuth = createLoginInputWithExternalOAuth();\n    CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInputExternalOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(\n            EXTERNAL_OAUTH_HOST, SOME_USER, CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue());\n    CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInputExternalOAuth);\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(\n            EXTERNAL_OAUTH_HOST, SOME_USER, CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue());\n\n    SFLoginInput loginInputDPoP = createLoginInputWithDPoPPublicKey();\n    CredentialManager.deleteDPoPBundledAccessTokenCacheEntry(loginInputDPoP);\n    verify(mockSecureStorageManager, times(1))\n        .deleteCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN.getValue());\n  }\n\n  @Test\n  public void shouldProperlyUpdateInputWithTokensFromCache() throws SFException {\n    Base64.Encoder encoder = Base64.getEncoder();\n    SFLoginInput loginInputSnowflakeOAuth = createLoginInputWithSnowflakeServer();\n    when(mockSecureStorageManager.getCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.ID_TOKEN.getValue()))\n        .thenReturn(\n            encoder.encodeToString(SOME_ID_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.fillCachedIdToken(loginInputSnowflakeOAuth);\n    when(mockSecureStorageManager.getCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.MFA_TOKEN.getValue()))\n        .thenReturn(\n            encoder.encodeToString(SOME_MFA_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.fillCachedMfaToken(loginInputSnowflakeOAuth);\n    assertEquals(SOME_ID_TOKEN_FROM_CACHE, loginInputSnowflakeOAuth.getIdToken());\n    assertEquals(SOME_MFA_TOKEN_FROM_CACHE, loginInputSnowflakeOAuth.getMfaToken());\n\n    when(mockSecureStorageManager.getCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue()))\n        .thenReturn(\n            encoder.encodeToString(ACCESS_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.fillCachedOAuthAccessToken(loginInputSnowflakeOAuth);\n    when(mockSecureStorageManager.getCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue()))\n        .thenReturn(\n            encoder.encodeToString(REFRESH_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.fillCachedOAuthRefreshToken(loginInputSnowflakeOAuth);\n    assertEquals(ACCESS_TOKEN_FROM_CACHE, loginInputSnowflakeOAuth.getOauthAccessToken());\n    assertEquals(REFRESH_TOKEN_FROM_CACHE, loginInputSnowflakeOAuth.getOauthRefreshToken());\n\n    SFLoginInput loginInputExternalOAuth = createLoginInputWithExternalOAuth();\n    when(mockSecureStorageManager.getCredential(\n            EXTERNAL_OAUTH_HOST, SOME_USER, CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue()))\n        .thenReturn(\n            encoder.encodeToString(\n                EXTERNAL_ACCESS_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.fillCachedOAuthAccessToken(loginInputExternalOAuth);\n    when(mockSecureStorageManager.getCredential(\n            EXTERNAL_OAUTH_HOST, SOME_USER, CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue()))\n        .thenReturn(\n            encoder.encodeToString(\n                EXTERNAL_REFRESH_TOKEN_FROM_CACHE.getBytes(StandardCharsets.UTF_8)));\n    CredentialManager.fillCachedOAuthRefreshToken(loginInputExternalOAuth);\n    assertEquals(EXTERNAL_ACCESS_TOKEN_FROM_CACHE, loginInputExternalOAuth.getOauthAccessToken());\n    assertEquals(EXTERNAL_REFRESH_TOKEN_FROM_CACHE, loginInputExternalOAuth.getOauthRefreshToken());\n\n    SFLoginInput loginInputDPoP = createLoginInputWithDPoPPublicKey();\n    String dpopBundledToken =\n        encoder.encodeToString(SOME_ACCESS_TOKEN.getBytes(StandardCharsets.UTF_8))\n            + \".\"\n            + encoder.encodeToString(SOME_DPOP_PUBLIC_KEY.getBytes(StandardCharsets.UTF_8));\n    when(mockSecureStorageManager.getCredential(\n            SNOWFLAKE_HOST, SOME_USER, CachedCredentialType.DPOP_BUNDLED_ACCESS_TOKEN.getValue()))\n        .thenReturn(dpopBundledToken);\n    CredentialManager.fillCachedDPoPBundledAccessToken(loginInputDPoP);\n    assertEquals(SOME_ACCESS_TOKEN, loginInputDPoP.getOauthAccessToken());\n    assertEquals(SOME_DPOP_PUBLIC_KEY, loginInputDPoP.getDPoPPublicKey());\n  }\n\n  private SFLoginInput createLoginInputWithExternalOAuth() {\n    SFLoginInput loginInput = createGenericLoginInput();\n    loginInput.setOauthLoginInput(\n        new SFOauthLoginInput(\n            null, null, null, null, \"https://some-external-oauth-host.com/oauth/token\", null));\n    return loginInput;\n  }\n\n  private SFLoginInput createLoginInputWithSnowflakeServer() {\n    SFLoginInput loginInput = createGenericLoginInput();\n    loginInput.setOauthLoginInput(new SFOauthLoginInput(null, null, null, null, null, null));\n    loginInput.setServerUrl(\"https://some-account.us-west-2.aws.snowflakecomputing.com:443/\");\n\n    return loginInput;\n  }\n\n  private SFLoginInput createLoginInputWithDPoPPublicKey() {\n    SFLoginInput loginInput = createLoginInputWithSnowflakeServer();\n    loginInput.setDPoPPublicKey(SOME_DPOP_PUBLIC_KEY);\n    return loginInput;\n  }\n\n  private SFLoginInput createGenericLoginInput() {\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setOauthAccessToken(SOME_ACCESS_TOKEN);\n    loginInput.setOauthRefreshToken(SOME_REFRESH_TOKEN);\n    loginInput.setUserName(SOME_USER);\n    return loginInput;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/EventHandlerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.nio.file.Files;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport java.util.zip.GZIPInputStream;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\npublic class EventHandlerTest {\n  @TempDir private File tmpFolder;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    new File(tmpFolder, \"snowflake_dumps\").mkdirs();\n    System.setProperty(\"snowflake.dump_path\", tmpFolder.getCanonicalPath());\n  }\n\n  @Test\n  public void testPublishRecord() {\n    LogRecord record = new LogRecord(Level.INFO, \"test message\");\n    EventHandler handler = new EventHandler(10, 5000);\n    assertEquals(0, handler.getLogBufferSize());\n    handler.publish(record);\n    assertEquals(1, handler.getLogBufferSize());\n  }\n\n  @Test\n  public void testDumpLogBuffer() throws IOException {\n    System.setProperty(\"snowflake.max_dumpfiles\", \"1\");\n    System.setProperty(\"snowflake.max_dumpdir_size_mb\", \"100\");\n\n    LogRecord record = new LogRecord(Level.INFO, \"test message\");\n    EventHandler handler = new EventHandler(10, 5000);\n    handler.publish(record);\n    handler.flush();\n\n    File logDumpFile = new File(EventUtil.getDumpPathPrefix() + \"/sf_log_.dmp.gz\");\n    GZIPInputStream gzip = new GZIPInputStream(Files.newInputStream(logDumpFile.toPath()));\n    StringWriter sWriter = new StringWriter();\n    IOUtils.copy(gzip, sWriter, \"UTF-8\");\n\n    assertTrue(sWriter.toString().contains(\"test message\"));\n\n    gzip.close();\n    sWriter.close();\n    logDumpFile.delete();\n  }\n\n  @Test\n  public void testEventFlusher() {\n    EventHandler handler = new EventHandler(2, 1000);\n    assertEquals(0, handler.getBufferSize());\n    handler.triggerBasicEvent(Event.EventType.STATE_TRANSITION, \"test event\");\n    assertEquals(1, handler.getBufferSize());\n    handler.triggerBasicEvent(Event.EventType.STATE_TRANSITION, \"test event 2\");\n    // buffer should flush when max entries is reached\n    assertEquals(0, handler.getBufferSize());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/EventTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.EventUtil.DUMP_PATH_PROP;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.StringWriter;\nimport java.nio.file.Files;\nimport java.util.zip.GZIPInputStream;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\npublic class EventTest {\n  @TempDir private File tmpFolder;\n  private File homeDirectory;\n  private File dmpDirectory;\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    homeDirectory = new File(tmpFolder, \"homedir\");\n    homeDirectory.mkdirs();\n    dmpDirectory = new File(homeDirectory, \"snowflake_dumps\");\n    dmpDirectory.mkdirs();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    dmpDirectory.delete();\n  }\n\n  @Test\n  public void testEvent() {\n    Event event = new BasicEvent(Event.EventType.NONE, \"basic event\");\n    event.setType(Event.EventType.NETWORK_ERROR);\n    event.setMessage(\"network timeout\");\n    assertEquals(1, event.getType().getId());\n    assertEquals(\"network timeout\", event.getMessage());\n  }\n\n  @Test\n  public void testWriteEventDumpLine() throws IOException {\n    try {\n      // Set dmp file path\n      String dumpPath = homeDirectory.getCanonicalPath();\n      System.setProperty(DUMP_PATH_PROP, dumpPath);\n      EventUtil.setDumpPathPrefixForTesting(dumpPath);\n      Event event = new BasicEvent(Event.EventType.NETWORK_ERROR, \"network timeout\");\n      event.writeEventDumpLine(\"network timeout after 60 seconds\");\n      // Assert the dump path prefix function correctly leads to the temporary dump directory\n      // created\n      String dmpPath1 = EventUtil.getDumpPathPrefix();\n      String dmpPath2 = dmpDirectory.getCanonicalPath();\n      assertEquals(dmpPath2, dmpPath1, \"dump path is: \" + EventUtil.getDumpPathPrefix());\n      File dumpFile =\n          new File(\n              EventUtil.getDumpPathPrefix()\n                  + File.separator\n                  + \"sf_event_\"\n                  + EventUtil.getDumpFileId()\n                  + \".dmp.gz\");\n      GZIPInputStream gzip = new GZIPInputStream(Files.newInputStream(dumpFile.toPath()));\n      StringWriter sWriter = new StringWriter();\n      IOUtils.copy(gzip, sWriter, \"UTF-8\");\n\n      assertTrue(sWriter.toString().contains(\"network timeout after 60 seconds\"));\n\n      gzip.close();\n      sWriter.close();\n      dumpFile.delete();\n    } finally {\n      System.clearProperty(\"snowflake.dump_path\");\n      EventUtil.setDumpPathPrefixForTesting(EventUtil.getDumpPathPrefix());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/FileCacheManagerDefaultDirTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.io.File;\nimport net.snowflake.client.annotations.RunOnLinuxOrMac;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\npublic class FileCacheManagerDefaultDirTest {\n\n  @Test\n  @RunOnLinuxOrMac\n  public void shouldCreateCacheDirForLinuxXDG() {\n    try (MockedStatic<Constants> constantsMockedStatic = Mockito.mockStatic(Constants.class)) {\n      constantsMockedStatic.when(Constants::getOS).thenReturn(Constants.OS.LINUX);\n      try (MockedStatic<SnowflakeUtil> snowflakeUtilMockedStatic =\n          Mockito.mockStatic(SnowflakeUtil.class)) {\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetEnv(\"XDG_CACHE_HOME\"))\n            .thenReturn(\"/XDG/Cache/\");\n        try (MockedStatic<FileUtil> fileUtilMockedStatic = Mockito.mockStatic(FileUtil.class)) {\n          fileUtilMockedStatic.when(() -> FileUtil.isWritable(\"/XDG/Cache/\")).thenReturn(true);\n          File defaultCacheDir = FileCacheUtil.getDefaultCacheDir();\n          Assertions.assertNotNull(defaultCacheDir);\n          Assertions.assertEquals(\"/XDG/Cache/snowflake\", defaultCacheDir.getAbsolutePath());\n        }\n      }\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void shouldCreateCacheDirForLinuxWithoutXDG() {\n    try (MockedStatic<Constants> constantsMockedStatic = Mockito.mockStatic(Constants.class)) {\n      constantsMockedStatic.when(Constants::getOS).thenReturn(Constants.OS.LINUX);\n      try (MockedStatic<SnowflakeUtil> snowflakeUtilMockedStatic =\n          Mockito.mockStatic(SnowflakeUtil.class)) {\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetEnv(\"XDG_CACHE_HOME\"))\n            .thenReturn(null);\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetProperty(\"user.home\"))\n            .thenReturn(\"/User/Home\");\n        try (MockedStatic<FileUtil> fileUtilMockedStatic = Mockito.mockStatic(FileUtil.class)) {\n          fileUtilMockedStatic.when(() -> FileUtil.isWritable(\"/User/Home\")).thenReturn(true);\n          File defaultCacheDir = FileCacheUtil.getDefaultCacheDir();\n          Assertions.assertNotNull(defaultCacheDir);\n          Assertions.assertEquals(\"/User/Home/.cache/snowflake\", defaultCacheDir.getAbsolutePath());\n        }\n      }\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void shouldCreateCacheDirForWindows() {\n    try (MockedStatic<Constants> constantsMockedStatic = Mockito.mockStatic(Constants.class)) {\n      constantsMockedStatic.when(Constants::getOS).thenReturn(Constants.OS.WINDOWS);\n      try (MockedStatic<SnowflakeUtil> snowflakeUtilMockedStatic =\n          Mockito.mockStatic(SnowflakeUtil.class)) {\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetProperty(\"user.home\"))\n            .thenReturn(\"/User/Home\");\n        try (MockedStatic<FileUtil> fileUtilMockedStatic = Mockito.mockStatic(FileUtil.class)) {\n          fileUtilMockedStatic.when(() -> FileUtil.isWritable(\"/User/Home\")).thenReturn(true);\n          File defaultCacheDir = FileCacheUtil.getDefaultCacheDir();\n          Assertions.assertNotNull(defaultCacheDir);\n          Assertions.assertEquals(\n              \"/User/Home/AppData/Local/Snowflake/Caches\", defaultCacheDir.getAbsolutePath());\n        }\n      }\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void shouldCreateCacheDirForMacOS() {\n    try (MockedStatic<Constants> constantsMockedStatic = Mockito.mockStatic(Constants.class)) {\n      constantsMockedStatic.when(Constants::getOS).thenReturn(Constants.OS.MAC);\n      try (MockedStatic<SnowflakeUtil> snowflakeUtilMockedStatic =\n          Mockito.mockStatic(SnowflakeUtil.class)) {\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetProperty(\"user.home\"))\n            .thenReturn(\"/User/Home\");\n        try (MockedStatic<FileUtil> fileUtilMockedStatic = Mockito.mockStatic(FileUtil.class)) {\n          fileUtilMockedStatic.when(() -> FileUtil.isWritable(\"/User/Home\")).thenReturn(true);\n          File defaultCacheDir = FileCacheUtil.getDefaultCacheDir();\n          Assertions.assertNotNull(defaultCacheDir);\n          Assertions.assertEquals(\n              \"/User/Home/Library/Caches/Snowflake\", defaultCacheDir.getAbsolutePath());\n        }\n      }\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void shouldReturnNullWhenNoHomeDirSet() {\n    try (MockedStatic<Constants> constantsMockedStatic = Mockito.mockStatic(Constants.class)) {\n      constantsMockedStatic.when(Constants::getOS).thenReturn(Constants.OS.LINUX);\n      try (MockedStatic<SnowflakeUtil> snowflakeUtilMockedStatic =\n          Mockito.mockStatic(SnowflakeUtil.class)) {\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetEnv(\"XDG_CACHE_HOME\"))\n            .thenReturn(null);\n        snowflakeUtilMockedStatic\n            .when(() -> SnowflakeUtil.systemGetProperty(\"user.home\"))\n            .thenReturn(null);\n        File defaultCacheDir = FileCacheUtil.getDefaultCacheDir();\n        Assertions.assertNull(defaultCacheDir);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/FileCacheManagerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.StmtUtil.mapper;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.isA;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.annotations.RunOnLinuxOrMac;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\n@Nested\n@Tag(TestTags.CORE)\nclass FileCacheManagerTest extends BaseJDBCTest {\n\n  private static final String CACHE_FILE_NAME = \"credential_cache_v1.json.json\";\n  private static final String CACHE_DIR_PROP = \"net.snowflake.jdbc.temporaryCredentialCacheDir\";\n  private static final String CACHE_DIR_ENV = \"SF_TEMPORARY_CREDENTIAL_CACHE_DIR\";\n  private static final long CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS = 60L;\n\n  private FileCacheManager fileCacheManager;\n  private File cacheFile;\n\n  @BeforeEach\n  public void setup() throws IOException {\n    fileCacheManager =\n        new FileCacheManagerBuilder()\n            .setCacheDirectorySystemProperty(CACHE_DIR_PROP)\n            .setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV)\n            .setBaseCacheFileName(CACHE_FILE_NAME)\n            .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS)\n            .build();\n    cacheFile = createCacheFile();\n  }\n\n  @AfterEach\n  public void clean() throws IOException {\n    if (Files.exists(cacheFile.toPath())) {\n      Files.delete(cacheFile.toPath());\n    }\n    if (Files.exists(cacheFile.getParentFile().toPath())) {\n      Files.delete(cacheFile.getParentFile().toPath());\n    }\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n    \"rwx------,rwx------,false\",\n    \"rw-------,rwx------,true\",\n    \"rw-------,rwx--xrwx,true\",\n    \"r-x------,rwx------,false\",\n    \"r--------,rwx------,true\",\n    \"rwxrwx---,rwx------,false\",\n    \"rwxrw----,rwx------,false\",\n    \"rwxr-x---,rwx------,false\",\n    \"rwxr-----,rwx------,false\",\n    \"rwx-wx---,rwx------,false\",\n    \"rwx-w----,rwx------,false\",\n    \"rwx--x---,rwx------,false\",\n    \"rwx---rwx,rwx------,false\",\n    \"rwx---rw-,rwx------,false\",\n    \"rwx---r-x,rwx------,false\",\n    \"rwx---r--,rwx------,false\",\n    \"rwx----wx,rwx------,false\",\n    \"rwx----w-,rwx------,false\",\n    \"rwx-----x,rwx------,false\"\n  })\n  @RunOnLinuxOrMac\n  public void throwWhenReadCacheFileWithPermissionDifferentThanReadWriteForUserTest(\n      String permission, String parentDirectoryPermissions, boolean isSucceed) throws IOException {\n    fileCacheManager.overrideCacheFile(cacheFile);\n    Files.setPosixFilePermissions(cacheFile.toPath(), PosixFilePermissions.fromString(permission));\n    Files.setPosixFilePermissions(\n        cacheFile.getParentFile().toPath(),\n        PosixFilePermissions.fromString(parentDirectoryPermissions));\n    if (isSucceed) {\n      assertDoesNotThrow(() -> fileCacheManager.readCacheFile());\n    } else {\n      SecurityException ex =\n          assertThrows(SecurityException.class, () -> fileCacheManager.readCacheFile());\n      assertTrue(ex.getMessage().contains(\"is wider than allowed.\"));\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void notThrowExceptionWhenCacheFolderIsNotAccessibleWhenReadFromCache()\n      throws IOException {\n    try {\n      Files.setPosixFilePermissions(\n          cacheFile.getParentFile().toPath(), PosixFilePermissions.fromString(\"---------\"));\n      FileCacheManager fcm =\n          new FileCacheManagerBuilder()\n              .setCacheDirectorySystemProperty(CACHE_DIR_PROP)\n              .setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV)\n              .setBaseCacheFileName(CACHE_FILE_NAME)\n              .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS)\n              .build();\n      assertDoesNotThrow(fcm::readCacheFile);\n    } finally {\n      Files.setPosixFilePermissions(\n          cacheFile.getParentFile().toPath(), PosixFilePermissions.fromString(\"rwx------\"));\n    }\n  }\n\n  @Test\n  public void shouldEnterNoopModeWhenCacheDirectoryIsNotAvailable() {\n    try (MockedStatic<FileCacheUtil> fileCacheUtilMock =\n        Mockito.mockStatic(FileCacheUtil.class, Mockito.CALLS_REAL_METHODS)) {\n      fileCacheUtilMock.when(FileCacheUtil::getDefaultCacheDir).thenReturn(null);\n      FileCacheManager fcm =\n          new FileCacheManagerBuilder()\n              .setCacheDirectorySystemProperty(\"nonexistent.system.property\")\n              .setCacheDirectoryEnvironmentVariable(\"NONEXISTENT_ENV_VAR\")\n              .setBaseCacheFileName(CACHE_FILE_NAME)\n              .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS)\n              .build();\n      assertTrue(fcm instanceof NoOpFileCacheManager);\n      assertNull(fcm.getCacheFilePath());\n      assertDoesNotThrow(fcm::readCacheFile);\n      assertDoesNotThrow(() -> fcm.writeCacheFile(mapper.createObjectNode()));\n      assertDoesNotThrow(fcm::deleteCacheFile);\n      assertNull(fcm.withLock(() -> \"test\"));\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void notThrowExceptionWhenCacheFolderIsNotAccessibleWhenWriteToCache() throws IOException {\n    String tmpDirPath = System.getProperty(\"java.io.tmpdir\");\n    String cacheDirPath = tmpDirPath + File.separator + \"snowflake-cache-dir-noaccess\";\n    System.setProperty(\"FILE_CACHE_MANAGER_CACHE_PATH\", cacheDirPath);\n    try {\n      Files.createDirectory(\n          Paths.get(cacheDirPath),\n          PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"---------\")));\n\n      FileCacheManager fcm =\n          new FileCacheManagerBuilder()\n              .setOnlyOwnerPermissions(false)\n              .setCacheDirectorySystemProperty(\"FILE_CACHE_MANAGER_CACHE_PATH\")\n              .setCacheDirectoryEnvironmentVariable(\"NONEXISTENT\")\n              .setBaseCacheFileName(\"cache-file\")\n              .build();\n      assertDoesNotThrow(() -> fcm.writeCacheFile(mapper.createObjectNode()));\n    } finally {\n      Files.deleteIfExists(Paths.get(cacheDirPath));\n      System.clearProperty(\"FILE_CACHE_MANAGER_CACHE_PATH\");\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void throwWhenOverrideCacheFileHasDifferentOwnerThanCurrentUserTest() {\n    try (MockedStatic<FileUtil> fileUtilMock =\n        Mockito.mockStatic(FileUtil.class, Mockito.CALLS_REAL_METHODS)) {\n      fileUtilMock.when(() -> FileUtil.getFileOwnerName(isA(Path.class))).thenReturn(\"anotherUser\");\n      SecurityException ex =\n          assertThrows(SecurityException.class, () -> fileCacheManager.readCacheFile());\n      assertTrue(ex.getMessage().contains(\"The file owner is different than current user\"));\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void notThrowWhenUserNameIsQuestionMarkInContainerEnvironment() {\n    try (MockedStatic<FileUtil> fileUtilMock =\n            Mockito.mockStatic(FileUtil.class, Mockito.CALLS_REAL_METHODS);\n        MockedStatic<SnowflakeUtil> snowflakeUtilMock =\n            Mockito.mockStatic(SnowflakeUtil.class, Mockito.CALLS_REAL_METHODS)) {\n      fileUtilMock.when(() -> FileUtil.getFileOwnerName(isA(Path.class))).thenReturn(\"root\");\n      snowflakeUtilMock.when(() -> SnowflakeUtil.systemGetProperty(\"user.name\")).thenReturn(\"?\");\n      assertDoesNotThrow(() -> fileCacheManager.readCacheFile());\n    }\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void notThrowForToWidePermissionsWhenOnlyOwnerPermissionsSetFalseTest()\n      throws IOException {\n    FileCacheManager fcm =\n        new FileCacheManagerBuilder()\n            .setOnlyOwnerPermissions(false)\n            .setCacheDirectorySystemProperty(CACHE_DIR_PROP)\n            .setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV)\n            .setBaseCacheFileName(CACHE_FILE_NAME)\n            .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS)\n            .build();\n    fcm.overrideCacheFile(cacheFile);\n    Files.setPosixFilePermissions(cacheFile.toPath(), PosixFilePermissions.fromString(\"rwxrwx---\"));\n    assertDoesNotThrow(fcm::readCacheFile);\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void throwWhenOverrideCacheFileNotFound() {\n    Path wrongPath =\n        Paths.get(systemGetProperty(\"user.home\"), \".cache\", \"snowflake2\", \"wrongFileName\");\n    SecurityException ex =\n        assertThrows(\n            SecurityException.class, () -> fileCacheManager.overrideCacheFile(wrongPath.toFile()));\n    assertTrue(\n        ex.getMessage()\n            .contains(\n                \"Unable to access the file/directory to check the permissions. Error: java.nio.file.NoSuchFileException:\"));\n  }\n\n  @Test\n  @RunOnLinuxOrMac\n  public void throwWhenSymlinkAsCache() throws IOException {\n    Path symlink = createSymlink();\n    try {\n      SecurityException ex =\n          assertThrows(\n              SecurityException.class, () -> fileCacheManager.overrideCacheFile(symlink.toFile()));\n      assertTrue(ex.getMessage().contains(\"Symbolic link is not allowed for file cache\"));\n    } finally {\n      if (Files.exists(symlink)) {\n        Files.delete(symlink);\n      }\n    }\n  }\n\n  private File createCacheFile() {\n    Path cacheFile =\n        Paths.get(systemGetProperty(\"user.home\"), \".cache\", \"snowflake_cache\", CACHE_FILE_NAME);\n    try {\n      if (Files.exists(cacheFile)) {\n        Files.delete(cacheFile);\n      }\n      if (Files.exists(cacheFile.getParent())) {\n        Files.delete(cacheFile.getParent());\n      }\n      if (!isWindows()) {\n        Files.createDirectories(\n            cacheFile.getParent(),\n            PosixFilePermissions.asFileAttribute(\n                Stream.of(\n                        PosixFilePermission.OWNER_READ,\n                        PosixFilePermission.OWNER_WRITE,\n                        PosixFilePermission.OWNER_EXECUTE)\n                    .collect(Collectors.toSet())));\n      } else {\n        Files.createDirectories(cacheFile.getParent());\n      }\n\n      if (!isWindows()) {\n        Files.createFile(\n            cacheFile,\n            PosixFilePermissions.asFileAttribute(\n                Stream.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)\n                    .collect(Collectors.toSet())));\n      } else {\n        Files.createFile(cacheFile);\n      }\n      ObjectNode cacheContent = mapper.createObjectNode();\n      cacheContent.put(\"token\", \"tokenValue\");\n      fileCacheManager.overrideCacheFile(cacheFile.toFile());\n      fileCacheManager.writeCacheFile(cacheContent);\n      return cacheFile.toFile();\n\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private Path createSymlink() throws IOException {\n    Path link = Paths.get(cacheFile.getParent(), \"symlink_\" + CACHE_FILE_NAME);\n    if (Files.exists(link)) {\n      Files.delete(link);\n    }\n    return Files.createSymbolicLink(link, cacheFile.toPath());\n  }\n\n  @Test\n  void shouldCreateDirAndFile() {\n    String tmpDirPath = System.getProperty(\"java.io.tmpdir\");\n    String cacheDirPath = tmpDirPath + File.separator + \"snowflake-cache-dir\";\n    System.setProperty(\"FILE_CACHE_MANAGER_SHOULD_CREATE_DIR_AND_FILE\", cacheDirPath);\n    new FileCacheManagerBuilder()\n        .setOnlyOwnerPermissions(false)\n        .setCacheDirectorySystemProperty(\"FILE_CACHE_MANAGER_SHOULD_CREATE_DIR_AND_FILE\")\n        .setBaseCacheFileName(\"cache-file\")\n        .build();\n    assertTrue(new File(tmpDirPath).exists());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HeaderCustomizerHttpRequestInterceptorTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.AttributeEnhancingHttpRequestRetryHandler.EXECUTION_COUNT_ATTRIBUTE;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyMap;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.lenient;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport org.apache.http.Header;\nimport org.apache.http.HttpException;\nimport org.apache.http.HttpRequest;\nimport org.apache.http.RequestLine;\nimport org.apache.http.message.BasicHeader;\nimport org.apache.http.protocol.BasicHttpContext;\nimport org.apache.http.protocol.HttpContext;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport software.amazon.awssdk.core.interceptor.Context;\nimport software.amazon.awssdk.core.interceptor.ExecutionAttributes;\nimport software.amazon.awssdk.http.SdkHttpMethod;\nimport software.amazon.awssdk.http.SdkHttpRequest;\n\nclass HeaderCustomizerHttpRequestInterceptorTest {\n  private HttpHeadersCustomizer mockCustomizer1;\n  private HttpHeadersCustomizer mockCustomizer2;\n  private HttpRequest mockHttpRequest;\n  private RequestLine mockRequestLine;\n  private Context.ModifyHttpRequest mockAwsRequestContext;\n  private SdkHttpRequest mockAwsRequest;\n  private ExecutionAttributes mockAwsRequestAttributes;\n  private SdkHttpRequest.Builder mockAwsRequestContextBuilder;\n\n  private HttpContext httpContext;\n  private HeaderCustomizerHttpRequestInterceptor interceptor;\n  private List<HttpHeadersCustomizer> customizersList;\n\n  private final String TEST_URI_STRING = \"https://test.snowflakecomputing.com/api/v1\";\n  private final URI TEST_URI = URI.create(TEST_URI_STRING);\n  private final String TEST_METHOD_GET = \"GET\";\n\n  @BeforeEach\n  void setUp() {\n    httpContext = new BasicHttpContext();\n    customizersList = new ArrayList<>();\n    // Default interceptor has empty list, tests will add customizers as needed\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    // Setup common mocks\n    mockCustomizer1 = mock();\n    mockCustomizer2 = mock();\n    mockHttpRequest = mock();\n    mockRequestLine = mock();\n    mockAwsRequestContext = mock();\n    mockAwsRequest = mock();\n    mockAwsRequestAttributes = mock();\n    mockAwsRequestContextBuilder = mock();\n\n    lenient().when(mockHttpRequest.getRequestLine()).thenReturn(mockRequestLine);\n    lenient().when(mockHttpRequest.getAllHeaders()).thenReturn(new Header[0]);\n    lenient().when(mockRequestLine.getMethod()).thenReturn(TEST_METHOD_GET);\n    lenient().when(mockRequestLine.getUri()).thenReturn(TEST_URI_STRING);\n\n    lenient().when(mockAwsRequest.method()).thenReturn(SdkHttpMethod.GET);\n    lenient().when(mockAwsRequest.getUri()).thenReturn(TEST_URI);\n    lenient()\n        .when(mockAwsRequest.headers())\n        .thenReturn(new HashMap<>()); // Mutable map for testing adds\n\n    lenient().when(mockAwsRequestContext.httpRequest()).thenReturn(mockAwsRequest);\n    lenient().when(mockAwsRequest.toBuilder()).thenReturn(mockAwsRequestContextBuilder);\n  }\n\n  @Test\n  void testApacheInterceptorWithoutCustomizersDoesNothing() throws Exception {\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(Collections.emptyList());\n    interceptor.process(mockHttpRequest, httpContext);\n    verify(mockHttpRequest, never()).addHeader(anyString(), anyString());\n  }\n\n  @Test\n  void testApacheInterceptorWithCustomizerWhichDoesNotApplyDoesNothing() throws Exception {\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(false);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    interceptor.process(mockHttpRequest, httpContext);\n\n    verify(mockCustomizer1).applies(eq(TEST_METHOD_GET), eq(TEST_URI_STRING), anyMap());\n    verify(mockCustomizer1, never()).newHeaders();\n    verify(mockHttpRequest, never()).addHeader(anyString(), anyString());\n  }\n\n  @ParameterizedTest\n  @ValueSource(booleans = {true, false})\n  void testApacheInterceptorWithCustomizerAddsHeaders(boolean invokeOnce) throws Exception {\n    Map<String, List<String>> newHeaders = new HashMap<>();\n    newHeaders.put(\"X-Static\", Collections.singletonList(\"StaticVal\"));\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(newHeaders);\n    when(mockCustomizer1.invokeOnce()).thenReturn(invokeOnce);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    httpContext.setAttribute(EXECUTION_COUNT_ATTRIBUTE, 0);\n\n    interceptor.process(mockHttpRequest, httpContext);\n\n    verify(mockCustomizer1).newHeaders();\n    verify(mockHttpRequest).addHeader(\"X-Static\", \"StaticVal\");\n  }\n\n  @Test\n  void testApacheInterceptorWithInvokeOnceTrueSkipsOnRetry() throws Exception {\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.invokeOnce()).thenReturn(true);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    httpContext.setAttribute(EXECUTION_COUNT_ATTRIBUTE, 1); // Simulate retry\n\n    interceptor.process(mockHttpRequest, httpContext);\n\n    verify(mockCustomizer1, never()).newHeaders();\n    verify(mockHttpRequest, never()).addHeader(anyString(), anyString());\n  }\n\n  @Test\n  void testApacheInterceptorWithInvokeOnceFalseAddsHeaderOnRetry() throws Exception {\n    Map<String, List<String>> newHeaders = new HashMap<>();\n    newHeaders.put(\"X-Dynamic\", Collections.singletonList(\"RetryValue\"));\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(newHeaders);\n    when(mockCustomizer1.invokeOnce()).thenReturn(false);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    httpContext.setAttribute(EXECUTION_COUNT_ATTRIBUTE, 1); // Simulate retry\n\n    interceptor.process(mockHttpRequest, httpContext);\n\n    verify(mockCustomizer1).newHeaders();\n    verify(mockHttpRequest).addHeader(\"X-Dynamic\", \"RetryValue\");\n  }\n\n  @Test\n  void testApacheInterceptorDoesNotAllowCustomizerToOverrideHeader()\n      throws HttpException, IOException {\n    // Simulate driver adding User-Agent initially\n    Header[] initialHeaders = {new BasicHeader(\"User-Agent\", \"SnowflakeDriver/1.0\")};\n    when(mockHttpRequest.getAllHeaders()).thenReturn(initialHeaders);\n\n    Map<String, List<String>> newHeaders = new HashMap<>();\n    newHeaders.put(\"User-Agent\", Collections.singletonList(\"MaliciousAgent/2.0\"));\n\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(newHeaders); // Attempting override\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    httpContext.setAttribute(EXECUTION_COUNT_ATTRIBUTE, 0);\n\n    interceptor.process(mockHttpRequest, httpContext);\n\n    // Verify the original map wasn't modified with the bad header\n    assertEquals(initialHeaders, mockHttpRequest.getAllHeaders());\n    verify(mockHttpRequest, never()).addHeader(eq(\"User-Agent\"), anyString());\n  }\n\n  @Test\n  void testMultipleCustomizersAddingHeaders() throws Exception {\n    Map<String, List<String>> headers1 = new HashMap<>();\n    headers1.put(\"X-Custom1\", Collections.singletonList(\"Val1\"));\n    Map<String, List<String>> headers2 = new HashMap<>();\n    headers2.put(\"X-Custom2\", Arrays.asList(\"Val2a\", \"Val2b\"));\n\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(headers1);\n    when(mockCustomizer1.invokeOnce()).thenReturn(false);\n\n    when(mockCustomizer2.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer2.newHeaders()).thenReturn(headers2);\n    when(mockCustomizer2.invokeOnce()).thenReturn(false);\n\n    customizersList.add(mockCustomizer1);\n    customizersList.add(mockCustomizer2);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    httpContext.setAttribute(EXECUTION_COUNT_ATTRIBUTE, 0);\n\n    interceptor.process(mockHttpRequest, httpContext);\n\n    verify(mockCustomizer1).newHeaders();\n    verify(mockCustomizer2).newHeaders();\n    verify(mockHttpRequest).addHeader(\"X-Custom1\", \"Val1\");\n    verify(mockHttpRequest).addHeader(\"X-Custom2\", \"Val2a\");\n    verify(mockHttpRequest).addHeader(\"X-Custom2\", \"Val2b\");\n  }\n\n  @Test\n  void testAWSInterceptorWithoutCustomizersDoesNothing() {\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(Collections.emptyList());\n    interceptor.modifyHttpRequest(mockAwsRequestContext, mockAwsRequestAttributes);\n    verify(mockAwsRequestContextBuilder, never()).appendHeader(anyString(), anyString());\n  }\n\n  @Test\n  void testAWSInterceptorWithCustomizerWhichDoesNotApplyDoesNothing() {\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(false);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    interceptor.modifyHttpRequest(mockAwsRequestContext, mockAwsRequestAttributes);\n\n    verify(mockCustomizer1).applies(eq(TEST_METHOD_GET), eq(TEST_URI_STRING), anyMap());\n    verify(mockCustomizer1, never()).newHeaders();\n    verify(mockAwsRequestContextBuilder, never()).appendHeader(anyString(), anyString());\n  }\n\n  @Test\n  void testAWSInterceptorWithCustomizerAddsHeaders() {\n    Map<String, List<String>> newHeaders = new HashMap<>();\n    newHeaders.put(\"X-AWS-Custom\", Collections.singletonList(\"AwsValue1\"));\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(newHeaders);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    interceptor.modifyHttpRequest(mockAwsRequestContext, mockAwsRequestAttributes);\n\n    verify(mockCustomizer1).newHeaders();\n    verify(mockAwsRequestContextBuilder).appendHeader(\"X-AWS-Custom\", \"AwsValue1\");\n  }\n\n  @Test\n  void testAWSInterceptorDoesNotAllowCustomizerToOverrideHeader() {\n    // Simulate driver adding User-Agent initially\n    Map<String, List<String>> initialAwsHeaders = new HashMap<>();\n    initialAwsHeaders.put(\"User-Agent\", Collections.singletonList(\"SnowflakeAWSClient/1.0\"));\n    when(mockAwsRequest.headers()).thenReturn(initialAwsHeaders); // Return mutable map for test\n\n    Map<String, List<String>> newHeaders = new HashMap<>();\n    newHeaders.put(\"User-Agent\", Collections.singletonList(\"MaliciousAgent/3.0\"));\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(newHeaders);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    interceptor.modifyHttpRequest(mockAwsRequestContext, mockAwsRequestAttributes);\n\n    // Verify the original map wasn't modified with the bad header\n    assertEquals(\"SnowflakeAWSClient/1.0\", mockAwsRequest.headers().get(\"User-Agent\").get(0));\n    verify(mockAwsRequestContextBuilder, never()).appendHeader(eq(\"User-Agent\"), anyString());\n  }\n\n  @Test\n  void testAWSInterceptorAddsMultiValueHeader() {\n    Map<String, List<String>> newHeaders = new HashMap<>();\n    newHeaders.put(\"X-Multi\", Arrays.asList(\"ValA\", \"ValB\"));\n    when(mockCustomizer1.applies(anyString(), anyString(), anyMap())).thenReturn(true);\n    when(mockCustomizer1.newHeaders()).thenReturn(newHeaders);\n    customizersList.add(mockCustomizer1);\n    interceptor = new HeaderCustomizerHttpRequestInterceptor(customizersList);\n\n    interceptor.modifyHttpRequest(mockAwsRequestContext, mockAwsRequestAttributes);\n\n    verify(mockCustomizer1).newHeaders();\n    verify(mockAwsRequestContextBuilder).appendHeader(\"X-Multi\", \"ValA\");\n    verify(mockAwsRequestContextBuilder).appendHeader(\"X-Multi\", \"ValB\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HeartbeatIntervalSelectorTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\n\npublic class HeartbeatIntervalSelectorTest {\n\n  // Helper method for Java 8 compatibility (setOf() is Java 9+)\n  private static Set<Long> setOf(Long... values) {\n    return new HashSet<>(Arrays.asList(values));\n  }\n\n  // === Validation Tests ===\n\n  @Test\n  public void testSelectBestInterval_NullExistingIntervals_ThrowsException() {\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> HeartbeatIntervalSelector.selectBestInterval(10, null));\n    assertTrue(exception.getMessage().contains(\"cannot be null\"));\n  }\n\n  @Test\n  public void testSelectBestInterval_EmptyExistingIntervals_ThrowsException() {\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> HeartbeatIntervalSelector.selectBestInterval(10, new HashSet<>()));\n    assertTrue(exception.getMessage().contains(\"cannot be null or empty\"));\n  }\n\n  @Test\n  public void testSelectBestInterval_NegativeRequestedInterval_ThrowsException() {\n    Set<Long> existing = setOf(10L);\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> HeartbeatIntervalSelector.selectBestInterval(-5, existing));\n    assertTrue(exception.getMessage().contains(\"must be positive\"));\n  }\n\n  @Test\n  public void testSelectBestInterval_ZeroRequestedInterval_ThrowsException() {\n    Set<Long> existing = setOf(10L);\n    IllegalArgumentException exception =\n        assertThrows(\n            IllegalArgumentException.class,\n            () -> HeartbeatIntervalSelector.selectBestInterval(0, existing));\n    assertTrue(exception.getMessage().contains(\"must be positive\"));\n  }\n\n  // === Core Behavior Tests ===\n\n  @Test\n  public void testSelectBestInterval_ExactMatch_ReturnsExactMatch() {\n    Set<Long> existing = setOf(5L, 10L, 15L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(10, result, \"Should return exact match when it exists\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RequestedBetweenTwo_ReturnsClosestSmaller() {\n    Set<Long> existing = setOf(5L, 15L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(\n        5, result, \"Should return closest smaller interval (more frequent heartbeats are safer)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RequestedBetweenMany_ReturnsClosestSmaller() {\n    Set<Long> existing = setOf(3L, 5L, 8L, 15L, 20L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(8, result, \"Should return 8 (closest to 10 but still smaller)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RequestedSmallerThanAll_ReturnsShortestInterval() {\n    // Critical case: If requested is 9s but all existing are [10s, 15s, 20s],\n    // there is no interval that is less than or equal to the requested value.\n    // In that case, return the shortest available interval (10s) rather than a longer one.\n    Set<Long> existing = setOf(10L, 15L, 20L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(9, existing);\n    assertEquals(10, result, \"Should return shortest interval when no smaller exists\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RequestedLargerThanAll_ReturnsLargestSmallerInterval() {\n    Set<Long> existing = setOf(5L, 10L, 15L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(20, existing);\n    assertEquals(\n        15, result, \"Should return largest of the smaller intervals (closest but still safe)\");\n  }\n\n  // === Edge Cases ===\n\n  @Test\n  public void testSelectBestInterval_SingleExistingIntervalSmaller_ReturnsIt() {\n    Set<Long> existing = setOf(5L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(5, result, \"Should return the only existing interval\");\n  }\n\n  @Test\n  public void testSelectBestInterval_SingleExistingIntervalLarger_ReturnsIt() {\n    Set<Long> existing = setOf(15L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(\n        15,\n        result,\n        \"Should return the only existing interval even if it's larger (no other choice)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_SingleExistingIntervalEqual_ReturnsIt() {\n    Set<Long> existing = setOf(10L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(10, result, \"Should return exact match\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RequestedVeryLarge_ReturnsLargestSmaller() {\n    Set<Long> existing = setOf(1L, 5L, 10L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(1000, existing);\n    assertEquals(10, result, \"Should return largest of smaller intervals\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RequestedVerySmall_ReturnsSmallestAvailable() {\n    Set<Long> existing = setOf(10L, 20L, 30L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(1, existing);\n    assertEquals(10, result, \"Should return smallest available interval\");\n  }\n\n  // === Real-World Scenarios ===\n\n  @Test\n  public void testSelectBestInterval_RealWorldScenario1_ShortLivedSession() {\n    // Short-lived session: 60s validity, wants 10s heartbeat\n    // But we have threads at [5s, 30s, 3600s]\n    Set<Long> existing = setOf(5L, 30L, 3600L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(5, result, \"Should use 5s (more frequent than needed, but safe)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RealWorldScenario2_LongLivedSession() {\n    // Long-lived session: 4h validity, wants 3600s (1h) heartbeat\n    // But we have threads at [10s, 60s, 300s]\n    Set<Long> existing = setOf(10L, 60L, 300L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(3600, existing);\n    assertEquals(300, result, \"Should use 300s (closest smaller interval)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RealWorldScenario3_CriticalBugCase() {\n    // This was the original bug scenario:\n    // Session needs 10s heartbeat (short validity)\n    // Existing threads: [12s] (from a longer-lived session)\n    // PROBLEM: 12s is LONGER than requested 10s, which could cause expiration\n    // BEHAVIOR: Return 12s as only available option (telemetry will alert on this)\n    Set<Long> existing = setOf(12L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(\n        12,\n        result,\n        \"Should select 12s as only option (session might expire - telemetry will alert)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RealWorldScenario4_MaxThreadsReached() {\n    // 10 threads already exist with various intervals\n    // New session needs 25s heartbeat\n    Set<Long> existing = setOf(5L, 10L, 15L, 20L, 30L, 60L, 120L, 300L, 600L, 3600L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(25, existing);\n    assertEquals(20, result, \"Should select 20s (closest smaller to 25s)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_RealWorldScenario5_EdgeOfEquality() {\n    // Requested = 10, existing = [9, 10, 11]\n    Set<Long> existing = setOf(9L, 10L, 11L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(10, result, \"Should select exact match (10s)\");\n  }\n\n  // === Boundary Tests ===\n\n  @Test\n  public void testSelectBestInterval_OneSmallerOneEqual_ReturnsEqual() {\n    Set<Long> existing = setOf(5L, 10L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(10, result, \"Should prefer exact match over smaller\");\n  }\n\n  @Test\n  public void testSelectBestInterval_OneEqualOneLarger_ReturnsEqual() {\n    Set<Long> existing = setOf(10L, 15L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(10, result, \"Should return exact match\");\n  }\n\n  @Test\n  public void testSelectBestInterval_AllSmaller_ReturnsLargestOfSmaller() {\n    Set<Long> existing = setOf(1L, 2L, 3L, 4L, 5L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(5, result, \"Should return largest of all smaller intervals\");\n  }\n\n  @Test\n  public void testSelectBestInterval_AllLarger_ReturnsSmallestOfLarger() {\n    Set<Long> existing = setOf(20L, 30L, 40L, 50L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(20, result, \"Should return smallest of all larger intervals (best fallback)\");\n  }\n\n  // === Safety Verification Tests ===\n\n  @Test\n  public void testSelectBestInterval_NeverReturnsLongerWhenShorterExists() {\n    // Verify that we NEVER select a longer interval when shorter exists\n    Set<Long> existing = setOf(5L, 20L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(10, existing);\n    assertEquals(5, result, \"Must select 5s (shorter/safer), never 20s (longer/risky)\");\n  }\n\n  @Test\n  public void testSelectBestInterval_AlwaysReturnsValidInterval() {\n    // Result should always be one of the existing intervals\n    Set<Long> existing = setOf(5L, 10L, 15L);\n    long result = HeartbeatIntervalSelector.selectBestInterval(12, existing);\n    assertTrue(existing.contains(result), \"Result must be from existing intervals\");\n  }\n\n  @Test\n  public void testSelectBestInterval_ConsistentResults() {\n    // Same inputs should always produce same output\n    Set<Long> existing = setOf(5L, 10L, 15L, 20L);\n    long result1 = HeartbeatIntervalSelector.selectBestInterval(12, existing);\n    long result2 = HeartbeatIntervalSelector.selectBestInterval(12, existing);\n    long result3 = HeartbeatIntervalSelector.selectBestInterval(12, existing);\n    assertEquals(result1, result2, \"Results should be consistent\");\n    assertEquals(result2, result3, \"Results should be consistent\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HeartbeatRegistryTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.sql.SQLException;\nimport java.time.Clock;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class HeartbeatRegistryTest {\n  private ScheduledExecutorService mockExecutor;\n  private ManualClock manualClock;\n  private HeartbeatRegistry registry;\n  private SFSession mockSession1;\n  private SFSession mockSession2;\n  private SFSession mockSession3;\n\n  @BeforeEach\n  public void setUp() {\n    mockExecutor = mock(ScheduledExecutorService.class);\n    manualClock = new ManualClock(Instant.ofEpochSecond(1000000));\n\n    // Mock schedule to return a future\n    @SuppressWarnings(\"unchecked\")\n    ScheduledFuture<Object> mockFuture = mock(ScheduledFuture.class);\n    when(mockExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class)))\n        .thenAnswer(invocation -> mockFuture);\n    when(mockExecutor.isShutdown()).thenReturn(false);\n\n    registry = new HeartbeatRegistry(mockExecutor, manualClock);\n\n    mockSession1 = mock(SFSession.class);\n    mockSession2 = mock(SFSession.class);\n    mockSession3 = mock(SFSession.class);\n\n    when(mockSession1.getSessionId()).thenReturn(\"session-1\");\n    when(mockSession2.getSessionId()).thenReturn(\"session-2\");\n    when(mockSession3.getSessionId()).thenReturn(\"session-3\");\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (registry != null) {\n      registry.shutdown();\n    }\n  }\n\n  @Test\n  public void testAddSession_SingleSession() {\n    registry.addSession(mockSession1, 60, 10);\n\n    assertEquals(1, registry.getActiveThreadCount());\n    assertEquals(1, registry.getSessionCountForInterval(10));\n  }\n\n  @Test\n  public void testAddSession_MultipleSessionsSameInterval_NoReschedule() {\n    registry.addSession(mockSession1, 60, 10);\n    registry.addSession(mockSession2, 60, 10);\n\n    // Only ONE thread should exist\n    assertEquals(1, registry.getActiveThreadCount());\n    assertEquals(2, registry.getSessionCountForInterval(10));\n\n    // Verify scheduler called only ONCE (no reschedule)\n    verify(mockExecutor, times(1)).schedule(any(Runnable.class), anyLong(), any(TimeUnit.class));\n  }\n\n  @Test\n  public void testAddSession_MultipleSessionsDifferentIntervals_Independent() {\n    // Session 1: 60s validity, 10s heartbeat (interval = 10s)\n    registry.addSession(mockSession1, 60, 10);\n\n    // Session 2: 4h validity, 1h heartbeat (interval = 3600s)\n    registry.addSession(mockSession2, 14400, 3600);\n\n    // TWO independent threads should exist\n    assertEquals(2, registry.getActiveThreadCount());\n    assertEquals(1, registry.getSessionCountForInterval(10));\n    assertEquals(1, registry.getSessionCountForInterval(3600));\n  }\n\n  @Test\n  public void testAddSession_CalculatesIntervalCorrectly() {\n    // Requested 100s, but validity/4 = 60/4 = 15s\n    registry.addSession(mockSession1, 60, 100);\n\n    // Should use 15s (minimum of 100 and 60/4)\n    assertEquals(1, registry.getActiveThreadCount());\n    assertEquals(1, registry.getSessionCountForInterval(15));\n  }\n\n  @Test\n  public void testAddSession_NullSession_ThrowsException() {\n    assertThrows(IllegalArgumentException.class, () -> registry.addSession(null, 60, 10));\n  }\n\n  @Test\n  public void testAddSession_InvalidValidity_ThrowsException() {\n    assertThrows(IllegalArgumentException.class, () -> registry.addSession(mockSession1, 0, 10));\n\n    assertThrows(IllegalArgumentException.class, () -> registry.addSession(mockSession1, -60, 10));\n  }\n\n  @Test\n  public void testAddSession_InvalidFrequency_ThrowsException() {\n    assertThrows(IllegalArgumentException.class, () -> registry.addSession(mockSession1, 60, 0));\n\n    assertThrows(IllegalArgumentException.class, () -> registry.addSession(mockSession1, 60, -10));\n  }\n\n  @Test\n  public void testRemoveSession_RemovesFromCorrectThread() {\n    registry.addSession(mockSession1, 60, 10);\n    registry.addSession(mockSession2, 60, 10);\n\n    assertEquals(2, registry.getSessionCountForInterval(10));\n\n    registry.removeSession(mockSession1);\n\n    assertEquals(1, registry.getSessionCountForInterval(10));\n    assertEquals(1, registry.getActiveThreadCount());\n  }\n\n  @Test\n  public void testRemoveSession_CleansUpEmptyThread() {\n    registry.addSession(mockSession1, 60, 10);\n    assertEquals(1, registry.getActiveThreadCount());\n\n    registry.removeSession(mockSession1);\n\n    // Thread should be cleaned up\n    assertEquals(0, registry.getActiveThreadCount());\n    assertEquals(0, registry.getSessionCountForInterval(10));\n  }\n\n  @Test\n  public void testRemoveSession_DoesNotAffectOtherThreads() {\n    registry.addSession(mockSession1, 60, 10);\n    registry.addSession(mockSession2, 14400, 3600);\n\n    assertEquals(2, registry.getActiveThreadCount());\n\n    registry.removeSession(mockSession1);\n\n    // Thread for interval 10s should be gone\n    assertEquals(1, registry.getActiveThreadCount());\n    assertEquals(0, registry.getSessionCountForInterval(10));\n\n    // Thread for interval 3600s should still exist\n    assertEquals(1, registry.getSessionCountForInterval(3600));\n  }\n\n  @Test\n  public void testRemoveSession_NonExistentSession_DoesNotThrow() {\n    assertDoesNotThrow(() -> registry.removeSession(mockSession1));\n  }\n\n  @Test\n  public void testRemoveSession_NullSession_DoesNotThrow() {\n    assertDoesNotThrow(() -> registry.removeSession(null));\n  }\n\n  @Test\n  public void testCriticalBug_ShortSessionNotExpiredByLongSession() throws Exception {\n    // This tests the fix for the critical bug\n\n    // Session A: 60s validity, 10s heartbeat (interval = min(10, 60/4) = 10s)\n    registry.addSession(mockSession1, 60, 10);\n\n    // Session B: 4h validity, 1h heartbeat (interval = min(3600, 14400/4) = 3600s)\n    registry.addSession(mockSession2, 14400, 3600);\n\n    // Verify TWO independent threads exist\n    assertEquals(2, registry.getActiveThreadCount());\n\n    // Advance time by 65 seconds (past session1's validity)\n    manualClock.advance(Duration.ofSeconds(65));\n\n    // Clear previous invocations\n    clearInvocations(mockSession1, mockSession2);\n\n    // Trigger 10s heartbeat (for short-lived session)\n    registry.triggerHeartbeatForInterval(10);\n\n    // Verify: Session1 got heartbeat (NOT expired)\n    try {\n      verify(mockSession1, times(1)).heartbeat();\n      // Verify: Session2 NOT heartbeated yet (its interval is 3600s)\n      verify(mockSession2, never()).heartbeat();\n    } catch (SFException | SQLException e) {\n      fail(\"Verification should not throw: \" + e.getMessage());\n    }\n  }\n\n  @Test\n  public void testTriggerHeartbeatForInterval_OnlyAffectsSpecifiedInterval() throws Exception {\n    registry.addSession(mockSession1, 60, 10);\n    registry.addSession(mockSession2, 14400, 3600);\n\n    // Trigger only 10s interval\n    registry.triggerHeartbeatForInterval(10);\n\n    try {\n      verify(mockSession1, times(1)).heartbeat();\n      verify(mockSession2, never()).heartbeat();\n    } catch (SFException | SQLException e) {\n      fail(\"Verification should not throw: \" + e.getMessage());\n    }\n\n    // Trigger only 3600s interval\n    clearInvocations(mockSession1, mockSession2);\n    registry.triggerHeartbeatForInterval(3600);\n\n    try {\n      verify(mockSession1, never()).heartbeat();\n      verify(mockSession2, times(1)).heartbeat();\n    } catch (SFException | SQLException e) {\n      fail(\"Verification should not throw: \" + e.getMessage());\n    }\n  }\n\n  @Test\n  public void testTriggerHeartbeatForInterval_NonExistentInterval_DoesNotThrow() {\n    assertDoesNotThrow(() -> registry.triggerHeartbeatForInterval(999));\n  }\n\n  @Test\n  public void testShutdown_CleansUpAllThreads() {\n    registry.addSession(mockSession1, 60, 10);\n    registry.addSession(mockSession2, 14400, 3600);\n\n    assertEquals(2, registry.getActiveThreadCount());\n\n    registry.shutdown();\n\n    assertEquals(0, registry.getActiveThreadCount());\n    verify(mockExecutor, times(1)).shutdown();\n  }\n\n  /** Manual clock implementation for testing. */\n  private static class ManualClock extends Clock {\n    private Instant currentTime;\n\n    ManualClock(Instant startTime) {\n      this.currentTime = startTime;\n    }\n\n    void advance(Duration duration) {\n      currentTime = currentTime.plus(duration);\n    }\n\n    @Override\n    public ZoneId getZone() {\n      return ZoneId.systemDefault();\n    }\n\n    @Override\n    public Clock withZone(ZoneId zone) {\n      return this;\n    }\n\n    @Override\n    public Instant instant() {\n      return currentTime;\n    }\n\n    @Override\n    public long millis() {\n      return currentTime.toEpochMilli();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HeartbeatThreadTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyLong;\nimport static org.mockito.Mockito.clearInvocations;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.sql.SQLException;\nimport java.time.Clock;\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\n@Tag(TestTags.CORE)\npublic class HeartbeatThreadTest {\n  private ScheduledExecutorService mockExecutor;\n  private Clock fixedClock;\n  private SFSession mockSession1;\n  private SFSession mockSession2;\n\n  @BeforeEach\n  public void setUp() {\n    mockExecutor = mock(ScheduledExecutorService.class);\n    fixedClock = Clock.fixed(Instant.ofEpochSecond(1000000), ZoneId.systemDefault());\n    mockSession1 = mock(SFSession.class);\n    mockSession2 = mock(SFSession.class);\n\n    when(mockSession1.getSessionId()).thenReturn(\"session-1\");\n    when(mockSession2.getSessionId()).thenReturn(\"session-2\");\n\n    // Mock schedule to return a future\n    @SuppressWarnings(\"unchecked\")\n    ScheduledFuture<Object> mockFuture = mock(ScheduledFuture.class);\n    when(mockExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class)))\n        .thenAnswer(invocation -> mockFuture);\n  }\n\n  @Test\n  public void testConstructor_ValidParameters() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    assertEquals(10, thread.getIntervalSeconds());\n    assertEquals(0, thread.getSessionCount());\n    assertTrue(thread.isEmpty());\n  }\n\n  @Test\n  public void testConstructor_InvalidInterval() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new HeartbeatThread(0, mockExecutor, fixedClock),\n        \"Should reject zero interval\");\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new HeartbeatThread(-10, mockExecutor, fixedClock),\n        \"Should reject negative interval\");\n  }\n\n  @Test\n  public void testConstructor_NullExecutor() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new HeartbeatThread(10, null, fixedClock),\n        \"Should reject null executor\");\n  }\n\n  @Test\n  public void testConstructor_NullClock() {\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> new HeartbeatThread(10, mockExecutor, null),\n        \"Should reject null clock\");\n  }\n\n  @Test\n  public void testAddSession_FirstSession_StartsThread() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    thread.addSession(mockSession1);\n\n    assertEquals(1, thread.getSessionCount());\n    assertFalse(thread.isEmpty());\n\n    // Verify scheduler was called to start heartbeat\n    verify(mockExecutor, times(1)).schedule(any(Runnable.class), anyLong(), eq(TimeUnit.SECONDS));\n  }\n\n  @Test\n  public void testAddSession_MultipleSessionsSameThread_NoReschedule() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    thread.addSession(mockSession1);\n    thread.addSession(mockSession2);\n\n    assertEquals(2, thread.getSessionCount());\n\n    // Verify scheduler was called only ONCE (no reschedule on second add)\n    verify(mockExecutor, times(1)).schedule(any(Runnable.class), anyLong(), eq(TimeUnit.SECONDS));\n  }\n\n  @Test\n  public void testRemoveSession() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    thread.addSession(mockSession1);\n    thread.addSession(mockSession2);\n    assertEquals(2, thread.getSessionCount());\n\n    thread.removeSession(mockSession1);\n    assertEquals(1, thread.getSessionCount());\n    assertFalse(thread.isEmpty());\n\n    thread.removeSession(mockSession2);\n    assertEquals(0, thread.getSessionCount());\n    assertTrue(thread.isEmpty());\n  }\n\n  @Test\n  public void testShutdown() {\n    @SuppressWarnings(\"unchecked\")\n    ScheduledFuture<Object> mockFuture = mock(ScheduledFuture.class);\n    when(mockExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class)))\n        .thenAnswer(invocation -> mockFuture);\n\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n    thread.addSession(mockSession1);\n\n    thread.shutdown();\n\n    // Verify future was cancelled\n    verify(mockFuture, times(1)).cancel(false);\n\n    // Thread should be empty after shutdown\n    assertEquals(0, thread.getSessionCount());\n    assertTrue(thread.isEmpty());\n  }\n\n  @Test\n  public void testRun_HeartbeatsAllSessions() throws Exception {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    thread.addSession(mockSession1);\n    thread.addSession(mockSession2);\n\n    // Trigger heartbeat manually\n    thread.triggerHeartbeatNow();\n\n    // Verify both sessions were heartbeated\n    try {\n      verify(mockSession1, times(1)).heartbeat();\n      verify(mockSession2, times(1)).heartbeat();\n    } catch (SFException | SQLException e) {\n      fail(\"Verification should not throw: \" + e.getMessage());\n    }\n  }\n\n  @Test\n  public void testRun_ContinuesOnException() throws Exception {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    // Make session1 throw exception\n    try {\n      doThrow(new RuntimeException(\"Heartbeat failed\")).when(mockSession1).heartbeat();\n    } catch (SFException | SQLException e) {\n      fail(\"Stubbing should not throw: \" + e.getMessage());\n    }\n\n    thread.addSession(mockSession1);\n    thread.addSession(mockSession2);\n\n    // Trigger heartbeat - should not throw\n    thread.triggerHeartbeatNow();\n\n    // Verify both sessions were called (session1 threw, session2 succeeded)\n    try {\n      verify(mockSession1, times(1)).heartbeat();\n      verify(mockSession2, times(1)).heartbeat();\n    } catch (SFException | SQLException e) {\n      fail(\"Verification should not throw: \" + e.getMessage());\n    }\n  }\n\n  @Test\n  public void testRun_ReschedulesIfSessionsRemain() throws Exception {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n    thread.addSession(mockSession1);\n\n    // Clear previous invocations\n    clearInvocations(mockExecutor);\n\n    // Run heartbeat\n    thread.triggerHeartbeatNow();\n\n    // Should schedule next heartbeat since session still exists\n    verify(mockExecutor, times(1)).schedule(any(Runnable.class), anyLong(), eq(TimeUnit.SECONDS));\n  }\n\n  @Test\n  public void testRun_DoesNotRescheduleAfterShutdown() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n    thread.addSession(mockSession1);\n\n    thread.shutdown();\n    clearInvocations(mockExecutor);\n\n    // Try to run - should not reschedule\n    thread.run();\n\n    verify(mockExecutor, never()).schedule(any(Runnable.class), anyLong(), any(TimeUnit.class));\n  }\n\n  @Test\n  public void testScheduleHeartbeat_CalculatesCorrectDelay() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    thread.addSession(mockSession1);\n\n    ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);\n    verify(mockExecutor).schedule(any(Runnable.class), delayCaptor.capture(), eq(TimeUnit.SECONDS));\n\n    long delay = delayCaptor.getValue();\n    assertTrue(delay >= 0, \"Delay should be non-negative\");\n    assertTrue(delay <= 10, \"Delay should not exceed interval\");\n  }\n\n  @Test\n  public void testAddSession_AfterShutdown_ReturnsFalse() {\n    HeartbeatThread thread = new HeartbeatThread(10, mockExecutor, fixedClock);\n\n    thread.shutdown();\n\n    // Try to add session after shutdown - should return false\n    assertFalse(thread.addSession(mockSession1));\n\n    assertEquals(0, thread.getSessionCount());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HttpUtilLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.io.IOException;\nimport java.net.SocketTimeoutException;\nimport java.time.Duration;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\n\n@Tag(TestTags.CORE)\npublic class HttpUtilLatestIT {\n\n  private static final String HANG_WEBSERVER_ADDRESS = \"http://localhost:12345/hang\";\n\n  @BeforeEach\n  public void resetHttpClientsCache() {\n    HttpUtil.httpClient.clear();\n  }\n\n  @AfterEach\n  public void resetHttpTimeouts() {\n    HttpUtil.setConnectionTimeout(60000);\n    HttpUtil.setSocketTimeout(300000);\n  }\n\n  /** Added in > 3.14.5 */\n  @Test\n  public void shouldGetDefaultConnectionAndSocketTimeouts() {\n    assertEquals(Duration.ofMillis(60_000), HttpUtil.getConnectionTimeout());\n    assertEquals(Duration.ofMillis(300_000), HttpUtil.getSocketTimeout());\n  }\n\n  /** Added in > 3.14.5 */\n  @Test\n  @Timeout(1)\n  public void shouldOverrideConnectionAndSocketTimeouts() {\n    // it's hard to test connection timeout so there is only a test for socket timeout\n    HttpUtil.setConnectionTimeout(100);\n    HttpUtil.setSocketTimeout(200);\n\n    CloseableHttpClient httpClient =\n        HttpUtil.getHttpClient(new HttpClientSettingsKey(OCSPMode.INSECURE));\n    IOException e =\n        assertThrows(\n            IOException.class,\n            () -> {\n              httpClient.execute(new HttpGet(HANG_WEBSERVER_ADDRESS));\n            });\n    MatcherAssert.assertThat(e, CoreMatchers.instanceOf(SocketTimeoutException.class));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HttpUtilTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.lang.reflect.Field;\nimport java.util.AbstractMap;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport javax.net.ssl.TrustManager;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.Configurable;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.hamcrest.CoreMatchers;\nimport org.hamcrest.Matcher;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\npublic class HttpUtilTest {\n\n  /**\n   * Test based on <a href=\"https://github.com/snowflakedb/snowflake-jdbc/issues/2047\">reported\n   * issue in SNOW-1898533</a>\n   */\n  @Test\n  public void buildHttpClientRace() throws InterruptedException {\n    HttpUtil.httpClient.clear();\n    // start two threads but only need one to fail\n    CountDownLatch latch = new CountDownLatch(1);\n    final Queue<AbstractMap.SimpleEntry<Thread, Throwable>> failures =\n        new ConcurrentLinkedQueue<>();\n    final HttpClientSettingsKey noProxyKey = new HttpClientSettingsKey(null);\n    final HttpClientSettingsKey proxyKey =\n        new HttpClientSettingsKey(\n            null, \"some.proxy.host\", 8080, null, null, null, \"http\", null, false);\n\n    Thread noProxyThread =\n        new Thread(() -> verifyProxyUsage(noProxyKey, failures, latch), \"noProxyThread\");\n    noProxyThread.start();\n\n    Thread withProxyThread =\n        new Thread(() -> verifyProxyUsage(proxyKey, failures, latch), \"withProxyThread\");\n    withProxyThread.start();\n\n    // if latch goes to zero, then one of the threads failed\n    // if await times out (returns false), then neither thread has failed (both still running)\n    boolean failed = latch.await(1, TimeUnit.SECONDS);\n    noProxyThread.interrupt();\n    withProxyThread.interrupt();\n    if (failed) {\n      AbstractMap.SimpleEntry<Thread, Throwable> failure = failures.remove();\n      fail(failure.getKey().getName() + \" failed\", failure.getValue());\n    }\n  }\n\n  @Test\n  void testConfigureTrustManagerIfNeededWithNullKey() {\n    TrustManager[] result = HttpUtil.configureTrustManagerIfNeeded(null, null);\n    assertNull(result);\n  }\n\n  @Test\n  void testConfigureTrustManagerIfNeededWithOcspDisabledAndNoCrlChecks() {\n    HttpClientSettingsKey key = new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS);\n\n    TrustManager[] result = HttpUtil.configureTrustManagerIfNeeded(key, null);\n    assertNull(result);\n  }\n\n  @ParameterizedTest\n  @EnumSource(\n      value = OCSPMode.class,\n      names = {\"DISABLE_OCSP_CHECKS\"},\n      mode = EnumSource.Mode.EXCLUDE)\n  void testConfigureTrustManagerIfNeededForOcsp(OCSPMode mode) {\n    HttpClientSettingsKey key = new HttpClientSettingsKey(mode);\n\n    TrustManager[] result = HttpUtil.configureTrustManagerIfNeeded(key, null);\n    assertNotNull(result);\n    assertInstanceOf(SFTrustManager.class, result[0]);\n  }\n\n  @ParameterizedTest\n  @EnumSource(\n      value = CertRevocationCheckMode.class,\n      names = {\"DISABLED\"},\n      mode = EnumSource.Mode.EXCLUDE)\n  void testConfigureTrustManagerIfNeededWithCrlModes(CertRevocationCheckMode mode) {\n    HttpClientSettingsKey key = new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS);\n    key.setRevocationCheckMode(mode);\n\n    TrustManager[] result = HttpUtil.configureTrustManagerIfNeeded(key, null);\n    assertNotNull(result);\n    assertInstanceOf(SFExtendedCrlTrustManager.class, result[0]);\n  }\n\n  @Test\n  void testConfigureTrustManagerWithOcspAndCrlDisabled() {\n    HttpClientSettingsKey key = new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS);\n    key.setRevocationCheckMode(CertRevocationCheckMode.DISABLED);\n\n    TrustManager[] result = HttpUtil.configureTrustManagerIfNeeded(key, null);\n    assertNull(result);\n  }\n\n  private static void verifyProxyUsage(\n      HttpClientSettingsKey key,\n      Queue<AbstractMap.SimpleEntry<Thread, Throwable>> failures,\n      CountDownLatch latch) {\n    while (!Thread.currentThread().isInterrupted()) {\n      try (CloseableHttpClient client = HttpUtil.buildHttpClient(key, null, false)) {\n        assertHttpClientUsesProxy(client, key.usesProxy());\n      } catch (Throwable e) {\n        failures.add(new AbstractMap.SimpleEntry<>(Thread.currentThread(), e));\n        latch.countDown();\n        break;\n      }\n    }\n  }\n\n  private static void assertHttpClientUsesProxy(CloseableHttpClient client, boolean proxyUsed) {\n    assertRequestConfigWithoutProxyConfig(client);\n    assertRoutePlannerOverridden(client, proxyUsed);\n  }\n\n  private static void assertRequestConfigWithoutProxyConfig(CloseableHttpClient client) {\n    MatcherAssert.assertThat(client, CoreMatchers.instanceOf(Configurable.class));\n    Configurable c = (Configurable) client;\n    RequestConfig config = c.getConfig();\n    assertNull(config.getProxy(), \"request config has configured proxy\");\n  }\n\n  private static void assertRoutePlannerOverridden(CloseableHttpClient client, boolean proxyUsed) {\n    try {\n      // HTTP client does not provide information about proxy settings so to detect that we are\n      // using proxy we have to look inside via reflection and if the route planner is overridden to\n      // our proxy class\n      Field routePlannerField = client.getClass().getDeclaredField(\"routePlanner\");\n      routePlannerField.setAccessible(true);\n      Matcher<Object> snowflakeProxyPlannerClassMatcher =\n          CoreMatchers.instanceOf(SnowflakeMutableProxyRoutePlanner.class);\n      MatcherAssert.assertThat(\n          routePlannerField.get(client),\n          proxyUsed\n              ? snowflakeProxyPlannerClassMatcher\n              : CoreMatchers.not(snowflakeProxyPlannerClassMatcher));\n    } catch (NoSuchFieldException | IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/HttpUtilWiremockLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class HttpUtilWiremockLatestIT extends BaseWiremockTest {\n  private static final String SUCCESS_AT_THIRD_RETRY_ECHOING_HEADERS_SENT =\n      \"{\\n\"\n          + \"  \\\"mappings\\\": [\\n\"\n          + \"    {\\n\"\n          + \"      \\\"scenarioName\\\": \\\"Retry Scenario Example\\\",\\n\"\n          + \"      \\\"requiredScenarioState\\\": \\\"Started\\\",\\n\"\n          + \"      \\\"newScenarioState\\\": \\\"First Attempt Failed\\\",\\n\"\n          + \"      \\\"request\\\": {\\n\"\n          + \"        \\\"method\\\": \\\"GET\\\",\\n\"\n          + \"        \\\"urlPath\\\": \\\"/echo-headers\\\"\\n\"\n          + \"      },\\n\"\n          + \"      \\\"response\\\": {\\n\"\n          + \"        \\\"fault\\\": \\\"EMPTY_RESPONSE\\\"\\n\"\n          + \"      }\\n\"\n          + \"    },\\n\"\n          + \"    {\\n\"\n          + \"      \\\"scenarioName\\\": \\\"Retry Scenario Example\\\",\\n\"\n          + \"      \\\"requiredScenarioState\\\": \\\"First Attempt Failed\\\",\\n\"\n          + \"      \\\"newScenarioState\\\": \\\"Second Attempt Failed\\\",\\n\"\n          + \"      \\\"request\\\": {\\n\"\n          + \"        \\\"method\\\": \\\"GET\\\",\\n\"\n          + \"        \\\"urlPath\\\": \\\"/echo-headers\\\"\\n\"\n          + \"      },\\n\"\n          + \"      \\\"response\\\": {\\n\"\n          + \"        \\\"fault\\\": \\\"EMPTY_RESPONSE\\\"\\n\"\n          + \"      }\\n\"\n          + \"    },\\n\"\n          + \"    {\\n\"\n          + \"      \\\"scenarioName\\\": \\\"Retry Scenario Example\\\",\\n\"\n          + \"      \\\"requiredScenarioState\\\": \\\"Second Attempt Failed\\\",\\n\"\n          + \"      \\\"request\\\": {\\n\"\n          + \"        \\\"method\\\": \\\"GET\\\",\\n\"\n          + \"        \\\"urlPath\\\": \\\"/echo-headers\\\"\\n\"\n          + \"      },\\n\"\n          + \"      \\\"response\\\": {\\n\"\n          + \"        \\\"status\\\": 200,\\n\"\n          + \"        \\\"headers\\\": {\\n\"\n          + \"          \\\"Content-Type\\\": \\\"application/json\\\"\\n\"\n          + \"        },\\n\"\n          + \"        \\\"body\\\": \\\"{{request.headers}}\\\",\\n\"\n          + \"        \\\"transformers\\\": [\\n\"\n          + \"          \\\"response-template\\\"\\n\"\n          + \"        ]\\n\"\n          + \"      }\\n\"\n          + \"    }\\n\"\n          + \"  ]\\n\"\n          + \"}\\n\";\n\n  @AfterEach\n  public void resetHttpClients() {\n    HttpUtil.httpClient.clear();\n  }\n\n  @Test\n  public void testAddHttpInterceptorsIfPresent() throws IOException {\n    importMapping(SUCCESS_AT_THIRD_RETRY_ECHOING_HEADERS_SENT);\n    AtomicInteger invocations = new AtomicInteger();\n\n    HttpHeadersCustomizer customizer =\n        new HttpHeadersCustomizer() {\n          @Override\n          public boolean applies(\n              String method, String uri, Map<String, List<String>> currentHeaders) {\n            return true;\n          }\n\n          @Override\n          public Map<String, List<String>> newHeaders() {\n            invocations.incrementAndGet();\n            Map<String, List<String>> stringListMap = new java.util.HashMap<>();\n            stringListMap.put(\"test-header\", Collections.singletonList(\"test-header-value\"));\n            return stringListMap;\n          }\n\n          @Override\n          public boolean invokeOnce() {\n            return false;\n          }\n        };\n\n    try (CloseableHttpClient httpClient =\n        HttpUtil.buildHttpClient(\n            new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS),\n            null,\n            false,\n            Collections.singletonList(customizer))) {\n      CloseableHttpResponse response =\n          httpClient.execute(\n              new HttpGet(\n                  String.format(\"http://%s:%d/echo-headers\", WIREMOCK_HOST, wiremockHttpPort)));\n      String content = EntityUtils.toString(response.getEntity());\n\n      assertHttpHeadersAdded(content);\n      assertCustomizerInvokedForEachRetry(3, invocations.get());\n    }\n  }\n\n  private void assertHttpHeadersAdded(String responseBody) {\n    assertTrue(responseBody.contains(\"test-header-value\"));\n  }\n\n  private void assertCustomizerInvokedForEachRetry(int expectedInvocations, int actualInvocations) {\n    assertEquals(expectedInvocations, actualInvocations);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/MinicoreTelemetryWiremockIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.awaitility.Awaitility.await;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.minicore.Minicore;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class MinicoreTelemetryWiremockIT extends BaseWiremockTest {\n\n  private static final String LOGIN_MAPPING_PATH =\n      \"/wiremock/mappings/minicore/minicore_telemetry.json\";\n\n  private final String WIREMOCK_HOST_WITH_HTTPS_AND_PORT =\n      \"https://\" + WIREMOCK_HOST + \":\" + wiremockHttpsPort;\n\n  @Test\n  public void testMinicoreTelemetryIncludedInLoginRequest() throws Exception, SFException {\n    Minicore.initializeAsync();\n    await().atMost(Duration.ofSeconds(5)).until(() -> Minicore.getInstance() != null);\n\n    importMappingFromResources(LOGIN_MAPPING_PATH);\n    setCustomTrustStorePropertyPath();\n\n    SFLoginInput loginInput = createLoginInput();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n    connectionPropertiesMap.put(SFSessionProperty.TRACING, \"ALL\");\n\n    SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n\n    verifyRequestCount(1, \"/session/v1/login-request.*\");\n  }\n\n  private SFLoginInput createLoginInput() {\n    SFLoginInput input = new SFLoginInput();\n    input.setServerUrl(WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n    input.setUserName(\"TEST_USER\");\n    input.setPassword(\"TEST_PASSWORD\");\n    input.setAccountName(\"TEST_ACCOUNT\");\n    input.setAppId(\"TEST_APP_ID\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(30);\n    input.setSessionParameters(new HashMap<>());\n    return input;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/OAuthAuthorizationCodeFlowLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SessionUtilExternalBrowser.AuthExternalBrowserHandlers;\n\nimport java.net.URI;\nimport java.time.Duration;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.auth.oauth.AccessTokenProvider;\nimport net.snowflake.client.internal.core.auth.oauth.OAuthAuthorizationCodeAccessTokenProvider;\nimport net.snowflake.client.internal.core.auth.oauth.StateProvider;\nimport net.snowflake.client.internal.core.auth.oauth.TokenResponseDTO;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class OAuthAuthorizationCodeFlowLatestIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/oauth/authorization_code\";\n  private static final String SUCCESSFUL_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow.json\";\n  private static final String SUCCESSFUL_DPOP_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_dpop_flow.json\";\n  private static final String SUCCESSFUL_FLOW_WITH_SINGLE_USE_REFRESH_TOKENS_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow_with_single_use_refresh_tokens.json\";\n  private static final String DPOP_NONCE_ERROR_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/dpop_nonce_error_flow.json\";\n  private static final String BROWSER_TIMEOUT_SCENARIO_MAPPING =\n      SCENARIOS_BASE_DIR + \"/browser_timeout_authorization_error.json\";\n  private static final String INVALID_SCOPE_SCENARIO_MAPPING =\n      SCENARIOS_BASE_DIR + \"/invalid_scope_error.json\";\n  private static final String INVALID_STATE_SCENARIO_MAPPING =\n      SCENARIOS_BASE_DIR + \"/invalid_state_error.json\";\n  private static final String TOKEN_REQUEST_ERROR_SCENARIO_MAPPING =\n      SCENARIOS_BASE_DIR + \"/token_request_error.json\";\n  private static final String CUSTOM_URLS_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/external_idp_custom_urls.json\";\n\n  private static final SFLogger logger =\n      SFLoggerFactory.getLogger(OAuthAuthorizationCodeFlowLatestIT.class);\n\n  private final AuthExternalBrowserHandlers wiremockProxyRequestBrowserHandler =\n      new WiremockProxyRequestBrowserHandler();\n\n  private final AccessTokenProvider provider =\n      new OAuthAuthorizationCodeAccessTokenProvider(\n          wiremockProxyRequestBrowserHandler, new MockStateProvider(), 30);\n\n  public OAuthAuthorizationCodeFlowLatestIT() throws SFException {}\n\n  @Test\n  public void successfulFlowScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput =\n        createLoginInputStub(\"http://localhost:8009/snowflake/oauth-redirect\", null, null, false);\n\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void successfulFlowDPoPScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_DPOP_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput =\n        createLoginInputStubWithDPoPEnabled(\n            \"http://localhost:8012/snowflake/oauth-redirect\", null, null);\n\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void successfulFlowDPoPScenarioWithNonce() throws SFException {\n    importMappingFromResources(DPOP_NONCE_ERROR_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput =\n        createLoginInputStubWithDPoPEnabled(\n            \"http://localhost:8013/snowflake/oauth-redirect\", null, null);\n\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void successfulFlowWithSingleUseRefreshTokensScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_WITH_SINGLE_USE_REFRESH_TOKENS_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput =\n        createLoginInputStub(\"http://localhost:8009/snowflake/oauth-redirect\", null, null, true);\n\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n    String refreshToken = tokenResponse.getRefreshToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(refreshToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n    Assertions.assertEquals(\"refresh-token-123\", refreshToken);\n  }\n\n  @Test\n  public void customUrlsScenario() throws SFException {\n    importMappingFromResources(CUSTOM_URLS_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"http://localhost:8007/snowflake/oauth-redirect\",\n            String.format(\"http://%s:%d/authorization\", WIREMOCK_HOST, wiremockHttpPort),\n            String.format(\"http://%s:%d/tokenrequest\", WIREMOCK_HOST, wiremockHttpPort),\n            false);\n\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void browserTimeoutFlowScenario() throws SFException {\n    importMappingFromResources(BROWSER_TIMEOUT_SCENARIO_MAPPING);\n    SFLoginInput loginInput =\n        createLoginInputStub(\"http://localhost:8004/snowflake/oauth-redirect\", null, null, false);\n\n    AccessTokenProvider provider =\n        new OAuthAuthorizationCodeAccessTokenProvider(\n            wiremockProxyRequestBrowserHandler, new MockStateProvider(), 1);\n    SFException e =\n        Assertions.assertThrows(SFException.class, () -> provider.getAccessToken(loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Authorization request timed out. Snowflake driver did not receive authorization code back to the redirect URI. Verify your security integration and driver configuration.\"));\n  }\n\n  @Test\n  public void invalidScopeFlowScenario() {\n    importMappingFromResources(INVALID_SCOPE_SCENARIO_MAPPING);\n    SFLoginInput loginInput =\n        createLoginInputStub(\"http://localhost:8002/snowflake/oauth-redirect\", null, null, false);\n    SFException e =\n        Assertions.assertThrows(SFException.class, () -> provider.getAccessToken(loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Error during authorization: invalid_scope, One or more scopes are not configured for the authorization server resource.\"));\n  }\n\n  @Test\n  public void invalidStateFlowScenario() {\n    importMappingFromResources(INVALID_STATE_SCENARIO_MAPPING);\n    SFLoginInput loginInput =\n        createLoginInputStub(\"http://localhost:8010/snowflake/oauth-redirect\", null, null, false);\n    SFException e =\n        Assertions.assertThrows(SFException.class, () -> provider.getAccessToken(loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Error during OAuth Authorization Code authentication: Invalid authorization request redirection state: invalidstate, expected: abc123\"));\n  }\n\n  @Test\n  public void tokenRequestErrorFlowScenario() {\n    importMappingFromResources(TOKEN_REQUEST_ERROR_SCENARIO_MAPPING);\n    SFLoginInput loginInput =\n        createLoginInputStub(\"http://localhost:8003/snowflake/oauth-redirect\", null, null, false);\n\n    SFException e =\n        Assertions.assertThrows(SFException.class, () -> provider.getAccessToken(loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\"JDBC driver encountered communication error. Message: HTTP status=400\"));\n  }\n\n  private SFLoginInput createLoginInputStub(\n      String redirectUri,\n      String authorizationUrl,\n      String tokenRequestUrl,\n      boolean enableSingleUseRefreshTokens) {\n    SFLoginInput loginInputStub = new SFLoginInput();\n    loginInputStub.setServerUrl(String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort));\n    loginInputStub.setOauthLoginInput(\n        new SFOauthLoginInput(\n            \"123\",\n            \"123\",\n            redirectUri,\n            authorizationUrl,\n            tokenRequestUrl,\n            \"session:role:ANALYST\",\n            enableSingleUseRefreshTokens));\n    loginInputStub.setSocketTimeout(Duration.ofMinutes(5));\n    loginInputStub.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n\n    return loginInputStub;\n  }\n\n  private SFLoginInput createLoginInputStubWithDPoPEnabled(\n      String redirectUri, String authorizationUrl, String tokenRequestUrl) {\n    SFLoginInput loginInputStub =\n        createLoginInputStub(redirectUri, authorizationUrl, tokenRequestUrl, false);\n    loginInputStub.setDPoPEnabled(true);\n    return loginInputStub;\n  }\n\n  static class WiremockProxyRequestBrowserHandler implements AuthExternalBrowserHandlers {\n    @Override\n    public HttpPost build(URI uri) {\n      // do nothing\n      return null;\n    }\n\n    @Override\n    public void openBrowser(String ssoUrl) {\n      try (CloseableHttpClient client = HttpClients.createDefault()) {\n        logger.debug(\"executing browser request to redirect uri: {}\", ssoUrl);\n        HttpResponse response = client.execute(new HttpGet(ssoUrl));\n        if (response.getStatusLine().getStatusCode() != 200) {\n          throw new RuntimeException(\"Invalid response from \" + ssoUrl);\n        }\n      } catch (Exception e) {\n        throw new RuntimeException(e);\n      }\n    }\n\n    @Override\n    public void output(String msg) {\n      // do nothing\n    }\n  }\n\n  static class MockStateProvider implements StateProvider<String> {\n\n    @Override\n    public String getState() {\n      return \"abc123\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/OAuthClientCredentialsFlowLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.time.Duration;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.auth.oauth.AccessTokenProvider;\nimport net.snowflake.client.internal.core.auth.oauth.OAuthClientCredentialsAccessTokenProvider;\nimport net.snowflake.client.internal.core.auth.oauth.TokenResponseDTO;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class OAuthClientCredentialsFlowLatestIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/oauth/client_credentials\";\n  private static final String SUCCESSFUL_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow.json\";\n  private static final String SUCCESSFUL_DPOP_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_dpop_flow.json\";\n  private static final String DPOP_NONCE_ERROR_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/dpop_nonce_error_flow.json\";\n  private static final String TOKEN_REQUEST_ERROR_SCENARIO_MAPPING =\n      SCENARIOS_BASE_DIR + \"/token_request_error.json\";\n\n  @Test\n  public void successfulFlowScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    AccessTokenProvider provider = new OAuthClientCredentialsAccessTokenProvider();\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void successfulFlowScenarioDPoP() throws SFException {\n    importMappingFromResources(SUCCESSFUL_DPOP_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStubWithDPoPEnabled();\n    AccessTokenProvider provider = new OAuthClientCredentialsAccessTokenProvider();\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void successfulFlowScenarioDPoPNonceError() throws SFException {\n    importMappingFromResources(DPOP_NONCE_ERROR_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStubWithDPoPEnabled();\n    AccessTokenProvider provider = new OAuthClientCredentialsAccessTokenProvider();\n    TokenResponseDTO tokenResponse = provider.getAccessToken(loginInput);\n    String accessToken = tokenResponse.getAccessToken();\n\n    Assertions.assertFalse(SnowflakeUtil.isNullOrEmpty(accessToken));\n    Assertions.assertEquals(\"access-token-123\", accessToken);\n  }\n\n  @Test\n  public void tokenRequestErrorFlowScenario() throws SFException {\n    importMappingFromResources(TOKEN_REQUEST_ERROR_SCENARIO_MAPPING);\n    SFLoginInput loginInput = createLoginInputStub();\n    AccessTokenProvider provider = new OAuthClientCredentialsAccessTokenProvider();\n    SFException e =\n        Assertions.assertThrows(SFException.class, () -> provider.getAccessToken(loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\"JDBC driver encountered communication error. Message: HTTP status=400\"));\n  }\n\n  private SFLoginInput createLoginInputStub() {\n    SFLoginInput loginInputStub = new SFLoginInput();\n    loginInputStub.setServerUrl(String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort));\n    loginInputStub.setOauthLoginInput(\n        new SFOauthLoginInput(\n            \"123\",\n            \"123\",\n            null,\n            null,\n            String.format(\"http://%s:%d/oauth/token-request\", WIREMOCK_HOST, wiremockHttpPort),\n            \"session:role:ANALYST\"));\n    loginInputStub.setSocketTimeout(Duration.ofMinutes(5));\n    loginInputStub.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n\n    return loginInputStub;\n  }\n\n  private SFLoginInput createLoginInputStubWithDPoPEnabled() {\n    SFLoginInput loginInputStub = createLoginInputStub();\n    loginInputStub.setDPoPEnabled(true);\n    return loginInputStub;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/OAuthLegacyFlowLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class OAuthLegacyFlowLatestIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/oauth/legacy_oauth\";\n  private static final String EXPIRED_TOKEN_SCENARIO = SCENARIOS_BASE_DIR + \"/token_expired.json\";\n\n  @Test\n  public void shouldThrowExpirationExceptionUponExpiredTokenResponse() {\n    SFLoginInput loginInput = createLoginInputStub();\n    importMappingFromResources(EXPIRED_TOKEN_SCENARIO);\n    SnowflakeSQLException e =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\"));\n    assertTrue(\n        e.getMessage().contains(\"OAuth access token expired. [1172527951366]\"),\n        \"Expected expiration error message, but got: \" + e.getMessage());\n  }\n\n  private SFLoginInput createLoginInputStub() {\n    SFLoginInput input = new SFLoginInput();\n    input.setAuthenticator(AuthenticatorType.OAUTH.name());\n    input.setOriginalAuthenticator(AuthenticatorType.OAUTH.name());\n    input.setServerUrl(String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort));\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setAppVersion(\"MOCK_APP_VERSION\");\n    input.setToken(\"expired-access-token-123\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setBrowserResponseTimeout(Duration.ofSeconds(5));\n    input.setBrowserHandler(\n        new OAuthAuthorizationCodeFlowLatestIT.WiremockProxyRequestBrowserHandler());\n    input.setLoginTimeout(1000);\n    HashMap<String, Object> sessionParameters = new HashMap<>();\n    sessionParameters.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", \"true\");\n    input.setSessionParameters(sessionParameters);\n    input.setOauthLoginInput(new SFOauthLoginInput(null, null, null, null, null, null));\n    return input;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/OAuthTokenCacheLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedStatic;\nimport org.mockito.stubbing.Answer;\n\n@Tag(TestTags.CORE)\npublic class OAuthTokenCacheLatestIT extends BaseWiremockTest {\n\n  public static final String MOCK_DPOP_PUBLIC_KEY =\n      \"{\\\"kty\\\":\\\"EC\\\",\\\"d\\\":\\\"j5-J-nLE4J1I8ZWtArP8eQbxUbYMPmRvaEjEkHFlHds\\\",\\\"crv\\\":\\\"P-256\\\",\\\"x\\\":\\\"RL5cE-TC4Jr6CxtT4lEI2Yu6wT6LbwojPQsgHUg01F0\\\",\\\"y\\\":\\\"UAdLUSWTJ6czXaS3SfEFUZzKPcVVq4OZAD8e7Rp75y4\\\"}\"; // pragma: allowlist secret\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/oauth/token_caching\";\n  private static final String CACHING_TOKENS_AFTER_CONNECTING_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/caching_tokens_after_connecting.json\";\n  private static final String NOT_CACHING_TOKENS_FOR_CLIENT_CREDENTIALS_FLOW =\n      SCENARIOS_BASE_DIR + \"/not_caching_after_client_credentials_flow.json\";\n  private static final String CACHING_TOKENS_AND_DPOP_KEY_AFTER_CONNECTING_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/caching_tokens_and_dpop_key_after_connecting.json\";\n  private static final String REUSING_CACHED_ACCESS_TOKEN_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/reusing_cached_access_token_to_authenticate.json\";\n  private static final String REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/refreshing_expired_access_token.json\";\n  private static final String REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS_DPOP =\n      SCENARIOS_BASE_DIR + \"/refreshing_expired_access_token_dpop.json\";\n  private static final String REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS_DPOP_NONCE_ERROR =\n      SCENARIOS_BASE_DIR + \"/refreshing_expired_access_token_dpop_nonce_error.json\";\n  private static final String\n      CACHING_REFRESHED_ACCESS_TOKEN_AND_NEW_REFRESH_TOKEN_SCENARIO_MAPPINGS =\n          SCENARIOS_BASE_DIR + \"/caching_refreshed_access_token_and_new_refresh_token.json\";\n  private static final String RESTARTING_FULL_FLOW_ON_EXPIRATION_AND_ERROR_WHEN_REFRESHING =\n      SCENARIOS_BASE_DIR + \"/restarting_full_flow_on_refresh_token_error.json\";\n  private static final String RESTARTING_FULL_FLOW_ON_EXPIRATION_AND_NO_REFRESH_TOKEN =\n      SCENARIOS_BASE_DIR + \"/restarting_full_flow_on_expiration_and_no_refresh_token.json\";\n\n  @Test\n  public void shouldCacheAccessTokenAfterConnecting() throws SFException, SnowflakeSQLException {\n    importMappingFromResources(CACHING_TOKENS_AFTER_CONNECTING_SCENARIO_MAPPINGS);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      SFLoginInput loginInput = createLoginInputStub();\n      SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n      captureAndAssertSavedTokenValues(\n          credentialManagerMockedStatic, \"access-token-123\", \"refresh-token-123\");\n    }\n  }\n\n  @Test\n  public void shouldCacheAccessTokenAndDPoPKeyAfterConnecting()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(CACHING_TOKENS_AND_DPOP_KEY_AFTER_CONNECTING_SCENARIO_MAPPINGS);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      SFLoginInput loginInput = createLoginInputStubWithDPoPEnabled();\n      SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n      captureAndAssertSavedTokenValuesAndDPoPKey(\n          credentialManagerMockedStatic, \"access-token-123\", \"refresh-token-123\");\n    }\n  }\n\n  @Test\n  public void shouldReuseCachedAccessTokenWhenConnecting()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(REUSING_CACHED_ACCESS_TOKEN_SCENARIO_MAPPINGS);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(\n          credentialManagerMockedStatic, \"reused-access-token-123\", \"reused-refresh-token-123\");\n      SFLoginInput loginInput = createLoginInputStub();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n      assertEquals(\"reused-access-token-123\", loginOutput.getOauthAccessToken());\n      assertEquals(\"reused-refresh-token-123\", loginOutput.getOauthRefreshToken());\n    }\n  }\n\n  @Test\n  public void shouldNotCacheTokensForClientCredentialsFlow()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(NOT_CACHING_TOKENS_FOR_CLIENT_CREDENTIALS_FLOW);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      SFLoginInput loginInput = createLoginInputStub();\n      loginInput.setAuthenticator(AuthenticatorType.OAUTH_CLIENT_CREDENTIALS.name());\n      loginInput.setOriginalAuthenticator(AuthenticatorType.OAUTH_CLIENT_CREDENTIALS.name());\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      Assertions.assertNotNull(loginOutput);\n      Assertions.assertEquals(\"session token\", loginOutput.getSessionToken());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.fillCachedOAuthAccessToken(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.fillCachedOAuthRefreshToken(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.fillCachedDPoPBundledAccessToken(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.writeOAuthAccessToken(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.writeOAuthRefreshToken(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.writeDPoPBundledAccessToken(loginInput), never());\n    }\n  }\n\n  @Test\n  public void shouldRefreshExpiredAccessTokenAndConnectSuccessfully()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(\n          credentialManagerMockedStatic, \"expired-access-token-123\", \"some-refresh-token-123\");\n      SFLoginInput loginInput = createLoginInputStub();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInput), never());\n\n      assertEquals(\"new-refreshed-access-token-123\", loginOutput.getOauthAccessToken());\n      captureAndAssertSavedTokenValues(\n          credentialManagerMockedStatic,\n          \"new-refreshed-access-token-123\",\n          \"some-refresh-token-123\");\n    }\n  }\n\n  @Test\n  public void shouldRefreshExpiredAccessTokenWithDPoPAndConnectSuccessfully()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS_DPOP);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(credentialManagerMockedStatic, null, \"some-refresh-token-123\");\n      mockLoadingDPoPPublicKeyFromCache(credentialManagerMockedStatic, \"expired-access-token-123\");\n      SFLoginInput loginInput = createLoginInputStubWithDPoPEnabled();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteDPoPBundledAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInput), never());\n\n      assertEquals(\"new-refreshed-access-token-123\", loginOutput.getOauthAccessToken());\n      captureAndAssertSavedTokenValuesAndDPoPKey(\n          credentialManagerMockedStatic,\n          \"new-refreshed-access-token-123\",\n          \"some-refresh-token-123\");\n    }\n  }\n\n  @Test\n  public void shouldRefreshExpiredAccessTokenWithDPoPNonceErrorAndConnectSuccessfully()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS_DPOP_NONCE_ERROR);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(credentialManagerMockedStatic, null, \"some-refresh-token-123\");\n      mockLoadingDPoPPublicKeyFromCache(credentialManagerMockedStatic, \"expired-access-token-123\");\n      SFLoginInput loginInput = createLoginInputStubWithDPoPEnabled();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteDPoPBundledAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInput), never());\n\n      assertEquals(\"new-refreshed-access-token-123\", loginOutput.getOauthAccessToken());\n      captureAndAssertSavedTokenValuesAndDPoPKey(\n          credentialManagerMockedStatic,\n          \"new-refreshed-access-token-123\",\n          \"some-refresh-token-123\");\n    }\n  }\n\n  @Test\n  public void shouldCacheRefreshedAccessTokenAndNewRefreshToken()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(\n        CACHING_REFRESHED_ACCESS_TOKEN_AND_NEW_REFRESH_TOKEN_SCENARIO_MAPPINGS);\n\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(\n          credentialManagerMockedStatic, \"expired-access-token-123\", \"some-refresh-token-123\");\n      SFLoginInput loginInput = createLoginInputStub();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), times(1));\n      assertEquals(\"new-refreshed-access-token-123\", loginOutput.getOauthAccessToken());\n      captureAndAssertSavedTokenValues(\n          credentialManagerMockedStatic, \"new-refreshed-access-token-123\", \"new-refresh-token-123\");\n    }\n  }\n\n  @Test\n  public void shouldRestartFullFlowOnAccessTokenExpirationAndErrorWhenRefreshing()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(RESTARTING_FULL_FLOW_ON_EXPIRATION_AND_ERROR_WHEN_REFRESHING);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(\n          credentialManagerMockedStatic, \"expired-access-token-123\", \"some-refresh-token-123\");\n      SFLoginInput loginInput = createLoginInputStub();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInput), times(1));\n      assertEquals(\"newly-obtained-access-token-123\", loginOutput.getOauthAccessToken());\n      captureAndAssertSavedTokenValues(\n          credentialManagerMockedStatic,\n          \"newly-obtained-access-token-123\",\n          \"newly-obtained-refresh-token\");\n    }\n  }\n\n  @Test\n  public void shouldRestartFullFlowOnAccessTokenExpirationAndNoRefreshToken()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(RESTARTING_FULL_FLOW_ON_EXPIRATION_AND_NO_REFRESH_TOKEN);\n\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      mockLoadingTokensFromCache(credentialManagerMockedStatic, \"expired-access-token-123\", null);\n      SFLoginInput loginInput = createLoginInputStub();\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), times(1));\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthRefreshTokenCacheEntry(loginInput), never());\n      assertEquals(\"newly-obtained-access-token-123\", loginOutput.getOauthAccessToken());\n      captureAndAssertSavedTokenValues(\n          credentialManagerMockedStatic, \"newly-obtained-access-token-123\", null);\n    }\n  }\n\n  @Test\n  public void shouldNotDeleteCachedTokensWhenCachingIsDisabled()\n      throws SFException, SnowflakeSQLException {\n    importMappingFromResources(REFRESHING_EXPIRED_ACCESS_TOKEN_SCENARIO_MAPPINGS);\n    try (MockedStatic<CredentialManager> credentialManagerMockedStatic =\n        mockStatic(CredentialManager.class)) {\n      SFLoginInput loginInput = createLoginInputStubWithCachingDisabled();\n      loginInput.setOauthAccessToken(\"expired-access-token-123\");\n      loginInput.setOauthRefreshToken(\"some-refresh-token-123\");\n      SFLoginOutput loginOutput = SessionUtil.openSession(loginInput, new HashMap<>(), \"INFO\");\n\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteOAuthAccessTokenCacheEntry(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.deleteDPoPBundledAccessTokenCacheEntry(loginInput), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.writeOAuthAccessToken(any(SFLoginInput.class)), never());\n      credentialManagerMockedStatic.verify(\n          () -> CredentialManager.writeOAuthRefreshToken(any(SFLoginInput.class)), never());\n\n      assertEquals(\"new-refreshed-access-token-123\", loginOutput.getOauthAccessToken());\n    }\n  }\n\n  private SFLoginInput createLoginInputStub() {\n    SFLoginInput input = new SFLoginInput();\n    input.setAuthenticator(AuthenticatorType.OAUTH_AUTHORIZATION_CODE.name());\n    input.setOriginalAuthenticator(AuthenticatorType.OAUTH_AUTHORIZATION_CODE.name());\n    input.setServerUrl(String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort));\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setAppVersion(\"MOCK_APP_VERSION\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setBrowserResponseTimeout(Duration.ofSeconds(5));\n    input.setBrowserHandler(\n        new OAuthAuthorizationCodeFlowLatestIT.WiremockProxyRequestBrowserHandler());\n    input.setLoginTimeout(1000);\n    input.setSessionParameters(Collections.singletonMap(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true));\n    input.setOauthLoginInput(\n        new SFOauthLoginInput(\n            \"123\",\n            \"123\",\n            null,\n            String.format(\"http://%s:%d/oauth/authorize\", WIREMOCK_HOST, wiremockHttpPort),\n            String.format(\"http://%s:%d/oauth/token-request\", WIREMOCK_HOST, wiremockHttpPort),\n            \"session:role:ANALYST\"));\n    return input;\n  }\n\n  private SFLoginInput createLoginInputStubWithDPoPEnabled() {\n    SFLoginInput input = createLoginInputStub();\n    input.setDPoPEnabled(true);\n    return input;\n  }\n\n  private SFLoginInput createLoginInputStubWithCachingDisabled() {\n    SFLoginInput input = new SFLoginInput();\n    input.setAuthenticator(AuthenticatorType.OAUTH_AUTHORIZATION_CODE.name());\n    input.setOriginalAuthenticator(AuthenticatorType.OAUTH_AUTHORIZATION_CODE.name());\n    input.setServerUrl(String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort));\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setAppVersion(\"MOCK_APP_VERSION\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setBrowserResponseTimeout(Duration.ofSeconds(5));\n    input.setBrowserHandler(\n        new OAuthAuthorizationCodeFlowLatestIT.WiremockProxyRequestBrowserHandler());\n    input.setLoginTimeout(1000);\n    Map<String, Object> sessionParams = new HashMap<>();\n    sessionParams.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", false);\n    input.setSessionParameters(sessionParams);\n    input.setOauthLoginInput(\n        new SFOauthLoginInput(\n            \"123\",\n            \"123\",\n            null,\n            String.format(\"http://%s:%d/oauth/authorize\", WIREMOCK_HOST, wiremockHttpPort),\n            String.format(\"http://%s:%d/oauth/token-request\", WIREMOCK_HOST, wiremockHttpPort),\n            \"session:role:ANALYST\"));\n    return input;\n  }\n\n  private static void mockLoadingTokensFromCache(\n      MockedStatic<CredentialManager> credentialManagerMock,\n      String oauthAccessToken,\n      String oauthRefreshToken) {\n    Answer<Object> fillCachedOAuthAccessTokenInvocation =\n        invocation -> {\n          ((SFLoginInput) invocation.getArguments()[0]).setOauthAccessToken(oauthAccessToken);\n          return null;\n        };\n    Answer<Object> fillCachedOAuthRefreshTokenInvocation =\n        invocation -> {\n          ((SFLoginInput) invocation.getArguments()[0]).setOauthRefreshToken(oauthRefreshToken);\n          return null;\n        };\n    credentialManagerMock\n        .when(() -> CredentialManager.fillCachedOAuthAccessToken(any(SFLoginInput.class)))\n        .then(fillCachedOAuthAccessTokenInvocation);\n    credentialManagerMock\n        .when(() -> CredentialManager.fillCachedOAuthRefreshToken(any(SFLoginInput.class)))\n        .then(fillCachedOAuthRefreshTokenInvocation);\n  }\n\n  private static void mockLoadingDPoPPublicKeyFromCache(\n      MockedStatic<CredentialManager> credentialManagerMock, String oauthAccessToken) {\n    Answer<Object> fillCachedDPoPPublicKeyInvocation =\n        invocation -> {\n          ((SFLoginInput) invocation.getArguments()[0]).setOauthAccessToken(oauthAccessToken);\n          ((SFLoginInput) invocation.getArguments()[0]).setDPoPPublicKey(MOCK_DPOP_PUBLIC_KEY);\n          return null;\n        };\n    credentialManagerMock\n        .when(() -> CredentialManager.fillCachedDPoPBundledAccessToken(any(SFLoginInput.class)))\n        .then(fillCachedDPoPPublicKeyInvocation);\n  }\n\n  private static void captureAndAssertSavedTokenValues(\n      MockedStatic<CredentialManager> credentialManagerMock,\n      String expectedAccessToken,\n      String expectedRefreshToken) {\n    ArgumentCaptor<SFLoginInput> accessTokenInputCaptor =\n        ArgumentCaptor.forClass(SFLoginInput.class);\n    credentialManagerMock.verify(\n        () -> CredentialManager.writeOAuthAccessToken(accessTokenInputCaptor.capture()));\n    assertEquals(expectedAccessToken, accessTokenInputCaptor.getValue().getOauthAccessToken());\n\n    if (expectedRefreshToken != null) {\n      ArgumentCaptor<SFLoginInput> refreshTokenInputCaptor =\n          ArgumentCaptor.forClass(SFLoginInput.class);\n      credentialManagerMock.verify(\n          () -> CredentialManager.writeOAuthRefreshToken(refreshTokenInputCaptor.capture()));\n      assertEquals(expectedRefreshToken, refreshTokenInputCaptor.getValue().getOauthRefreshToken());\n    } else {\n      credentialManagerMock.verify(\n          () -> CredentialManager.writeOAuthRefreshToken(any(SFLoginInput.class)), never());\n    }\n  }\n\n  private static void captureAndAssertSavedTokenValuesAndDPoPKey(\n      MockedStatic<CredentialManager> credentialManagerMock,\n      String expectedAccessToken,\n      String expectedRefreshToken) {\n    if (expectedRefreshToken != null) {\n      ArgumentCaptor<SFLoginInput> refreshTokenInputCaptor =\n          ArgumentCaptor.forClass(SFLoginInput.class);\n      credentialManagerMock.verify(\n          () -> CredentialManager.writeOAuthRefreshToken(refreshTokenInputCaptor.capture()));\n      assertEquals(expectedRefreshToken, refreshTokenInputCaptor.getValue().getOauthRefreshToken());\n    }\n    ArgumentCaptor<SFLoginInput> dpopBundledAccessTokenInputCaptor =\n        ArgumentCaptor.forClass(SFLoginInput.class);\n    credentialManagerMock.verify(\n        () ->\n            CredentialManager.writeDPoPBundledAccessToken(\n                dpopBundledAccessTokenInputCaptor.capture()));\n    assertEquals(\n        expectedAccessToken, dpopBundledAccessTokenInputCaptor.getValue().getOauthAccessToken());\n    assertNotNull(dpopBundledAccessTokenInputCaptor.getValue().getDPoPPublicKey());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/OCSPCacheServerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class OCSPCacheServerTest {\n\n  static class URLProvider implements ArgumentsProvider {\n\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(\n              \"bla-12345.global.snowflakecomputing.com\",\n              \"https://ocspssd-12345.global.snowflakecomputing.com/ocsp/fetch\",\n              \"https://ocspssd-12345.global.snowflakecomputing.com/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.global.snowflakecomputing.cn\",\n              \"https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/fetch\",\n              \"https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.global.snowflakecomputing.xyz\",\n              \"https://ocspssd-12345.global.snowflakecomputing.xyz/ocsp/fetch\",\n              \"https://ocspssd-12345.global.snowflakecomputing.xyz/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.GLOBAL.snowflakecomputing.xyz\",\n              \"https://ocspssd-12345.GLOBAL.snowflakecomputing.xyz/ocsp/fetch\",\n              \"https://ocspssd-12345.GLOBAL.snowflakecomputing.xyz/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.snowflakecomputing.com\",\n              \"https://ocspssd.snowflakecomputing.com/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.com/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.snowflakecomputing.cn\",\n              \"https://ocspssd.snowflakecomputing.cn/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.cn/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.snowflakecomputing.xyz\",\n              \"https://ocspssd.snowflakecomputing.xyz/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.xyz/ocsp/retry\"),\n          Arguments.of(\n              \"bla-12345.SNOWFLAKEcomputing.xyz\",\n              \"https://ocspssd.SNOWFLAKEcomputing.xyz/ocsp/fetch\",\n              \"https://ocspssd.SNOWFLAKEcomputing.xyz/ocsp/retry\"),\n          Arguments.of(\n              \"s3.amazoncomaws.com\",\n              \"https://ocspssd.snowflakecomputing.com/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.com/ocsp/retry\"),\n          Arguments.of(\n              \"s3.amazoncomaws.COM\",\n              \"https://ocspssd.snowflakecomputing.COM/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.COM/ocsp/retry\"),\n          Arguments.of(\n              \"s3.amazoncomaws.com.cn\",\n              \"https://ocspssd.snowflakecomputing.cn/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.cn/ocsp/retry\"),\n          Arguments.of(\n              \"S3.AMAZONCOMAWS.COM.CN\",\n              \"https://ocspssd.snowflakecomputing.CN/ocsp/fetch\",\n              \"https://ocspssd.snowflakecomputing.CN/ocsp/retry\"));\n    }\n  }\n\n  @ParameterizedTest(name = \"For host {0} cache server fetch url should be {1} and retry url {2}\")\n  @ArgumentsSource(URLProvider.class)\n  public void shouldChooseOcspCacheServerUrls(\n      String host, String expectedFetchUrl, String expectedRetryUrl) {\n    SFTrustManager.OCSPCacheServer ocspCacheServer = new SFTrustManager.OCSPCacheServer();\n    ocspCacheServer.resetOCSPResponseCacheServer(host);\n\n    assertEquals(expectedFetchUrl, ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER);\n    assertEquals(expectedRetryUrl, ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/ObjectMapperTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.MapperFeature;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializationFeature;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.SQLException;\nimport java.util.Base64;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Stream;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\nimport org.mockito.Mockito;\n\npublic class ObjectMapperTest {\n  private static final int jacksonDefaultMaxStringLength = 20_000_000;\n  static String originalLogger;\n\n  static class DataProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(16 * 1024 * 1024, jacksonDefaultMaxStringLength),\n          Arguments.of(16 * 1024 * 1024, 23_000_000),\n          Arguments.of(32 * 1024 * 1024, 45_000_000),\n          Arguments.of(64 * 1024 * 1024, 90_000_000),\n          Arguments.of(128 * 1024 * 1024, 180_000_000));\n    }\n  }\n\n  @BeforeAll\n  public static void setProperty() {\n    originalLogger = System.getProperty(\"net.snowflake.jdbc.loggerImpl\");\n    System.setProperty(\"net.snowflake.jdbc.loggerImpl\", \"net.snowflake.client.log.JDK14Logger\");\n  }\n\n  @AfterAll\n  public static void clearProperty() {\n    if (originalLogger != null) {\n      System.setProperty(\"net.snowflake.jdbc.loggerImpl\", originalLogger);\n    } else {\n      System.clearProperty(\"net.snowflake.jdbc.loggerImpl\");\n    }\n    System.clearProperty(ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM);\n  }\n\n  private static void setJacksonDefaultMaxStringLength(int maxJsonStringLength) {\n    System.setProperty(\n        ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM, Integer.toString(maxJsonStringLength));\n  }\n\n  @Test\n  public void testInvalidMaxJsonStringLength() throws SQLException {\n    System.setProperty(ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM, \"abc\");\n    // calling getObjectMapper() should log the exception but not throw\n    // default maxJsonStringLength value will be used\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    int stringLengthInMapper = mapper.getFactory().streamReadConstraints().getMaxStringLength();\n    assertEquals(ObjectMapperFactory.DEFAULT_MAX_JSON_STRING_LEN, stringLengthInMapper);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testObjectMapperWithLargeJsonString(int lobSizeInBytes, int maxJsonStringLength) {\n    setJacksonDefaultMaxStringLength(maxJsonStringLength);\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    try {\n      JsonNode jsonNode = mapper.readTree(generateBase64EncodedJsonString(lobSizeInBytes));\n      assertNotNull(jsonNode);\n    } catch (Exception e) {\n      // exception is expected when jackson's default maxStringLength value is used while retrieving\n      // 16M string data\n      assertEquals(jacksonDefaultMaxStringLength, maxJsonStringLength);\n    }\n  }\n\n  // -------------------------------------------------------------------------\n  // getObjectMapper() -- config flag assertions\n  // -------------------------------------------------------------------------\n\n  @Test\n  public void testGetObjectMapper_enablesUseBigDecimalForFloats() {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    assertTrue(mapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS));\n  }\n\n  @Test\n  public void testGetObjectMapper_disablesAccessModifierOverrides() {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    assertFalse(mapper.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));\n    assertFalse(mapper.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS));\n  }\n\n  @Test\n  public void testGetObjectMapper_appliesDefaultMaxStringLength() {\n    System.clearProperty(ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM);\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    assertEquals(\n        ObjectMapperFactory.DEFAULT_MAX_JSON_STRING_LEN,\n        mapper.getFactory().streamReadConstraints().getMaxStringLength());\n  }\n\n  // -------------------------------------------------------------------------\n  // getObjectMapperForSession() -- coverage of the session-aware path\n  // -------------------------------------------------------------------------\n\n  @Test\n  public void testGetObjectMapperForSession_withDateFormat_serializesDate() throws Exception {\n    ObjectMapper mapper = objectMapperForSession(\"YYYY-MM-DD\");\n    assertEquals(\n        \"\\\"2025-03-09\\\"\", mapper.writeValueAsString(new Date(2025 - 1900, Calendar.MARCH, 9)));\n  }\n\n  @Test\n  public void testGetObjectMapperForSession_withDateFormat_deserializesDate() throws Exception {\n    ObjectMapper mapper = objectMapperForSession(\"YYYY-MM-DD\");\n    assertEquals(\n        new Date(2025 - 1900, Calendar.MARCH, 9), mapper.readValue(\"\\\"2025-03-09\\\"\", Date.class));\n  }\n\n  @Test\n  public void testGetObjectMapperForSession_withDateTimeFormat_serializesDate() throws Exception {\n    ObjectMapper mapper = objectMapperForSession(\"YYYY-MM-DD HH24:MI:SS\");\n    assertEquals(\n        \"\\\"2025-03-09 14:30:00\\\"\",\n        mapper.writeValueAsString(new Date(2025 - 1900, Calendar.MARCH, 9, 14, 30, 0)));\n  }\n\n  @Test\n  public void testGetObjectMapperForSession_nullSession_returnsBaseMapper() {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapperForSession(null);\n    assertNotNull(mapper);\n    assertTrue(mapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS));\n  }\n\n  @Test\n  public void testGetObjectMapperForSession_nullParameters_returnsBaseMapper() {\n    SFBaseSession session = Mockito.mock(SFBaseSession.class);\n    Mockito.when(session.getCommonParameters()).thenReturn(null);\n\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapperForSession(session);\n    assertNotNull(mapper);\n    assertTrue(mapper.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS));\n  }\n\n  @Test\n  public void testGetObjectMapperForSession_missingDateOutputFormat_keepsTimestampsEnabled() {\n    SFBaseSession session = Mockito.mock(SFBaseSession.class);\n    Mockito.when(session.getCommonParameters()).thenReturn(new HashMap<>());\n\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapperForSession(session);\n    assertTrue(mapper.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS));\n  }\n\n  private static ObjectMapper objectMapperForSession(String dateOutputFormat) {\n    SFBaseSession session = Mockito.mock(SFBaseSession.class);\n    Map<String, Object> params = new HashMap<>();\n    params.put(\"DATE_OUTPUT_FORMAT\", dateOutputFormat);\n    Mockito.when(session.getCommonParameters()).thenReturn(params);\n    return ObjectMapperFactory.getObjectMapperForSession(session);\n  }\n\n  private String generateBase64EncodedJsonString(int numChar) {\n    StringBuilder jsonStr = new StringBuilder();\n    String largeStr = SnowflakeUtil.randomAlphaNumeric(numChar);\n\n    // encode the string and put it into a JSON formatted string\n    jsonStr.append(\"[\\\"\").append(encodeStringToBase64(largeStr)).append(\"\\\"]\");\n    return jsonStr.toString();\n  }\n\n  private String encodeStringToBase64(String stringToBeEncoded) {\n    return Base64.getEncoder().encodeToString(stringToBeEncoded.getBytes(StandardCharsets.UTF_8));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/PrivateLinkDetectorTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class PrivateLinkDetectorTest {\n  static class DataProvider implements ArgumentsProvider {\n\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(\"snowhouse.snowflakecomputing.com\", false),\n          Arguments.of(\"snowhouse.privatelink.snowflakecomputing.com\", true),\n          Arguments.of(\"snowhouse.PRIVATELINK.snowflakecomputing.com\", true),\n          Arguments.of(\"snowhouse.snowflakecomputing.cn\", false),\n          Arguments.of(\"snowhouse.privatelink.snowflakecomputing.cn\", true),\n          Arguments.of(\"snowhouse.PRIVATELINK.snowflakecomputing.cn\", true),\n          Arguments.of(\"snowhouse.snowflakecomputing.xyz\", false),\n          Arguments.of(\"snowhouse.privatelink.snowflakecomputing.xyz\", true),\n          Arguments.of(\"snowhouse.PRIVATELINK.snowflakecomputing.xyz\", true));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void shouldDetectPrivateLinkHost(String host, boolean expectedToBePrivateLink) {\n    assertEquals(\n        expectedToBePrivateLink,\n        PrivateLinkDetector.isPrivateLink(host),\n        String.format(\"Expecting %s to be private link: %s\", host, expectedToBePrivateLink));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/ProgrammaticAccessTokenAuthFlowLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport java.util.HashMap;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class ProgrammaticAccessTokenAuthFlowLatestIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/pat\";\n  private static final String SUCCESSFUL_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow.json\";\n  private static final String INVALID_TOKEN_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/invalid_pat_token.json\";\n\n  @Test\n  public void successfulFlowScenarioPatAsToken() throws SFException, SnowflakeSQLException {\n    importMappingFromResources(SUCCESSFUL_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInputWithPatAsToken = createLoginInputStub();\n    SFLoginOutput loginOutput =\n        SessionUtil.newSession(loginInputWithPatAsToken, new HashMap<>(), \"INFO\");\n    assertSuccessfulLoginOutput(loginOutput);\n  }\n\n  @Test\n  public void invalidTokenScenario() {\n    importMappingFromResources(INVALID_TOKEN_SCENARIO_MAPPINGS);\n    SnowflakeSQLException e =\n        Assertions.assertThrows(\n            SnowflakeSQLException.class,\n            () -> SessionUtil.newSession(createLoginInputStub(), new HashMap<>(), \"INFO\"));\n    Assertions.assertEquals(\"Programmatic access token is invalid.\", e.getMessage());\n  }\n\n  private void assertSuccessfulLoginOutput(SFLoginOutput loginOutput) {\n    Assertions.assertNotNull(loginOutput);\n    Assertions.assertEquals(\"session token\", loginOutput.getSessionToken());\n    Assertions.assertEquals(\"master token\", loginOutput.getMasterToken());\n    Assertions.assertEquals(14400, loginOutput.getMasterTokenValidityInSeconds());\n    Assertions.assertEquals(\"8.48.0\", loginOutput.getDatabaseVersion());\n    Assertions.assertEquals(\"TEST_DHEYMAN\", loginOutput.getSessionDatabase());\n    Assertions.assertEquals(\"TEST_JDBC\", loginOutput.getSessionSchema());\n    Assertions.assertEquals(\"ANALYST\", loginOutput.getSessionRole());\n    Assertions.assertEquals(\"TEST_XSMALL\", loginOutput.getSessionWarehouse());\n    Assertions.assertEquals(\"1172562260498\", loginOutput.getSessionId());\n    Assertions.assertEquals(1, loginOutput.getCommonParams().size());\n    Assertions.assertEquals(4, loginOutput.getCommonParams().get(\"CLIENT_PREFETCH_THREADS\"));\n  }\n\n  private SFLoginInput createLoginInputStub() {\n    SFLoginInput input = new SFLoginInput();\n    input.setAuthenticator(AuthenticatorType.PROGRAMMATIC_ACCESS_TOKEN.name());\n    input.setServerUrl(String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort));\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setAppVersion(\"MOCK_APP_VERSION\");\n    input.setToken(\"MOCK_TOKEN\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(1000);\n    input.setSessionParameters(new HashMap<>());\n    return input;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/QueryContextCacheTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport org.junit.jupiter.api.Test;\n\npublic class QueryContextCacheTest {\n  private QueryContextCache qcc = null;\n  private long BASE_READ_TIMESTAMP = 1668727958;\n  private String CONTEXT = \"Some query context\";\n  private long BASE_ID = 0;\n  private long BASE_PRIORITY = 0;\n\n  private int MAX_CAPACITY = 5;\n  private long[] expectedIDs;\n  private long[] expectedReadTimestamp;\n  private long[] expectedPriority;\n\n  private void initCache() {\n    qcc = new QueryContextCache(MAX_CAPACITY);\n  }\n\n  private void initCacheWithData() {\n    initCacheWithDataWithContext(CONTEXT);\n  }\n\n  private void initCacheWithDataWithContext(String context) {\n    qcc = new QueryContextCache(MAX_CAPACITY);\n    expectedIDs = new long[MAX_CAPACITY];\n    expectedReadTimestamp = new long[MAX_CAPACITY];\n    expectedPriority = new long[MAX_CAPACITY];\n    for (int i = 0; i < MAX_CAPACITY; i++) {\n      expectedIDs[i] = BASE_ID + i;\n      expectedReadTimestamp[i] = BASE_READ_TIMESTAMP + i;\n      expectedPriority[i] = BASE_PRIORITY + i;\n      qcc.merge(expectedIDs[i], expectedReadTimestamp[i], expectedPriority[i], context);\n    }\n    qcc.syncPriorityMap();\n  }\n\n  private void initCacheWithDataInRandomOrder() {\n    qcc = new QueryContextCache(MAX_CAPACITY);\n    expectedIDs = new long[MAX_CAPACITY];\n    expectedReadTimestamp = new long[MAX_CAPACITY];\n    expectedPriority = new long[MAX_CAPACITY];\n    for (int i = 0; i < MAX_CAPACITY; i++) {\n      expectedIDs[i] = BASE_ID + i;\n      expectedReadTimestamp[i] = BASE_READ_TIMESTAMP + i;\n      expectedPriority[i] = BASE_PRIORITY + i;\n    }\n\n    qcc.merge(expectedIDs[3], expectedReadTimestamp[3], expectedPriority[3], CONTEXT);\n    qcc.merge(expectedIDs[2], expectedReadTimestamp[2], expectedPriority[2], CONTEXT);\n    qcc.merge(expectedIDs[4], expectedReadTimestamp[4], expectedPriority[4], CONTEXT);\n    qcc.merge(expectedIDs[0], expectedReadTimestamp[0], expectedPriority[0], CONTEXT);\n    qcc.merge(expectedIDs[1], expectedReadTimestamp[1], expectedPriority[1], CONTEXT);\n    qcc.syncPriorityMap();\n  }\n\n  /** Test for empty cache */\n  @Test\n  public void testIsEmpty() throws Exception {\n    initCache();\n    assertThat(\"Empty cache\", qcc.getSize() == 0);\n  }\n\n  @Test\n  public void testWithSomeData() throws Exception {\n    initCacheWithData();\n    // Compare elements\n    assertCacheData();\n  }\n\n  @Test\n  public void testWithSomeDataInRandomOrder() throws Exception {\n    initCacheWithDataInRandomOrder();\n    // Compare elements\n    assertCacheData();\n  }\n\n  @Test\n  public void testMoreThanCapacity() throws Exception {\n    initCacheWithData();\n\n    // Add one more element at the end\n    int i = MAX_CAPACITY;\n    qcc.merge(BASE_ID + i, BASE_READ_TIMESTAMP + i, BASE_PRIORITY + i, CONTEXT);\n    qcc.syncPriorityMap();\n    qcc.checkCacheCapacity();\n\n    // Compare elements\n    assertCacheData();\n  }\n\n  @Test\n  public void testUpdateTimestamp() throws Exception {\n    initCacheWithData();\n\n    // Add one more element with new TS with existing id\n    int updatedID = 1;\n    expectedReadTimestamp[updatedID] = BASE_READ_TIMESTAMP + updatedID + 10;\n    qcc.merge(\n        BASE_ID + updatedID, expectedReadTimestamp[updatedID], BASE_PRIORITY + updatedID, CONTEXT);\n    qcc.syncPriorityMap();\n    qcc.checkCacheCapacity();\n\n    // Compare elements\n    assertCacheData();\n  }\n\n  @Test\n  public void testUpdatePriority() throws Exception {\n    initCacheWithData();\n\n    // Add one more element with new priority with existing id\n    int updatedID = 3;\n    long updatedPriority = BASE_PRIORITY + updatedID + 7;\n\n    expectedPriority[updatedID] = updatedPriority;\n    qcc.merge(\n        BASE_ID + updatedID, BASE_READ_TIMESTAMP + updatedID, expectedPriority[updatedID], CONTEXT);\n    qcc.syncPriorityMap();\n    qcc.checkCacheCapacity();\n\n    for (int i = updatedID; i < MAX_CAPACITY - 1; i++) {\n      expectedIDs[i] = expectedIDs[i + 1];\n      expectedReadTimestamp[i] = expectedReadTimestamp[i + 1];\n      expectedPriority[i] = expectedPriority[i + 1];\n    }\n\n    expectedIDs[MAX_CAPACITY - 1] = BASE_ID + updatedID;\n    expectedReadTimestamp[MAX_CAPACITY - 1] = BASE_READ_TIMESTAMP + updatedID;\n    expectedPriority[MAX_CAPACITY - 1] = updatedPriority;\n\n    assertCacheData();\n  }\n\n  @Test\n  public void testAddSamePriority() throws Exception {\n    initCacheWithData();\n\n    // Add one more element with same priority\n    int i = MAX_CAPACITY;\n    long UpdatedPriority = BASE_PRIORITY + 1;\n    qcc.merge(BASE_ID + i, BASE_READ_TIMESTAMP + i, UpdatedPriority, CONTEXT);\n    qcc.syncPriorityMap();\n    qcc.checkCacheCapacity();\n    expectedIDs[1] = BASE_ID + i;\n    expectedReadTimestamp[1] = BASE_READ_TIMESTAMP + i;\n\n    // Compare elements\n    assertCacheData();\n  }\n\n  @Test\n  public void testAddSameIDButStaleTimestamp() throws Exception {\n    initCacheWithData();\n\n    // Add one more element with same priority\n    int i = 2;\n    qcc.merge(BASE_ID + i, BASE_READ_TIMESTAMP + i - 10, BASE_PRIORITY + i, CONTEXT);\n    qcc.syncPriorityMap();\n    qcc.checkCacheCapacity();\n\n    // Compare elements\n    assertCacheData();\n  }\n\n  @Test\n  public void testEmptyCacheWithNullData() throws Exception {\n    initCacheWithData();\n\n    qcc.deserializeQueryContextJson(null);\n    assertThat(\"Empty cache\", qcc.getSize() == 0);\n  }\n\n  @Test\n  public void testEmptyCacheWithEmptyResponseData() throws Exception {\n    initCacheWithData();\n\n    qcc.deserializeQueryContextJson(\"\");\n    assertThat(\"Empty cache\", qcc.getSize() == 0);\n  }\n\n  @Test\n  public void testSerializeRequestAndDeserializeResponseData() throws Exception {\n    // Init qcc\n    initCacheWithData();\n    assertCacheData();\n\n    QueryContextDTO requestData = qcc.serializeQueryContextDTO();\n\n    // Clear qcc\n    qcc.clearCache();\n    assertThat(\"Empty cache\", qcc.getSize() == 0);\n\n    qcc.deserializeQueryContextDTO(requestData);\n    assertCacheData();\n  }\n\n  @Test\n  public void testSerializeRequestAndDeserializeResponseDataWithNullContext() throws Exception {\n    // Init qcc\n    initCacheWithDataWithContext(null);\n    assertCacheDataWithContext(null);\n\n    QueryContextDTO requestData = qcc.serializeQueryContextDTO();\n\n    // Clear qcc\n    qcc.clearCache();\n    assertThat(\"Empty cache\", qcc.getSize() == 0);\n\n    qcc.deserializeQueryContextDTO(requestData);\n    assertCacheDataWithContext(null);\n\n    QueryContextCache mockQcc = spy(qcc);\n    mockQcc.deserializeQueryContextDTO(null);\n    verify(mockQcc).clearCache();\n    verify(mockQcc, times(2)).logCacheEntries();\n  }\n\n  private void assertCacheData() {\n    assertCacheDataWithContext(CONTEXT);\n  }\n\n  private void assertCacheDataWithContext(String context) {\n    int size = qcc.getSize();\n    assertThat(\"Non empty cache\", size == MAX_CAPACITY);\n\n    long[] ids = new long[size];\n    long[] readTimestamps = new long[size];\n    long[] priorities = new long[size];\n    String[] contexts = new String[size];\n\n    // Compare elements\n    qcc.getElements(ids, readTimestamps, priorities, contexts);\n    for (int i = 0; i < size; i++) {\n      assertEquals(expectedIDs[i], ids[i]);\n      assertEquals(expectedReadTimestamp[i], readTimestamps[i]);\n      assertEquals(expectedPriority[i], priorities[i]);\n      assertEquals(context, contexts[i]);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/QueryContextEntryDTOTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\nclass QueryContextEntryDTOTest {\n\n  @Mock private OpaqueContextDTO mockContext;\n\n  @BeforeEach\n  void setUp() {\n    MockitoAnnotations.openMocks(this);\n  }\n\n  @Test\n  void testDefaultConstructor() {\n    QueryContextEntryDTO entry = new QueryContextEntryDTO();\n    assertNotNull(entry);\n    assertEquals(0, entry.getId());\n    assertEquals(0, entry.getTimestamp());\n    assertEquals(0, entry.getPriority());\n    assertNull(entry.getContext());\n  }\n\n  @Test\n  void testParameterizedConstructor() {\n    QueryContextEntryDTO entry = new QueryContextEntryDTO(1L, 100L, 10L, mockContext);\n\n    assertEquals(1L, entry.getId());\n    assertEquals(100L, entry.getTimestamp());\n    assertEquals(10L, entry.getPriority());\n    assertEquals(mockContext, entry.getContext());\n  }\n\n  @Test\n  void testSettersAndGetters() {\n    QueryContextEntryDTO entry = new QueryContextEntryDTO();\n\n    entry.setId(2L);\n    entry.setTimestamp(200L);\n    entry.setPriority(20L);\n    entry.setContext(mockContext);\n\n    assertEquals(2L, entry.getId());\n    assertEquals(200L, entry.getTimestamp());\n    assertEquals(20L, entry.getPriority());\n    assertEquals(mockContext, entry.getContext());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFArrowResultSetIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport net.snowflake.client.annotations.DontRunOnThinJar;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.ArrowResultChunk;\nimport net.snowflake.client.internal.jdbc.BaseJDBCWithSharedConnectionIT;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultChunk;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.client.internal.jdbc.telemetry.NoOpTelemetryClient;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.BitVector;\nimport org.apache.arrow.vector.DateDayVector;\nimport org.apache.arrow.vector.DecimalVector;\nimport org.apache.arrow.vector.FieldVector;\nimport org.apache.arrow.vector.Float8Vector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.SmallIntVector;\nimport org.apache.arrow.vector.TinyIntVector;\nimport org.apache.arrow.vector.VarBinaryVector;\nimport org.apache.arrow.vector.VarCharVector;\nimport org.apache.arrow.vector.VectorSchemaRoot;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.dictionary.DictionaryProvider;\nimport org.apache.arrow.vector.ipc.ArrowStreamWriter;\nimport org.apache.arrow.vector.ipc.ArrowWriter;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.ArrowType;\nimport org.apache.arrow.vector.types.pojo.Field;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.apache.arrow.vector.types.pojo.Schema;\nimport org.apache.arrow.vector.util.Text;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n@Tag(TestTags.ARROW)\npublic class SFArrowResultSetIT extends BaseJDBCWithSharedConnectionIT {\n  private Random random = new Random();\n\n  /**\n   * allocator for arrow RootAllocator is shaded so it cannot be overridden when testing thin or fat\n   * jar\n   */\n  protected BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  /** temporary folder to store result files */\n  @TempDir private File tempDir;\n\n  /** Test the case that all results are returned in first chunk */\n  @Test\n  @DontRunOnThinJar\n  public void testNoOfflineData() throws Throwable {\n    List<Field> fieldList = new ArrayList<>();\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"scale\", \"0\");\n    FieldType type = new FieldType(false, Types.MinorType.INT.getType(), null, customFieldMeta);\n    fieldList.add(new Field(\"\", type, null));\n    Schema schema = new Schema(fieldList);\n\n    Object[][] data = generateData(schema, 1000);\n    File file = createArrowFile(\"testNoOfflineData_0_0_0\", schema, data, 10);\n\n    int dataSize = (int) file.length();\n    byte[] dataBytes = new byte[dataSize];\n\n    try (InputStream is = new FileInputStream(file)) {\n      is.read(dataBytes, 0, dataSize);\n    }\n\n    SnowflakeResultSetSerializableV1 resultSetSerializable = new SnowflakeResultSetSerializableV1();\n    resultSetSerializable.setRootAllocator(new RootAllocator(Long.MAX_VALUE));\n    resultSetSerializable.setFirstChunkStringData(Base64.getEncoder().encodeToString(dataBytes));\n    resultSetSerializable.setFirstChunkByteData(dataBytes);\n    resultSetSerializable.setChunkFileCount(0);\n\n    SFArrowResultSet resultSet =\n        new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), false);\n\n    int i = 0;\n    while (resultSet.next()) {\n      int val = resultSet.getInt(1);\n      assertThat(val, equalTo(data[0][i]));\n      i++;\n    }\n\n    // assert that total rowcount is 1000\n    assertThat(i, is(1000));\n  }\n\n  @Test\n  public void testEmptyResultSet() throws Throwable {\n    SnowflakeResultSetSerializableV1 resultSetSerializable = new SnowflakeResultSetSerializableV1();\n    resultSetSerializable.setFirstChunkStringData(\n        Base64.getEncoder().encodeToString(\"\".getBytes(StandardCharsets.UTF_8)));\n    resultSetSerializable.setChunkFileCount(0);\n\n    SFArrowResultSet resultSet =\n        new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), false);\n    assertThat(resultSet.next(), is(false));\n    assertThat(resultSet.isLast(), is(false));\n    assertThat(resultSet.isAfterLast(), is(true));\n\n    resultSetSerializable.setFirstChunkStringData(null);\n    resultSet = new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), false);\n\n    assertThat(resultSet.next(), is(false));\n    assertThat(resultSet.isLast(), is(false));\n    assertThat(resultSet.isAfterLast(), is(true));\n  }\n\n  /** Testing the case that all data comes from chunk downloader */\n  @Test\n  @DontRunOnThinJar\n  public void testOnlyOfflineData() throws Throwable {\n    final int colCount = 2;\n    final int chunkCount = 10;\n\n    // generate data\n    List<Field> fieldList = new ArrayList<>();\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"scale\", \"0\");\n    FieldType type = new FieldType(false, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    for (int i = 0; i < colCount; i++) {\n      fieldList.add(new Field(\"col_\" + i, type, null));\n    }\n    Schema schema = new Schema(fieldList);\n\n    // generate 10 chunk of data\n    List<Object[][]> dataLists = new ArrayList<>();\n    List<File> fileLists = new ArrayList<>();\n    for (int i = 0; i < chunkCount; i++) {\n      Object[][] data = generateData(schema, 500);\n      File file = createArrowFile(\"testOnlyOfflineData_\" + i, schema, data, 10);\n      dataLists.add(data);\n      fileLists.add(file);\n    }\n\n    SnowflakeResultSetSerializableV1 resultSetSerializable = new SnowflakeResultSetSerializableV1();\n    resultSetSerializable.setChunkDownloader(new MockChunkDownloader(fileLists));\n    resultSetSerializable.setChunkFileCount(chunkCount);\n\n    SFArrowResultSet resultSet =\n        new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), false);\n\n    int index = 0;\n    while (resultSet.next()) {\n      for (int i = 0; i < colCount; i++) {\n        int val = resultSet.getInt(i + 1);\n        Integer expectedVal = (Integer) dataLists.get(index / 500)[i][index % 500];\n        assertThat(val, is(expectedVal));\n      }\n      index++;\n    }\n\n    // assert that total rowcount is 5000\n    assertThat(index, is(5000));\n  }\n\n  /** Testing the case that all data comes from chunk downloader */\n  @Test\n  @DontRunOnThinJar\n  public void testFirstResponseAndOfflineData() throws Throwable {\n    final int colCount = 2;\n    final int chunkCount = 10;\n\n    // generate data\n    List<Field> fieldList = new ArrayList<>();\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"scale\", \"0\");\n    FieldType type = new FieldType(false, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    for (int i = 0; i < colCount; i++) {\n      fieldList.add(new Field(\"col_\" + i, type, null));\n    }\n    Schema schema = new Schema(fieldList);\n\n    // generate 10 chunk of data\n    List<Object[][]> dataLists = new ArrayList<>();\n    List<File> fileLists = new ArrayList<>();\n\n    // first chunk set to base64 rowset\n    Object[][] firstChunkData = generateData(schema, 500);\n    File arrowFile = createArrowFile(\"testOnlyOfflineData_0\", schema, firstChunkData, 10);\n\n    dataLists.add(firstChunkData);\n\n    int dataSize = (int) arrowFile.length();\n    byte[] dataBytes = new byte[dataSize];\n\n    try (InputStream is = new FileInputStream(arrowFile)) {\n      is.read(dataBytes, 0, dataSize);\n    }\n\n    SnowflakeResultSetSerializableV1 resultSetSerializable = new SnowflakeResultSetSerializableV1();\n    resultSetSerializable.setFirstChunkStringData(Base64.getEncoder().encodeToString(dataBytes));\n    resultSetSerializable.setFirstChunkByteData(dataBytes);\n    resultSetSerializable.setChunkFileCount(chunkCount);\n    resultSetSerializable.setRootAllocator(new RootAllocator(Long.MAX_VALUE));\n    // build chunk downloader\n    for (int i = 0; i < chunkCount; i++) {\n      Object[][] data = generateData(schema, 500);\n      File file = createArrowFile(\"testOnlyOfflineData_\" + (i + 1), schema, data, 10);\n      dataLists.add(data);\n      fileLists.add(file);\n    }\n    resultSetSerializable.setChunkDownloader(new MockChunkDownloader(fileLists));\n\n    SFArrowResultSet resultSet =\n        new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), false);\n\n    int index = 0;\n    while (resultSet.next()) {\n      for (int i = 0; i < colCount; i++) {\n        int val = resultSet.getInt(i + 1);\n        Integer expectedVal = (Integer) dataLists.get(index / 500)[i][index % 500];\n        assertThat(val, is(expectedVal));\n      }\n      index++;\n    }\n\n    // assert that total rowcount is 5500\n    assertThat(index, is(5500));\n  }\n\n  /** Class to mock chunk downloader. It is just reading data from tmp directory one by one */\n  private class MockChunkDownloader implements ChunkDownloader {\n    private List<File> resultFileNames;\n\n    private int currentFileIndex;\n\n    private RootAllocator rootAllocator = new RootAllocator(Long.MAX_VALUE);\n\n    MockChunkDownloader(List<File> resultFileNames) {\n      this.resultFileNames = resultFileNames;\n      this.currentFileIndex = 0;\n    }\n\n    @Override\n    public SnowflakeResultChunk getNextChunkToConsume() throws SnowflakeSQLException {\n      if (currentFileIndex < resultFileNames.size()) {\n        ArrowResultChunk resultChunk = new ArrowResultChunk(\"\", 0, 0, 0, rootAllocator, null);\n        try (InputStream is = new FileInputStream(resultFileNames.get(currentFileIndex))) {\n          resultChunk.readArrowStream(is);\n\n          currentFileIndex++;\n          return resultChunk;\n        } catch (IOException e) {\n          throw new SnowflakeSQLException(ErrorCode.INTERNAL_ERROR, \"Failed \" + \"to read data\");\n        }\n      } else {\n        return null;\n      }\n    }\n\n    @Override\n    public DownloaderMetrics terminate() {\n      return null;\n    }\n  }\n\n  Object[][] generateData(Schema schema, int rowCount) {\n    Object[][] data = new Object[schema.getFields().size()][rowCount];\n\n    for (int i = 0; i < schema.getFields().size(); i++) {\n      Types.MinorType type = Types.getMinorTypeForArrowType(schema.getFields().get(i).getType());\n\n      switch (type) {\n        case BIT:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = random.nextBoolean();\n            }\n            break;\n          }\n        case INT:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = 0;\n            }\n            break;\n          }\n        case DATEDAY:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = Date.from(Instant.now());\n            }\n            break;\n          }\n        case BIGINT:\n        case DECIMAL:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = 154639183700000l;\n            }\n            break;\n          }\n        case FLOAT8:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = random.nextDouble();\n            }\n            break;\n          }\n        case TINYINT:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = (byte) random.nextInt(1 << 8);\n            }\n            break;\n          }\n        case SMALLINT:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = (short) random.nextInt(1 << 16);\n            }\n            break;\n          }\n        case VARBINARY:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = RandomStringUtils.random(20).getBytes();\n            }\n            break;\n          }\n        case VARCHAR:\n          {\n            for (int j = 0; j < rowCount; j++) {\n              data[i][j] = RandomStringUtils.random(20);\n            }\n            break;\n          }\n          // add other data types as needed later\n      }\n    }\n\n    return data;\n  }\n\n  File createArrowFile(String fileName, Schema schema, Object[][] data, int rowsPerRecordBatch)\n      throws IOException {\n    File file = new File(tempDir, fileName);\n    file.createNewFile();\n    VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator);\n\n    try (FileOutputStream fos = new FileOutputStream(file);\n        ArrowWriter writer =\n            new ArrowStreamWriter(root, new DictionaryProvider.MapDictionaryProvider(), fos)) {\n      writer.start();\n\n      for (int i = 0; i < data[0].length; ) {\n        int rowsToAppend = Math.min(rowsPerRecordBatch, data[0].length - i);\n        root.setRowCount(rowsToAppend);\n\n        for (int j = 0; j < data.length; j++) {\n          FieldVector vector = root.getFieldVectors().get(j);\n\n          switch (vector.getMinorType()) {\n            case BIT:\n              writeBitToField(vector, data[j], i, rowsToAppend);\n              break;\n            case INT:\n              writeIntToField(vector, data[j], i, rowsToAppend);\n              break;\n            case TINYINT:\n              writeTinyIntToField(vector, data[j], i, rowsToAppend);\n              break;\n            case SMALLINT:\n              writeSmallIntToField(vector, data[j], i, rowsToAppend);\n              break;\n            case DATEDAY:\n              writeDateToField(vector, data[j], i, rowsToAppend);\n              break;\n            case BIGINT:\n              writeLongToField(vector, data[j], i, rowsToAppend);\n              break;\n            case FLOAT8:\n              writeDoubleToField(vector, data[j], i, rowsToAppend);\n              break;\n            case VARBINARY:\n              writeBytesToField(vector, data[j], i, rowsToAppend);\n              break;\n            case VARCHAR:\n              writeTextToField(vector, data[j], i, rowsToAppend);\n              break;\n            case DECIMAL:\n              writeDecimalToField(vector, data[j], i, rowsToAppend);\n              break;\n            case STRUCT:\n              writeTimestampStructToField(vector, data[j], data[j + 1], i, rowsToAppend);\n              j++;\n              break;\n          }\n        }\n\n        writer.writeBatch();\n        i += rowsToAppend;\n      }\n    }\n\n    return file;\n  }\n\n  private void writeLongToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    BigIntVector vector = (BigIntVector) fieldVector;\n\n    vector.setInitialCapacity(rowsToAppend);\n    vector.allocateNew();\n    vector.setNull(0);\n    for (int i = 0; i < rowsToAppend; i++) {\n      vector.setSafe(i, 1, (long) data[startIndex + i]);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeBitToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    BitVector vector = (BitVector) fieldVector;\n    vector.setInitialCapacity(rowsToAppend);\n    vector.allocateNew();\n    vector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      int val = (Boolean) data[startIndex + i] == true ? 1 : 0;\n      vector.setSafe(i, 1, val);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeDateToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    DateDayVector datedayVector = (DateDayVector) fieldVector;\n    datedayVector.setInitialCapacity(rowsToAppend);\n    datedayVector.allocateNew();\n    datedayVector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      datedayVector.setSafe(i, 1, (int) ((Date) data[startIndex + i]).getTime() / 1000);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeDecimalToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    DecimalVector datedayVector = (DecimalVector) fieldVector;\n    datedayVector.setInitialCapacity(rowsToAppend);\n    datedayVector.allocateNew();\n    datedayVector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      datedayVector.setSafe(i, (long) data[startIndex + i]);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeDoubleToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    Float8Vector vector = (Float8Vector) fieldVector;\n    vector.setInitialCapacity(rowsToAppend);\n    vector.allocateNew();\n    vector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      vector.setSafe(i, 1, (double) data[startIndex + i]);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeIntToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    IntVector intVector = (IntVector) fieldVector;\n    intVector.setInitialCapacity(rowsToAppend);\n    intVector.allocateNew();\n    intVector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      intVector.setSafe(i, 1, (int) data[startIndex + i]);\n    }\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeSmallIntToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    SmallIntVector intVector = (SmallIntVector) fieldVector;\n    intVector.setInitialCapacity(rowsToAppend);\n    intVector.allocateNew();\n    intVector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      intVector.setSafe(i, 1, (short) data[startIndex + i]);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeTinyIntToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    TinyIntVector vector = (TinyIntVector) fieldVector;\n    vector.setInitialCapacity(rowsToAppend);\n    vector.allocateNew();\n    vector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      vector.setSafe(i, 1, (byte) data[startIndex + i]);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeBytesToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    VarBinaryVector vector = (VarBinaryVector) fieldVector;\n    vector.setInitialCapacity(rowsToAppend);\n    vector.allocateNew();\n    vector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      vector.setSafe(i, (byte[]) data[startIndex + i], 0, ((byte[]) data[startIndex + i]).length);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeTextToField(\n      FieldVector fieldVector, Object[] data, int startIndex, int rowsToAppend) {\n    VarCharVector intVector = (VarCharVector) fieldVector;\n    intVector.setInitialCapacity(rowsToAppend);\n    intVector.allocateNew();\n    intVector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      intVector.setSafe(i, new Text((String) data[startIndex + i]));\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  private void writeTimestampStructToField(\n      FieldVector fieldVector, Object[] data, Object[] data2, int startIndex, int rowsToAppend) {\n    StructVector vector = (StructVector) fieldVector;\n    vector.setInitialCapacity(rowsToAppend);\n    vector.allocateNew();\n    vector.setNull(0);\n    for (int i = 1; i < rowsToAppend; i++) {\n      List<FieldVector> childVectors = vector.getChildrenFromFields();\n      BigIntVector v1 = (BigIntVector) childVectors.get(0);\n      v1.setSafe(i, 1, (long) data[startIndex + i]);\n\n      IntVector v2 = (IntVector) childVectors.get(1);\n      v2.setSafe(i, 1, (int) data2[startIndex + i]);\n    }\n    // how many are set\n    fieldVector.setValueCount(rowsToAppend);\n  }\n\n  /** Test that first chunk containing struct vectors (used for timestamps) can be sorted */\n  @Test\n  @DontRunOnThinJar\n  public void testSortedResultChunkWithStructVectors() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table teststructtimestamp (t1 timestamp_ltz)\");\n      try (ResultSet rs = statement.executeQuery(\"select * from teststructtimestamp\")) {\n        List<SnowflakeResultSetSerializable> resultSetSerializables =\n            ((SnowflakeResultSet) rs).getResultSetSerializables(100 * 1024 * 1024);\n        SnowflakeResultSetSerializableV1 resultSetSerializable =\n            (SnowflakeResultSetSerializableV1) resultSetSerializables.get(0);\n\n        Map<String, String> customFieldMeta = new HashMap<>();\n        customFieldMeta.put(\"logicalType\", \"TIMESTAMP_LTZ\");\n        customFieldMeta.put(\"scale\", \"38\");\n        // test normal date\n        FieldType fieldType =\n            new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n        FieldType fieldType2 =\n            new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n        StructVector structVector = StructVector.empty(\"testListVector\", allocator);\n        List<Field> fieldList = new LinkedList<Field>();\n        Field bigIntField = new Field(\"epoch\", fieldType, null);\n\n        Field intField = new Field(\"fraction\", fieldType2, null);\n\n        fieldList.add(bigIntField);\n        fieldList.add(intField);\n\n        FieldType structFieldType =\n            new FieldType(true, Types.MinorType.STRUCT.getType(), null, customFieldMeta);\n        Field structField = new Field(\"timestamp\", structFieldType, fieldList);\n\n        structVector.initializeChildrenFromFields(fieldList);\n\n        List<Field> fieldListMajor = new LinkedList<Field>();\n        fieldListMajor.add(structField);\n        Schema dataSchema = new Schema(fieldList);\n        Object[][] data = generateData(dataSchema, 1000);\n\n        Schema schema = new Schema(fieldListMajor);\n\n        File file = createArrowFile(\"testTimestamp\", schema, data, 10);\n\n        int dataSize = (int) file.length();\n        byte[] dataBytes = new byte[dataSize];\n\n        try (InputStream is = new FileInputStream(file)) {\n          is.read(dataBytes, 0, dataSize);\n        }\n\n        resultSetSerializable.setRootAllocator(new RootAllocator(Long.MAX_VALUE));\n        resultSetSerializable.setFirstChunkStringData(\n            Base64.getEncoder().encodeToString(dataBytes));\n        resultSetSerializable.setFirstChunkByteData(dataBytes);\n        resultSetSerializable.setChunkFileCount(0);\n\n        SFArrowResultSet resultSet =\n            new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), true);\n\n        for (int i = 0; i < 1000; i++) {\n          resultSet.next();\n        }\n        // We inserted a null row at the beginning so when sorted, the last row should be null\n        assertEquals(null, resultSet.getObject(1));\n        assertFalse(resultSet.next());\n        statement.execute(\"drop table teststructtimestamp;\");\n      }\n    }\n  }\n\n  /** Test that the first chunk can be sorted */\n  @Test\n  @DontRunOnThinJar\n  public void testSortedResultChunk() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace table alltypes (i1 int, d1 date, b1 bigint, f1 float, s1 smallint, t1 tinyint, b2 binary, t2 text, b3 boolean, d2 decimal)\");\n      try (ResultSet rs = statement.executeQuery(\"select * from alltypes\")) {\n        List<SnowflakeResultSetSerializable> resultSetSerializables =\n            ((SnowflakeResultSet) rs).getResultSetSerializables(100 * 1024 * 1024);\n        SnowflakeResultSetSerializableV1 resultSetSerializable =\n            (SnowflakeResultSetSerializableV1) resultSetSerializables.get(0);\n\n        List<Field> fieldList = new ArrayList<>();\n        Map<String, String> customFieldMeta = new HashMap<>();\n        customFieldMeta.put(\"logicalType\", \"FIXED\");\n        customFieldMeta.put(\"scale\", \"0\");\n        FieldType type = new FieldType(false, Types.MinorType.INT.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"DATE\");\n        type = new FieldType(false, Types.MinorType.DATEDAY.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"FIXED\");\n        type = new FieldType(false, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"REAL\");\n        type = new FieldType(false, Types.MinorType.FLOAT8.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"FIXED\");\n        type = new FieldType(false, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"FIXED\");\n        type = new FieldType(false, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"BINARY\");\n        type = new FieldType(false, Types.MinorType.VARBINARY.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"TEXT\");\n        type = new FieldType(false, Types.MinorType.VARCHAR.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"BOOLEAN\");\n        type = new FieldType(false, Types.MinorType.BIT.getType(), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        customFieldMeta.put(\"logicalType\", \"REAL\");\n        type = new FieldType(false, new ArrowType.Decimal(38, 16, 128), null, customFieldMeta);\n        fieldList.add(new Field(\"\", type, null));\n\n        Schema schema = new Schema(fieldList);\n\n        Object[][] data = generateData(schema, 1000);\n        File file = createArrowFile(\"testVectorTypes\", schema, data, 10);\n\n        int dataSize = (int) file.length();\n        byte[] dataBytes = new byte[dataSize];\n\n        try (InputStream is = new FileInputStream(file)) {\n          is.read(dataBytes, 0, dataSize);\n        }\n\n        resultSetSerializable.setRootAllocator(new RootAllocator(Long.MAX_VALUE));\n        resultSetSerializable.setFirstChunkStringData(\n            Base64.getEncoder().encodeToString(dataBytes));\n        resultSetSerializable.setFirstChunkByteData(dataBytes);\n        resultSetSerializable.setChunkFileCount(0);\n\n        SFArrowResultSet resultSet =\n            new SFArrowResultSet(resultSetSerializable, new NoOpTelemetryClient(), true);\n\n        for (int i = 0; i < 1000; i++) {\n          resultSet.next();\n        }\n        // We inserted a null row at the beginning so when sorted, the last row should be null\n        assertEquals(null, resultSet.getObject(1));\n        assertFalse(resultSet.next());\n        statement.execute(\"drop table alltypes;\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFCrlTrustManagerDelegationTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.core.crl.CrlRevocationManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class SFCrlTrustManagerDelegationTest {\n  private X509TrustManager mockTrustManager;\n  private CrlRevocationManager mockRevocationManager;\n  private X509Certificate[] certChain;\n  private String authType;\n\n  @BeforeEach\n  void setUp() {\n    mockTrustManager = mock(X509TrustManager.class);\n    mockRevocationManager = mock(CrlRevocationManager.class);\n    certChain = new X509Certificate[] {mock(X509Certificate.class)};\n    authType = \"RSA\";\n\n    when(mockTrustManager.getAcceptedIssuers()).thenReturn(new X509Certificate[0]);\n  }\n\n  @Test\n  void testCheckClientTrustedDelegatesToTrustManager() throws CertificateException {\n    SFBasicCrlTrustManager trustManager =\n        new SFBasicCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkClientTrusted(certChain, authType);\n\n    verify(mockTrustManager).checkClientTrusted(certChain, authType);\n  }\n\n  @Test\n  void testCheckServerTrustedDelegatesToTrustManager() throws CertificateException {\n    SFBasicCrlTrustManager trustManager =\n        new SFBasicCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkServerTrusted(certChain, authType);\n\n    verify(mockTrustManager).checkServerTrusted(certChain, authType);\n    verify(mockRevocationManager).validateRevocationStatus(certChain, authType);\n  }\n\n  @Test\n  void testGetAcceptedIssuersDelegatesToTrustManager() {\n    SFBasicCrlTrustManager trustManager =\n        new SFBasicCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.getAcceptedIssuers();\n\n    verify(mockTrustManager).getAcceptedIssuers();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFCrlTrustManagerFactoryTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.when;\n\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.MockedStatic;\n\npublic class SFCrlTrustManagerFactoryTest {\n  private HttpClientSettingsKey testKey;\n  private MockedStatic<TrustManagerFactory> mockedTrustManagerFactory;\n\n  @BeforeEach\n  void setUp() {\n    testKey = new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS);\n    testKey.setRevocationCheckMode(CertRevocationCheckMode.ENABLED);\n  }\n\n  @AfterEach\n  void tearDown() {\n    if (mockedTrustManagerFactory != null) {\n      mockedTrustManagerFactory.close();\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(classes = {X509TrustManager.class, X509ExtendedTrustManager.class})\n  void testCreateProperCrlTrustManagerBasedOnJvmProvided(Class<? extends X509TrustManager> clazz)\n      throws Exception {\n    mockTrustManagerFactoryToReturn(clazz);\n\n    X509TrustManager result = SFCrlTrustManagerFactory.createCrlTrustManager(testKey);\n\n    assertInstanceOf(clazz, result);\n  }\n\n  private <T extends X509TrustManager> void mockTrustManagerFactoryToReturn(Class<T> mockClass) {\n    mockedTrustManagerFactory = mockStatic(TrustManagerFactory.class);\n    TrustManagerFactory mockFactory = mock(TrustManagerFactory.class);\n\n    mockedTrustManagerFactory\n        .when(() -> TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()))\n        .thenReturn(mockFactory);\n\n    T trustManagerMock = mock(mockClass);\n    when(trustManagerMock.getAcceptedIssuers()).thenReturn(new X509Certificate[0]);\n    when(mockFactory.getTrustManagers()).thenReturn(new TrustManager[] {trustManagerMock});\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFCrlTrustManagerLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.not;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.AnyOf.anyOf;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.security.Security;\nimport java.sql.Connection;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.Properties;\nimport java.util.stream.Stream;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.crl.CRLCacheConfig;\nimport net.snowflake.client.internal.core.crl.CertRevocationCheckMode;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpGet;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.CORE)\npublic class SFCrlTrustManagerLatestIT extends BaseJDBCTest {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFTrustManagerIT.class);\n  @TempDir static File tmpFolder;\n\n  private static class HostProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(\"storage.googleapis.com\"),\n          Arguments.of(\"ocspssd.us-east-1.snowflakecomputing.com/ocsp/fetch\"),\n          Arguments.of(\"sfcsupport.snowflakecomputing.com\"),\n          Arguments.of(\"sfcsupport.us-east-1.snowflakecomputing.com\"),\n          Arguments.of(\"sfcsupport.eu-central-1.snowflakecomputing.com\"),\n          Arguments.of(\"sfc-dev1-regression.s3.amazonaws.com\"),\n          Arguments.of(\"sfc-ds2-customer-stage.s3.amazonaws.com\"),\n          Arguments.of(\"snowflake.okta.com\"),\n          Arguments.of(\"sfcdev2.blob.core.windows.net\"));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(HostProvider.class)\n  public void testCrl(String host) throws Throwable {\n    System.setProperty(CRLCacheConfig.CRL_RESPONSE_CACHE_DIR, tmpFolder.getAbsolutePath());\n    HttpClientSettingsKey httpClientSettings =\n        new HttpClientSettingsKey(OCSPMode.DISABLE_OCSP_CHECKS);\n    httpClientSettings.setRevocationCheckMode(CertRevocationCheckMode.ENABLED);\n    HttpClient client = HttpUtil.buildHttpClient(httpClientSettings, null, false);\n    accessHost(host, client);\n  }\n\n  private static void accessHost(String host, HttpClient client) throws IOException {\n    HttpGet httpRequest = new HttpGet(String.format(\"https://%s:443/\", host));\n    HttpResponse response = client.execute(httpRequest);\n\n    await()\n        .atMost(Duration.ofSeconds(10))\n        .until(() -> response.getStatusLine().getStatusCode(), not(equalTo(-1)));\n\n    assertThat(\n        String.format(\"response code for %s\", host),\n        response.getStatusLine().getStatusCode(),\n        anyOf(equalTo(200), equalTo(400), equalTo(403), equalTo(404), equalTo(513)));\n  }\n\n  @Test\n  void shouldNotFailWhenSimpleTrustManagerIsUsed() throws Exception {\n    Security.insertProviderAt(new TestSecurityProvider(), 1);\n    HttpUtil.reset();\n    Properties props = new Properties();\n    props.setProperty(\"insecureMode\", \"true\");\n    props.setProperty(\"disableOCSPChecks\", \"true\");\n    props.setProperty(\"CERT_REVOCATION_CHECK_MODE\", \"ENABLED\");\n    try (Connection connection = getConnection(props)) {\n      Statement statement = connection.createStatement();\n      statement.execute(\"SELECT 1\");\n    } finally {\n      Security.removeProvider(TestSecurityProvider.class.getSimpleName());\n      HttpUtil.reset();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFExtendedCrlTrustManagerDelegationTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.net.Socket;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport net.snowflake.client.internal.core.crl.CrlRevocationManager;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class SFExtendedCrlTrustManagerDelegationTest {\n  private X509ExtendedTrustManager mockTrustManager;\n  private CrlRevocationManager mockRevocationManager;\n  private X509Certificate[] certChain;\n  private String authType;\n  private Socket socket;\n  private SSLEngine sslEngine;\n\n  @BeforeEach\n  void setUp() {\n    mockTrustManager = mock(X509ExtendedTrustManager.class);\n    mockRevocationManager = mock(CrlRevocationManager.class);\n    certChain = new X509Certificate[] {mock(X509Certificate.class)};\n    authType = \"RSA\";\n    socket = mock(Socket.class);\n    sslEngine = mock(SSLEngine.class);\n\n    when(mockTrustManager.getAcceptedIssuers()).thenReturn(new X509Certificate[0]);\n  }\n\n  @Test\n  void testCheckClientTrustedDelegatesToTrustManager() throws CertificateException {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkClientTrusted(certChain, authType);\n\n    verify(mockTrustManager).checkClientTrusted(certChain, authType);\n  }\n\n  @Test\n  void testCheckClientTrustedWithSocketDelegatesToTrustManager() throws CertificateException {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkClientTrusted(certChain, authType, socket);\n\n    verify(mockTrustManager).checkClientTrusted(certChain, authType, socket);\n  }\n\n  @Test\n  void testCheckClientTrustedWithSSLEngineDelegatesToTrustManager() throws CertificateException {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkClientTrusted(certChain, authType, sslEngine);\n\n    verify(mockTrustManager).checkClientTrusted(certChain, authType, sslEngine);\n  }\n\n  @Test\n  void testCheckServerTrustedDelegatesToTrustManager() throws CertificateException {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkServerTrusted(certChain, authType);\n\n    verify(mockTrustManager).checkServerTrusted(certChain, authType);\n    verify(mockRevocationManager).validateRevocationStatus(certChain, authType);\n  }\n\n  @Test\n  void testCheckServerTrustedWithSocketDelegatesToTrustManager() throws CertificateException {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkServerTrusted(certChain, authType, socket);\n\n    verify(mockTrustManager).checkServerTrusted(certChain, authType, socket);\n    verify(mockRevocationManager).validateRevocationStatus(certChain, authType);\n  }\n\n  @Test\n  void testCheckServerTrustedWithSSLEngineDelegatesToTrustManager() throws CertificateException {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.checkServerTrusted(certChain, authType, sslEngine);\n\n    verify(mockTrustManager).checkServerTrusted(certChain, authType, sslEngine);\n    verify(mockRevocationManager).validateRevocationStatus(certChain, authType);\n  }\n\n  @Test\n  void testGetAcceptedIssuersDelegatesToTrustManager() {\n    SFExtendedCrlTrustManager trustManager =\n        new SFExtendedCrlTrustManager(mockRevocationManager, mockTrustManager);\n\n    trustManager.getAcceptedIssuers();\n\n    verify(mockTrustManager).getAcceptedIssuers();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFLoginInputTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport org.junit.jupiter.api.Test;\n\npublic class SFLoginInputTest {\n\n  @Test\n  public void testGetHostFromServerUrlWithoutProtocolShouldNotThrow() throws SFException {\n    SFLoginInput sfLoginInput = new SFLoginInput();\n    sfLoginInput.setServerUrl(\"host.com:443\");\n    assertEquals(\"host.com\", sfLoginInput.getHostFromServerUrl());\n  }\n\n  @Test\n  public void testGetHostFromServerUrlWithProtocolShouldNotThrow() throws SFException {\n    SFLoginInput sfLoginInput = new SFLoginInput();\n    sfLoginInput.setServerUrl(\"https://host.com\");\n    assertEquals(\"host.com\", sfLoginInput.getHostFromServerUrl());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFSSLConnectionSocketFactoryTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.security.NoSuchAlgorithmException;\nimport javax.net.ssl.SSLContext;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\npublic class SFSSLConnectionSocketFactoryTest {\n  @BeforeEach\n  public void setUp() {\n    SFSSLConnectionSocketFactory.setMinTlsVersion(\"TLSv1.2\");\n    SFSSLConnectionSocketFactory.setMaxTlsVersion(\"TLSv1.3\");\n  }\n\n  @AfterEach\n  public void tearDown() {\n    SFSSLConnectionSocketFactory.setMinTlsVersion(\"TLSv1.2\");\n    SFSSLConnectionSocketFactory.setMaxTlsVersion(\"TLSv1.3\");\n  }\n\n  @Test\n  public void testDefaultTlsVersions() throws Exception {\n    String[] supportedVersions = getSupportedTlsVersions();\n\n    if (isTls13Available()) {\n      assertEquals(2, supportedVersions.length);\n      assertEquals(\"TLSv1.2\", supportedVersions[0]);\n      assertEquals(\"TLSv1.3\", supportedVersions[1]);\n    } else {\n      assertEquals(1, supportedVersions.length);\n      assertEquals(\"TLSv1.2\", supportedVersions[0]);\n    }\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n    \"TLSv1.2,TLSv1.3,TLSv1.2 TLSv1.3\",\n    \"TLSv1.2,TLSv1.2,TLSv1.2\",\n    \"TLSv1.3,TLSv1.3,TLSv1.3\"\n  })\n  public void testTlsConstraints(String min, String max, String expected) throws Exception {\n    if (isTls13Available()) {\n      SFSSLConnectionSocketFactory.setMinTlsVersion(min);\n      SFSSLConnectionSocketFactory.setMaxTlsVersion(max);\n\n      String versions = String.join(\" \", getSupportedTlsVersions());\n      assertEquals(expected, versions);\n    }\n  }\n\n  @Test\n  public void testMinGreaterThanMax() {\n    SFSSLConnectionSocketFactory.setMinTlsVersion(\"TLSv1.3\");\n    SFSSLConnectionSocketFactory.setMaxTlsVersion(\"TLSv1.2\");\n\n    InvocationTargetException thrown =\n        assertThrows(InvocationTargetException.class, this::getSupportedTlsVersions);\n    assertInstanceOf(IllegalArgumentException.class, thrown.getCause());\n  }\n\n  private String[] getSupportedTlsVersions() throws Exception {\n    Method method = SFSSLConnectionSocketFactory.class.getDeclaredMethod(\"getSupportedTlsVersions\");\n    method.setAccessible(true);\n    return (String[]) method.invoke(null);\n  }\n\n  private boolean isTls13Available() {\n    try {\n      SSLContext.getInstance(\"TLSv1.3\");\n      return true;\n    } catch (NoSuchAlgorithmException e) {\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFSessionPropertyTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemSetEnv;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemUnsetEnv;\nimport static org.hamcrest.CoreMatchers.endsWith;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.CALLS_REAL_METHODS;\nimport static org.mockito.Mockito.mock;\n\nimport java.lang.reflect.Field;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class SFSessionPropertyTest {\n  private static final String SF_ENABLE_WIF_AWS_EXTERNAL_ID = \"SF_ENABLE_WIF_AWS_EXTERNAL_ID\";\n  private String originalEnvValue;\n\n  @BeforeEach\n  public void setUp() {\n    originalEnvValue = System.getenv(SF_ENABLE_WIF_AWS_EXTERNAL_ID);\n    systemUnsetEnv(SF_ENABLE_WIF_AWS_EXTERNAL_ID);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (originalEnvValue != null) {\n      systemSetEnv(SF_ENABLE_WIF_AWS_EXTERNAL_ID, originalEnvValue);\n    } else {\n      systemUnsetEnv(SF_ENABLE_WIF_AWS_EXTERNAL_ID);\n    }\n  }\n\n  @Test\n  public void testCheckApplicationName() throws SFException {\n    String[] validApplicationName = {\"test1234\", \"test_1234\", \"test-1234\", \"test.1234\"};\n\n    String[] invalidApplicationName = {\"1234test\", \"test$A\", \"test<script>\"};\n\n    for (String valid : validApplicationName) {\n      Object value = SFSessionProperty.checkPropertyValue(SFSessionProperty.APPLICATION, valid);\n\n      assertThat((String) value, is(valid));\n    }\n\n    for (String invalid : invalidApplicationName) {\n      SFException e =\n          assertThrows(\n              SFException.class,\n              () -> {\n                SFSessionProperty.checkPropertyValue(SFSessionProperty.APPLICATION, invalid);\n              });\n      assertThat(e.getVendorCode(), is(ErrorCode.INVALID_PARAMETER_VALUE.getMessageCode()));\n    }\n  }\n\n  @Test\n  public void testCustomSuffixForUserAgentHeaders() {\n    String customSuffix = \"test-suffix\";\n    String userAgentHeader = HttpUtil.buildUserAgent(customSuffix);\n\n    assertThat(\n        \"user-agent header should contain the suffix \", userAgentHeader, endsWith(customSuffix));\n  }\n\n  @Test\n  public void testInvalidMaxRetries() {\n    SFException e =\n        assertThrows(\n            SFException.class,\n            () -> {\n              SFSessionProperty.checkPropertyValue(\n                  SFSessionProperty.MAX_HTTP_RETRIES, \"invalidValue\");\n            });\n    assertThat(e.getVendorCode(), is(ErrorCode.INVALID_PARAMETER_VALUE.getMessageCode()));\n  }\n\n  @Test\n  public void testvalidMaxRetries() throws SFException {\n    int expectedVal = 10;\n    Object value =\n        SFSessionProperty.checkPropertyValue(SFSessionProperty.MAX_HTTP_RETRIES, expectedVal);\n\n    assertThat(\"Integer value should match\", (int) value == expectedVal);\n  }\n\n  @Test\n  public void testInvalidPutGetMaxRetries() {\n    SFException e =\n        assertThrows(\n            SFException.class,\n            () -> {\n              SFSessionProperty.checkPropertyValue(\n                  SFSessionProperty.PUT_GET_MAX_RETRIES, \"invalidValue\");\n            });\n    assertThat(e.getVendorCode(), is(ErrorCode.INVALID_PARAMETER_VALUE.getMessageCode()));\n  }\n\n  @Test\n  public void testvalidPutGetMaxRetries() throws SFException {\n    int expectedVal = 10;\n    Object value =\n        SFSessionProperty.checkPropertyValue(SFSessionProperty.PUT_GET_MAX_RETRIES, expectedVal);\n\n    assertThat(\"Integer value should match\", (int) value == expectedVal);\n  }\n\n  @Test\n  public void testEnableCopyResultSetPropertyRegistered() {\n    SFSessionProperty prop = SFSessionProperty.lookupByKey(\"enableCopyResultSet\");\n    assertNotNull(prop);\n    assertEquals(SFSessionProperty.ENABLE_COPY_RESULT_SET, prop);\n    assertEquals(Boolean.class, prop.getValueType());\n  }\n\n  @Test\n  void testEnableCopyResultSetDefaultFalse() {\n    SFBaseSession session = mock(SFBaseSession.class, CALLS_REAL_METHODS);\n    assertFalse(\n        session.isEnableCopyResultSet(), \"default must be false for backwards compatibility\");\n  }\n\n  @Test\n  void testEnableCopyResultSetCanBeSetTrue() {\n    SFBaseSession session = mock(SFBaseSession.class, CALLS_REAL_METHODS);\n    session.setEnableCopyResultSet(true);\n    assertTrue(session.isEnableCopyResultSet());\n  }\n\n  @Test\n  void testEnableCopyResultSetCanBeReset() {\n    SFBaseSession session = mock(SFBaseSession.class, CALLS_REAL_METHODS);\n    session.setEnableCopyResultSet(true);\n    session.setEnableCopyResultSet(false);\n    assertFalse(session.isEnableCopyResultSet(), \"flag must be resettable to false\");\n  }\n\n  @Test\n  void testAddSFSessionPropertyWiresEnableCopyResultSet()\n      throws SFException, ReflectiveOperationException {\n    SFSession session = mock(SFSession.class, CALLS_REAL_METHODS);\n    Field mapField = SFBaseSession.class.getDeclaredField(\"connectionPropertiesMap\");\n    mapField.setAccessible(true);\n    mapField.set(session, new HashMap<>());\n    session.addSFSessionProperty(\n        SFSessionProperty.ENABLE_COPY_RESULT_SET.getPropertyKey(), Boolean.TRUE);\n    assertTrue(session.isEnableCopyResultSet());\n  }\n\n  @Test\n  public void shouldThrowWhenAwsExternalIdSetAndFeatureDisabled() {\n    // SF_ENABLE_WIF_AWS_EXTERNAL_ID env var is not set in unit tests, so defaults to false\n    Map<SFSessionProperty, Object> props = new HashMap<>();\n    props.put(SFSessionProperty.WORKLOAD_IDENTITY_AWS_EXTERNAL_ID, \"my-external-id\");\n    props.put(SFSessionProperty.AUTHENTICATOR, \"workload_identity\");\n    props.put(SFSessionProperty.WORKLOAD_IDENTITY_PROVIDER, \"aws\");\n\n    SFException e =\n        assertThrows(SFException.class, () -> SFSession.checkAwsExternalIdEnabled(props));\n    assertThat(e.getVendorCode(), is(ErrorCode.WORKLOAD_IDENTITY_FLOW_ERROR.getMessageCode()));\n  }\n\n  @Test\n  public void shouldNotThrowWhenAuthenticatorIsNotWorkloadIdentity() throws SFException {\n    Map<SFSessionProperty, Object> props = new HashMap<>();\n    props.put(SFSessionProperty.AUTHENTICATOR, \"snowflake\");\n    props.put(SFSessionProperty.WORKLOAD_IDENTITY_AWS_EXTERNAL_ID, \"my-external-id\");\n\n    // Should not throw — external ID check only applies to WORKLOAD_IDENTITY + AWS\n    SFSession.checkAwsExternalIdEnabled(props);\n  }\n\n  @Test\n  public void shouldNotThrowWhenAwsExternalIdIsNullAndFeatureDisabled() throws SFException {\n    Map<SFSessionProperty, Object> props = new HashMap<>();\n    props.put(SFSessionProperty.WORKLOAD_IDENTITY_AWS_EXTERNAL_ID, null);\n\n    // Should not throw — null means the property was not provided\n    SFSession.checkAwsExternalIdEnabled(props);\n  }\n\n  @Test\n  public void shouldNotThrowWhenAwsExternalIdIsEmptyAndFeatureDisabled() throws SFException {\n    Map<SFSessionProperty, Object> props = new HashMap<>();\n    props.put(SFSessionProperty.WORKLOAD_IDENTITY_AWS_EXTERNAL_ID, \"\");\n\n    // Should not throw — empty string is treated the same as not provided\n    SFSession.checkAwsExternalIdEnabled(props);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFStatementTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.sql.SQLException;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.jdbc.SnowflakeReauthenticationRequest;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.common.core.SqlState;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass SFStatementTest {\n\n  private SFSession mockSession;\n  private SFStatement statement;\n\n  @BeforeEach\n  void setUp() {\n    mockSession = mock(SFSession.class);\n    when(mockSession.getQueryTimeout()).thenReturn(0);\n    statement = new SFStatement(mockSession);\n  }\n\n  @Test\n  void testRenewSessionOnExpiry_rethrowsNonSessionExpiryError() {\n    SnowflakeSQLException otherException =\n        new SnowflakeSQLException(\"unknown\", \"Some other error\", SqlState.INTERNAL_ERROR, 12345);\n\n    SnowflakeSQLException thrown =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> statement.renewSessionOnExpiry(otherException, \"token\"));\n    assertSame(otherException, thrown);\n  }\n\n  @Test\n  void testRenewSessionOnExpiry_renewsSessionOnSessionExpired() throws SFException, SQLException {\n    SnowflakeSQLException sessionExpiredException =\n        new SnowflakeSQLException(\n            \"unknown\",\n            \"Your session has expired\",\n            SqlState.INTERNAL_ERROR,\n            Constants.SESSION_EXPIRED_GS_CODE);\n\n    doNothing().when(mockSession).renewSession(\"old-token\");\n\n    // Should not throw — session renewal succeeds\n    statement.renewSessionOnExpiry(sessionExpiredException, \"old-token\");\n\n    verify(mockSession).renewSession(\"old-token\");\n  }\n\n  @Test\n  void testRenewSessionOnExpiry_handlesReauthForExternalBrowser() throws SFException, SQLException {\n    SnowflakeSQLException sessionExpiredException =\n        new SnowflakeSQLException(\n            \"unknown\",\n            \"Your session has expired\",\n            SqlState.INTERNAL_ERROR,\n            Constants.SESSION_EXPIRED_GS_CODE);\n\n    doThrow(\n            new SnowflakeReauthenticationRequest(\n                \"qid\", \"reauth needed\", SqlState.INTERNAL_ERROR, 390110))\n        .when(mockSession)\n        .renewSession(\"old-token\");\n    when(mockSession.isExternalbrowserOrOAuthFullFlowAuthenticator()).thenReturn(true);\n\n    // Should call session.open() and not throw\n    statement.renewSessionOnExpiry(sessionExpiredException, \"old-token\");\n\n    verify(mockSession).open(any(InternalCallMarker.class));\n  }\n\n  @Test\n  void testRenewSessionOnExpiry_throwsReauthWhenNotExternalBrowser()\n      throws SFException, SQLException {\n    SnowflakeSQLException sessionExpiredException =\n        new SnowflakeSQLException(\n            \"unknown\",\n            \"Your session has expired\",\n            SqlState.INTERNAL_ERROR,\n            Constants.SESSION_EXPIRED_GS_CODE);\n\n    SnowflakeReauthenticationRequest reauthEx =\n        new SnowflakeReauthenticationRequest(\n            \"qid\", \"reauth needed\", SqlState.INTERNAL_ERROR, 390110);\n    doThrow(reauthEx).when(mockSession).renewSession(\"old-token\");\n    when(mockSession.isExternalbrowserOrOAuthFullFlowAuthenticator()).thenReturn(false);\n\n    // Should re-throw the SnowflakeReauthenticationRequest\n    SnowflakeSQLException thrown =\n        assertThrows(\n            SnowflakeReauthenticationRequest.class,\n            () -> statement.renewSessionOnExpiry(sessionExpiredException, \"old-token\"));\n    assertSame(reauthEx, thrown);\n  }\n\n  @Test\n  void testRenewSessionOnExpiry_preservesErrorCodeOnRethrow() throws SFException {\n    int originalErrorCode = 99999;\n    SnowflakeSQLException otherException =\n        new SnowflakeSQLException(\n            \"qid-123\", \"Something went wrong\", SqlState.INTERNAL_ERROR, originalErrorCode);\n\n    SnowflakeSQLException thrown =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> statement.renewSessionOnExpiry(otherException, \"token\"));\n    assertEquals(originalErrorCode, thrown.getErrorCode());\n    assertEquals(\"qid-123\", thrown.getQueryId());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFTrustManagerIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE;\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.not;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.AnyOf.anyOf;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.Provider;\nimport java.security.Security;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.sql.Connection;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Stream;\nimport javax.net.ssl.ManagerFactoryParameters;\nimport javax.net.ssl.SSLHandshakeException;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactorySpi;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.SystemPropertyOverrider;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpGet;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\nimport org.junit.jupiter.params.provider.CsvSource;\n\n@Tag(TestTags.CORE)\npublic class SFTrustManagerIT extends BaseJDBCTest {\n  private static final SFLogger logger = SFLoggerFactory.getLogger(SFTrustManagerIT.class);\n\n  private static class HostProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          // this host generates many \"SSLHandshake Certificate Revocation\n          // check failed. Could not retrieve OCSP Response.\" when running in parallel CI builds\n          // Arguments.of(\"storage.googleapis.com\"),\n          Arguments.of(\"ocspssd.us-east-1.snowflakecomputing.com/ocsp/fetch\"),\n          Arguments.of(\"sfcsupport.snowflakecomputing.com\"),\n          Arguments.of(\"sfcsupport.us-east-1.snowflakecomputing.com\"),\n          Arguments.of(\"sfcsupport.eu-central-1.snowflakecomputing.com\"),\n          Arguments.of(\"sfc-dev1-regression.s3.amazonaws.com\"),\n          Arguments.of(\"sfc-ds2-customer-stage.s3.amazonaws.com\"),\n          Arguments.of(\"snowflake.okta.com\"),\n          Arguments.of(\"sfcdev2.blob.core.windows.net\"));\n    }\n  }\n\n  private boolean defaultState;\n\n  @BeforeEach\n  public void setUp() {\n    TelemetryService service = TelemetryService.getInstance();\n    service.updateContextForIT(getConnectionParameters());\n    defaultState = service.isEnabled();\n    service.setNumOfRetryToTriggerTelemetry(3);\n    service.enable();\n  }\n\n  @AfterEach\n  public void tearDown() throws InterruptedException {\n    TelemetryService service = TelemetryService.getInstance();\n    // wait 5 seconds while the service is flushing\n    TimeUnit.SECONDS.sleep(5);\n    if (defaultState) {\n      service.enable();\n    } else {\n      service.disable();\n    }\n    System.clearProperty(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED);\n    System.clearProperty(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL);\n  }\n\n  @TempDir File tmpFolder;\n\n  /**\n   * OCSP tests for the Snowflake and AWS S3 HTTPS connections.\n   *\n   * <p>Whatever the default method is used.\n   */\n  @ParameterizedTest\n  @ArgumentsSource(HostProvider.class)\n  public void testOcsp(String host) throws Throwable {\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.TRUE.toString());\n    // this initialization normally happens on first call\n    SFTrustManager.setOCSPResponseCacheServerURL(String.format(\"http://%s\", host));\n    HttpClient client =\n        HttpUtil.buildHttpClient(\n            new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED),\n            null, // default OCSP response cache file\n            false // enable decompression\n            );\n    accessHost(host, client);\n  }\n\n  /**\n   * OCSP tests for the Snowflake and AWS S3 HTTPS connections using the file cache.\n   *\n   * <p>Specifying an non-existing file will force to fetch OCSP response.\n   */\n  @ParameterizedTest\n  @ArgumentsSource(HostProvider.class)\n  public void testOcspWithFileCache(String host) throws Throwable {\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    File ocspCacheFile = new File(tmpFolder, \"ocsp-cache\");\n    ocspCacheFile.createNewFile();\n    HttpClient client =\n        HttpUtil.buildHttpClient(\n            new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED),\n            ocspCacheFile, // a temp OCSP response cache file\n            false // enable decompression\n            );\n    accessHost(host, client);\n  }\n\n  /** OCSP tests for the Snowflake and AWS S3 HTTPS connections using the server cache. */\n  @ParameterizedTest\n  @ArgumentsSource(HostProvider.class)\n  public void testOcspWithServerCache(String host) throws Throwable {\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.TRUE.toString());\n    SFTrustManager.setOCSPResponseCacheServerURL(String.format(\"http://%s\", host));\n    File ocspCacheFile = new File(tmpFolder, \"ocsp-cache\");\n    ocspCacheFile.createNewFile();\n    HttpClient client =\n        HttpUtil.buildHttpClient(\n            new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED),\n            ocspCacheFile, // a temp OCSP response cache file\n            false // enable decompression\n            );\n    accessHost(host, client);\n  }\n\n  /**\n   * OCSP tests for the Snowflake and AWS S3 HTTPS connections without using the server cache. This\n   * test should always pass - even with OCSP Outage.\n   */\n  @ParameterizedTest\n  @ArgumentsSource(HostProvider.class)\n  public void testOcspWithoutServerCache(String host) throws Throwable {\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    File ocspCacheFile = new File(tmpFolder, \"ocsp-cache\");\n    ocspCacheFile.createNewFile();\n    HttpClient client =\n        HttpUtil.buildHttpClient(\n            new HttpClientSettingsKey(OCSPMode.FAIL_OPEN),\n            ocspCacheFile, // a temp OCSP response cache file\n            false // enable decompression\n            );\n    accessHost(host, client);\n  }\n\n  /** OCSP tests for the Snowflake and AWS S3 HTTPS connections using the server cache. */\n  @ParameterizedTest\n  @ArgumentsSource(HostProvider.class)\n  public void testInvalidCacheFile(String host) throws Throwable {\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.TRUE.toString());\n    SFTrustManager.setOCSPResponseCacheServerURL(String.format(\"http://%s\", host));\n    // a file under never exists.\n    File ocspCacheFile = new File(\"NEVER_EXISTS\", \"NEVER_EXISTS\");\n    HttpClient client =\n        HttpUtil.buildHttpClient(\n            new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED),\n            ocspCacheFile, // a temp OCSP response cache file\n            false // enable decompression\n            );\n    accessHost(host, client);\n  }\n\n  private static void accessHost(String host, HttpClient client)\n      throws IOException, InterruptedException {\n    HttpResponse response = executeWithRetries(host, client);\n\n    await()\n        .atMost(Duration.ofSeconds(10))\n        .until(() -> response.getStatusLine().getStatusCode(), not(equalTo(-1)));\n\n    assertThat(\n        String.format(\"response code for %s\", host),\n        response.getStatusLine().getStatusCode(),\n        anyOf(equalTo(200), equalTo(400), equalTo(403), equalTo(404), equalTo(513)));\n  }\n\n  private static HttpResponse executeWithRetries(String host, HttpClient client)\n      throws IOException, InterruptedException {\n    // There is one host that causes SSLHandshakeException very often - let's retry\n    int maxRetries = host.equals(\"storage.googleapis.com\") ? 5 : 0;\n    int retries = 0;\n    HttpGet httpRequest = new HttpGet(String.format(\"https://%s:443/\", host));\n    while (true) {\n      try {\n        return client.execute(httpRequest);\n      } catch (SSLHandshakeException e) {\n        logger.warn(\"SSL handshake failed (host = {}, retries={}}\", host, retries, e);\n        ++retries;\n        if (retries >= maxRetries) {\n          throw e;\n        }\n        Thread.sleep(retries * 1000);\n      }\n    }\n  }\n\n  /**\n   * TODO: we should re-enable this https://snowflakecomputing.atlassian.net/browse/SNOW-146911\n   * Revoked certificate test. @Test public void testRevokedCertificate() throws Throwable {\n   * System.setProperty(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED,\n   * Boolean.TRUE.toString()); File ocspCacheFile = tmpFolder.newFile(); List<X509Certificate>\n   * certList = getX509CertificatesFromFile( \"revoked_certs.pem\"); SFTrustManager sft = new\n   * SFTrustManager( OCSPMode.FAIL_OPEN, ocspCacheFile // a temp OCSP response cache file ); int\n   * queueSize = TelemetryService.getInstance().size(); try {\n   * sft.validateRevocationStatus(certList.toArray(new X509Certificate[0]), \"test_host\"); fail(); }\n   * catch (CertificateException ex) { assertThat(ex.getMessage(), containsString(\"has been\n   * revoked\")); if (TelemetryService.getInstance().isDeploymentEnabled()) {\n   * assertEquals(TelemetryService.getInstance().size(), queueSize + 1); TelemetryEvent te =\n   * TelemetryService.getInstance().peek(); JSONObject values = (JSONObject) te.get(\"Value\"); Object\n   * cacheHit = values.get(\"cacheHit\"); assertNotNull(cacheHit); assertEquals(\"OCSPException\",\n   * te.get(\"Name\")); assertEquals(SFTrustManager.SF_OCSP_EVENT_TYPE_REVOKED_CERTIFICATE_ERROR,\n   * values.get(\"eventType\").toString()); assertNotNull(values.get(\"sfcPeerHost\"));\n   * assertNotNull(values.get(\"certId\")); if (cacheHit instanceof Boolean && !((Boolean) cacheHit))\n   * { // Only if the cache is not available, no OCSP Responder URL or OCSP request is valid,\n   * assertNotNull(values.get(\"ocspResponderURL\")); assertNotNull(values.get(\"ocspReqBase64\")); }\n   * assertEquals(OCSPMode.FAIL_OPEN.name(), values.get(\"ocspMode\"));\n   * assertNotNull(values.get(\"cacheEnabled\")); assertNotNull(values.get(\"exceptionStackTrace\"));\n   * assertNotNull(values.get(\"exceptionMessage\")); } } }\n   */\n\n  /**\n   * Read certificates from a file.\n   *\n   * @param filename file name under resources directory\n   * @return an array of X509Certificate\n   * @throws Throwable raise if any error occurs\n   */\n  private List<X509Certificate> getX509CertificatesFromFile(String filename) throws Throwable {\n    CertificateFactory fact = CertificateFactory.getInstance(\"X.509\");\n    List<X509Certificate> certList = new ArrayList<>();\n    for (Certificate cert : fact.generateCertificates(getFile(filename))) {\n      certList.add((X509Certificate) cert);\n    }\n    return certList;\n  }\n\n  private InputStream getFile(String fileName) throws Throwable {\n    ClassLoader classLoader = getClass().getClassLoader();\n    URL url = classLoader.getResource(fileName);\n    return url != null ? url.openStream() : null;\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n    \"jdbc:snowflake://someaccount.snowflakecomputing.com:443,http://ocsp.snowflakecomputing.com/ocsp_response_cache.json\",\n    \"jdbc:snowflake://someaccount.snowflakecomputing.cn:443,http://ocsp.snowflakecomputing.cn/ocsp_response_cache.json\",\n  })\n  void testOCSPCacheServerUrlWithoutProxy(String sfHost, String ocspHost) throws Exception {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USER.getPropertyKey(), \"testUser\");\n    props.setProperty(SFSessionProperty.PASSWORD.getPropertyKey(), \"testPassword\");\n    props.setProperty(SFSessionProperty.LOGIN_TIMEOUT.getPropertyKey(), \"1\");\n    try {\n      new SnowflakeConnectionImpl(sfHost, props);\n    } catch (Exception e) {\n      // do nothing, we don't want to connect, just check the value below\n    }\n    assertEquals(SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE, ocspHost);\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n    \"jdbc:snowflake://someaccount.snowflakecomputing.com:443,http://ocsp.snowflakecomputing.com/ocsp_response_cache.json\",\n    \"jdbc:snowflake://someaccount.snowflakecomputing.cn:443,http://ocsp.snowflakecomputing.cn/ocsp_response_cache.json\",\n  })\n  void testOCSPCacheServerUrlWithProxy(String sfHost, String ocspHost) {\n    SystemPropertyOverrider useProxyOverrider =\n        new SystemPropertyOverrider(\"http.useProxy\", \"true\");\n    SystemPropertyOverrider proxyHostOverrider =\n        new SystemPropertyOverrider(\"http.proxyHost\", \"localhost\");\n    SystemPropertyOverrider proxyPortOverrider =\n        new SystemPropertyOverrider(\"http.proxyPort\", \"8080\");\n    try {\n      Properties props = new Properties();\n      props.setProperty(SFSessionProperty.USER.getPropertyKey(), \"testUser\");\n      props.setProperty(SFSessionProperty.PASSWORD.getPropertyKey(), \"testPassword\");\n      props.setProperty(SFSessionProperty.LOGIN_TIMEOUT.getPropertyKey(), \"1\");\n      try {\n        new SnowflakeConnectionImpl(sfHost, props);\n      } catch (Exception e) {\n        // do nothing, we don't want to connect, just check the value below\n      }\n      assertEquals(SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE, ocspHost);\n    } finally {\n      Arrays.asList(useProxyOverrider, proxyHostOverrider, proxyPortOverrider)\n          .forEach(SystemPropertyOverrider::rollback);\n    }\n  }\n\n  @Test\n  void shouldNotFailWhenSimpleTrustManagerIsUsed() throws Exception {\n    Security.insertProviderAt(new TestSecurityProvider(), 1);\n    HttpUtil.reset();\n    try (Connection connection = getConnection()) {\n      Statement statement = connection.createStatement();\n      statement.execute(\"SELECT 1\");\n    } finally {\n      Security.removeProvider(TestSecurityProvider.class.getSimpleName());\n      HttpUtil.reset();\n    }\n  }\n\n  @BeforeEach\n  @AfterEach\n  void cleanup() {\n    SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = null;\n  }\n}\n\n// Standard JCA implementation for registering custom TrustManagerFactory\nclass TestTrustManagerSpi extends TrustManagerFactorySpi {\n\n  @Override\n  protected void engineInit(KeyStore ks) throws KeyStoreException {}\n\n  @Override\n  protected void engineInit(ManagerFactoryParameters spec)\n      throws InvalidAlgorithmParameterException {}\n\n  @Override\n  protected TrustManager[] engineGetTrustManagers() {\n    return new TrustManager[] {\n      new X509TrustManager() {\n        @Override\n        public void checkClientTrusted(X509Certificate[] chain, String authType)\n            throws CertificateException {}\n\n        @Override\n        public void checkServerTrusted(X509Certificate[] chain, String authType)\n            throws CertificateException {}\n\n        @Override\n        public X509Certificate[] getAcceptedIssuers() {\n          return new X509Certificate[0];\n        }\n      }\n    };\n  }\n}\n\nclass TestSecurityProvider extends Provider {\n  public TestSecurityProvider() {\n    super(TestSecurityProvider.class.getSimpleName(), 1.0, \"Test security provider\");\n    put(\"TrustManagerFactory.PKIX\", TestTrustManagerSpi.class.getName());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFTrustManagerMockitoMockLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.when;\n\nimport java.io.File;\nimport java.io.IOException;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.MockedStatic;\n\n@Tag(TestTags.CORE)\npublic class SFTrustManagerMockitoMockLatestIT {\n\n  @TempDir private File tmpFolder;\n\n  /*\n   * Test SF_OCSP_RESPONSE_CACHE_DIR environment variable changes the\n   * location of the OCSP cache directory.\n   */\n  @Test\n  @Disabled(\"static initialization block of SFTrustManager class doesn't run sometimes\")\n  public void testUnitOCSPWithCustomCacheDirectory() throws IOException {\n    try (MockedStatic<TrustManagerFactory> mockedTrustManagerFactory =\n            mockStatic(TrustManagerFactory.class);\n        MockedStatic<SnowflakeUtil> mockedSnowflakeUtil = mockStatic(SnowflakeUtil.class)) {\n\n      File cacheFolder = new File(tmpFolder, \"cache\");\n      cacheFolder.mkdirs();\n      mockedSnowflakeUtil\n          .when(() -> TestUtil.systemGetEnv(\"SF_OCSP_RESPONSE_CACHE_DIR\"))\n          .thenReturn(cacheFolder.getCanonicalPath());\n\n      TrustManagerFactory tested = mock(TrustManagerFactory.class);\n      when(tested.getTrustManagers()).thenReturn(new TrustManager[] {});\n\n      mockedTrustManagerFactory\n          .when(() -> TrustManagerFactory.getInstance(\"SunX509\"))\n          .thenReturn(tested);\n\n      new SFTrustManager(\n          new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED), null); // cache file location\n\n      // The goal is to check if the cache file location is changed to the specified\n      // directory, so it doesn't need to do OCSP check in this test.\n      assertThat(\n          \"The cache file doesn't exist.\",\n          new File(cacheFolder, SFTrustManager.CACHE_FILE_NAME).exists());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFTrustManagerOcspCachePoisoningTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport net.snowflake.client.internal.jdbc.OCSPErrorCode;\nimport net.snowflake.client.internal.util.SFPair;\nimport org.apache.commons.codec.binary.Base64;\nimport org.junit.jupiter.api.Test;\n\n/** Regression: a non-SUCCESSFUL OCSP cache entry must surface as SFOCSPException, not NPE. */\npublic class SFTrustManagerOcspCachePoisoningTest {\n\n  /** RFC 6960 unauthorized(6) OCSP response: SEQUENCE { ENUMERATED 6 }. */\n  private static final String UNAUTHORIZED_OCSP_B64 =\n      Base64.encodeBase64String(new byte[] {0x30, 0x03, 0x0A, 0x01, 0x06});\n\n  @Test\n  public void validateRevocationStatusMain_throwsSfOcspExceptionForUnauthorizedResponse() {\n    SFTrustManager tm = new SFTrustManager(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN), null);\n\n    SFOCSPException ex =\n        assertThrows(\n            SFOCSPException.class,\n            () -> tm.validateRevocationStatusMain(SFPair.of(null, null), UNAUTHORIZED_OCSP_B64),\n            \"Expected SFOCSPException for an unauthorized(6) OCSP payload (was NPE before fix)\");\n\n    assertSame(\n        OCSPErrorCode.INVALID_OCSP_RESPONSE,\n        ex.getErrorCode(),\n        \"Unauthorized(6) payloads must surface as INVALID_OCSP_RESPONSE so isCached evicts them\"\n            + \" and the fail-open gate engages\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFTrustManagerProxyWiremockIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.io.IOException;\nimport java.security.cert.X509Certificate;\nimport java.time.Duration;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.crl.CertificateGeneratorUtil;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Verifies OCSP cache honor the proxy configured on the connection. Regression coverage for a bug\n * where OCSP HTTP clients were cached only by timeout, causing OCSP requests for subsequent\n * connections to keep using the first proxy port.\n */\n@Tag(TestTags.CORE)\npublic class SFTrustManagerProxyWiremockIT extends BaseWiremockTest {\n\n  private static final String OCSP_CACHE_PATH = \"/ocsp_response_cache.json\";\n  private static Process secondaryWiremock;\n  private static final String OCSP_MAPPING_BODY =\n      \"{\\n\"\n          + \"  \\\"request\\\": {\\\"method\\\": \\\"GET\\\", \\\"urlPattern\\\": \\\".*\"\n          + OCSP_CACHE_PATH\n          + \".*\\\"},\\n\"\n          + \"  \\\"response\\\": {\\\"status\\\": 200, \\\"jsonBody\\\": {\\\"ok\\\": true}}\\n\"\n          + \"}\";\n  private static int secondaryWiremockHttpPort;\n  private static int secondaryWiremockHttpsPort;\n\n  @BeforeAll\n  public static void setUpClass() {\n    await()\n        .alias(\"wait for wiremock responding\")\n        .atMost(Duration.ofSeconds(10))\n        .until(\n            () -> {\n              try {\n                secondaryWiremockHttpPort = findFreePort();\n                secondaryWiremockHttpsPort = findFreePort();\n                secondaryWiremock =\n                    startWiremockProcess(secondaryWiremockHttpPort, secondaryWiremockHttpsPort);\n                waitForWiremockOnPort(secondaryWiremockHttpPort);\n                return true;\n              } catch (Exception e) {\n                logger.warn(\"Failed to start wiremock, retrying: \", e);\n                return false;\n              }\n            });\n  }\n\n  @Test\n  public void ocspRequestsUseConfiguredProxyPerConnection() throws Exception {\n    String ocspUrl = \"http://dummy-host\" + OCSP_CACHE_PATH;\n    System.setProperty(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL, ocspUrl);\n    SFTrustManager.setOCSPResponseCacheServerURL(ocspUrl);\n    System.clearProperty(\"net.snowflake.jdbc.ocsp_activate_new_endpoint\");\n    X509Certificate[] chain = generateLeafChain();\n    addMappingOnPort(OCSP_MAPPING_BODY, wiremockHttpPort);\n\n    // First connection uses proxy #1\n    SFTrustManager trustManagerProxyOne =\n        new SFTrustManager(\n            new HttpClientSettingsKey(\n                OCSPMode.FAIL_OPEN, WIREMOCK_HOST, wiremockHttpPort, \"\", \"\", \"\", \"http\", \"\", false),\n            null);\n    trustManagerProxyOne.validateRevocationStatus(chain, WIREMOCK_HOST);\n\n    verifySingleResponderRequest(wiremockHttpPort);\n\n    // Second connection uses proxy #2 – should not reuse the client built for proxy #1.\n    addMappingOnPort(OCSP_MAPPING_BODY, secondaryWiremockHttpPort);\n\n    SFTrustManager trustManagerProxyTwo =\n        new SFTrustManager(\n            new HttpClientSettingsKey(\n                OCSPMode.FAIL_OPEN,\n                WIREMOCK_HOST,\n                secondaryWiremockHttpPort,\n                \"\",\n                \"\",\n                \"\",\n                \"http\",\n                \"\",\n                false),\n            null);\n    trustManagerProxyTwo.validateRevocationStatus(chain, WIREMOCK_HOST);\n\n    verifySingleResponderRequest(wiremockHttpPort);\n    verifySingleResponderRequest(secondaryWiremockHttpPort);\n  }\n\n  @AfterAll\n  public static void stopSecondaryProxy() {\n    stopWiremockStandAlone(secondaryWiremock);\n  }\n\n  private void addMappingOnPort(String mapping, int port) {\n    HttpPost postRequest = createWiremockPostRequest(mapping, \"/__admin/mappings\", port);\n    try (CloseableHttpClient client = HttpClients.createDefault();\n        CloseableHttpResponse response = client.execute(postRequest)) {\n      assertEquals(201, response.getStatusLine().getStatusCode());\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private X509Certificate[] generateLeafChain() throws Exception {\n    X509TrustManager jvmTrustManager = getDefaultTrustManager();\n    X509Certificate issuer = jvmTrustManager.getAcceptedIssuers()[0];\n    CertificateGeneratorUtil util = new CertificateGeneratorUtil();\n    X509Certificate leaf = util.createWithIssuer(issuer.getSubjectX500Principal().getName());\n    return new X509Certificate[] {leaf};\n  }\n\n  private X509TrustManager getDefaultTrustManager() throws Exception {\n    TrustManagerFactory tmf =\n        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n    tmf.init((java.security.KeyStore) null);\n    for (TrustManager tm : tmf.getTrustManagers()) {\n      if (tm instanceof X509TrustManager) {\n        return (X509TrustManager) tm;\n      }\n    }\n    throw new IllegalStateException(\"No X509TrustManager found\");\n  }\n\n  private void verifySingleResponderRequest(int adminPort) {\n    long actualCount =\n        getAllServeEvents(adminPort).stream()\n            .filter(event -> event.getRequest() != null && event.getRequest().getUrl() != null)\n            .filter(event -> event.getRequest().getUrl().contains(OCSP_CACHE_PATH))\n            .count();\n    assertEquals(1, actualCount);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SFTrustManagerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_MEMORY_LIMIT;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.nio.charset.StandardCharsets;\nimport java.sql.ResultSet;\nimport java.util.Base64;\nimport java.util.Properties;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\npublic class SFTrustManagerTest {\n  /** Test building OCSP retry URL */\n  static String originalRetryUrlPattern;\n\n  @BeforeAll\n  public static void saveStaticValues() {\n    originalRetryUrlPattern = SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN;\n  }\n\n  @AfterAll\n  public static void restoreStaticValues() {\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = originalRetryUrlPattern;\n  }\n\n  @Test\n  public void testBuildRetryURL() throws Exception {\n    // private link\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    SFTrustManager.resetOCSPResponseCacherServerURL(\n        \"http://ocsp.us-east-1.privatelink.snowflakecomputing.com/\"\n            + SFTrustManager.CACHE_FILE_NAME);\n    assertThat(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN,\n        equalTo(\"http://ocsp.us-east-1.privatelink.snowflakecomputing.com/retry/%s/%s\"));\n\n    // private link with port\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    SFTrustManager.resetOCSPResponseCacherServerURL(\n        \"http://ocsp.us-east-1.privatelink.snowflakecomputing.com:80/\"\n            + SFTrustManager.CACHE_FILE_NAME);\n    assertThat(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN,\n        equalTo(\"http://ocsp.us-east-1.privatelink.snowflakecomputing.com:80/retry/%s/%s\"));\n\n    // non-privatelink\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    SFTrustManager.resetOCSPResponseCacherServerURL(\n        \"http://ocsp.snowflakecomputing.com/\" + SFTrustManager.CACHE_FILE_NAME);\n    assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());\n\n    // non-privatelink with port\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    SFTrustManager.resetOCSPResponseCacherServerURL(\n        \"http://ocsp.snowflakecomputing.com:80/\" + SFTrustManager.CACHE_FILE_NAME);\n    assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());\n\n    // default OCSP Cache server URL in specific domain without port\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    SFTrustManager.resetOCSPResponseCacherServerURL(\n        \"http://ocsp.snowflakecomputing.cn/\" + SFTrustManager.CACHE_FILE_NAME);\n    assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());\n\n    // default OCSP Cache server URL in specific domain with port\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    SFTrustManager.resetOCSPResponseCacherServerURL(\n        \"http://ocsp.snowflakecomputing.cn:80/\" + SFTrustManager.CACHE_FILE_NAME);\n    assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());\n  }\n\n  @Test\n  public void testBuildNewRetryURL() {\n    try {\n      System.setProperty(\"net.snowflake.jdbc.ocsp_activate_new_endpoint\", Boolean.TRUE.toString());\n\n      SFTrustManager tManager =\n          new SFTrustManager(\n              new HttpClientSettingsKey(OCSPMode.FAIL_OPEN), null // OCSP Cache file custom location\n              ); // Use OCSP Cache Server\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\"a1.snowflakecomputing.com\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd.snowflakecomputing.com/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd.snowflakecomputing.com/ocsp/retry\"));\n\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\"a1.snowflakecomputing.cn\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd.snowflakecomputing.cn/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd.snowflakecomputing.cn/ocsp/retry\"));\n\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\n          \"a1-12345.global.snowflakecomputing.com\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd-12345.global.snowflakecomputing.com/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd-12345.global.snowflakecomputing.com/ocsp/retry\"));\n\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\n          \"a1-12345.global.snowflakecomputing.cn\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd-12345.global.snowflakecomputing.cn/ocsp/retry\"));\n\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\"okta.snowflake.com\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd.snowflakecomputing.com/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd.snowflakecomputing.com/ocsp/retry\"));\n\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\n          \"a1.us-east-1.privatelink.snowflakecomputing.com\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd.us-east-1.privatelink.snowflakecomputing.com/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd.us-east-1.privatelink.snowflakecomputing.com/ocsp/retry\"));\n\n      tManager.ocspCacheServer.resetOCSPResponseCacheServer(\n          \"a1.us-east-1.privatelink.snowflakecomputing.cn\");\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER,\n          equalTo(\"https://ocspssd.us-east-1.privatelink.snowflakecomputing.cn/ocsp/fetch\"));\n      assertThat(\n          tManager.ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL,\n          equalTo(\"https://ocspssd.us-east-1.privatelink.snowflakecomputing.cn/ocsp/retry\"));\n    } finally {\n      System.clearProperty(\"net.snowflake.jdbc.ocsp_activate_new_endpoint\");\n    }\n  }\n\n  /**\n   * Test resultSetSerializable.getResultSet(ResultSetRetrieveConfig) can work with private link\n   * URL.\n   *\n   * @throws Exception\n   */\n  @Test\n  public void testSnowflakeResultSetSerializable_getResultSet() throws Exception {\n    // Create an empty result set serializable object\n    SnowflakeResultSetSerializableV1 resultSetSerializable = new SnowflakeResultSetSerializableV1();\n    resultSetSerializable.setFirstChunkStringData(\n        Base64.getEncoder().encodeToString(\"\".getBytes(StandardCharsets.UTF_8)));\n    resultSetSerializable.setChunkFileCount(0);\n    resultSetSerializable.getParameters().put(CLIENT_MEMORY_LIMIT, 10);\n    resultSetSerializable.setQueryResultFormat(QueryResultFormat.ARROW);\n\n    // Get ResultSet with NON private link URL.\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    ResultSet rs =\n        resultSetSerializable.getResultSet(\n            SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                .setProxyProperties(new Properties())\n                .setSfFullURL(\"https://sfctest0.snowflakecomputing.com\")\n                .build());\n    // For non-private link, do nothing for SFTrustManager\n    assertThat(SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, nullValue());\n\n    // Get ResultSet with private link URL.\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n    rs =\n        resultSetSerializable.getResultSet(\n            SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                .setProxyProperties(new Properties())\n                .setSfFullURL(\"https://sfctest0.us-west-2.privatelink.snowflakecomputing.com\")\n                .build());\n    // For private link, SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN is reset accordingly.\n    assertThat(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN,\n        equalTo(\"http://ocsp.sfctest0.us-west-2.privatelink.snowflakecomputing.com/retry/%s/%s\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SQLInputOutputTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.TestUtil.expectSnowflakeLoggedFeatureNotSupportedException;\nimport static org.mockito.Mockito.mock;\n\nimport java.sql.SQLData;\nimport org.junit.jupiter.api.Test;\n\npublic class SQLInputOutputTest {\n\n  @Test\n  public void testBaseSQLUnSupportedException() {\n    BaseSqlInput sqlInput = new ArrowSqlInput(null, null, null, null);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readCharacterStream);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readAsciiStream);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readBinaryStream);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readRef);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readBlob);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readClob);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readArray);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readURL);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readNClob);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readNString);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readSQLXML);\n    expectSnowflakeLoggedFeatureNotSupportedException(sqlInput::readRowId);\n  }\n\n  @Test\n  public void testJsonSqlOutPutUnSupportedTest() {\n    JsonSqlOutput sqloutput = new JsonSqlOutput(mock(SQLData.class), mock(SFBaseSession.class));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeRef(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeBlob(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeClob(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeStruct(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeArray(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeURL(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeNString(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeNClob(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeRowId(null));\n    expectSnowflakeLoggedFeatureNotSupportedException(() -> sqloutput.writeSQLXML(null));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SecureStorageManagerTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport com.sun.jna.Memory;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.ptr.PointerByReference;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport net.snowflake.client.annotations.RunOnLinux;\nimport net.snowflake.client.annotations.RunOnMac;\nimport net.snowflake.client.annotations.RunOnWindows;\nimport net.snowflake.client.annotations.RunOnWindowsOrMac;\nimport net.snowflake.client.internal.jdbc.SnowflakeUtil;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\nclass MockAdvapi32Lib implements SecureStorageWindowsManager.Advapi32Lib {\n  @Override\n  public boolean CredReadW(String targetName, int type, int flags, PointerByReference pcred) {\n    Pointer target = MockWindowsCredentialManager.getCredential(targetName);\n    pcred.setValue(target);\n    return target == null ? false : true;\n  }\n\n  @Override\n  public boolean CredWriteW(\n      SecureStorageWindowsManager.SecureStorageWindowsCredential cred, int flags) {\n    MockWindowsCredentialManager.addCredential(cred);\n    return true;\n  }\n\n  @Override\n  public boolean CredDeleteW(String targetName, int type, int flags) {\n    MockWindowsCredentialManager.deleteCredential(targetName);\n    return true;\n  }\n\n  @Override\n  public void CredFree(Pointer cred) {\n    // mock function\n  }\n}\n\nclass MockSecurityLib implements SecureStorageAppleManager.SecurityLib {\n  @Override\n  public int SecKeychainFindGenericPassword(\n      Pointer keychainOrArray,\n      int serviceNameLength,\n      byte[] serviceName,\n      int accountNameLength,\n      byte[] accountName,\n      int[] passwordLength,\n      Pointer[] passwordData,\n      Pointer[] itemRef) {\n    MockMacKeychainManager.MockMacKeychainItem credItem =\n        MockMacKeychainManager.getCredential(serviceName, accountName);\n    if (credItem == null) {\n      return SecureStorageAppleManager.SecurityLib.ERR_SEC_ITEM_NOT_FOUND;\n    }\n\n    if (passwordLength != null && passwordLength.length > 0) {\n      passwordLength[0] = credItem.getLength();\n    }\n\n    if (passwordData != null && passwordData.length > 0) {\n      passwordData[0] = credItem.getPointer();\n    }\n\n    if (itemRef != null && itemRef.length > 0) {\n      itemRef[0] = credItem.getPointer();\n    }\n    return SecureStorageAppleManager.SecurityLib.ERR_SEC_SUCCESS;\n  }\n\n  @Override\n  public int SecKeychainAddGenericPassword(\n      Pointer keychain,\n      int serviceNameLength,\n      byte[] serviceName,\n      int accountNameLength,\n      byte[] accountName,\n      int passwordLength,\n      byte[] passwordData,\n      Pointer[] itemRef) {\n    MockMacKeychainManager.addCredential(serviceName, accountName, passwordLength, passwordData);\n    return SecureStorageAppleManager.SecurityLib.ERR_SEC_SUCCESS;\n  }\n\n  @Override\n  public int SecKeychainItemModifyContent(\n      Pointer itemRef, Pointer attrList, int length, byte[] data) {\n    MockMacKeychainManager.replaceCredential(itemRef, length, data);\n    return SecureStorageAppleManager.SecurityLib.ERR_SEC_SUCCESS;\n  }\n\n  @Override\n  public int SecKeychainItemDelete(Pointer itemRef) {\n    MockMacKeychainManager.deleteCredential(itemRef);\n    return SecureStorageAppleManager.SecurityLib.ERR_SEC_SUCCESS;\n  }\n\n  @Override\n  public int SecKeychainItemFreeContent(Pointer[] attrList, Pointer data) {\n    // mock function\n    return SecureStorageAppleManager.SecurityLib.ERR_SEC_SUCCESS;\n  }\n}\n\nclass MockWindowsCredentialManager {\n  private static final Map<String, Pointer> credentialManager = new HashMap<>();\n\n  static void addCredential(SecureStorageWindowsManager.SecureStorageWindowsCredential cred) {\n    cred.write();\n    credentialManager.put(cred.TargetName.toString(), cred.getPointer());\n  }\n\n  static Pointer getCredential(String target) {\n    return credentialManager.get(target);\n  }\n\n  static void deleteCredential(String target) {\n    credentialManager.remove(target);\n  }\n}\n\nclass MockMacKeychainManager {\n  private static final Map<String, Map<String, MockMacKeychainItem>> keychainManager =\n      new HashMap<>();\n\n  static void addCredential(byte[] targetName, byte[] userName, int credLength, byte[] credData) {\n    String target = new String(targetName);\n    String user = new String(userName);\n\n    keychainManager.computeIfAbsent(target, newMap -> new HashMap<>());\n    Map<String, MockMacKeychainItem> currentTargetMap = keychainManager.get(target);\n\n    currentTargetMap.put(user, buildMacKeychainItem(credLength, credData));\n  }\n\n  static MockMacKeychainItem getCredential(byte[] targetName, byte[] userName) {\n    Map<String, MockMacKeychainItem> targetMap = keychainManager.get(new String(targetName));\n    return targetMap != null ? targetMap.get(new String(userName)) : null;\n  }\n\n  static void replaceCredential(Pointer itemRef, int credLength, byte[] credData) {\n    for (Map.Entry<String, Map<String, MockMacKeychainItem>> elem : keychainManager.entrySet()) {\n      Map<String, MockMacKeychainItem> targetMap = elem.getValue();\n      for (Map.Entry<String, MockMacKeychainItem> elem0 : targetMap.entrySet()) {\n        if (elem0.getValue().getPointer().toString().equals(itemRef.toString())) {\n          targetMap.put(elem0.getKey(), buildMacKeychainItem(credLength, credData));\n          return;\n        }\n      }\n    }\n  }\n\n  static void deleteCredential(Pointer itemRef) {\n    Iterator<Map.Entry<String, Map<String, MockMacKeychainItem>>> targetIter =\n        keychainManager.entrySet().iterator();\n    while (targetIter.hasNext()) {\n      Map.Entry<String, Map<String, MockMacKeychainItem>> targetMap = targetIter.next();\n      Iterator<Map.Entry<String, MockMacKeychainItem>> userIter =\n          targetMap.getValue().entrySet().iterator();\n      while (userIter.hasNext()) {\n        Map.Entry<String, MockMacKeychainItem> cred = userIter.next();\n        if (cred.getValue().getPointer().toString().equals(itemRef.toString())) {\n          userIter.remove();\n          return;\n        }\n      }\n    }\n  }\n\n  static MockMacKeychainItem buildMacKeychainItem(int itemLength, byte[] itemData) {\n    Memory itemMem = new Memory(itemLength);\n    itemMem.write(0, itemData, 0, itemLength);\n    return new MockMacKeychainItem(itemLength, itemMem);\n  }\n\n  static class MockMacKeychainItem {\n    private int length;\n    private Pointer pointer;\n\n    MockMacKeychainItem(int length, Pointer pointer) {\n      this.length = length;\n      this.pointer = pointer;\n    }\n\n    void setLength(int length) {\n      this.length = length;\n    }\n\n    int getLength() {\n      return length;\n    }\n\n    void setPointer(Pointer pointer) {\n      this.pointer = pointer;\n    }\n\n    Pointer getPointer() {\n      return pointer;\n    }\n  }\n}\n\npublic class SecureStorageManagerTest {\n\n  private static final String host = \"fakeHost\";\n  private static final String user = \"fakeUser\";\n  private static final String idToken = \"fakeIdToken\";\n  private static final String idToken0 = \"fakeIdToken0\";\n\n  private static final String mfaToken = \"fakeMfaToken\";\n\n  private static final String ID_TOKEN = \"ID_TOKEN\";\n  private static final String MFA_TOKEN = \"MFATOKEN\";\n\n  @Test\n  public void testBuildCredentialsKey() {\n    // hex values obtained using https://emn178.github.io/online-tools/sha256.html\n    String hashedKey =\n        SecureStorageManager.buildCredentialsKey(\n            host, user, CachedCredentialType.OAUTH_ACCESS_TOKEN.getValue());\n    Assertions.assertEquals(\n        // pragma: allowlist nextline secret\n        \"A7C7EBB89312E88552CD00664A0E20929801FACFBD682BF7C2363FB6EC8F914E\", hashedKey);\n\n    hashedKey =\n        SecureStorageManager.buildCredentialsKey(\n            host, user, CachedCredentialType.OAUTH_REFRESH_TOKEN.getValue());\n    Assertions.assertEquals(\n        // pragma: allowlist nextline secret\n        \"DB37028833FA02B125FBD6DE8CE679C7E62E7D38FAC585E98060E00987F96772\", hashedKey);\n\n    hashedKey =\n        SecureStorageManager.buildCredentialsKey(\n            host, user, CachedCredentialType.ID_TOKEN.getValue());\n    Assertions.assertEquals(\n        // pragma: allowlist nextline secret\n        \"6AA3F783E07D1D2182DAB59442806E2433C55C2BD4D9240790FD5B4B91FD4FDB\", hashedKey);\n\n    hashedKey =\n        SecureStorageManager.buildCredentialsKey(\n            host, user, CachedCredentialType.MFA_TOKEN.getValue());\n    Assertions.assertEquals(\n        // pragma: allowlist nextline secret\n        \"9D10D4EFE45605D85993C6AC95334F1B63D36611B83615656EC7F277A947BF4B\", hashedKey);\n  }\n\n  @Test\n  @RunOnWindowsOrMac\n  public void testLoadNativeLibrary() {\n    // Only run on Mac or Windows. Make sure the loading of native platform library won't break.\n    if (Constants.getOS() == Constants.OS.MAC) {\n      assertThat(SecureStorageAppleManager.SecurityLibManager.getInstance(), is(notNullValue()));\n    }\n\n    if (Constants.getOS() == Constants.OS.WINDOWS) {\n      assertThat(SecureStorageWindowsManager.Advapi32LibManager.getInstance(), is(notNullValue()));\n    }\n  }\n\n  @Test\n  @RunOnWindows\n  public void testWindowsManager() {\n    SecureStorageWindowsManager.Advapi32LibManager.setInstance(new MockAdvapi32Lib());\n    SecureStorageManager manager = SecureStorageWindowsManager.builder();\n\n    testBody(manager);\n    SecureStorageWindowsManager.Advapi32LibManager.resetInstance();\n  }\n\n  @Test\n  @RunOnMac\n  public void testMacManager() {\n    SecureStorageAppleManager.SecurityLibManager.setInstance(new MockSecurityLib());\n    SecureStorageManager manager = SecureStorageAppleManager.builder();\n\n    testBody(manager);\n    SecureStorageAppleManager.SecurityLibManager.resetInstance();\n  }\n\n  @Test\n  @RunOnLinux\n  public void testLinuxManager() {\n    String cacheDirectory =\n        Paths.get(systemGetProperty(\"user.home\"), \".cache\", \"snowflake_test_cache\")\n            .toAbsolutePath()\n            .toString();\n    try (MockedStatic<SnowflakeUtil> snowflakeUtilMockedStatic =\n        Mockito.mockStatic(SnowflakeUtil.class)) {\n      snowflakeUtilMockedStatic\n          .when(() -> SnowflakeUtil.byteToHexString(Mockito.any(byte[].class)))\n          .thenCallRealMethod();\n      snowflakeUtilMockedStatic\n          .when(\n              () ->\n                  SnowflakeUtil.systemGetProperty(\"net.snowflake.jdbc.temporaryCredentialCacheDir\"))\n          .thenReturn(cacheDirectory);\n      SecureStorageManager manager = SecureStorageLinuxManager.getInstance();\n\n      testBody(manager);\n      testDeleteLinux(manager);\n    }\n  }\n\n  private void testBody(SecureStorageManager manager) {\n    // first delete possible old credential\n    assertThat(\n        manager.deleteCredential(host, user, ID_TOKEN),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n\n    // ensure no old credential exists\n    assertThat(manager.getCredential(host, user, ID_TOKEN), is(nullValue()));\n\n    // set token\n    assertThat(\n        manager.setCredential(host, user, ID_TOKEN, idToken),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n    assertThat(manager.getCredential(host, user, ID_TOKEN), equalTo(idToken));\n\n    // update token\n    assertThat(\n        manager.setCredential(host, user, ID_TOKEN, idToken0),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n    assertThat(manager.getCredential(host, user, ID_TOKEN), equalTo(idToken0));\n\n    // delete token\n    assertThat(\n        manager.deleteCredential(host, user, ID_TOKEN),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n    assertThat(manager.getCredential(host, user, ID_TOKEN), is(nullValue()));\n  }\n\n  private void testDeleteLinux(SecureStorageManager manager) {\n    // The old delete api of local file cache on Linux was to remove the whole file, where we can't\n    // partially remove some credentials\n    // This test aims to test the new delete api\n\n    // first create two credentials\n    assertThat(\n        manager.setCredential(host, user, ID_TOKEN, idToken),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n    assertThat(\n        manager.setCredential(host, user, MFA_TOKEN, mfaToken),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n    assertThat(manager.getCredential(host, user, ID_TOKEN), equalTo(idToken));\n    assertThat(manager.getCredential(host, user, MFA_TOKEN), equalTo(mfaToken));\n\n    // delete one of them\n    assertThat(\n        manager.deleteCredential(host, user, ID_TOKEN),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n    assertThat(manager.getCredential(host, user, ID_TOKEN), equalTo(null));\n\n    // check another one\n    assertThat(manager.getCredential(host, user, MFA_TOKEN), equalTo(mfaToken));\n\n    assertThat(\n        manager.deleteCredential(host, user, MFA_TOKEN),\n        equalTo(SecureStorageManager.SecureStorageStatus.SUCCESS));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SessionUtilExternalBrowserTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.junit.Assert.fail;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.when;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\nimport net.snowflake.client.api.datasource.SnowflakeDataSourceFactory;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.hamcrest.MatcherAssert;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\nclass MockAuthExternalBrowserHandlers\n    implements SessionUtilExternalBrowser.AuthExternalBrowserHandlers {\n  @Override\n  public HttpPost build(URI uri) {\n    HttpPost httpPost = mock(HttpPost.class);\n    when(httpPost.getMethod()).thenReturn(\"POST\");\n    return httpPost;\n  }\n\n  @Override\n  public void openBrowser(String ssoUrl) throws SFException {\n    // nop. Don't open browser\n  }\n\n  @Override\n  public void output(String msg) {\n    // nop. No output\n  }\n}\n\n/** Simulates SessionUtilExternalBrower without popping up a browser window */\nclass FakeSessionUtilExternalBrowser extends SessionUtilExternalBrowser {\n  static final String MOCK_SAML_TOKEN = \"MOCK_SAML_TOKEN\";\n  private final ServerSocket mockServerSocket;\n\n  public static SessionUtilExternalBrowser createInstance(SFLoginInput loginInput, boolean isPost) {\n    return new FakeSessionUtilExternalBrowser(loginInput, isPost);\n  }\n\n  private FakeSessionUtilExternalBrowser(SFLoginInput loginInput, boolean isPost) {\n    super(loginInput, new MockAuthExternalBrowserHandlers());\n    try {\n      this.mockServerSocket = initMockServerSocket(isPost);\n    } catch (IOException ex) {\n      throw new RuntimeException(\"Failed to initialize ServerSocket mock\");\n    }\n  }\n\n  /**\n   * Mock ServerSocket and Socket.\n   *\n   * <p>Socket mock will be included in ServerSocket mock.\n   *\n   * @param isPost true if the response is POST request otherwise GET request\n   * @return Server socket\n   * @throws IOException if any IO error occurs\n   */\n  private static ServerSocket initMockServerSocket(boolean isPost) throws IOException {\n    // mock client socket\n    final Socket mockSocket = mock(Socket.class);\n    String str;\n    if (isPost) {\n      str =\n          String.format(\n              \"POST / HTTP/1.1\\r\\n\"\n                  + \"USER-AGENT: snowflake client\\r\\n\"\n                  + \"\\r\\n\"\n                  + \"token=%s&confirm=1\",\n              MOCK_SAML_TOKEN);\n    } else {\n      str =\n          String.format(\n              \"GET /?token=%s&confirm=1 HTTP/1.1\\r\\n\" + \"USER-AGENT: snowflake client\",\n              MOCK_SAML_TOKEN);\n    }\n    InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));\n    when(mockSocket.getInputStream()).thenReturn(stream);\n    when(mockSocket.getOutputStream()).thenReturn(new NullOutputStream());\n\n    // mock server socket\n    final ServerSocket mockServerSocket = mock(ServerSocket.class);\n    when(mockServerSocket.getLocalPort()).thenReturn(12345);\n    when(mockServerSocket.accept()).thenReturn(mockSocket);\n    return mockServerSocket;\n  }\n\n  static class NullOutputStream extends OutputStream {\n    @Override\n    public void write(int b) throws IOException {}\n  }\n\n  @Override\n  protected ServerSocket getServerSocket() throws SFException {\n    return mockServerSocket;\n  }\n\n  @Override\n  protected int getLocalPort(ServerSocket ssocket) {\n    return super.getLocalPort(ssocket);\n  }\n}\n\npublic class SessionUtilExternalBrowserTest {\n  private static final String MOCK_PROOF_KEY = \"specialkey\";\n  private static final String MOCK_SSO_URL = \"https://sso.someidp.net/\";\n\n  /**\n   * Unit test for SessionUtilExternalBrowser\n   *\n   * @throws Throwable if any error occurs\n   */\n  @Test\n  public void testSessionUtilExternalBrowser() throws Throwable {\n    final SFLoginInput loginInput = initMockLoginInput();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              new HttpResponseWithHeaders(\n                  \"{\\\"success\\\":\\\"true\\\",\\\"data\\\":{\\\"proofKey\\\":\\\"\"\n                      + MOCK_PROOF_KEY\n                      + \"\\\",\"\n                      + \" \\\"ssoUrl\\\":\\\"\"\n                      + MOCK_SSO_URL\n                      + \"\\\"}}\",\n                  new HashMap<>()));\n\n      SessionUtilExternalBrowser sub =\n          FakeSessionUtilExternalBrowser.createInstance(loginInput, false);\n      sub.authenticate();\n      MatcherAssert.assertThat(\n          \"\", sub.getToken(), equalTo(FakeSessionUtilExternalBrowser.MOCK_SAML_TOKEN));\n\n      sub = FakeSessionUtilExternalBrowser.createInstance(loginInput, true);\n      sub.authenticate();\n      MatcherAssert.assertThat(\n          \"\", sub.getToken(), equalTo(FakeSessionUtilExternalBrowser.MOCK_SAML_TOKEN));\n\n      sub = FakeSessionUtilExternalBrowser.createInstance(loginInput, false);\n      Mockito.when(loginInput.getDisableConsoleLogin()).thenReturn(false);\n      sub.authenticate();\n      MatcherAssert.assertThat(\n          \"\", sub.getToken(), equalTo(FakeSessionUtilExternalBrowser.MOCK_SAML_TOKEN));\n\n      sub = FakeSessionUtilExternalBrowser.createInstance(loginInput, false);\n      Mockito.when(loginInput.getDisableConsoleLogin())\n          .thenAnswer(\n              invocation -> {\n                throw new SocketTimeoutException(\"Test exception\");\n              });\n      try {\n        sub.authenticate();\n        fail(\"should have failed with an exception.\");\n      } catch (SFException ex) {\n        assertTrue(ex.getMessage().contains(\"External browser authentication failed\"));\n      }\n    }\n  }\n\n  /**\n   * Unit test for SessionUtilExternalBrowser (fail)\n   *\n   * @throws Throwable if any error occurs\n   */\n  @Test\n  public void testSessionUtilExternalBrowserFail() throws Throwable {\n    final SFLoginInput loginInput = initMockLoginInput();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              new HttpResponseWithHeaders(\n                  \"{\\\"success\\\":\\\"false\\\",\\\"code\\\":\\\"123456\\\",\\\"message\\\":\\\"errormes\\\"}\",\n                  new HashMap<>()));\n\n      SessionUtilExternalBrowser sub =\n          FakeSessionUtilExternalBrowser.createInstance(loginInput, false);\n      SnowflakeSQLException ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> {\n                sub.authenticate();\n              });\n      MatcherAssert.assertThat(\"Error is expected\", ex.getErrorCode(), equalTo(123456));\n    }\n  }\n\n  @Test\n  public void testBuildDefaultHandler() throws URISyntaxException {\n    SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers handler =\n        new SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers();\n    URI uri =\n        new URI(\"https://testaccount.snowflakecomputing.com:443/session/authenticator-request\");\n    HttpPost postReq = handler.build(uri);\n    assertEquals(\n        \"POST https://testaccount.snowflakecomputing.com:443/session/authenticator-request HTTP/1.1\",\n        postReq.toString());\n  }\n\n  @Test\n  public void testInvalidSSOUrl() {\n    SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers handler =\n        new SessionUtilExternalBrowser.DefaultAuthExternalBrowserHandlers();\n    SFException ex =\n        assertThrows(\n            SFException.class,\n            () -> {\n              handler.openBrowser(\"file://invalidUrl\");\n            });\n    assertTrue(ex.getMessage().contains(\"Invalid SSOUrl found\"));\n  }\n\n  /**\n   * Mock HttpUtil and SFLoginInput\n   *\n   * @return a mock object for SFLoginInput\n   */\n  private SFLoginInput initMockLoginInput() {\n    // mock SFLoginInput\n    SFLoginInput loginInput = mock(SFLoginInput.class);\n    when(loginInput.getServerUrl()).thenReturn(\"https://testaccount.snowflakecomputing.com/\");\n    when(loginInput.getAuthenticator()).thenReturn(\"externalbrowser\");\n    when(loginInput.getAccountName()).thenReturn(\"testaccount\");\n    when(loginInput.getUserName()).thenReturn(\"testuser\");\n    when(loginInput.getDisableConsoleLogin()).thenReturn(true);\n    return loginInput;\n  }\n\n  // Run this test manually to test disabling storing temporary credetials with external browser\n  // auth. This is valid for versions after 3.18.0.\n  @Test\n  @Disabled\n  public void testEnableClientStoreTemporaryCredential() throws Exception {\n    Map<String, String> params = AbstractDriverIT.getConnectionParameters();\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setAccount(params.get(\"account\"));\n    ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n    ds.setUser(params.get(\"user\"));\n    ds.setEnableClientStoreTemporaryCredential(false);\n\n    for (int i = 0; i < 3; i++) {\n      try (Connection con = ds.getConnection();\n          ResultSet rs = con.createStatement().executeQuery(\"SELECT 1\")) {\n        assertTrue(rs.next());\n      }\n    }\n  }\n\n  // Run this test manually to confirm external browser timeout is working. When test runs it will\n  // open a browser window for authentication, close the window, and you should get the expected\n  // error message within the set timeout. Valid for driver versions after 3.18.0.\n  @Test\n  @Disabled\n  public void testExternalBrowserTimeout() throws Exception {\n    Map<String, String> params = AbstractDriverIT.getConnectionParameters();\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setAccount(params.get(\"account\"));\n    ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n    ds.setUser(params.get(\"user\"));\n    ds.setBrowserResponseTimeout(10);\n    SnowflakeSQLLoggedException e =\n        assertThrows(\n            SnowflakeSQLLoggedException.class,\n            () -> {\n              ds.getConnection();\n            });\n    assertTrue(e.getMessage().contains(\"External browser authentication failed\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SessionUtilKeyPairTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.nimbusds.jwt.JWTClaimsSet;\nimport com.nimbusds.jwt.SignedJWT;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.PrivateKey;\nimport java.security.SecureRandom;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\n\npublic class SessionUtilKeyPairTest {\n\n  @ParameterizedTest\n  @CsvSource({\n    \"name.azure.deployment,NAME\",\n    \"account.region-cloud,ACCOUNT\",\n    \"multi.part.long.name,MULTI\",\n    \"single,SINGLE\"\n  })\n  public void testAccountNameWithDotsInJwtToken(String testName, String expected)\n      throws SFException, Exception {\n    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\");\n    keyPairGenerator.initialize(2048, new SecureRandom());\n    KeyPair keyPair = keyPairGenerator.generateKeyPair();\n    PrivateKey privateKey = keyPair.getPrivate();\n\n    String userName = \"testuser\";\n\n    SessionUtilKeyPair sessionUtil =\n        new SessionUtilKeyPair(privateKey, null, null, null, testName, userName);\n    String jwtToken = sessionUtil.issueJwtToken();\n\n    assertNotNull(jwtToken);\n\n    SignedJWT signedJWT = SignedJWT.parse(jwtToken);\n    JWTClaimsSet claims = signedJWT.getJWTClaimsSet();\n    String expectedSubject = expected + \".\" + userName.toUpperCase();\n\n    assertEquals(expectedSubject, claims.getSubject());\n\n    String issuer = claims.getIssuer();\n\n    assertTrue(issuer.startsWith(expectedSubject));\n\n    if (testName.contains(\".\")) {\n      assertFalse(issuer.contains(testName.toUpperCase()));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SessionUtilLatestIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static net.snowflake.client.TestUtil.systemGetEnv;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.when;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.RetryContextManager;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.Header;\nimport org.apache.http.HttpEntity;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.MockedStatic.Verification;\nimport org.mockito.Mockito;\n\n@Tag(TestTags.CORE)\npublic class SessionUtilLatestIT extends BaseJDBCTest {\n\n  /**\n   * Tests the JWT renew functionality when retrying login requests. To run, update environment\n   * variables to use connect with JWT authentication.\n   *\n   * @throws SFException\n   * @throws SnowflakeSQLException\n   */\n  @Disabled\n  @Test\n  public void testJwtAuthTimeoutRetry() throws SFException, SnowflakeSQLException {\n    final SFLoginInput loginInput = initMockLoginInput();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n    MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class);\n    SnowflakeSQLException ex =\n        new SnowflakeSQLException(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT, 0, true, 0);\n\n    mockedHttpUtil\n        .when(\n            () ->\n                HttpUtil.executeGeneralRequest(\n                    Mockito.any(HttpRequestBase.class),\n                    Mockito.anyInt(),\n                    Mockito.anyInt(),\n                    Mockito.anyInt(),\n                    Mockito.anyInt(),\n                    Mockito.nullable(HttpClientSettingsKey.class),\n                    Mockito.nullable(SFBaseSession.class)))\n        .thenThrow(ex) // fail first\n        .thenReturn(\n            \"{\\\"data\\\":null,\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\"); // succeed on retry\n\n    SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n  }\n\n  /**\n   * Mock SFLoginInput\n   *\n   * @return a mock object for SFLoginInput\n   */\n  private SFLoginInput initMockLoginInput() {\n    // mock SFLoginInput\n    SFLoginInput loginInput = mock(SFLoginInput.class);\n    when(loginInput.getServerUrl()).thenReturn(systemGetEnv(\"SNOWFLAKE_TEST_HOST\"));\n    when(loginInput.getAuthenticator()).thenReturn(AuthenticatorType.SNOWFLAKE_JWT.name());\n    when(loginInput.getPrivateKeyFile())\n        .thenReturn(systemGetEnv(\"SNOWFLAKE_TEST_PRIVATE_KEY_FILE\"));\n    when(loginInput.getPrivateKeyPwd()).thenReturn(systemGetEnv(\"SNOWFLAKE_TEST_PRIVATE_KEY_PWD\"));\n    when(loginInput.getUserName()).thenReturn(systemGetEnv(\"SNOWFLAKE_TEST_USER\"));\n    when(loginInput.getAccountName()).thenReturn(\"testaccount\");\n    when(loginInput.getAppId()).thenReturn(\"testid\");\n    when(loginInput.getOCSPMode()).thenReturn(OCSPMode.FAIL_OPEN);\n    when(loginInput.getHttpClientSettingsKey())\n        .thenReturn(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    return loginInput;\n  }\n\n  /**\n   * Initialize the connection properties map.\n   *\n   * @return connectionPropertiesMap\n   */\n  private Map<SFSessionProperty, Object> initConnectionPropertiesMap() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n    connectionPropertiesMap.put(SFSessionProperty.TRACING, \"ALL\");\n    return connectionPropertiesMap;\n  }\n\n  @Test\n  public void testConvertSystemPropertyToIntValue() {\n    assertEquals(1, SystemUtil.convertSystemPropertyToIntValue(\"test.property\", 1));\n    System.setProperty(\"test.property\", \"-1\");\n    assertEquals(-1, SystemUtil.convertSystemPropertyToIntValue(\"test.property\", 1));\n  }\n\n  /**\n   * SNOW-862760 Tests that when additional headers are set on a login request, they are forwarded\n   * to the recipient.\n   */\n  @Test\n  public void testForwardedHeaders() throws Throwable {\n    SFLoginInput input = createLoginInput();\n    Map<String, String> additionalHeaders = new HashMap<>();\n    additionalHeaders.put(\"Extra-Snowflake-Header\", \"present\");\n\n    input.setAdditionalHttpHeadersForSnowsight(additionalHeaders);\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      // Both mocks the call _and_ verifies that the headers are forwarded.\n      Verification httpCalledWithHeaders =\n          () ->\n              HttpUtil.executeGeneralRequestWithContext(\n                  Mockito.argThat(\n                      arg -> {\n                        for (Entry<String, String> definedHeader : additionalHeaders.entrySet()) {\n                          Header actualHeader = arg.getLastHeader(definedHeader.getKey());\n                          if (actualHeader == null) {\n                            return false;\n                          }\n\n                          if (!definedHeader.getValue().equals(actualHeader.getValue())) {\n                            return false;\n                          }\n                        }\n\n                        return true;\n                      }),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.nullable(HttpClientSettingsKey.class),\n                  Mockito.nullable(SFBaseSession.class));\n      mockedHttpUtil\n          .when(httpCalledWithHeaders)\n          .thenReturn(\n              new HttpResponseWithHeaders(\n                  \"{\\\"data\\\":null,\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\",\n                  new HashMap<>()));\n\n      mockedHttpUtil\n          .when(() -> HttpUtil.applyAdditionalHeadersForSnowsight(any(), any()))\n          .thenCallRealMethod();\n\n      SessionUtil.openSession(input, connectionPropertiesMap, \"ALL\");\n\n      // After login, the only invocation to http should have been with the new\n      // headers.\n      // No calls should have happened without additional headers.\n      mockedHttpUtil.verify(httpCalledWithHeaders, times(1));\n    }\n  }\n\n  /**\n   * SNOW-862760 Verifies that, if inFlightCtx is provided to the login input, it's forwarded as\n   * part of the message body.\n   */\n  @Test\n  public void testForwardInflightCtx() throws Throwable {\n    SFLoginInput input = createLoginInput();\n    String inflightCtx = UUID.randomUUID().toString();\n    input.setInFlightCtx(inflightCtx);\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      // Both mocks the call _and_ verifies that the headers are forwarded.\n      Verification httpCalledWithHeaders =\n          () ->\n              HttpUtil.executeGeneralRequestWithContext(\n                  Mockito.argThat(\n                      arg -> {\n                        try {\n                          // This gets tricky because the entity is a string.\n                          // To not fail on JSON parsing changes, we'll verify that the key\n                          // inFlightCtx is present and the random UUID body\n                          HttpEntity entity = ((HttpPost) arg).getEntity();\n                          InputStream is = entity.getContent();\n                          ByteArrayOutputStream out = new ByteArrayOutputStream();\n                          IOUtils.copy(is, out);\n                          String body = new String(out.toByteArray());\n                          return body.contains(\"inFlightCtx\") && body.contains(inflightCtx);\n                        } catch (UnsupportedOperationException | IOException e) {\n                        }\n                        return false;\n                      }),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.nullable(HttpClientSettingsKey.class),\n                  Mockito.nullable(SFBaseSession.class));\n      mockedHttpUtil\n          .when(httpCalledWithHeaders)\n          .thenReturn(\n              new HttpResponseWithHeaders(\n                  \"{\\\"data\\\":null,\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\",\n                  new HashMap<>()));\n\n      mockedHttpUtil\n          .when(() -> HttpUtil.applyAdditionalHeadersForSnowsight(any(), any()))\n          .thenCallRealMethod();\n\n      SessionUtil.openSession(input, connectionPropertiesMap, \"ALL\");\n\n      // After login, the only invocation to http should have been with the new\n      // headers.\n      // No calls should have happened without additional headers.\n      mockedHttpUtil.verify(httpCalledWithHeaders, times(1));\n    }\n  }\n\n  private SFLoginInput createLoginInput() {\n    SFLoginInput input = new SFLoginInput();\n    input.setServerUrl(\"MOCK_TEST_HOST\");\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setPassword(\"MOCK_PASSWORD\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(1000);\n    input.setSessionParameters(new HashMap<>());\n    return input;\n  }\n\n  @Test\n  public void testOktaAuthPostFail() throws Throwable {\n    SFLoginInput loginInput = createLoginInput();\n    loginInput.setAuthenticator(\"https://testauth.okta.com\");\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\"{\\\"code\\\":null,\\\"message\\\":\\\"POST request failed\\\",\\\"success\\\":false}\");\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n      assertEquals(\"POST request failed\", e.getMessage());\n      assertEquals(SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, e.getSQLState());\n    }\n  }\n\n  @Test\n  public void testOktaAuthMalformedUrl() throws Throwable {\n    SFLoginInput loginInput = createLoginInput();\n    loginInput.setAuthenticator(\"invalid!@url$%^\");\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"invalid!@url$%^\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"invalid!@url$%^\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n      assertEquals((int) ErrorCode.NETWORK_ERROR.getMessageCode(), e.getErrorCode());\n      assertEquals(SqlState.IO_ERROR, e.getSQLState());\n    }\n  }\n\n  @Test\n  public void testOktaAuthURISyntaxError() throws Throwable {\n    SFLoginInput loginInput = createLoginInput();\n    loginInput.setAuthenticator(\"https://testauth.okta.com/^123\");\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/^123\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/^123\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n      assertEquals((int) ErrorCode.CONNECTION_ERROR.getMessageCode(), e.getErrorCode());\n      assertEquals(SqlState.SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, e.getSQLState());\n    }\n  }\n\n  @Test\n  public void testOktaAuthGetFail() throws Throwable {\n    SFLoginInput loginInput = createLoginInput();\n    loginInput.setAuthenticator(\"https://testauth.okta.com\");\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/api/v1/authn\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeRequestWithoutCookies(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(AtomicBoolean.class),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"expiresAt\\\":\\\"2023-10-13T19:18:09.000Z\\\",\\\"status\\\":\\\"SUCCESS\\\",\\\"sessionToken\\\":\\\"testsessiontoken\\\"}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpGet.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(RetryContextManager.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenThrow(new IOException());\n\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n      assertEquals((int) ErrorCode.NETWORK_ERROR.getMessageCode(), e.getErrorCode());\n      assertEquals(SqlState.IO_ERROR, e.getSQLState());\n    }\n  }\n\n  private SFLoginInput createOktaLoginInput() {\n    SFLoginInput input = new SFLoginInput();\n    input.setServerUrl(\"https://testauth.okta.com\");\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setPassword(\"MOCK_PASSWORD\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(1000);\n    input.setSessionParameters(new HashMap<>());\n    input.setAuthenticator(\"https://testauth.okta.com\");\n    return input;\n  }\n\n  // Testing retry with Okta calls the service to get a new unique token. This is valid after\n  // version 3.15.1.\n  @Test\n  public void testOktaAuthRetriesUsingTimeoutExceptionRaisedWhenAuthenticatingInSnowflakeUsingOTT()\n      throws Throwable {\n    SFLoginInput loginInput = createOktaLoginInput();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n    SnowflakeSQLException ex =\n        new SnowflakeSQLException(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT, 0, true, 0);\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/api/v1/authn\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\")\n          .thenThrow(ex)\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/api/v1/authn\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeRequestWithoutCookies(\n                      any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(AtomicBoolean.class),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"expiresAt\\\":\\\"2023-10-13T19:18:09.000Z\\\",\\\"status\\\":\\\"SUCCESS\\\",\\\"sessionToken\\\":\\\"testsessiontoken\\\"}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpGet.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(RetryContextManager.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\"<body><form action=\\\"https://testauth.okta.com\\\"></form></body>\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              new HttpResponseWithHeaders(\n                  \"{\\\"data\\\":null,\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\",\n                  new HashMap<>()));\n\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n    }\n  }\n\n  /**\n   * Tests the disableSamlURLCheck. If the disableSamlUrl is provided to the login input with true,\n   * the driver will skip checking the format of the saml URL response. This latest test will work\n   * with jdbc > 3.16.0\n   *\n   * @throws Throwable\n   */\n  @Test\n  public void testOktaDisableSamlUrlCheck() throws Throwable {\n    SFLoginInput loginInput = createOktaLoginInput();\n    loginInput.setDisableSamlURLCheck(true);\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/api/v1/authn\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeRequestWithoutCookies(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(AtomicBoolean.class),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"expiresAt\\\":\\\"2023-10-13T19:18:09.000Z\\\",\\\"status\\\":\\\"SUCCESS\\\",\\\"sessionToken\\\":\\\"test-session-token-2\\\"}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpGet.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(RetryContextManager.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\"<body><form action=\\\"invalidformError\\\"></form></body>\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              new HttpResponseWithHeaders(\n                  \"{\\\"data\\\":null,\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\",\n                  new HashMap<>()));\n\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n    }\n  }\n\n  @Test\n  public void testInvalidOktaSamlFormat() throws Throwable {\n    SFLoginInput loginInput = createOktaLoginInput();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/api/v1/authn\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeRequestWithoutCookies(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(AtomicBoolean.class),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"expiresAt\\\":\\\"2023-10-13T19:18:09.000Z\\\",\\\"status\\\":\\\"SUCCESS\\\",\\\"sessionToken\\\":\\\"testsessiontoken\\\"}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpGet.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(RetryContextManager.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\"<body><form action=\\\"invalidformError\\\"></form></body>\");\n\n      SnowflakeSQLException ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n      assertEquals((int) ErrorCode.NETWORK_ERROR.getMessageCode(), ex.getErrorCode());\n    }\n  }\n\n  @Test\n  public void testOktaWithInvalidHostName() throws Throwable {\n    SFLoginInput loginInput = createOktaLoginInput();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpPost.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"data\\\":{\\\"tokenUrl\\\":\\\"https://testauth.okta.com/api/v1/authn\\\",\"\n                  + \"\\\"ssoUrl\\\":\\\"https://testauth.okta.com/app/snowflake/abcdefghijklmnopqrstuvwxyz/sso/saml\\\",\"\n                  + \"\\\"proofKey\\\":null},\\\"code\\\":null,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeRequestWithoutCookies(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(AtomicBoolean.class),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\n              \"{\\\"expiresAt\\\":\\\"2023-10-13T19:18:09.000Z\\\",\\\"status\\\":\\\"SUCCESS\\\",\\\"sessionToken\\\":\\\"testsessiontoken\\\"}\");\n\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequest(\n                      Mockito.any(HttpGet.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.nullable(HttpClientSettingsKey.class),\n                      Mockito.nullable(RetryContextManager.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(\"<body><form action=\\\"https://helloworld.okta.com\\\"></form></body>\");\n\n      SnowflakeSQLException ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n      assertEquals((int) ErrorCode.IDP_INCORRECT_DESTINATION.getMessageCode(), ex.getErrorCode());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SessionUtilTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.BooleanNode;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.file.InvalidPathException;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.internal.core.minicore.MinicoreLoadResult;\nimport net.snowflake.client.internal.core.minicore.MinicoreTelemetry;\nimport net.snowflake.client.internal.jdbc.MockConnectionTest;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\npublic class SessionUtilTest {\n  private static String originalUrlValue;\n  private static String originalRetryUrlPattern;\n\n  @BeforeAll\n  public static void saveStaticValues() {\n    originalUrlValue = SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE;\n    originalRetryUrlPattern = SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN;\n  }\n\n  @AfterAll\n  public static void restoreStaticValues() {\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = originalUrlValue;\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = originalRetryUrlPattern;\n  }\n\n  /** Test isPrefixEqual */\n  @Test\n  public void testIsPrefixEqual() throws Exception {\n    assertThat(\n        \"no port number\",\n        SessionUtil.isPrefixEqual(\n            \"https://testaccount.snowflakecomputing.com/blah\",\n            \"https://testaccount.snowflakecomputing.com/\"));\n    assertThat(\n        \"no port number with a slash\",\n        SessionUtil.isPrefixEqual(\n            \"https://testaccount.snowflakecomputing.com/blah\",\n            \"https://testaccount.snowflakecomputing.com\"));\n    assertThat(\n        \"including a port number on one of them\",\n        SessionUtil.isPrefixEqual(\n            \"https://testaccount.snowflakecomputing.com/blah\",\n            \"https://testaccount.snowflakecomputing.com:443/\"));\n\n    // negative\n    assertThat(\n        \"different hostnames\",\n        !SessionUtil.isPrefixEqual(\n            \"https://testaccount1.snowflakecomputing.com/blah\",\n            \"https://testaccount2.snowflakecomputing.com/\"));\n    assertThat(\n        \"different port numbers\",\n        !SessionUtil.isPrefixEqual(\n            \"https://testaccount.snowflakecomputing.com:123/blah\",\n            \"https://testaccount.snowflakecomputing.com:443/\"));\n    assertThat(\n        \"different protocols\",\n        !SessionUtil.isPrefixEqual(\n            \"http://testaccount.snowflakecomputing.com/blah\",\n            \"https://testaccount.snowflakecomputing.com/\"));\n  }\n\n  @Test\n  public void testParameterParsing() {\n    Map<String, Object> parameterMap = new HashMap<>();\n    parameterMap.put(\"other_parameter\", BooleanNode.getTrue());\n    SFBaseSession session = new MockConnectionTest.MockSnowflakeConnectionImpl().getSFSession();\n    SessionUtil.updateSfDriverParamValues(parameterMap, session);\n    assertTrue(((BooleanNode) session.getOtherParameter(\"other_parameter\")).asBoolean());\n  }\n\n  @Test\n  public void testConvertSystemPropertyToIntValue() {\n    try {\n      // Test that setting real value works\n      System.setProperty(\"net.snowflake.jdbc.max_connections\", \"500\");\n      assertEquals(\n          500,\n          SystemUtil.convertSystemPropertyToIntValue(\n              HttpUtil.JDBC_MAX_CONNECTIONS_PROPERTY, HttpUtil.DEFAULT_MAX_CONNECTIONS));\n      // Test that entering a non-int sets the value to the default\n      System.setProperty(\"net.snowflake.jdbc.max_connections\", \"notAnInteger\");\n      assertEquals(\n          HttpUtil.DEFAULT_MAX_CONNECTIONS,\n          SystemUtil.convertSystemPropertyToIntValue(\n              HttpUtil.JDBC_MAX_CONNECTIONS_PROPERTY, HttpUtil.DEFAULT_MAX_CONNECTIONS));\n      // Test another system property\n      System.setProperty(\"net.snowflake.jdbc.max_connections_per_route\", \"30\");\n      assertEquals(\n          30,\n          SystemUtil.convertSystemPropertyToIntValue(\n              HttpUtil.JDBC_MAX_CONNECTIONS_PER_ROUTE_PROPERTY,\n              HttpUtil.DEFAULT_MAX_CONNECTIONS_PER_ROUTE));\n    } finally {\n      System.clearProperty(\"net.snowflake.jdbc.max_connections\");\n      System.clearProperty(\"net.snowflake.jdbc.max_connections_per_route\");\n    }\n  }\n\n  @Test\n  public void testIsLoginRequest() {\n    List<String> testCases = new ArrayList<String>();\n    testCases.add(\"/session/v1/login-request\");\n    testCases.add(\"/session/token-request\");\n    testCases.add(\"/session/authenticator-request\");\n\n    for (String testCase : testCases) {\n      try {\n        URIBuilder uriBuilder = new URIBuilder(\"https://test.snowflakecomputing.com\");\n        uriBuilder.setPath(testCase);\n        URI uri = uriBuilder.build();\n        HttpPost postRequest = new HttpPost(uri);\n        assertTrue(SessionUtil.isNewRetryStrategyRequest(postRequest));\n      } catch (URISyntaxException e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }\n\n  @Test\n  public void testIsLoginRequestInvalidURIPath() {\n    List<String> testCases = new ArrayList<String>();\n    testCases.add(\"/session/not-a-real-path\");\n\n    for (String testCase : testCases) {\n      try {\n        URIBuilder uriBuilder = new URIBuilder(\"https://test.snowflakecomputing.com\");\n        uriBuilder.setPath(testCase);\n        URI uri = uriBuilder.build();\n        HttpPost postRequest = new HttpPost(uri);\n        assertFalse(SessionUtil.isNewRetryStrategyRequest(postRequest));\n      } catch (URISyntaxException e) {\n        throw new RuntimeException(e);\n      }\n    }\n  }\n\n  @Test\n  public void shouldDerivePrivateLinkOcspCacheServerUrlBasedOnHost() throws IOException {\n    resetOcspConfiguration();\n\n    SessionUtil.resetOCSPUrlIfNecessary(\"https://test.privatelink.snowflakecomputing.com\");\n    assertEquals(\n        \"http://ocsp.test.privatelink.snowflakecomputing.com/ocsp_response_cache.json\",\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);\n    assertEquals(\n        \"http://ocsp.test.privatelink.snowflakecomputing.com/retry/%s/%s\",\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);\n\n    resetOcspConfiguration();\n\n    SessionUtil.resetOCSPUrlIfNecessary(\"https://test.privatelink.snowflakecomputing.cn\");\n    assertEquals(\n        \"http://ocsp.test.privatelink.snowflakecomputing.cn/ocsp_response_cache.json\",\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);\n    assertEquals(\n        \"http://ocsp.test.privatelink.snowflakecomputing.cn/retry/%s/%s\",\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);\n\n    resetOcspConfiguration();\n\n    SessionUtil.resetOCSPUrlIfNecessary(\"https://test.privatelink.snowflakecomputing.xyz\");\n    assertEquals(\n        \"http://ocsp.test.privatelink.snowflakecomputing.xyz/ocsp_response_cache.json\",\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE);\n    assertEquals(\n        \"http://ocsp.test.privatelink.snowflakecomputing.xyz/retry/%s/%s\",\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN);\n  }\n\n  @Test\n  public void testGetCommonParams() throws Exception {\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n\n    // Test unknown param name\n    Map<String, Object> result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"testParam\\\", \\\"value\\\": true}]\"));\n    assertTrue((boolean) result.get(\"testParam\"));\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"testParam\\\", \\\"value\\\": false}]\"));\n    assertFalse((boolean) result.get(\"testParam\"));\n\n    result =\n        SessionUtil.getCommonParams(mapper.readTree(\"[{\\\"name\\\": \\\"testParam\\\", \\\"value\\\": 0}]\"));\n    assertEquals(0, (int) result.get(\"testParam\"));\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"testParam\\\", \\\"value\\\": 1000}]\"));\n    assertEquals(1000, (int) result.get(\"testParam\"));\n\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"testParam\\\", \\\"value\\\": \\\"\\\"}]\"));\n    assertEquals(\"\", result.get(\"testParam\"));\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"testParam\\\", \\\"value\\\": \\\"value\\\"}]\"));\n    assertEquals(\"value\", result.get(\"testParam\"));\n\n    // Test known param name\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"CLIENT_DISABLE_INCIDENTS\\\", \\\"value\\\": true}]\"));\n    assertTrue((boolean) result.get(\"CLIENT_DISABLE_INCIDENTS\"));\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"CLIENT_DISABLE_INCIDENTS\\\", \\\"value\\\": false}]\"));\n    assertFalse((boolean) result.get(\"CLIENT_DISABLE_INCIDENTS\"));\n\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\n                \"[{\\\"name\\\": \\\"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\\\", \\\"value\\\": 0}]\"));\n    assertEquals(0, (int) result.get(\"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\"));\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\n                \"[{\\\"name\\\": \\\"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\\\", \\\"value\\\": 1000}]\"));\n    assertEquals(1000, (int) result.get(\"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\"));\n\n    result =\n        SessionUtil.getCommonParams(mapper.readTree(\"[{\\\"name\\\": \\\"TIMEZONE\\\", \\\"value\\\": \\\"\\\"}]\"));\n    assertEquals(\"\", result.get(\"TIMEZONE\"));\n    result =\n        SessionUtil.getCommonParams(\n            mapper.readTree(\"[{\\\"name\\\": \\\"TIMEZONE\\\", \\\"value\\\": \\\"value\\\"}]\"));\n    assertEquals(\"value\", result.get(\"TIMEZONE\"));\n  }\n\n  private void resetOcspConfiguration() {\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL_VALUE = null;\n    SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = null;\n  }\n\n  @Test\n  public void testOktaAuthRequestsAreRetriedUsingLoginRetryStrategy() {\n    final String oktaSSOAuthPath = \"api/v1/authn\";\n    final String oktaTokenAuthPath = \"app/snowflake/tokenlikepartofurl/sso/saml\";\n    List<String> oktaAuthURLs = new ArrayList<String>();\n    oktaAuthURLs.add(\"https://anytestpath.okta.com\"); // default *.okta.com URL\n    oktaAuthURLs.add(\"https://vanity-url.somecompany.com\"); // some custom Vanity OKTA URL\n\n    for (String oktaAuthURL : oktaAuthURLs) {\n      try {\n        // Check that SSO path is recognized as the new retry strategy\n        assertThatPathIsRecognizedAsNewRetryStrategy(oktaAuthURL, oktaSSOAuthPath);\n        // Check that Token path is recognized as the new retry strategy\n        assertThatPathIsRecognizedAsNewRetryStrategy(oktaAuthURL, oktaTokenAuthPath);\n      } catch (URISyntaxException e) {\n        fail(\n            \"Test case data cannot be treated as a valid URL and path. Check test input data. Error: \"\n                + e.getMessage());\n      }\n    }\n  }\n\n  private void assertThatPathIsRecognizedAsNewRetryStrategy(String uriToTest, String pathToTest)\n      throws URISyntaxException {\n    URIBuilder uriBuilder = new URIBuilder(uriToTest);\n    uriBuilder.setPath(pathToTest);\n    URI uri = uriBuilder.build();\n    HttpPost postRequest = new HttpPost(uri);\n    assertThat(\n        \"New retry strategy (designed to serve login-like requests) should be used for okta authn endpoint authentication.\",\n        SessionUtil.isNewRetryStrategyRequest(postRequest));\n  }\n\n  @Test\n  public void testCreateClientEnvironmentInfo() {\n    // GIVEN\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setOCSPMode(OCSPMode.FAIL_OPEN);\n    loginInput.setApplication(\"TestApp\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n    connectionPropertiesMap.put(SFSessionProperty.USER, \"testuser\");\n    connectionPropertiesMap.put(SFSessionProperty.PASSWORD, \"testpass\");\n    connectionPropertiesMap.put(\n        SFSessionProperty.SERVER_URL, \"https://test.snowflakecomputing.com\");\n    connectionPropertiesMap.put(SFSessionProperty.HTTP_CLIENT_SOCKET_TIMEOUT, 30000);\n    connectionPropertiesMap.put(SFSessionProperty.HTTP_CLIENT_CONNECTION_TIMEOUT, 10000);\n\n    String tracingLevel = \"INFO\";\n    AuthenticatorType authenticatorType = AuthenticatorType.SNOWFLAKE;\n\n    // WHEN\n    Map<String, Object> clientEnv =\n        SessionUtil.createClientEnvironmentInfo(\n            loginInput, connectionPropertiesMap, tracingLevel, authenticatorType);\n\n    // THEN\n    // Verify basic environment properties\n    assertThat(\"OS should be set\", clientEnv.containsKey(\"OS\"));\n    assertThat(\"OS_VERSION should be set\", clientEnv.containsKey(\"OS_VERSION\"));\n    assertThat(\"JAVA_VERSION should be set\", clientEnv.containsKey(\"JAVA_VERSION\"));\n    assertThat(\"JAVA_RUNTIME should be set\", clientEnv.containsKey(\"JAVA_RUNTIME\"));\n    assertThat(\"JAVA_VM should be set\", clientEnv.containsKey(\"JAVA_VM\"));\n    assertThat(\"OCSP_MODE should be set\", clientEnv.containsKey(\"OCSP_MODE\"));\n    assertThat(\"JDBC_JAR_NAME should be set\", clientEnv.containsKey(\"JDBC_JAR_NAME\"));\n\n    // Verify application path is set\n    assertThat(\"APPLICATION_PATH should be set\", clientEnv.containsKey(\"APPLICATION_PATH\"));\n    assertThat(\"APPLICATION_PATH should not be null\", clientEnv.get(\"APPLICATION_PATH\") != null);\n    assertThat(\n        \"APPLICATION_PATH should be a string\", clientEnv.get(\"APPLICATION_PATH\") instanceof String);\n\n    // Verify application name is set from loginInput\n    assertThat(\n        \"APPLICATION should be set to TestApp\", \"TestApp\".equals(clientEnv.get(\"APPLICATION\")));\n\n    // Verify OCSP mode is set correctly\n    assertThat(\"OCSP_MODE should be FAIL_OPEN\", \"FAIL_OPEN\".equals(clientEnv.get(\"OCSP_MODE\")));\n\n    // Verify connection parameters are included (with masked values)\n    assertThat(\"User parameter should be included\", clientEnv.containsKey(\"user\"));\n    assertThat(\"User has correct value\", clientEnv.get(\"user\").toString().contains(\"testuser\"));\n    assertThat(\"Server URL should be included\", clientEnv.containsKey(\"serverURL\"));\n    assertThat(\n        \"Socket timeout should be included\", clientEnv.containsKey(\"HTTP_CLIENT_SOCKET_TIMEOUT\"));\n    assertThat(\n        \"Connection timeout should be included\",\n        clientEnv.containsKey(\"HTTP_CLIENT_CONNECTION_TIMEOUT\"));\n\n    // Verify tracing level is set\n    assertThat(\"Tracing should be set to INFO\", \"INFO\".equals(clientEnv.get(\"tracing\")));\n\n    // Verify APPLICATION_PATH is a valid file path\n    String applicationPath = (String) clientEnv.get(\"APPLICATION_PATH\");\n    assertThat(\"APPLICATION_PATH should not be empty\", !applicationPath.isEmpty());\n    assertThat(\"APPLICATION_PATH should contain file path\", isValidPath(applicationPath));\n\n    // Verify logging implementation is reported\n    assertThat(\n        \"LOGGING_IMPLEMENTATION should be set\", clientEnv.containsKey(\"LOGGING_IMPLEMENTATION\"));\n    String loggingImpl = (String) clientEnv.get(\"LOGGING_IMPLEMENTATION\");\n    assertThat(\n        \"LOGGING_IMPLEMENTATION should be JUL or SLF4J\",\n        \"JUL\".equals(loggingImpl) || \"SLF4J\".equals(loggingImpl));\n  }\n\n  @Test\n  public void testMinicoreTelemetryWithSuccessfulLoad() {\n    // Create a mock successful load result\n    List<String> logs = new ArrayList<>();\n    logs.add(\"Starting minicore loading\");\n    logs.add(\"Platform supported\");\n    logs.add(\"Minicore library loaded successfully\");\n\n    MinicoreLoadResult successResult =\n        MinicoreLoadResult.success(\n            \"libsf_mini_core.so\",\n            null, // library instance not needed for this test\n            \"1.0.0\",\n            logs);\n\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(successResult);\n    Map<String, Object> telemetryMap = telemetry.toClientEnvironmentTelemetryMap();\n\n    // THEN - Telemetry should contain success information\n    assertTrue(telemetryMap.containsKey(\"ISA\"), \"ISA should be set\");\n    assertNotNull(telemetryMap.get(\"ISA\"), \"ISA should not be null\");\n\n    assertTrue(telemetryMap.containsKey(\"CORE_FILE_NAME\"), \"CORE_FILE_NAME should be set\");\n    assertEquals(\"libsf_mini_core.so\", telemetryMap.get(\"CORE_FILE_NAME\"));\n\n    assertTrue(telemetryMap.containsKey(\"CORE_VERSION\"), \"CORE_VERSION should be set on success\");\n    assertEquals(\"1.0.0\", telemetryMap.get(\"CORE_VERSION\"));\n    assertFalse(\n        telemetryMap.containsKey(\"CORE_LOAD_ERROR\"),\n        \"CORE_LOAD_ERROR should not be set on success\");\n  }\n\n  @Test\n  public void testMinicoreTelemetryWithFailedLoad() {\n    // Create a mock failed load result\n    List<String> logs = new ArrayList<>();\n    logs.add(\"Starting minicore loading\");\n    logs.add(\"Failed to load library\");\n\n    MinicoreLoadResult failedResult =\n        MinicoreLoadResult.failure(\n            \"Failed to load library: UnsatisfiedLinkError\",\n            \"libsf_mini_core.so\",\n            new UnsatisfiedLinkError(\"Cannot load library\"),\n            logs);\n\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(failedResult);\n    Map<String, Object> telemetryMap = telemetry.toClientEnvironmentTelemetryMap();\n\n    // THEN - Telemetry should contain error information\n    assertTrue(telemetryMap.containsKey(\"ISA\"), \"ISA should be set\");\n    assertNotNull(telemetryMap.get(\"ISA\"), \"ISA should not be null\");\n\n    assertTrue(telemetryMap.containsKey(\"CORE_FILE_NAME\"), \"CORE_FILE_NAME should be set\");\n    assertEquals(\"libsf_mini_core.so\", telemetryMap.get(\"CORE_FILE_NAME\"));\n\n    assertFalse(\n        telemetryMap.containsKey(\"CORE_VERSION\"), \"CORE_VERSION should not be set on failure\");\n  }\n\n  private static boolean isValidPath(String path) {\n    try {\n      Paths.get(path);\n    } catch (InvalidPathException | NullPointerException ex) {\n      return false;\n    }\n    return true;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SessionUtilWiremockIT.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mockConstruction;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport net.snowflake.client.internal.util.LibcDetails;\nimport net.snowflake.client.internal.util.LibcInfo;\nimport net.snowflake.client.internal.util.OsReleaseDetails;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedConstruction;\nimport org.mockito.MockedStatic;\n\n@Tag(TestTags.CORE)\npublic class SessionUtilWiremockIT extends BaseWiremockTest {\n  private static final int DECREASED_LOGIN_TIMEOUT = 5;\n  private static final String OKTA_VANITY_PATH = \"/okta-stub/vanity-url\";\n  private static final String OKTA_AUTH_API_ENDPOINT = OKTA_VANITY_PATH + \"/api/v1\";\n  private static final String OKTA_SAML_RESPONSE_SUBPATH = \"/sso/saml\";\n  private static final String ALWAYS_429_IN_FEDERATED_STEP_3 =\n      \"/wiremock/mappings/session/session-util-wiremock-it-always-429-in-federated-step-3.json\";\n  private static final String ALWAYS_429_IN_FEDERATED_STEP_4 =\n      \"/wiremock/mappings/session/session-util-wiremock-it-always-429-in-federated-step-4.json\";\n  private static final String MULTIPLE_429_IN_FEDERATED_STEP_3 =\n      \"/wiremock/mappings/session/session-util-wiremock-it-multiple-429-in-federated-step-3.json\";\n  private static final String MULTIPLE_429_IN_FEDERATED_STEP_4 =\n      \"/wiremock/mappings/session/session-util-wiremock-it-multiple-429-in-federated-step-4.json\";\n  private static final String UNSUPPORTED_MFA_IN_FEDERATED_STEP_3 =\n      \"/wiremock/mappings/session/session-util-wiremock-it-unsupported-mfa-in-federated-step-3.json\";\n\n  private static final String OS_DETAILS_LOGIN_MAPPING_PATH =\n      \"/wiremock/mappings/session/session-util-wiremock-it-os-details-in-login-request.json\";\n\n  private static final String LIBC_DETAILS_LOGIN_MAPPING_PATH =\n      \"/wiremock/mappings/session/session-util-wiremock-it-libc-details-in-login-request.json\";\n\n  private static final String SPCS_TOKEN_LOGIN_MAPPING_PATH =\n      \"/wiremock/mappings/session/session-util-wiremock-it-spcs-token-in-login-request.json\";\n\n  /**\n   * Minimum spacing we expect between consecutive requests, in milliseconds - associated with\n   * RestRequest minBackoff.\n   */\n  private static final long EXPECTED_MIN_RETRY_DELAY_MS = 1000;\n\n  private final String WIREMOCK_HOST_WITH_HTTPS = \"https://\" + WIREMOCK_HOST;\n  private final String WIREMOCK_HOST_WITH_HTTPS_AND_PORT =\n      WIREMOCK_HOST_WITH_HTTPS + \":\" + wiremockHttpsPort;\n\n  private SFLoginInput createOktaLoginInputBase() {\n    SFLoginInput input = new SFLoginInput();\n    input.setServerUrl(WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setPassword(\"MOCK_PASSWORD\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(1000);\n    input.setSessionParameters(new HashMap<>());\n    input.setAuthenticator(WIREMOCK_HOST_WITH_HTTPS_AND_PORT + OKTA_VANITY_PATH);\n    return input;\n  }\n\n  private Map<SFSessionProperty, Object> initConnectionPropertiesMap() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n    connectionPropertiesMap.put(SFSessionProperty.TRACING, \"ALL\");\n    return connectionPropertiesMap;\n  }\n\n  @Test\n  public void testOktaRetryWaitsUsingDefaultRetryStrategyWhen429InFederatedStep3()\n      throws Throwable {\n    // GIVEN\n    Map<String, Object> placeholders = new HashMap<>();\n    placeholders.put(\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}\", WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n\n    String wireMockMapping =\n        getWireMockMappingFromFile(MULTIPLE_429_IN_FEDERATED_STEP_3, placeholders);\n    importMapping(wireMockMapping);\n\n    setCustomTrustStorePropertyPath();\n\n    SFLoginInput loginInput = createOktaLoginInputBase();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    // WHEN\n    try {\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n    } catch (SnowflakeSQLException ex) {\n      fail(\"SessionUtil test failed with error: \" + ex.getMessage());\n    }\n\n    // THEN\n    List<MinimalServeEvent> allEvents = getAllServeEvents();\n\n    // Filter only events that hit \"/okta-stub/vanity-url/\".\n    List<MinimalServeEvent> vanityUrlCalls =\n        allEvents.stream()\n            .filter(e -> e.getRequest().getUrl().contains(OKTA_AUTH_API_ENDPOINT))\n            .sorted(Comparator.comparing(e -> e.getRequest().getLoggedDate()))\n            .collect(Collectors.toList());\n\n    assertThat(\n        \"Expected multiple calls to \" + OKTA_AUTH_API_ENDPOINT + \", got \" + vanityUrlCalls.size(),\n        vanityUrlCalls.size(),\n        greaterThan(2));\n\n    // Ensure each consecutive pair of calls has at least EXPECTED_MIN_RETRY_DELAY_MS gap\n    // (determined by RestRequest.minBackoffInMilli (=1000 ms)).\n    assertRequestsToWiremockHaveDelay(vanityUrlCalls, EXPECTED_MIN_RETRY_DELAY_MS);\n  }\n\n  @Test\n  public void testOktaRetryWaitsUsingDefaultRetryStrategyWhen429InFederatedStep4()\n      throws Throwable {\n    // GIVEN\n    Map<String, Object> placeholders = new HashMap<>();\n    placeholders.put(\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}\", WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n\n    String wireMockMapping =\n        getWireMockMappingFromFile(MULTIPLE_429_IN_FEDERATED_STEP_4, placeholders);\n    importMapping(wireMockMapping);\n\n    setCustomTrustStorePropertyPath();\n\n    SFLoginInput loginInput = createOktaLoginInputBase();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    // WHEN\n    try {\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n    } catch (SnowflakeSQLException ex) {\n      fail(ex.getMessage());\n    }\n\n    // THEN\n    List<MinimalServeEvent> allEvents = getAllServeEvents();\n\n    // Filter only events that hit the final endpoint (federated step 4) - using the retrieved\n    // token.\n    List<MinimalServeEvent> vanityUrlCalls =\n        allEvents.stream()\n            .filter(e -> e.getRequest().getUrl().contains(OKTA_SAML_RESPONSE_SUBPATH))\n            .sorted(Comparator.comparing(e -> e.getRequest().getLoggedDate()))\n            .collect(Collectors.toList());\n\n    assertThat(\n        \"Expected multiple calls to \"\n            + OKTA_SAML_RESPONSE_SUBPATH\n            + \", got \"\n            + vanityUrlCalls.size(),\n        vanityUrlCalls.size(),\n        greaterThan(2));\n\n    assertRequestsToWiremockHaveDelay(vanityUrlCalls, EXPECTED_MIN_RETRY_DELAY_MS);\n    assertRequestsToWiremockHaveDifferentValuesOfParameter(vanityUrlCalls, \"onetimetoken\");\n  }\n\n  @Test\n  public void testOktaRetriesUntilTimeoutThenRaisesAuthTimeoutExceptionWhen429InFederatedStep3()\n      throws Throwable {\n    // GIVEN\n    Map<String, Object> placeholders = new HashMap<>();\n    placeholders.put(\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}\", WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n\n    String wireMockMapping =\n        getWireMockMappingFromFile(ALWAYS_429_IN_FEDERATED_STEP_3, placeholders);\n    importMapping(wireMockMapping);\n\n    setCustomTrustStorePropertyPath();\n\n    SFLoginInput loginInput = createOktaLoginInputBase();\n    loginInput.setLoginTimeout(DECREASED_LOGIN_TIMEOUT); // decreased timeout for test purposes\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    // WHEN\n    try {\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n    } catch (SnowflakeSQLException ex) {\n      assertThat(\n          \"When timeout for login in retrieving OKTA auth response is reached NETWORK_ERROR should be raised\",\n          ex.getErrorCode(),\n          equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n    }\n  }\n\n  @Test\n  // This test can be flake due to approximated value of duration of retries execution\n  public void testOktaRetriesUntilTimeoutThenRaisesAuthTimeoutExceptionWhen429InFederatedStep4()\n      throws Throwable {\n    // GIVEN\n    final int ALLOWED_DIFFERENCE_BETWEEN_LOGIN_TIMEOUT_AND_ACTUAL_DURATION_IN_MS = 2000;\n    Map<String, Object> placeholders = new HashMap<>();\n    placeholders.put(\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}\", WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n    String wireMockMapping =\n        getWireMockMappingFromFile(ALWAYS_429_IN_FEDERATED_STEP_4, placeholders);\n    importMapping(wireMockMapping);\n\n    setCustomTrustStorePropertyPath();\n\n    SFLoginInput loginInput = createOktaLoginInputBase();\n    loginInput.setLoginTimeout(DECREASED_LOGIN_TIMEOUT); // decreased timeout for test purposes\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    try {\n      // WHEN\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n      // THEN\n    } catch (SnowflakeSQLException ex) {\n      assertThat(\n          \"When timeout for login in retrieving OKTA auth response is reached NETWORK_ERROR should be raised\",\n          ex.getErrorCode(),\n          equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n    }\n\n    List<MinimalServeEvent> allEvents = getAllServeEvents();\n\n    // Filter only events that hit the final endpoint (federated step 4) - using the retrieved\n    // token.\n    List<MinimalServeEvent> vanityUrlCalls =\n        allEvents.stream()\n            .filter(e -> e.getRequest().getUrl().contains(OKTA_VANITY_PATH))\n            .sorted(Comparator.comparing(e -> e.getRequest().getLoggedDate()))\n            .collect(Collectors.toList());\n\n    // This can cause test to be flaky - if for some reason execution of steps before sending the\n    // first request takes too long (time is approximated based on time of arrival of the first and\n    // the last request to wiremock)\n    // Most important for this check is to make sure that we honor the login timeout even when\n    // retryContext is injected (which issues requests as well inside)\n    assertThatTotalLoginTimeoutIsKeptWhenRetrying(\n        vanityUrlCalls,\n        loginInput.getLoginTimeout(),\n        ALLOWED_DIFFERENCE_BETWEEN_LOGIN_TIMEOUT_AND_ACTUAL_DURATION_IN_MS);\n  }\n\n  @Test\n  public void testErrorHandlingWhenOktaReturnsUnsupportedMfaInFederatedStep3() throws Throwable {\n    // GIVEN\n    Map<String, Object> placeholders = new HashMap<>();\n    placeholders.put(\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}\", WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n    String wireMockMapping =\n        getWireMockMappingFromFile(UNSUPPORTED_MFA_IN_FEDERATED_STEP_3, placeholders);\n    importMapping(wireMockMapping);\n\n    setCustomTrustStorePropertyPath();\n\n    SFLoginInput loginInput = createOktaLoginInputBase();\n    Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n    SnowflakeSQLLoggedException thrown =\n        assertThrows(\n            SnowflakeSQLLoggedException.class,\n            () -> SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\"));\n    assertThat(thrown.getErrorCode(), equalTo(ErrorCode.OKTA_MFA_NOT_SUPPORTED.getMessageCode()));\n    assertThat(\n        thrown.getMessage(),\n        equalTo(\n            \"MFA enabled in Okta is not supported with this authenticator type. Please use 'externalbrowser' instead or a different authentication method.\"));\n  }\n\n  private void assertThatTotalLoginTimeoutIsKeptWhenRetrying(\n      List<MinimalServeEvent> requestEvents, long loginTimeout, long allowedDifferenceInMs) {\n    final int SECONDS_TO_MS_FACTOR = 1000;\n    long firstRequestTime = requestEvents.get(0).getRequest().getLoggedDate().getTime();\n    long lastRequestTime =\n        requestEvents.get(requestEvents.size() - 1).getRequest().getLoggedDate().getTime();\n    long approximatedDurationOfOktaRetriesBasedOnLogsInMs = lastRequestTime - firstRequestTime;\n    long differenceBetweenTimeoutAndCalculatedDurationInMs =\n        Math.abs(\n            loginTimeout * SECONDS_TO_MS_FACTOR - approximatedDurationOfOktaRetriesBasedOnLogsInMs);\n    assertThat(\n        String.format(\n            \"Retrying calls to okta lasted %d ms, while login timeout was set to %d ms.\",\n            approximatedDurationOfOktaRetriesBasedOnLogsInMs, loginTimeout),\n        allowedDifferenceInMs,\n        greaterThanOrEqualTo(differenceBetweenTimeoutAndCalculatedDurationInMs));\n  }\n\n  private void assertRequestsToWiremockHaveDelay(\n      List<MinimalServeEvent> requestEvents, long minExpectedDelayBetweenCalls) {\n    for (int i = 1; i < requestEvents.size(); i++) {\n      long t1 = requestEvents.get(i - 1).getRequest().getLoggedDate().getTime();\n      long t2 = requestEvents.get(i).getRequest().getLoggedDate().getTime();\n      long deltaMillis = t2 - t1;\n      assertThat(\n          String.format(\n              \"Consecutive calls were only %d ms apart (index %d -> %d).\", deltaMillis, i - 1, i),\n          deltaMillis,\n          greaterThanOrEqualTo(minExpectedDelayBetweenCalls));\n    }\n  }\n\n  /**\n   * Ensures that each request *with* the given parameter uses a unique value. Requests that do not\n   * have the parameter are ignored. Fails if any duplicate parameter values are detected.\n   */\n  private void assertRequestsToWiremockHaveDifferentValuesOfParameter(\n      List<MinimalServeEvent> requestEvents, String parameterName) {\n    // Extract all parameter values from requests that have this parameter\n    List<String> paramValues =\n        requestEvents.stream()\n            .filter(e -> e.getRequest().getQueryParams().containsKey(parameterName))\n            .map(e -> e.getRequest().getQueryParams().get(parameterName).firstValue())\n            .collect(Collectors.toList());\n\n    long distinctCount = paramValues.stream().distinct().count();\n    assertThat(\n        \"Found duplicate value(s) for parameter '\" + parameterName + \"'. Values: \" + paramValues,\n        distinctCount,\n        equalTo((long) paramValues.size()));\n  }\n\n  @Test\n  public void testOsDetailsIncludedInLoginRequest() throws Throwable {\n    // GIVEN - Mock OsReleaseDetails.load() to return test data with all allowed keys\n    Map<String, String> mockOsDetails = new HashMap<>();\n    mockOsDetails.put(\"NAME\", \"Test Linux\");\n    mockOsDetails.put(\"PRETTY_NAME\", \"Test Linux 1.0.0 LTS\");\n    mockOsDetails.put(\"ID\", \"testlinux\");\n    mockOsDetails.put(\"IMAGE_ID\", \"testlinux-cloud\");\n    mockOsDetails.put(\"IMAGE_VERSION\", \"1.0.0-cloud\");\n    mockOsDetails.put(\"BUILD_ID\", \"20260130\");\n    mockOsDetails.put(\"VERSION\", \"1.0.0 LTS (Test Release)\");\n    mockOsDetails.put(\"VERSION_ID\", \"1.0.0\");\n\n    try (MockedStatic<OsReleaseDetails> mockedOsReleaseDetails =\n        mockStatic(OsReleaseDetails.class)) {\n      mockedOsReleaseDetails.when(OsReleaseDetails::load).thenReturn(mockOsDetails);\n\n      importMappingFromResources(OS_DETAILS_LOGIN_MAPPING_PATH);\n      setCustomTrustStorePropertyPath();\n\n      SFLoginInput loginInput = createBasicLoginInput();\n      Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n\n      verifyRequestCount(1, \"/session/v1/login-request.*\");\n    }\n  }\n\n  @Test\n  public void testLibcDetailsIncludedInLoginRequest() throws Throwable {\n    // GIVEN - Mock LibcDetails.load() to return deterministic test data so the assertion\n    // works on any host (including macOS / Windows where libc detection returns nulls).\n    LibcInfo mockLibcInfo = new LibcInfo(LibcDetails.GLIBC, \"2.34\");\n\n    try (MockedStatic<LibcDetails> mockedLibcDetails = mockStatic(LibcDetails.class)) {\n      mockedLibcDetails.when(LibcDetails::load).thenReturn(mockLibcInfo);\n\n      importMappingFromResources(LIBC_DETAILS_LOGIN_MAPPING_PATH);\n      setCustomTrustStorePropertyPath();\n\n      SFLoginInput loginInput = createBasicLoginInput();\n      Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n\n      verifyRequestCount(1, \"/session/v1/login-request.*\");\n    }\n  }\n\n  @Test\n  public void testSpcsTokenIncludedInLoginRequest() throws Throwable {\n    try (MockedConstruction<SpcsTokenReader> mockedReader =\n        mockConstruction(\n            SpcsTokenReader.class,\n            (mock, ctx) -> when(mock.readSpcsToken()).thenReturn(\"test-spcs-token\"))) {\n\n      importMappingFromResources(SPCS_TOKEN_LOGIN_MAPPING_PATH);\n      setCustomTrustStorePropertyPath();\n\n      SFLoginInput loginInput = createBasicLoginInput();\n      Map<SFSessionProperty, Object> connectionPropertiesMap = initConnectionPropertiesMap();\n\n      SessionUtil.openSession(loginInput, connectionPropertiesMap, \"ALL\");\n\n      verifyRequestCount(1, \"/session/v1/login-request.*\");\n    }\n  }\n\n  private SFLoginInput createBasicLoginInput() {\n    SFLoginInput input = new SFLoginInput();\n    input.setServerUrl(WIREMOCK_HOST_WITH_HTTPS_AND_PORT);\n    input.setUserName(\"TEST_USER\");\n    input.setPassword(\"TEST_PASSWORD\");\n    input.setAccountName(\"TEST_ACCOUNT\");\n    input.setAppId(\"TEST_APP_ID\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(30);\n    input.setSessionParameters(new HashMap<>());\n    return input;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SnowflakeMFACacheTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.nullable;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.sun.jna.Pointer;\nimport com.sun.jna.ptr.PointerByReference;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.StringWriter;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\nimport net.snowflake.client.api.datasource.SnowflakeDataSourceFactory;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.client.methods.HttpPost;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\npublic class SnowflakeMFACacheTest {\n  private static final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n  private static final String[] mockedMfaToken = {\"mockedMfaToken0\", \"mockedMfaToken1\"};\n  private static final String host = \"TESTACCOUNT.SNOWFLAKECOMPUTING.COM\";\n  private static final String account = \"testaccount\";\n  private static final String user = \"testuser\";\n  private static final String pwd = \"testpassword\";\n  private static final String authenticator = \"username_password_mfa\";\n  private static final boolean client_request_mfa_token = true;\n\n  private ObjectNode getNormalMockedHttpResponse(boolean success, int mfaTokenIdx) {\n    ObjectNode respNode = mapper.createObjectNode();\n    ObjectNode dataNode = mapper.createObjectNode();\n    ArrayNode paraArray = mapper.createArrayNode();\n    ObjectNode autocommit = mapper.createObjectNode();\n\n    autocommit.put(\"name\", \"AUTOCOMMIT\");\n    autocommit.put(\"value\", true);\n    paraArray.add(autocommit);\n\n    dataNode.set(\"parameters\", paraArray);\n    dataNode.put(\"masterToken\", \"mockedMasterToken\");\n    dataNode.put(\"token\", \"mockedToken\");\n    if (mfaTokenIdx >= 0) {\n      dataNode.put(\"mfaToken\", mockedMfaToken[mfaTokenIdx]);\n    }\n\n    respNode.set(\"data\", dataNode);\n    respNode.put(\"success\", success);\n    respNode.put(\"message\", \"msg\");\n\n    return respNode;\n  }\n\n  private JsonNode parseRequest(HttpPost post) throws IOException {\n    StringWriter writer = null;\n    String theString;\n    try {\n      writer = new StringWriter();\n      try (InputStream ins = post.getEntity().getContent()) {\n        IOUtils.copy(ins, writer, \"UTF-8\");\n      }\n      theString = writer.toString();\n    } finally {\n      IOUtils.closeQuietly(writer);\n    }\n\n    JsonNode jsonNode = mapper.readTree(theString);\n    return jsonNode;\n  }\n\n  private Properties getBaseProp() {\n    Properties prop = new Properties();\n    prop.put(\"account\", account);\n    prop.put(\"user\", user);\n    prop.put(\"password\", pwd);\n    prop.put(\"authenticator\", authenticator);\n    prop.put(\"CLIENT_REQUEST_MFA_TOKEN\", client_request_mfa_token);\n    return prop;\n  }\n\n  @Test\n  public void testMFAFunctionality() throws SQLException {\n    SessionUtil.deleteMfaTokenCache(host, user);\n    try (MockedStatic<HttpUtil> mockedHttpUtil = Mockito.mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      any(HttpPost.class),\n                      anyInt(),\n                      anyInt(),\n                      anyInt(),\n                      anyInt(),\n                      anyInt(),\n                      any(HttpClientSettingsKey.class),\n                      nullable(SFBaseSession.class)))\n          .thenAnswer(\n              new Answer<HttpResponseWithHeaders>() {\n                int callCount = 0;\n\n                @Override\n                public HttpResponseWithHeaders answer(InvocationOnMock invocation)\n                    throws Throwable {\n                  String res;\n                  JsonNode jsonNode;\n                  final Object[] args = invocation.getArguments();\n\n                  if (callCount == 0) {\n                    // First connection request\n                    jsonNode = parseRequest((HttpPost) args[0]);\n                    assertTrue(\n                        jsonNode\n                            .path(\"data\")\n                            .path(\"SESSION_PARAMETERS\")\n                            .path(\"CLIENT_REQUEST_MFA_TOKEN\")\n                            .asBoolean());\n                    // return first mfa token\n                    res = getNormalMockedHttpResponse(true, 0).toString();\n                  } else if (callCount == 1) {\n                    // First close() request\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else if (callCount == 2) {\n                    // Second connection request\n                    jsonNode = parseRequest((HttpPost) args[0]);\n                    assertTrue(\n                        jsonNode\n                            .path(\"data\")\n                            .path(\"SESSION_PARAMETERS\")\n                            .path(\"CLIENT_REQUEST_MFA_TOKEN\")\n                            .asBoolean());\n                    assertEquals(mockedMfaToken[0], jsonNode.path(\"data\").path(\"TOKEN\").asText());\n                    // Normally backend won't send a new mfa token in this case. For testing\n                    // purpose, we issue a new token to test whether the mfa token can be\n                    // refreshed\n                    // when receiving a new one from server.\n                    res = getNormalMockedHttpResponse(true, 1).toString();\n                  } else if (callCount == 3) {\n                    // Second close() request\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else if (callCount == 4) {\n                    // Third connection request\n                    // Check for the new mfa token\n                    jsonNode = parseRequest((HttpPost) args[0]);\n                    assertTrue(\n                        jsonNode\n                            .path(\"data\")\n                            .path(\"SESSION_PARAMETERS\")\n                            .path(\"CLIENT_REQUEST_MFA_TOKEN\")\n                            .asBoolean());\n                    assertEquals(mockedMfaToken[1], jsonNode.path(\"data\").path(\"TOKEN\").asText());\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else if (callCount == 5) {\n                    // Third close() request\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else if (callCount == 6) {\n                    // test if failed log in response can delete the cached mfa token\n                    res = getNormalMockedHttpResponse(false, -1).toString();\n                  } else if (callCount == 7) {\n                    jsonNode = parseRequest((HttpPost) args[0]);\n                    assertTrue(\n                        jsonNode\n                            .path(\"data\")\n                            .path(\"SESSION_PARAMETERS\")\n                            .path(\"CLIENT_REQUEST_MFA_TOKEN\")\n                            .asBoolean());\n                    // no token should be included this time.\n                    assertEquals(\"\", jsonNode.path(\"data\").path(\"TOKEN\").asText());\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else if (callCount == 8) {\n                    // final close()\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else {\n                    // unexpected request\n                    res = getNormalMockedHttpResponse(false, -1).toString();\n                  }\n\n                  callCount += 1; // this will be incremented on both connecting and closing\n                  return new HttpResponseWithHeaders(res, new HashMap<>());\n                }\n              });\n\n      Properties prop = getBaseProp();\n\n      // connect url\n      String url = \"jdbc:snowflake://testaccount.snowflakecomputing.com\";\n\n      // The first connection contains no mfa token. After the connection, a mfa token will be\n      // saved\n      Connection con = DriverManager.getConnection(url, prop);\n      con.close();\n\n      // The second connection is expected to include the mfa token issued for the first\n      // connection\n      // and a new mfa token is issued\n      Connection con1 = DriverManager.getConnection(url, prop);\n      con1.close();\n\n      // The third connection is expected to include the new mfa token.\n      Connection con2 = DriverManager.getConnection(url, prop);\n      con2.close();\n\n      // This connection would receive an exception and then should clean up the mfa cache\n      assertThrows(SnowflakeSQLException.class, () -> DriverManager.getConnection(url, prop));\n\n      // This connect request should not contain mfa cached token\n      Connection con4 = DriverManager.getConnection(url, prop);\n      con4.close();\n    }\n    SessionUtil.deleteMfaTokenCache(host, user);\n  }\n\n  class MockUnavailableAdvapi32Lib implements SecureStorageWindowsManager.Advapi32Lib {\n    @Override\n    public boolean CredReadW(String targetName, int type, int flags, PointerByReference pcred) {\n      return false;\n    }\n\n    @Override\n    public boolean CredWriteW(\n        SecureStorageWindowsManager.SecureStorageWindowsCredential cred, int flags) {\n      return false;\n    }\n\n    @Override\n    public boolean CredDeleteW(String targetName, int type, int flags) {\n      return false;\n    }\n\n    @Override\n    public void CredFree(Pointer cred) {}\n  }\n\n  private void unavailableLSSWindowsTestBody() throws SQLException {\n    SessionUtil.deleteMfaTokenCache(host, user);\n    try (MockedStatic<HttpUtil> mockedHttpUtil = Mockito.mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      any(HttpPost.class),\n                      anyInt(),\n                      anyInt(),\n                      anyInt(),\n                      anyInt(),\n                      anyInt(),\n                      any(HttpClientSettingsKey.class),\n                      nullable(SFBaseSession.class)))\n          .thenAnswer(\n              new Answer<HttpResponseWithHeaders>() {\n                int callCount = 0;\n\n                private String validationHelper(Object[] args) throws IOException {\n                  JsonNode node = parseRequest((HttpPost) args[0]);\n                  assertTrue(\n                      node.path(\"data\")\n                          .path(\"SESSION_PARAMETERS\")\n                          .path(\"CLIENT_REQUEST_MFA_TOKEN\")\n                          .asBoolean());\n                  // no token should be included.\n                  assertEquals(\"\", node.path(\"data\").path(\"TOKEN\").asText());\n                  return getNormalMockedHttpResponse(true, 0).toString();\n                }\n\n                @Override\n                public HttpResponseWithHeaders answer(InvocationOnMock invocation)\n                    throws Throwable {\n                  String res = \"\";\n                  final Object[] args = invocation.getArguments();\n\n                  if (callCount == 0) {\n                    // First connection request\n                    res = validationHelper(args);\n                  } else if (callCount == 1) {\n                    // First close() request\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  } else if (callCount == 2) {\n                    // Second connection request\n                    res = validationHelper(args);\n                  } else if (callCount == 3) {\n                    // Second close() request\n                    res = getNormalMockedHttpResponse(true, -1).toString();\n                  }\n                  callCount += 1; // this will be incremented on both connecting and closing\n                  return new HttpResponseWithHeaders(res, new HashMap<>());\n                }\n              });\n\n      Properties prop = getBaseProp();\n      String url = \"jdbc:snowflake://testaccount.snowflakecomputing.com\";\n\n      // Both of below two connections will try to use unavailable secure local storage to store mfa\n      // cache. We need to make sure this situation won't break.\n      Connection con = DriverManager.getConnection(url, prop);\n      con.close();\n      Connection con1 = DriverManager.getConnection(url, prop);\n      con1.close();\n    }\n    SessionUtil.deleteMfaTokenCache(host, user);\n  }\n\n  private void testUnavailableLSSWindowsHelper() throws SQLException {\n    try {\n      SecureStorageWindowsManager.Advapi32LibManager.setInstance(new MockUnavailableAdvapi32Lib());\n      SecureStorageWindowsManager manager = SecureStorageWindowsManager.builder();\n      CredentialManager.injectSecureStorageManager(manager);\n      unavailableLSSWindowsTestBody();\n    } finally {\n      SecureStorageWindowsManager.Advapi32LibManager.resetInstance();\n    }\n  }\n\n  @Test\n  public void testUnavailableLocalSecureStorage() throws SQLException {\n    try {\n      testUnavailableLSSWindowsHelper();\n    } finally {\n      CredentialManager.resetSecureStorageManager();\n    }\n  }\n\n  // Run this test manually to test disabling the client request MFA token. Use an MFA\n  // authentication enabled user. This is valid for versions after 3.18.0.\n  @Test\n  @Disabled\n  public void testEnableClientRequestMfaToken() throws SQLException {\n    Map<String, String> params = AbstractDriverIT.getConnectionParameters();\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setAccount(params.get(\"account\"));\n    ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n    ds.setUser(params.get(\"user\"));\n    ds.setPassword(params.get(\"password\"));\n    ds.setEnableClientRequestMfaToken(false);\n\n    for (int i = 0; i < 3; i++) {\n      try (Connection con = ds.getConnection();\n          ResultSet rs = con.createStatement().executeQuery(\"SELECT 1\")) {\n        assertTrue(rs.next());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SpcsTokenReaderTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.spy;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport org.junit.jupiter.api.Test;\n\npublic class SpcsTokenReaderTest {\n\n  /**\n   * Per the design spec: when {@code SNOWFLAKE_RUNNING_INSIDE_SPCS} is unset (or empty per Node.js\n   * {@code !process.env[…]} parity), the driver MUST NOT attempt to access the token file.\n   */\n  @Test\n  public void shouldReturnNullWhenNotRunningInsideSpcs() {\n    SpcsTokenReader reader = spy(new SpcsTokenReader());\n    doReturn(false).when(reader).isRunningInsideSpcs();\n\n    assertNull(reader.readSpcsToken());\n  }\n\n  @Test\n  public void shouldReturnTrimmedTokenWhenRunningInsideSpcsAndFilePresent() throws IOException {\n    SpcsTokenReader reader = spy(new SpcsTokenReader());\n    doReturn(true).when(reader).isRunningInsideSpcs();\n    doReturn(\"  \\n\\tservice-token-value\\n  \".getBytes(StandardCharsets.UTF_8))\n        .when(reader)\n        .readTokenFileBytes();\n\n    assertEquals(\"service-token-value\", reader.readSpcsToken());\n  }\n\n  @Test\n  public void shouldReadFileAsUtf8() throws IOException {\n    String value = \"tókén\";\n    SpcsTokenReader reader = spy(new SpcsTokenReader());\n    doReturn(true).when(reader).isRunningInsideSpcs();\n    doReturn(value.getBytes(StandardCharsets.UTF_8)).when(reader).readTokenFileBytes();\n\n    assertEquals(value, reader.readSpcsToken());\n  }\n\n  @Test\n  public void shouldReturnNullForBlankFile() throws IOException {\n    SpcsTokenReader reader = spy(new SpcsTokenReader());\n    doReturn(true).when(reader).isRunningInsideSpcs();\n    doReturn(\"  \\n\\t \\n\".getBytes(StandardCharsets.UTF_8)).when(reader).readTokenFileBytes();\n\n    assertNull(reader.readSpcsToken());\n  }\n\n  @Test\n  public void shouldReturnNullAndNotThrowOnIoError() throws IOException {\n    SpcsTokenReader reader = spy(new SpcsTokenReader());\n    doReturn(true).when(reader).isRunningInsideSpcs();\n    doThrow(new IOException(\"boom\")).when(reader).readTokenFileBytes();\n\n    assertNull(reader.readSpcsToken());\n  }\n\n  @Test\n  public void productionConstantsMatchDesignSpec() {\n    assertEquals(\"SNOWFLAKE_RUNNING_INSIDE_SPCS\", SpcsTokenReader.SPCS_RUNNING_INSIDE_ENV_VAR);\n    assertEquals(\"/snowflake/session/spcs_token\", SpcsTokenReader.SPCS_TOKEN_FILE_PATH);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/SqlInputTimestampUtilTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Timestamp;\nimport java.time.LocalDateTime;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\n@Disabled\npublic class SqlInputTimestampUtilTest {\n\n  private static final String TIMESTAMP_IN_FORMAT_1 = \"2021-12-22 09:43:44.000 +0100\";\n  private static final String TIMESTAMP_IN_FORMAT_2 = \"Wed, 22 Dec 2021 09:43:44 +0100\";\n  private static final Map<String, Object> CONNECTION_PARAMS = new HashMap<>();\n  private static final Timestamp EXPECTED_TIMESTAMP =\n      Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44));\n\n  private static SFBaseSession mockSession;\n\n  @BeforeAll\n  public static void setup() {\n    CONNECTION_PARAMS.put(\"TIMESTAMP_OUTPUT_FORMAT\", \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\");\n    CONNECTION_PARAMS.put(\"TIMESTAMP_TZ_OUTPUT_FORMAT\", \"DY, DD MON YYYY HH24:MI:SS TZHTZM\");\n    mockSession = Mockito.mock(SFBaseSession.class);\n    Mockito.when(mockSession.getCommonParameters()).thenReturn(CONNECTION_PARAMS);\n  }\n\n  @Test\n  public void shouldGetTimestampForDifferentType() {\n    // when\n    Timestamp resultLtz =\n        getFromType(SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ, TIMESTAMP_IN_FORMAT_1, null);\n    Timestamp resultTz =\n        getFromType(SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ, TIMESTAMP_IN_FORMAT_2, null);\n    Timestamp resultNtz =\n        getFromType(SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ, TIMESTAMP_IN_FORMAT_1, null);\n\n    assertEquals(EXPECTED_TIMESTAMP, resultLtz);\n    assertEquals(EXPECTED_TIMESTAMP, resultTz);\n    assertEquals(EXPECTED_TIMESTAMP, resultNtz);\n  }\n\n  private Timestamp getFromType(int type, String value, TimeZone explicitTimezone) {\n    return SfTimestampUtil.getTimestampFromType(\n        type, value, mockSession, TimeZone.getTimeZone(\"GMT\"), explicitTimezone);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/StmtUtilTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.times;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.StmtUtil.StmtInput;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport org.apache.http.Header;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.MockedStatic.Verification;\nimport org.mockito.Mockito;\n\n@Tag(TestTags.CORE)\npublic class StmtUtilTest extends BaseJDBCTest {\n\n  /** SNOW-862760 Verify that additional headers are added to request */\n  @Test\n  public void testForwardedHeaders() throws Throwable {\n    SFLoginInput input = createLoginInput();\n    Map<String, String> additionalHeaders = new HashMap<>();\n    additionalHeaders.put(\"Extra-Snowflake-Header\", \"present\");\n    input.setAdditionalHttpHeadersForSnowsight(additionalHeaders);\n\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class)) {\n      // Both mocks the call _and_ verifies that the headers are forwarded.\n      Verification httpCalledWithHeaders =\n          () ->\n              HttpUtil.executeRequest(\n                  Mockito.argThat(\n                      arg -> {\n                        for (Entry<String, String> definedHeader : additionalHeaders.entrySet()) {\n                          Header actualHeader = arg.getLastHeader(definedHeader.getKey());\n                          if (actualHeader == null) {\n                            return false;\n                          }\n\n                          if (!definedHeader.getValue().equals(actualHeader.getValue())) {\n                            return false;\n                          }\n                        }\n\n                        return true;\n                      }),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.anyInt(),\n                  Mockito.nullable(AtomicBoolean.class),\n                  Mockito.anyBoolean(),\n                  Mockito.anyBoolean(),\n                  Mockito.nullable(HttpClientSettingsKey.class),\n                  Mockito.nullable(ExecTimeTelemetryData.class),\n                  Mockito.nullable(SFBaseSession.class));\n      mockedHttpUtil\n          .when(httpCalledWithHeaders)\n          .thenReturn(\"{\\\"data\\\":null,\\\"code\\\":333334,\\\"message\\\":null,\\\"success\\\":true}\");\n\n      mockedHttpUtil\n          .when(() -> HttpUtil.applyAdditionalHeadersForSnowsight(any(), any()))\n          .thenCallRealMethod();\n\n      StmtInput stmtInput = new StmtInput();\n      stmtInput.setAdditionalHttpHeadersForSnowsight(additionalHeaders);\n      // Async mode skips result post-processing so we don't need to mock an advanced\n      // response\n      stmtInput.setAsync(true);\n      stmtInput.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n      stmtInput.setRequestId(UUID.randomUUID().toString());\n      stmtInput.setServiceName(\"MOCK_SERVICE_NAME\");\n      stmtInput.setServerUrl(\"MOCK_SERVER_URL\");\n      stmtInput.setSessionToken(\"MOCK_SESSION_TOKEN\");\n      stmtInput.setSequenceId(1);\n      stmtInput.setSql(\"SELECT * FROM MOCK_TABLE\");\n\n      StmtUtil.execute(stmtInput, new ExecTimeTelemetryData(), null);\n\n      // After login, the only invocation to http should have been with the new\n      // headers.\n      // No calls should have happened without additional headers.\n      mockedHttpUtil.verify(httpCalledWithHeaders, times(1));\n    }\n  }\n\n  private static final ObjectMapper MAPPER = new ObjectMapper();\n\n  /** SNOW-3063492 Verify QCC is merged from a failed query response */\n  @Test\n  public void testUpdateQueryContextFromFailedResponse() throws Exception {\n    String failedResponse =\n        \"{\\\"data\\\":{\\\"errorCode\\\":\\\"200001\\\",\\\"sqlState\\\":\\\"22000\\\",\"\n            + \"\\\"queryId\\\":\\\"test-query-id\\\",\"\n            + \"\\\"queryContext\\\":{\\\"entries\\\":[{\\\"id\\\":0,\\\"timestamp\\\":123456789,\\\"priority\\\":0,\\\"context\\\":\\\"opaque\\\"}]}\"\n            + \"},\\\"code\\\":\\\"200001\\\",\"\n            + \"\\\"message\\\":\\\"A primary key already exists.\\\",\"\n            + \"\\\"success\\\":false}\";\n\n    JsonNode responseJson = MAPPER.readTree(failedResponse);\n    SFSession session = createSessionWithQCC();\n\n    StmtUtil.updateQueryContextFromResponse(responseJson, session);\n\n    QueryContextDTO qcc = session.getQueryContextDTO();\n    assertNotNull(qcc, \"QCC should be populated from failed response\");\n    assertEquals(1, qcc.getEntries().size());\n    assertEquals(0, qcc.getEntries().get(0).getId());\n    assertEquals(123456789L, qcc.getEntries().get(0).getTimestamp());\n  }\n\n  private static SFSession createSessionWithQCC() {\n    SFSession session = new SFSession();\n    session.qcc = new QueryContextCache(session.getQueryContextCacheSize());\n    return session;\n  }\n\n  private SFLoginInput createLoginInput() {\n    SFLoginInput input = new SFLoginInput();\n    input.setServerUrl(\"MOCK_TEST_HOST\");\n    input.setUserName(\"MOCK_USERNAME\");\n    input.setPassword(\"MOCK_PASSWORD\");\n    input.setAccountName(\"MOCK_ACCOUNT_NAME\");\n    input.setAppId(\"MOCK_APP_ID\");\n    input.setOCSPMode(OCSPMode.FAIL_OPEN);\n    input.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    input.setLoginTimeout(1000);\n    input.setSessionParameters(new HashMap<>());\n\n    return input;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/URLUtilTest.java",
    "content": "package net.snowflake.client.internal.core;\n\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.UnsupportedEncodingException;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.CsvSource;\nimport org.junit.jupiter.params.provider.NullSource;\n\npublic class URLUtilTest {\n\n  @Test\n  public void testValidURL() throws Exception {\n    assertTrue(URLUtil.isValidURL(\"https://ssoTestURL.okta.com\"));\n    assertTrue(URLUtil.isValidURL(\"https://ssoTestURL.okta.com:8080\"));\n    assertTrue(URLUtil.isValidURL(\"https://ssoTestURL.okta.com/testpathvalue\"));\n  }\n\n  @Test\n  public void testInvalidURL() throws Exception {\n    assertFalse(URLUtil.isValidURL(\"-a Calculator\"));\n    assertFalse(URLUtil.isValidURL(\"This is random text\"));\n    assertFalse(URLUtil.isValidURL(\"file://TestForFile\"));\n  }\n\n  @Test\n  public void testEncodeURL() throws Exception {\n    assertEquals(URLUtil.urlEncode(\"Hello @World\"), \"Hello+%40World\");\n    assertEquals(URLUtil.urlEncode(\"Test//String\"), \"Test%2F%2FString\");\n  }\n\n  @Test\n  void testIsValidURL_InvalidCases() {\n    assertFalse(URLUtil.isValidURL(\"htp://invalid-url\"));\n    assertFalse(URLUtil.isValidURL(\"://missing-protocol.com\"));\n    assertFalse(URLUtil.isValidURL(\"https:/missing-slash.com\"));\n    assertFalse(URLUtil.isValidURL(\"https://\"));\n    assertFalse(URLUtil.isValidURL(\"\"));\n  }\n\n  @ParameterizedTest\n  @CsvSource({\"'hello world!', hello+world%21\", \"'', ''\"})\n  @DisplayName(\"URL encoding valid and empty strings\")\n  void testUrlEncode_ValidInputs(String input, String expected)\n      throws UnsupportedEncodingException {\n    assertEquals(expected, URLUtil.urlEncode(input));\n  }\n\n  @ParameterizedTest\n  @NullSource\n  @DisplayName(\"URL encoding null should throw NullPointerException\")\n  void testUrlEncode_NullInput(String input) {\n    assertThrows(NullPointerException.class, () -> URLUtil.urlEncode(input));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/ArrowResultUtilTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.TimeZone;\nimport java.util.stream.Stream;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class ArrowResultUtilTest {\n  @AfterAll\n  public static void clearTimeZone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  public static void setTimeZone(String string) {\n    System.setProperty(\"user.timezone\", string);\n  }\n\n  @ParameterizedTest(name = \"Timezone = {0}\")\n  @ArgumentsSource(TimezoneProvider.class)\n  @Disabled\n  /** This is to show we can have 30X improvement using new API */\n  public void testGetDatePerformance(String timezone) throws SFException {\n    setTimeZone(timezone);\n    Random random = new Random();\n    int dateBound = 50000;\n    int times = 100000;\n    SFSession session = new SFSession();\n    long start = System.currentTimeMillis();\n    TimeZone tz = TimeZone.getDefault();\n    int[] days = new int[times];\n    for (int i = 0; i < times; i++) {\n      days[i] = random.nextInt(dateBound) - dateBound / 2;\n    }\n\n    for (int i = 0; i < times; i++) {\n      ResultUtil.getDate(Integer.toString(days[i]), tz, session);\n    }\n    long duration1 = System.currentTimeMillis() - start;\n\n    start = System.currentTimeMillis();\n    for (int i = 0; i < times; i++) {\n      ArrowResultUtil.getDate(days[i], tz, tz);\n    }\n    long duration2 = System.currentTimeMillis() - start;\n\n    start = System.currentTimeMillis();\n    for (int i = 0; i < times; i++) {\n      ArrowResultUtil.getDate(days[i]);\n    }\n    long duration3 = System.currentTimeMillis() - start;\n    System.out.println(duration1 + \" \" + duration2 + \" \" + duration3);\n  }\n\n  private static class testCasesProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      List<String> timezones =\n          new ArrayList<String>() {\n            {\n              add(\"UTC\");\n              add(\"America/Los_Angeles\");\n              add(\"America/New_York\");\n              add(\"Asia/Singapore\");\n              add(\"MEZ\");\n            }\n          };\n\n      long[] cases = {-1123456789, -123456789, 123456789, 123123456789L, -123123456789L};\n      long[] millisecs = {-1124, -124, 123, 123123, -123124};\n      int[] nanos = {876543211, 876543211, 123456789, 123456789, 876543211};\n\n      List<Arguments> args = new ArrayList<>();\n      for (String timezone : timezones) {\n        for (int i = 0; i < cases.length; i++) {\n          args.add(Arguments.of(timezone, cases[i], millisecs[i], nanos[i]));\n        }\n      }\n\n      return args.stream();\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(testCasesProvider.class)\n  public void testToJavaTimestamp(String timezone, long cas, long millisecs, int nanos) {\n    // ex: -1.123456789, -0.123456789, 0.123456789, 123.123456789, -123.123456789\n    setTimeZone(timezone);\n    int scale = 9;\n    Timestamp ts = ArrowResultUtil.toJavaTimestamp(cas, scale);\n    assertEquals(millisecs, ts.getTime());\n    assertEquals(nanos, ts.getNanos());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/BaseConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport java.nio.ByteOrder;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeEach;\n\npublic class BaseConverterTest implements DataConversionContext {\n  private SnowflakeDateTimeFormat dateTimeFormat =\n      SnowflakeDateTimeFormat.fromSqlFormat(\"YYYY-MM-DD\");\n  private SnowflakeDateTimeFormat timeFormat = SnowflakeDateTimeFormat.fromSqlFormat(\"HH24:MI:SS\");\n  private SnowflakeDateTimeFormat timestampLTZFormat =\n      SnowflakeDateTimeFormat.fromSqlFormat(\"DY, DD MON YYYY HH24:MI:SS TZHTZM\");\n  private SnowflakeDateTimeFormat timestampNTZFormat =\n      SnowflakeDateTimeFormat.fromSqlFormat(\"DY, DD MON YYYY HH24:MI:SS TZHTZM\");\n  private SnowflakeDateTimeFormat timestampTZFormat =\n      SnowflakeDateTimeFormat.fromSqlFormat(\"DY, DD MON YYYY HH24:MI:SS TZHTZM\");\n\n  private SFSession session = new SFSession();\n  private int testScale = 9;\n  private boolean honorClientTZForTimestampNTZ;\n  protected final int invalidConversionErrorCode = ErrorCode.INVALID_VALUE_CONVERT.getMessageCode();\n\n  @AfterEach\n  public void clearTimeZone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  @BeforeEach\n  public void assumeLittleEndian() {\n    Assumptions.assumeTrue(\n        ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN),\n        \"Arrow doesn't support cross endianness\");\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimestampLTZFormatter() {\n    return timestampLTZFormat;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimestampNTZFormatter() {\n    return timestampNTZFormat;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimestampTZFormatter() {\n    return timestampTZFormat;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getDateFormatter() {\n    return dateTimeFormat;\n  }\n\n  @Override\n  public SnowflakeDateTimeFormat getTimeFormatter() {\n    return timeFormat;\n  }\n\n  @Override\n  public SFBinaryFormat getBinaryFormatter() {\n    return SFBinaryFormat.BASE64;\n  }\n\n  public void setScale(int scale) {\n    testScale = scale;\n  }\n\n  @Override\n  public int getScale(int columnIndex) {\n    return testScale;\n  }\n\n  @Override\n  public SFSession getSession() {\n    return session;\n  }\n\n  @Override\n  public TimeZone getTimeZone() {\n    return TimeZone.getDefault();\n  }\n\n  @Override\n  public boolean getHonorClientTZForTimestampNTZ() {\n    return honorClientTZForTimestampNTZ;\n  }\n\n  public void setHonorClientTZForTimestampNTZ(boolean val) {\n    honorClientTZForTimestampNTZ = val;\n  }\n\n  @Override\n  public long getResultVersion() {\n    // Note: only cover current result version\n    return 1;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/BigIntToFixedConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.Test;\n\npublic class BigIntToFixedConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  /** Random seed */\n  private Random random = new Random();\n\n  private ByteBuffer bb;\n\n  @Test\n  public void testFixedNoScale() throws SFException {\n    final int rowCount = 1000;\n    List<Long> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(random.nextLong());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new BigIntToFixedConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      long longVal = converter.toLong(i);\n      Object longObject = converter.toObject(i);\n      String longString = converter.toString(i);\n\n      if (longString != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(longVal, is(0L));\n        assertThat(longObject, is(nullValue()));\n        assertThat(longString, is(nullValue()));\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        assertThat(longVal, is(expectedValues.get(i)));\n        assertThat(longObject, is(expectedValues.get(i)));\n        assertThat(longString, is(expectedValues.get(i).toString()));\n        bb = ByteBuffer.wrap(converter.toBytes(i));\n        assertThat(longVal, is(bb.getLong()));\n      }\n    }\n    vector.clear();\n  }\n\n  @Test\n  public void testFixedWithScale() throws SFException {\n    final int rowCount = 1000;\n    List<Long> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(random.nextLong());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new BigIntToScaledFixedConverter(vector, 0, this, 3);\n\n    for (int i = 0; i < rowCount; i++) {\n      BigDecimal bigDecimalVal = converter.toBigDecimal(i);\n      Object objectVal = converter.toObject(i);\n      String stringVal = converter.toString(i);\n\n      if (nullValIndex.contains(i)) {\n        assertThat(bigDecimalVal, nullValue());\n        assertThat(objectVal, nullValue());\n        assertThat(stringVal, nullValue());\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        BigDecimal expectedVal = BigDecimal.valueOf(expectedValues.get(i), 3);\n        assertThat(bigDecimalVal, is(expectedVal));\n        assertThat(objectVal, is(expectedVal));\n        assertThat(stringVal, is(expectedVal.toString()));\n        assertThat(converter.toBytes(i), is(notNullValue()));\n      }\n    }\n\n    vector.clear();\n  }\n\n  @Test\n  public void testInvalidConversion() {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 123456789L);\n\n    final ArrowVectorConverter converter = new BigIntToScaledFixedConverter(vector, 0, this, 3);\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toLong(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toInt(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toShort(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toByte(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toDate(0, getTimeZone(), false));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toTime(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toTimestamp(0, TimeZone.getDefault()));\n    vector.clear();\n  }\n\n  @Test\n  public void testGetSmallerIntegralType() throws SFException {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    // test value which is out of range of int, but falls in long\n    BigIntVector vectorFoo = new BigIntVector(\"col_one\", fieldType, allocator);\n    vectorFoo.setSafe(0, 2147483650L);\n    vectorFoo.setSafe(1, -2147483650L);\n\n    final ArrowVectorConverter converterFoo = new BigIntToFixedConverter(vectorFoo, 0, this);\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toInt(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toShort(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toByte(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toInt(1));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toShort(1));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toByte(1));\n    vectorFoo.clear();\n\n    // test value which is in range of byte, all get method should return\n    BigIntVector vectorBar = new BigIntVector(\"col_one\", fieldType, allocator);\n    // set value which is out of range of int, but falls in long\n    vectorBar.setSafe(0, 10L);\n    vectorBar.setSafe(1, -10L);\n\n    final ArrowVectorConverter converterBar = new BigIntToFixedConverter(vectorBar, 0, this);\n\n    assertThat(converterBar.toByte(0), is((byte) 10));\n    assertThat(converterBar.toByte(1), is((byte) -10));\n    assertThat(converterBar.toShort(0), is((short) 10));\n    assertThat(converterBar.toShort(1), is((short) -10));\n    assertThat(converterBar.toInt(0), is(10));\n    assertThat(converterBar.toInt(1), is(-10));\n    vectorBar.clear();\n  }\n\n  @Test\n  public void testGetBooleanNoScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    ArrowVectorConverter converter = new BigIntToFixedConverter(vector, 0, this);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    assertThat(true, is(converter.toBoolean(1)));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n\n  @Test\n  public void testGetBooleanWithScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    final ArrowVectorConverter converter = new BigIntToScaledFixedConverter(vector, 0, this, 3);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/BigIntToTimeConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Time;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class BigIntToTimeConverterTest extends BaseConverterTest {\n  public void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  @AfterAll\n  public static void clearTimezone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int scale = 9;\n\n  @ParameterizedTest(name = \"{0}\")\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testTime(String tz) throws SFException {\n    setTimezone(tz);\n    // test old and new dates\n    long[] testTimesInt64 = {12345678000000L};\n\n    String[] testTimesJson = {\"12345.678000000\"};\n\n    Time[] expectedTimes = {new Time(12345678)};\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIME\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"date\", fieldType, allocator);\n    int i = 0, j = 0;\n    while (i < testTimesInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        vector.setSafe(j, testTimesInt64[i++]);\n      }\n      j++;\n    }\n\n    ArrowVectorConverter converter = new BigIntToTimeConverter(vector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    while (j < rowCount) {\n      String strVal = converter.toString(j);\n      Time time = converter.toTime(j);\n      Object obj = converter.toObject(j);\n      Time oldTime =\n          new Time(\n              ResultUtil.getSFTime(testTimesJson[i], scale, new SFSession())\n                  .getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS));\n\n      if (strVal != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n      if (nullValIndex.contains(j)) {\n        assertThat(obj, is(nullValue()));\n        assertThat(strVal, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        assertThat(expectedTimes[i], is(time));\n        assertThat(expectedTimes[i], is((Time) obj));\n        assertThat(oldTime, is(time));\n        assertThat(oldTime, is((Time) obj));\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n      }\n      j++;\n    }\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/BigIntToTimestampLTZConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class BigIntToTimestampLTZConverterTest extends BaseConverterTest {\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int oldScale = 9;\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testTimestampLTZ(String timezone) throws SFException {\n    System.setProperty(\"user.timezone\", timezone);\n    // test old and new dates\n    long[] testTimestampsInt64 = {\n      1546391837,\n      15463918370l,\n      154639183700l,\n      1546391837000l,\n      15463918370000l,\n      154639183700000l,\n      1546391837000000l\n    };\n\n    // test scale from seconds to microseconds\n    int[] testScales = {0, 1, 2, 3, 4, 5, 6};\n\n    String[] testTimesJson = {\"1546391837.000000000\"};\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIMESTAMP\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"timestamp\", fieldType, allocator);\n    int i = 0, j = 0;\n    while (i < testTimestampsInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        vector.setSafe(j, testTimestampsInt64[i++]);\n      }\n      j++;\n    }\n\n    ArrowVectorConverter converter = new BigIntToTimestampLTZConverter(vector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    this.setScale(testScales[i]);\n    while (j < rowCount) {\n      Timestamp ts = converter.toTimestamp(j, getTimeZone());\n      Date date = converter.toDate(j, getTimeZone(), false);\n      Time time = converter.toTime(j);\n      String tsStr = converter.toString(j);\n\n      if (tsStr != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n\n      if (nullValIndex.contains(j)) {\n        assertThat(ts, is(nullValue()));\n        assertThat(date, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        SFTimestamp sfTimestamp =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[0],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        Timestamp oldTs = sfTimestamp.getTimestamp();\n        oldTs = ResultUtil.adjustTimestamp(oldTs);\n        Date oldDate = new Date((oldTs).getTime());\n        SFTimestamp sfTS =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[0],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        String timestampStr =\n            ResultUtil.getSFTimestampAsString(\n                sfTS,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                oldScale,\n                getTimestampNTZFormatter(),\n                getTimestampLTZFormatter(),\n                getTimestampTZFormatter(),\n                getSession());\n        Time oldTime = new Time(oldTs.getTime());\n        assertThat(oldDate, is(date));\n        assertThat(oldTs, is(ts));\n        assertThat(oldTime, is(time));\n        assertThat(timestampStr, is(tsStr));\n        assertThat(converter.toBytes(j), is(notNullValue()));\n        i++;\n        if (i < testScales.length) {\n          this.setScale(testScales[i]);\n        }\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n      }\n      j++;\n    }\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/BigIntToTimestampNTZConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static net.snowflake.client.providers.ProvidersUtil.cartesianProduct;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.providers.SnowflakeArgumentsProvider;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class BigIntToTimestampNTZConverterTest extends BaseConverterTest {\n  static class FlagProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return Arrays.asList(Arguments.of(true), Arguments.of(false));\n    }\n  }\n\n  static class DataProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return cartesianProduct(context, new TimezoneProvider(), new FlagProvider());\n    }\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int oldScale = 9;\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testWithNullTimezone(String tz) throws SFException {\n    System.setProperty(\"user.timezone\", tz);\n    testTimestampNTZ(null);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampNTZ(String tz, boolean flag) throws SFException {\n    this.setHonorClientTZForTimestampNTZ(flag);\n    System.setProperty(\"user.timezone\", tz);\n    testTimestampNTZ(TimeZone.getDefault());\n  }\n\n  /**\n   * Helper function for 2 tests above- can be tested with or without a timezone.\n   *\n   * @param timezone the timezone to be used for testing\n   * @throws SFException\n   */\n  private void testTimestampNTZ(TimeZone timezone) throws SFException {\n    // test old and new dates\n    long[] testTimestampsInt64 = {\n      1546391837,\n      15463918370l,\n      154639183700l,\n      1546391837000l,\n      15463918370000l,\n      154639183700000l,\n      1546391837000000l\n    };\n\n    // test scale from seconds to microseconds\n    int[] testScales = {0, 1, 2, 3, 4, 5, 6};\n\n    String[] testTimesJson = {\"1546391837.000000000\"};\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIMESTAMP\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n\n    BigIntVector vector = new BigIntVector(\"timestamp\", fieldType, allocator);\n    int i = 0, j = 0;\n    while (i < testTimestampsInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        vector.setSafe(j, testTimestampsInt64[i++]);\n      }\n      j++;\n    }\n\n    ArrowVectorConverter converter = new BigIntToTimestampNTZConverter(vector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    this.setScale(testScales[i]);\n    while (j < rowCount) {\n      Timestamp ts = createTimestampObject(converter, j, timezone);\n      Date date = converter.toDate(j, getTimeZone(), false);\n      Time time = converter.toTime(j);\n      String tsStr = converter.toString(j);\n\n      if (tsStr != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n\n      if (nullValIndex.contains(j)) {\n        assertThat(ts, is(nullValue()));\n        assertThat(date, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        SFTimestamp sfTimestamp =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[0],\n                oldScale,\n                java.sql.Types.TIMESTAMP,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        Timestamp oldTs = sfTimestamp.getTimestamp();\n        if (getHonorClientTZForTimestampNTZ()) {\n          // Note: honorClientTZForTimestampNTZ is used except getString()\n          oldTs = sfTimestamp.moveToTimeZone(getTimeZone()).getTimestamp();\n        }\n        oldTs = ResultUtil.adjustTimestamp(oldTs);\n\n        SFTimestamp sfTS =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[0],\n                oldScale,\n                java.sql.Types.TIMESTAMP,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        String timestampStr =\n            ResultUtil.getSFTimestampAsString(\n                sfTS,\n                java.sql.Types.TIMESTAMP,\n                oldScale,\n                getTimestampNTZFormatter(),\n                getTimestampLTZFormatter(),\n                getTimestampTZFormatter(),\n                getSession());\n        Date oldDate = new Date((oldTs).getTime());\n        Time oldTime = new Time(oldTs.getTime());\n        assertThat(oldTs, is(ts));\n        assertThat(oldDate, is(date));\n        assertThat(timestampStr, is(tsStr));\n        assertThat(oldTime, is(time));\n        assertThat(converter.toBytes(j), is(notNullValue()));\n        i++;\n        if (i < testScales.length) {\n          this.setScale(testScales[i]);\n        }\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n      }\n      j++;\n    }\n    vector.clear();\n  }\n\n  private Timestamp createTimestampObject(ArrowVectorConverter converter, int j, TimeZone zone)\n      throws SFException {\n    return converter.toTimestamp(j, zone);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/BitToBooleanConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BitVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.Test;\n\npublic class BitToBooleanConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  @Test\n  public void testConvertToString() throws SFException {\n    final int rowCount = 1000;\n    List<Boolean> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(random.nextBoolean());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"BOOLEAN\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.BIT.getType(), null, customFieldMeta);\n\n    BitVector vector = new BitVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i) ? 1 : 0);\n      }\n    }\n\n    ArrowVectorConverter converter = new BitToBooleanConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      boolean boolVal = converter.toBoolean(i);\n      Object objectVal = converter.toObject(i);\n      String stringVal = converter.toString(i);\n\n      if (stringVal != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(boolVal, is(false));\n        assertThat(objectVal, is(nullValue())); // current behavior\n        assertThat(stringVal, is(nullValue())); // current behavior\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        assertThat(boolVal, is(expectedValues.get(i)));\n        assertThat(objectVal, is(expectedValues.get(i)));\n        assertThat(stringVal, is(expectedValues.get(i).toString().toUpperCase()));\n        if (boolVal) {\n          assertThat((byte) 0x1, is(converter.toBytes(i)[0]));\n        } else {\n          assertThat((byte) 0x0, is(converter.toBytes(i)[0]));\n        }\n      }\n    }\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/DateConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.json.DateTimeConverter;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.DateDayVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class DateConverterTest extends BaseConverterTest {\n\n  private static void setTimeZone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  // test old and new dates\n  int[] testDates = {-8865, -719162, -354285, -244712, -208156, -171664, -135107, 0, 16911};\n\n  String[] expectedDates = {\n    \"1945-09-24\",\n    \"0001-01-01\",\n    \"1000-01-01\",\n    \"1300-01-01\",\n    \"1400-02-02\",\n    \"1500-01-01\",\n    \"1600-02-03\",\n    \"1970-01-01\",\n    \"2016-04-20\"\n  };\n\n  // Map of timezone to ArrowResultUtil.getDate value and hours offset for testTimezoneDates test\n  // case\n  Map<String, List<Object>> timezoneDatesData =\n      new HashMap<String, List<Object>>() {\n        {\n          put(\"UTC\", Arrays.asList(\"2016-04-20\", 0));\n          put(\"America/Los_Angeles\", Arrays.asList(\"2016-04-20\", -7));\n          put(\"America/New_York\", Arrays.asList(\"2016-04-20\", -4));\n          put(\"Pacific/Honolulu\", Arrays.asList(\"2016-04-20\", -10));\n          put(\"Asia/Singapore\", Arrays.asList(\"2016-04-19\", 8));\n          put(\"CET\", Arrays.asList(\"2016-04-19\", 2)); // because of daylight savings\n          put(\"GMT+0200\", Arrays.asList(\"2016-04-19\", 2));\n        }\n      };\n\n  public static final int MILLIS_IN_ONE_HOUR = 3600000;\n  private TimeZone defaultTimeZone;\n\n  @BeforeEach\n  public void getDefaultTimeZone() {\n    this.defaultTimeZone = TimeZone.getDefault();\n  }\n\n  @AfterEach\n  public void restoreDefaultTimeZone() {\n    TimeZone.setDefault(defaultTimeZone);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testDate(String tz) throws SFException {\n    setTimeZone(tz);\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"DATE\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.DATEDAY.getType(), null, customFieldMeta);\n\n    DateDayVector vector = new DateDayVector(\"date\", fieldType, allocator);\n    int i = 0, j = 0;\n    while (i < testDates.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        vector.setSafe(j, testDates[i++]);\n      }\n      j++;\n    }\n\n    ArrowVectorConverter converter = new DateConverter(vector, 0, this, false);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    while (j < rowCount) {\n      int intVal = converter.toInt(j);\n      String strVal = converter.toString(j);\n      Object obj = converter.toObject(j);\n      if (strVal != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n      Object oldObj =\n          ArrowResultUtil.getDate(intVal, TimeZone.getTimeZone(\"UTC\"), TimeZone.getDefault());\n      if (nullValIndex.contains(j)) {\n        assertThat(intVal, is(0));\n        assertThat(obj, is(nullValue()));\n        assertThat(strVal, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        assertThat(intVal, is(testDates[i]));\n        assertThat(((Date) obj).getTime(), is(((Date) oldObj).getTime()));\n        assertThat(obj.toString(), is(expectedDates[i]));\n        assertThat(((Date) obj).getTime(), is(((Date) oldObj).getTime()));\n        assertThat(oldObj.toString(), is(expectedDates[i++]));\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n      }\n      j++;\n    }\n    vector.clear();\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testRandomDates(String tz) throws SFException {\n    setTimeZone(tz);\n    int dateBound = 50000;\n    int rowCount = 50000;\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"DATE\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.DATEDAY.getType(), null, customFieldMeta);\n\n    DateDayVector vector = new DateDayVector(\"date\", fieldType, allocator);\n    int[] rawDates = new int[rowCount];\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        nullValIndex.add(i);\n        vector.setNull(i);\n      } else {\n        rawDates[i] = random.nextInt(dateBound) - dateBound / 2;\n        vector.setSafe(i, rawDates[i]);\n      }\n    }\n\n    ArrowVectorConverter converter = new DateConverter(vector, 0, this, false);\n\n    for (int i = 0; i < rowCount; i++) {\n      int intVal = converter.toInt(i);\n      String strVal = converter.toString(i);\n      Date obj = converter.toDate(i, getTimeZone(), false);\n      String str = converter.toString(i);\n      if (nullValIndex.contains(i)) {\n        assertThat(intVal, is(0));\n        assertThat(strVal, is(nullValue()));\n        assertThat(obj, is(nullValue()));\n      } else {\n        Date oldObj = ArrowResultUtil.getDate(intVal);\n        assertThat(intVal, is(rawDates[i]));\n        assertThat(obj.getTime(), is(oldObj.getTime()));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testTimezoneDates(String tz) throws SFException {\n    setTimeZone(tz);\n    int testDay = 16911;\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"DATE\");\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.DATEDAY.getType(), null, customFieldMeta);\n\n    DateDayVector vector = new DateDayVector(\"date\", fieldType, allocator);\n\n    vector.setSafe(0, testDay);\n\n    // Test JDBC_FORMAT_DATE_WITH_TIMEZONE=TRUE with different session timezones\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    ArrowVectorConverter converter = new DateConverter(vector, 0, this, true);\n    converter.setUseSessionTimezone(true);\n    converter.setSessionTimeZone(TimeZone.getTimeZone(tz));\n    Object obj = converter.toObject(0);\n    DateTimeConverter jsonConverter =\n        new DateTimeConverter(\n            TimeZone.getTimeZone(tz), this.getSession(), 0, true, false, true, true);\n    Date jsonDate =\n        jsonConverter.getDate(Integer.toString(testDay), 91, 91, TimeZone.getTimeZone(tz), 0);\n    Object utcObj =\n        ArrowResultUtil.getDate(testDay, TimeZone.getTimeZone(\"UTC\"), TimeZone.getTimeZone(tz));\n\n    List<Object> testValues = this.timezoneDatesData.get(tz);\n    assertTrue(testValues.get(0) instanceof String);\n    assertTrue(testValues.get(1) instanceof Integer);\n    assertThat(obj.toString(), is(\"2016-04-20\"));\n    assertThat(jsonDate.toString(), is(obj.toString()));\n    assertThat(utcObj.toString(), is(testValues.get(0)));\n    assertThat(\n        ((Date) obj).getTime(),\n        is(((Date) utcObj).getTime() + ((Integer) testValues.get(1) * MILLIS_IN_ONE_HOUR)));\n\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/DoubleToRealConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.DataConversionContext;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.Float8Vector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.Test;\n\npublic class DoubleToRealConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  /** Random seed */\n  private Random random = new Random();\n\n  private ByteBuffer bb;\n\n  @Test\n  public void testConvertToDouble() throws SFException {\n    final int rowCount = 1000;\n    List<Double> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(random.nextDouble());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"REAL\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.FLOAT8.getType(), null, customFieldMeta);\n\n    Float8Vector vector = new Float8Vector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter =\n        new DoubleToRealConverter(vector, 0, (DataConversionContext) this);\n\n    for (int i = 0; i < rowCount; i++) {\n      double doubleVal = converter.toDouble(i);\n      float floatVal = converter.toFloat(i);\n      Object doubleObject = converter.toObject(i);\n      String doubleString = converter.toString(i);\n      if (doubleObject != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(doubleVal, is((double) 0));\n        assertThat(floatVal, is((float) 0));\n        assertThat(doubleObject, is(nullValue()));\n        assertThat(doubleString, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(i)));\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        assertThat(doubleVal, is(expectedValues.get(i)));\n        assertThat(floatVal, is(expectedValues.get(i).floatValue()));\n        assertThat(doubleObject, is(expectedValues.get(i)));\n        assertThat(doubleString, is(expectedValues.get(i).toString()));\n        final int x = i;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n        bb = ByteBuffer.wrap(converter.toBytes(i));\n        assertThat(doubleVal, is(bb.getDouble()));\n      }\n    }\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/IntToFixedConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.Test;\n\npublic class IntToFixedConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  /** Random seed */\n  private Random random = new Random();\n\n  private ByteBuffer bb;\n\n  @Test\n  public void testFixedNoScale() throws SFException {\n    final int rowCount = 1000;\n    List<Integer> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(random.nextInt());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    IntVector vector = new IntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new IntToFixedConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      int intVal = converter.toInt(i);\n      Object longObj = converter.toObject(i);\n      String intString = converter.toString(i);\n      if (intString != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(intVal, is(0));\n        assertThat(longObj, is(nullValue()));\n        assertThat(intString, is(nullValue()));\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        assertThat(intVal, is(expectedValues.get(i)));\n        assertEquals(longObj, (long) expectedValues.get(i));\n        assertThat(intString, is(expectedValues.get(i).toString()));\n        bb = ByteBuffer.wrap(converter.toBytes(i));\n        assertThat(intVal, is(bb.getInt()));\n      }\n    }\n    vector.clear();\n  }\n\n  @Test\n  public void testFixedWithScale() throws SFException {\n    final int rowCount = 1000;\n    List<Integer> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(random.nextInt());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    IntVector vector = new IntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new IntToScaledFixedConverter(vector, 0, this, 3);\n\n    for (int i = 0; i < rowCount; i++) {\n      BigDecimal bigDecimalVal = converter.toBigDecimal(i);\n      Object objectVal = converter.toObject(i);\n      String stringVal = converter.toString(i);\n\n      if (nullValIndex.contains(i)) {\n        assertThat(bigDecimalVal, nullValue());\n        assertThat(objectVal, nullValue());\n        assertThat(stringVal, nullValue());\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        BigDecimal expectedVal = BigDecimal.valueOf(expectedValues.get(i), 3);\n        assertThat(bigDecimalVal, is(expectedVal));\n        assertThat(objectVal, is(expectedVal));\n        assertThat(stringVal, is(expectedVal.toString()));\n        assertThat(converter.toBytes(i), is(notNullValue()));\n      }\n    }\n\n    vector.clear();\n  }\n\n  @Test\n  public void testInvalidConversion() {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    IntVector vector = new IntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 33000);\n\n    final ArrowVectorConverter converter = new IntToScaledFixedConverter(vector, 0, this, 3);\n    final int invalidConversionErrorCode = ErrorCode.INVALID_VALUE_CONVERT.getMessageCode();\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toLong(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toInt(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toShort(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toByte(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toDate(0, getTimeZone(), false));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toTime(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toTimestamp(0, TimeZone.getDefault()));\n    vector.clear();\n  }\n\n  @Test\n  public void testGetSmallerIntegralType() throws SFException {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    // test value which is out of range of int, but falls in long\n    IntVector vectorFoo = new IntVector(\"col_one\", fieldType, allocator);\n    vectorFoo.setSafe(0, 33000);\n    vectorFoo.setSafe(1, -33000);\n\n    final ArrowVectorConverter converterFoo = new IntToFixedConverter(vectorFoo, 0, this);\n    final int invalidConversionErrorCode = ErrorCode.INVALID_VALUE_CONVERT.getMessageCode();\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toShort(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toByte(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toShort(1));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toByte(1));\n\n    assertThat(converterFoo.toLong(0), is(33000L));\n    assertThat(converterFoo.toLong(1), is(-33000L));\n    vectorFoo.clear();\n\n    // test value which is in range of byte, all get method should return\n    IntVector vectorBar = new IntVector(\"col_one\", fieldType, allocator);\n    // set value which is out of range of int, but falls in long\n    vectorBar.setSafe(0, 10);\n    vectorBar.setSafe(1, -10);\n\n    final ArrowVectorConverter converterBar = new IntToFixedConverter(vectorBar, 0, this);\n\n    assertThat(converterBar.toByte(0), is((byte) 10));\n    assertThat(converterBar.toByte(1), is((byte) -10));\n    assertThat(converterBar.toShort(0), is((short) 10));\n    assertThat(converterBar.toShort(1), is((short) -10));\n    assertThat(converterBar.toLong(0), is(10L));\n    assertThat(converterBar.toLong(1), is(-10L));\n    vectorBar.clear();\n  }\n\n  @Test\n  public void testGetBooleanNoScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    IntVector vector = new IntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    ArrowVectorConverter converter = new IntToFixedConverter(vector, 0, this);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    assertThat(true, is(converter.toBoolean(1)));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n\n  @Test\n  public void testGetBooleanWithScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    IntVector vector = new IntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    final ArrowVectorConverter converter = new IntToScaledFixedConverter(vector, 0, this, 3);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/IntToTimeConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Time;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class IntToTimeConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  public IntToTimeConverterTest() {\n    this.setScale(scale);\n  }\n\n  private int scale = 3;\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testTime(String timezone) throws SFException {\n    System.setProperty(\"user.timezone\", timezone);\n    // test old and new dates\n    int[] testTimesInt = {12345678};\n\n    String[] testTimesJson = {\"12345.678\"};\n\n    Time[] expectedTimes = {new Time(12345678)};\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIME\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType = new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    IntVector vector = new IntVector(\"date\", fieldType, allocator);\n    int i = 0, j = 0;\n    while (i < testTimesInt.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        vector.setSafe(j, testTimesInt[i++]);\n      }\n      j++;\n    }\n\n    ArrowVectorConverter converter = new IntToTimeConverter(vector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    while (j < rowCount) {\n      String strVal = converter.toString(j);\n      Time time = converter.toTime(j);\n      Object obj = converter.toObject(j);\n      Time oldTime =\n          new Time(\n              ResultUtil.getSFTime(testTimesJson[i], scale, new SFSession())\n                  .getFractionalSeconds(ResultUtil.DEFAULT_SCALE_OF_SFTIME_FRACTION_SECONDS));\n      if (strVal != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n      if (nullValIndex.contains(j)) {\n        assertThat(obj, is(nullValue()));\n        assertThat(strVal, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n        assertThat(0, is(converter.toInt(j)));\n      } else {\n        assertThat(expectedTimes[i], is(time));\n        assertThat(expectedTimes[i], is((Time) obj));\n        assertThat(oldTime, is(time));\n        assertThat(oldTime, is((Time) obj));\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n        assertThat(converter.toBytes(j), is(notNullValue()));\n      }\n      j++;\n    }\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/SmallIntToFixedConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.SmallIntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.Test;\n\npublic class SmallIntToFixedConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  /** Random seed */\n  private Random random = new Random();\n\n  private ByteBuffer bb;\n\n  @Test\n  public void testFixedNoScale() throws SFException {\n    final int rowCount = 1000;\n    List<Short> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add((short) random.nextInt(1 << 16));\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n\n    SmallIntVector vector = new SmallIntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new SmallIntToFixedConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      short shortVal = converter.toShort(i);\n      Object longObject = converter.toObject(i); // the logical type is long\n      String shortString = converter.toString(i);\n      if (shortString != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(shortVal, is((short) 0));\n        assertThat(longObject, is(nullValue()));\n        assertThat(shortString, is(nullValue()));\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        assertThat(shortVal, is(expectedValues.get(i)));\n        assertEquals(longObject, (long) expectedValues.get(i));\n        assertThat(shortString, is(expectedValues.get(i).toString()));\n        bb = ByteBuffer.wrap(converter.toBytes(i));\n        assertThat(shortVal, is(bb.getShort()));\n      }\n    }\n    vector.clear();\n  }\n\n  @Test\n  public void testFixedWithScale() throws SFException {\n    final int rowCount = 1000;\n    List<Short> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add((short) random.nextInt(1 << 16));\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n\n    SmallIntVector vector = new SmallIntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new SmallIntToScaledFixedConverter(vector, 0, this, 3);\n\n    for (int i = 0; i < rowCount; i++) {\n      BigDecimal bigDecimalVal = converter.toBigDecimal(i);\n      Object objectVal = converter.toObject(i);\n      String stringVal = converter.toString(i);\n\n      if (nullValIndex.contains(i)) {\n        assertThat(bigDecimalVal, nullValue());\n        assertThat(objectVal, nullValue());\n        assertThat(stringVal, nullValue());\n        assertThat(converter.toBytes(i), is(nullValue()));\n      } else {\n        BigDecimal expectedVal = BigDecimal.valueOf(expectedValues.get(i), 3);\n        assertThat(bigDecimalVal, is(expectedVal));\n        assertThat(objectVal, is(expectedVal));\n        assertThat(stringVal, is(expectedVal.toString()));\n        assertThat(converter.toBytes(i), is(notNullValue()));\n      }\n    }\n\n    vector.clear();\n  }\n\n  @Test\n  public void testInvalidConversion() {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n\n    SmallIntVector vector = new SmallIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 200);\n\n    final ArrowVectorConverter converter = new SmallIntToScaledFixedConverter(vector, 0, this, 3);\n    final int invalidConversionErrorCode = ErrorCode.INVALID_VALUE_CONVERT.getMessageCode();\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toLong(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toInt(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toShort(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toByte(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toDate(0, getTimeZone(), false));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toTime(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toTimestamp(0, TimeZone.getDefault()));\n    vector.clear();\n  }\n\n  @Test\n  public void testGetSmallerIntegralType() throws SFException {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n\n    // test value which is out of range of int, but falls in long\n    SmallIntVector vectorFoo = new SmallIntVector(\"col_one\", fieldType, allocator);\n    vectorFoo.setSafe(0, 200);\n    vectorFoo.setSafe(1, -200);\n\n    final ArrowVectorConverter converterFoo = new SmallIntToFixedConverter(vectorFoo, 0, this);\n    final int invalidConversionErrorCode = ErrorCode.INVALID_VALUE_CONVERT.getMessageCode();\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toByte(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converterFoo.toByte(1));\n    vectorFoo.clear();\n\n    // test value which is in range of byte, all get method should return\n    SmallIntVector vectorBar = new SmallIntVector(\"col_one\", fieldType, allocator);\n    // set value which is out of range of int, but falls in long\n    vectorBar.setSafe(0, 10);\n    vectorBar.setSafe(1, -10);\n\n    final ArrowVectorConverter converterBar = new SmallIntToFixedConverter(vectorBar, 0, this);\n\n    assertThat(converterBar.toByte(0), is((byte) 10));\n    assertThat(converterBar.toByte(1), is((byte) -10));\n    assertThat(converterBar.toInt(0), is(10));\n    assertThat(converterBar.toInt(1), is(-10));\n    assertThat(converterBar.toLong(0), is(10L));\n    assertThat(converterBar.toLong(1), is(-10L));\n    vectorBar.clear();\n  }\n\n  @Test\n  public void testGetBooleanNoScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n\n    SmallIntVector vector = new SmallIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    ArrowVectorConverter converter = new SmallIntToFixedConverter(vector, 0, this);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    assertThat(true, is(converter.toBoolean(1)));\n    assertThat(false, is(converter.toBoolean(2)));\n    assertFalse(converter.isNull(0));\n    assertFalse(converter.isNull(1));\n    assertTrue(converter.isNull(2));\n    assertFalse(converter.isNull(3));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n\n  @Test\n  public void testGetBooleanWithScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.SMALLINT.getType(), null, customFieldMeta);\n\n    SmallIntVector vector = new SmallIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    final ArrowVectorConverter converter = new SmallIntToScaledFixedConverter(vector, 0, this, 3);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/ThreeFieldStructToTimestampTZConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static java.util.stream.Stream.concat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.Field;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class ThreeFieldStructToTimestampTZConverterTest extends BaseConverterTest {\n  private static class TimezoneProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      List<String> timezones =\n          new ArrayList<String>() {\n            {\n              add(\"America/Los_Angeles\");\n              add(\"America/New_York\");\n              add(\"Pacific/Honolulu\");\n              add(\"Asia/Singapore\");\n              add(\"MESZ\");\n              add(\"MEZ\");\n              add(\"UTC\");\n            }\n          };\n\n      Stream<Arguments> args = Stream.empty();\n\n      for (String timezone : timezones) {\n        args =\n            concat(\n                args,\n                Stream.of(\n                    Arguments.argumentSet(\n                        timezone,\n                        timezone,\n                        new long[] {1546391837, 1546391837, 0, 123, -12346, -12345},\n                        new int[] {0, 10, 100, 456, 876543211, 0},\n                        new int[] {960, 1440, 960, 960, 1440, 1440},\n                        new String[] {\n                          \"1546391837.000000000 960\",\n                          \"1546391837.000000010 1440\",\n                          \"0.000000100 960\",\n                          \"123.000000456 960\",\n                          \"-12345.123456789 1440\",\n                          \"-12345.000000000 1440\"\n                        }),\n                    Arguments.argumentSet(\n                        timezone + \" Overflow\",\n                        timezone,\n                        new long[] {1546391837},\n                        new int[] {0},\n                        new int[] {960},\n                        new String[] {\"1546391837.000000000 960\"})));\n      }\n\n      return args;\n    }\n  }\n\n  private static void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  @AfterAll\n  public static void clearTimezone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int oldScale = 9;\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testTimestampTZ(\n      String tz,\n      long[] testSecondsInt64,\n      int[] testNanos,\n      int[] testTimeZoneIndices,\n      String[] testTimesJson)\n      throws SFException {\n    setTimezone(tz);\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIMESTAMP\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n    FieldType fieldType2 =\n        new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n    FieldType fieldType3 =\n        new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    StructVector structVector = StructVector.empty(\"testVector\", allocator);\n    List<Field> fieldList = new LinkedList<Field>();\n    Field bigIntField =\n        new Field(ThreeFieldStructToTimestampTZConverter.FIELD_NAME_EPOCH, fieldType, null);\n    Field fractionField =\n        new Field(ThreeFieldStructToTimestampTZConverter.FIELD_NAME_FRACTION, fieldType2, null);\n    Field timeZoneIndexField =\n        new Field(\n            ThreeFieldStructToTimestampTZConverter.FIELD_NAME_TIME_ZONE_INDEX, fieldType3, null);\n\n    fieldList.add(bigIntField);\n    fieldList.add(fractionField);\n    fieldList.add(timeZoneIndexField);\n\n    structVector.initializeChildrenFromFields(fieldList);\n    BigIntVector seconds =\n        structVector.getChild(\n            ThreeFieldStructToTimestampTZConverter.FIELD_NAME_EPOCH, BigIntVector.class);\n    IntVector nanos =\n        structVector.getChild(\n            ThreeFieldStructToTimestampTZConverter.FIELD_NAME_FRACTION, IntVector.class);\n    IntVector timeZoneIdx =\n        structVector.getChild(\n            ThreeFieldStructToTimestampTZConverter.FIELD_NAME_TIME_ZONE_INDEX, IntVector.class);\n\n    int i = 0, j = 0;\n    while (i < testSecondsInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        seconds.setNull(j);\n        nanos.setNull(j);\n        timeZoneIdx.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        seconds.setSafe(j, testSecondsInt64[i]);\n        nanos.setSafe(j, testNanos[i]);\n        timeZoneIdx.setSafe(j, testTimeZoneIndices[i++]);\n        structVector.setIndexDefined(j);\n      }\n      j++;\n    }\n    structVector.setValueCount(j);\n\n    ArrowVectorConverter converter =\n        new ThreeFieldStructToTimestampTZConverter(structVector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    this.setScale(testNanos[i]);\n    while (j < rowCount) {\n      Timestamp ts = converter.toTimestamp(j, getTimeZone());\n      Date date = converter.toDate(j, getTimeZone(), false);\n      Time time = converter.toTime(j);\n      String tsStr = converter.toString(j);\n      if (tsStr != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n\n      if (nullValIndex.contains(j)) {\n        assertThat(ts, is(nullValue()));\n        assertThat(date, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        SFTimestamp sfTimestamp =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        Timestamp oldTs = sfTimestamp.getTimestamp();\n        oldTs = ResultUtil.adjustTimestamp(oldTs);\n        Date oldDate = new Date((oldTs).getTime());\n        SFTimestamp sfTS =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        String timestampStr =\n            ResultUtil.getSFTimestampAsString(\n                sfTS,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                oldScale,\n                getTimestampNTZFormatter(),\n                getTimestampLTZFormatter(),\n                getTimestampTZFormatter(),\n                getSession());\n        Time oldTime = new Time(oldTs.getTime());\n        assertThat(oldDate, is(date));\n        assertThat(oldTs, is(ts));\n        assertThat(oldTime, is(time));\n        assertThat(timestampStr, is(tsStr));\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBytes(x));\n        i++;\n        if (i < testNanos.length) {\n          this.setScale(testNanos[i]);\n        }\n      }\n      j++;\n    }\n    structVector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/TinyIntToFixedConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.TinyIntVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.Test;\n\npublic class TinyIntToFixedConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  /** Random seed */\n  private Random random = new Random();\n\n  @Test\n  public void testFixedNoScale() throws SFException {\n    final int rowCount = 1000;\n    List<Byte> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add((byte) random.nextInt(1 << 8));\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n\n    TinyIntVector vector = new TinyIntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new TinyIntToFixedConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      byte byteVal = converter.toByte(i);\n      Object longObject = converter.toObject(i); // the logical type is long\n      String byteString = converter.toString(i);\n\n      if (nullValIndex.contains(i)) {\n        assertThat(byteVal, is((byte) 0));\n        assertThat(longObject, is(nullValue()));\n        assertThat(byteString, is(nullValue()));\n      } else {\n        assertThat(byteVal, is(expectedValues.get(i)));\n        assertEquals(longObject, (long) expectedValues.get(i));\n        assertThat(byteString, is(expectedValues.get(i).toString()));\n      }\n    }\n    vector.clear();\n  }\n\n  @Test\n  public void testFixedWithScale() throws SFException {\n    final int rowCount = 1000;\n    List<Byte> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add((byte) random.nextInt(1 << 8));\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"1\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n\n    TinyIntVector vector = new TinyIntVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new TinyIntToScaledFixedConverter(vector, 0, this, 1);\n\n    for (int i = 0; i < rowCount; i++) {\n      BigDecimal bigDecimalVal = converter.toBigDecimal(i);\n      Object objectVal = converter.toObject(i);\n      String stringVal = converter.toString(i);\n\n      if (nullValIndex.contains(i)) {\n        assertThat(bigDecimalVal, nullValue());\n        assertThat(objectVal, nullValue());\n        assertThat(stringVal, nullValue());\n      } else {\n        BigDecimal expectedVal = BigDecimal.valueOf(expectedValues.get(i), 1);\n        assertThat(bigDecimalVal, is(expectedVal));\n        assertThat(objectVal, is(expectedVal));\n        assertThat(stringVal, is(expectedVal.toString()));\n      }\n    }\n\n    vector.clear();\n  }\n\n  @Test\n  public void testInvalidConversion() {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"1\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n\n    TinyIntVector vector = new TinyIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 200);\n\n    final ArrowVectorConverter converter = new TinyIntToScaledFixedConverter(vector, 0, this, 1);\n    final int invalidConversionErrorCode = ErrorCode.INVALID_VALUE_CONVERT.getMessageCode();\n\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toLong(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toInt(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toShort(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toDate(0, getTimeZone(), false));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toTime(0));\n    TestUtil.assertSFException(\n        invalidConversionErrorCode, () -> converter.toTimestamp(0, TimeZone.getDefault()));\n    vector.clear();\n  }\n\n  @Test\n  public void testGetSmallerIntegralType() throws SFException {\n    // try convert to int/long/byte/short with scale > 0\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n\n    // test value which is in range of byte, all get method should return\n    TinyIntVector vectorBar = new TinyIntVector(\"col_one\", fieldType, allocator);\n    // set value which is out of range of int, but falls in long\n    vectorBar.setSafe(0, 10);\n    vectorBar.setSafe(1, -10);\n\n    final ArrowVectorConverter converterBar = new TinyIntToFixedConverter(vectorBar, 0, this);\n\n    assertThat(converterBar.toShort(0), is((short) 10));\n    assertThat(converterBar.toShort(1), is((short) -10));\n    assertThat(converterBar.toInt(0), is(10));\n    assertThat(converterBar.toInt(1), is(-10));\n    assertThat(converterBar.toLong(0), is(10L));\n    assertThat(converterBar.toLong(1), is(-10L));\n    vectorBar.clear();\n  }\n\n  @Test\n  public void testGetBooleanNoScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"0\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n\n    TinyIntVector vector = new TinyIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    ArrowVectorConverter converter = new TinyIntToFixedConverter(vector, 0, this);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    assertThat(true, is(converter.toBoolean(1)));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n\n  @Test\n  public void testGetBooleanWithScale() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n    customFieldMeta.put(\"precision\", \"10\");\n    customFieldMeta.put(\"scale\", \"3\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.TINYINT.getType(), null, customFieldMeta);\n\n    TinyIntVector vector = new TinyIntVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, 0);\n    vector.setSafe(1, 1);\n    vector.setNull(2);\n    vector.setSafe(3, 5);\n\n    final ArrowVectorConverter converter = new TinyIntToScaledFixedConverter(vector, 0, this, 3);\n\n    assertFalse(converter.toBoolean(0));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n    assertFalse(converter.toBoolean(2));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    assertFalse(converter.isNull(0));\n    assertFalse(converter.isNull(1));\n    assertTrue(converter.isNull(2));\n    assertFalse(converter.isNull(3));\n    vector.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/TwoFieldStructToTimestampLTZConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static java.util.stream.Stream.concat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.Field;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class TwoFieldStructToTimestampLTZConverterTest extends BaseConverterTest {\n\n  static class DataProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      List<String> timezones =\n          new ArrayList<String>() {\n            {\n              add(\"America/Los_Angeles\");\n              add(\"America/New_York\");\n              add(\"Pacific/Honolulu\");\n              add(\"Asia/Singapore\");\n              add(\"MESZ\");\n              add(\"MEZ\");\n              add(\"UTC\");\n            }\n          };\n\n      Stream<Arguments> args = Stream.empty();\n\n      for (String timezone : timezones) {\n        args =\n            concat(\n                args,\n                Stream.of(\n                    Arguments.argumentSet(\n                        timezone,\n                        timezone,\n                        new long[] {1546391837, 0, -1546391838, -1546391838, -1546391838},\n                        new int[] {0, 1, 999999990, 876543211, 1},\n                        new String[] {\n                          \"1546391837.000000000\",\n                          \"0.000000001\",\n                          \"-1546391837.000000010\",\n                          \"-1546391837.123456789\",\n                          \"-1546391837.999999999\"\n                        }),\n                    Arguments.argumentSet(\n                        timezone + \" Overflow\",\n                        timezone,\n                        new long[] {154639183700000L},\n                        new int[] {0},\n                        new String[] {\"154639183700000.000000000\"})));\n      }\n      return args;\n    }\n  }\n\n  private static void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  @AfterAll\n  public static void clearTimezone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int oldScale = 9;\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampLTZ(\n      String timezone, long[] testSecondsInt64, int[] testNanoSecs, String[] testTimesJson)\n      throws SFException {\n\n    setTimezone(timezone);\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIMESTAMP\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n    FieldType fieldType2 =\n        new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    StructVector structVector = StructVector.empty(\"testListVector\", allocator);\n    List<Field> fieldList = new LinkedList<Field>();\n    Field bigIntField =\n        new Field(TwoFieldStructToTimestampLTZConverter.FIELD_NAME_EPOCH, fieldType, null);\n\n    Field intField =\n        new Field(TwoFieldStructToTimestampLTZConverter.FIELD_NAME_FRACTION, fieldType2, null);\n\n    fieldList.add(bigIntField);\n    fieldList.add(intField);\n\n    structVector.initializeChildrenFromFields(fieldList);\n    BigIntVector epochs =\n        structVector.getChild(\n            TwoFieldStructToTimestampLTZConverter.FIELD_NAME_EPOCH, BigIntVector.class);\n    IntVector fractions =\n        structVector.getChild(\n            TwoFieldStructToTimestampLTZConverter.FIELD_NAME_FRACTION, IntVector.class);\n\n    int i = 0, j = 0;\n    while (i < testSecondsInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        epochs.setNull(j);\n        fractions.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        epochs.setSafe(j, testSecondsInt64[i]);\n        fractions.setSafe(j, testNanoSecs[i++]);\n        structVector.setIndexDefined(j);\n      }\n      j++;\n    }\n    structVector.setValueCount(j);\n\n    ArrowVectorConverter converter =\n        new TwoFieldStructToTimestampLTZConverter(structVector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    while (j < rowCount) {\n      Timestamp ts = converter.toTimestamp(j, getTimeZone());\n      Date date = converter.toDate(j, getTimeZone(), false);\n      Time time = converter.toTime(j);\n      String tsStr = converter.toString(j);\n      if (tsStr != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n\n      if (nullValIndex.contains(j)) {\n        assertThat(ts, is(nullValue()));\n        assertThat(date, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        SFTimestamp sfTimestamp =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                java.sql.Types.TIMESTAMP,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        Timestamp oldTs = sfTimestamp.getTimestamp();\n        oldTs = ResultUtil.adjustTimestamp(oldTs);\n        Date oldDate = new Date((oldTs).getTime());\n        SFTimestamp sfTS =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        String timestampStr =\n            ResultUtil.getSFTimestampAsString(\n                sfTS,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ,\n                oldScale,\n                getTimestampNTZFormatter(),\n                getTimestampLTZFormatter(),\n                getTimestampTZFormatter(),\n                getSession());\n        Time oldTime = new Time(oldTs.getTime());\n        assertThat(oldDate, is(date));\n        assertThat(oldTs, is(ts));\n        assertThat(oldTime, is(time));\n        assertThat(timestampStr, is(tsStr));\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBytes(x));\n        i++;\n      }\n      j++;\n    }\n    structVector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/TwoFieldStructToTimestampNTZConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static java.util.stream.Stream.concat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport java.util.stream.Stream;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.Field;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class TwoFieldStructToTimestampNTZConverterTest extends BaseConverterTest {\n\n  private static void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  @AfterAll\n  public static void clearTimezone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int oldScale = 9;\n\n  static class DataProvider implements ArgumentsProvider {\n\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      List<String> timezones =\n          new ArrayList<String>() {\n            {\n              add(\"America/Los_Angeles\");\n              add(\"America/New_York\");\n              add(\"Pacific/Honolulu\");\n              add(\"Asia/Singapore\");\n              add(\"MESZ\");\n              add(\"MEZ\");\n              add(\"UTC\");\n            }\n          };\n\n      Stream<Arguments> args = Stream.empty();\n\n      for (String timezone : timezones) {\n        args =\n            concat(\n                args,\n                Stream.of(\n                    Arguments.argumentSet(\n                        timezone + \" Overflow\",\n                        timezone,\n                        false,\n                        new long[] {154639183700000L},\n                        new int[] {0},\n                        new String[] {\"154639183700000.000000000\"}),\n                    Arguments.argumentSet(\n                        timezone + \" HonorClientTZForTimestampNTZ Disabled\",\n                        timezone,\n                        false,\n                        new long[] {1546391837, 0, -1546391838, -1546391838, -1546391838},\n                        new int[] {0, 1, 999999990, 876543211, 1},\n                        new String[] {\n                          \"1546391837.000000000\",\n                          \"0.000000001\",\n                          \"-1546391837.000000010\",\n                          \"-1546391837.123456789\",\n                          \"-1546391837.999999999\"\n                        }),\n                    Arguments.argumentSet(\n                        timezone + \" HonorClientTZForTimestampNTZ Enabled\",\n                        timezone,\n                        true,\n                        new long[] {1546391837, 1546391837, 1546391837, 1546391837, 1546391837},\n                        new int[] {0, 1, 10, 100, 999999999},\n                        new String[] {\n                          \"1546391837.000000000\",\n                          \"1546391837.000000001\",\n                          \"1546391837.000000010\",\n                          \"1546391837.000000100\",\n                          \"1546391837.999999999\"\n                        })));\n      }\n\n      return args;\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampNTZ(\n      String timezone,\n      boolean honorClientTZForTimestampNTZ,\n      long[] testSecondsInt64,\n      int[] testNanoSecs,\n      String[] testTimesJson)\n      throws SFException {\n    this.setHonorClientTZForTimestampNTZ(honorClientTZForTimestampNTZ);\n    setTimezone(timezone);\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIMESTAMP\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n    FieldType fieldType2 =\n        new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    StructVector structVector = StructVector.empty(\"testListVector\", allocator);\n    List<Field> fieldList = new LinkedList<Field>();\n    Field bigIntField =\n        new Field(TwoFieldStructToTimestampNTZConverter.FIELD_NAME_EPOCH, fieldType, null);\n\n    Field intField =\n        new Field(TwoFieldStructToTimestampNTZConverter.FIELD_NAME_FRACTION, fieldType2, null);\n\n    fieldList.add(bigIntField);\n    fieldList.add(intField);\n\n    structVector.initializeChildrenFromFields(fieldList);\n    BigIntVector epochs =\n        structVector.getChild(\n            TwoFieldStructToTimestampNTZConverter.FIELD_NAME_EPOCH, BigIntVector.class);\n\n    IntVector fractions =\n        structVector.getChild(\n            TwoFieldStructToTimestampNTZConverter.FIELD_NAME_FRACTION, IntVector.class);\n\n    int i = 0, j = 0;\n    while (i < testSecondsInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        epochs.setNull(j);\n        fractions.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        epochs.setSafe(j, testSecondsInt64[i]);\n        fractions.setSafe(j, testNanoSecs[i++]);\n        structVector.setIndexDefined(j);\n      }\n      j++;\n    }\n    structVector.setValueCount(j);\n\n    ArrowVectorConverter converter =\n        new TwoFieldStructToTimestampNTZConverter(structVector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n\n    while (j < rowCount) {\n      Timestamp ts = converter.toTimestamp(j, TimeZone.getDefault());\n      Date date = converter.toDate(j, getTimeZone(), false);\n      Time time = converter.toTime(j);\n      String tsStr = converter.toString(j);\n      if (tsStr != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n\n      if (nullValIndex.contains(j)) {\n        assertThat(ts, is(nullValue()));\n        assertThat(date, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        SFTimestamp sfTimestamp =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                java.sql.Types.TIMESTAMP,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        Timestamp oldTs = sfTimestamp.getTimestamp();\n        if (getHonorClientTZForTimestampNTZ()) {\n          // Note: honorClientTZForTimestampNTZ is used except getString()\n          oldTs = sfTimestamp.moveToTimeZone(getTimeZone()).getTimestamp();\n        }\n        oldTs = ResultUtil.adjustTimestamp(oldTs);\n\n        SFTimestamp sfTS =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                java.sql.Types.TIMESTAMP,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        String timestampStr =\n            ResultUtil.getSFTimestampAsString(\n                sfTS,\n                java.sql.Types.TIMESTAMP,\n                oldScale,\n                getTimestampNTZFormatter(),\n                getTimestampLTZFormatter(),\n                getTimestampTZFormatter(),\n                getSession());\n        Date oldDate = new Date((oldTs).getTime());\n        Time oldTime = new Time(oldTs.getTime());\n        assertThat(oldTs, is(ts));\n        assertThat(oldDate, is(date));\n        assertThat(timestampStr, is(tsStr));\n        assertThat(oldTime, is(time));\n        i++;\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBytes(x));\n      }\n      j++;\n    }\n    structVector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/TwoFieldStructToTimestampTZConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport net.snowflake.common.core.SFTimestamp;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.arrow.vector.IntVector;\nimport org.apache.arrow.vector.complex.StructVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.Field;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class TwoFieldStructToTimestampTZConverterTest extends BaseConverterTest {\n  public static void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  @AfterAll\n  public static void clearTimezone() {\n    System.clearProperty(\"user.timezone\");\n  }\n\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  private int oldScale = 9;\n\n  @ParameterizedTest\n  @ArgumentsSource(TimezoneProvider.class)\n  public void testTimestampTZ(String tz) throws SFException {\n    setTimezone(tz);\n    // test old and new dates\n    long[] testEpochesInt64 = {1546391837, 1546391837, 0, 123, -12345, -12345678};\n\n    int[] testScales = {0, 3, 0, 0, 0, 6};\n\n    int[] testTimeZoneIndices = {960, 1440, 960, 960, 1440, 1440};\n\n    String[] testTimesJson = {\n      \"1546391837.000000000 960\",\n      \"1546391.8370000000 1440\",\n      \"0.000000000 960\",\n      \"123.000000000 960\",\n      \"-12345.000000000 1440\",\n      \"-12.345678000 1440\"\n    };\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"TIMESTAMP\");\n    Set<Integer> nullValIndex = new HashSet<>();\n    // test normal date\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.BIGINT.getType(), null, customFieldMeta);\n    FieldType fieldType2 =\n        new FieldType(true, Types.MinorType.INT.getType(), null, customFieldMeta);\n\n    StructVector structVector = StructVector.empty(\"testVector\", allocator);\n    List<Field> fieldList = new LinkedList<Field>();\n    Field bigIntField =\n        new Field(TwoFieldStructToTimestampTZConverter.FIELD_NAME_EPOCH, fieldType, null);\n\n    Field smallIntField =\n        new Field(\n            TwoFieldStructToTimestampTZConverter.FIELD_NAME_TIME_ZONE_INDEX, fieldType2, null);\n\n    fieldList.add(bigIntField);\n    fieldList.add(smallIntField);\n\n    structVector.initializeChildrenFromFields(fieldList);\n    BigIntVector epoch =\n        structVector.getChild(\n            TwoFieldStructToTimestampTZConverter.FIELD_NAME_EPOCH, BigIntVector.class);\n    IntVector timeZoneIdx =\n        structVector.getChild(\n            TwoFieldStructToTimestampTZConverter.FIELD_NAME_TIME_ZONE_INDEX, IntVector.class);\n\n    int i = 0, j = 0;\n    while (i < testEpochesInt64.length) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        epoch.setNull(j);\n        timeZoneIdx.setNull(j);\n        nullValIndex.add(j);\n      } else {\n        epoch.setSafe(j, testEpochesInt64[i]);\n        timeZoneIdx.setSafe(j, testTimeZoneIndices[i++]);\n        structVector.setIndexDefined(j);\n      }\n      j++;\n    }\n    structVector.setValueCount(j);\n\n    ArrowVectorConverter converter =\n        new TwoFieldStructToTimestampTZConverter(structVector, 0, this);\n    int rowCount = j;\n    i = 0;\n    j = 0;\n    this.setScale(testScales[i]);\n    while (j < rowCount) {\n      Timestamp ts = converter.toTimestamp(j, getTimeZone());\n      Date date = converter.toDate(j, getTimeZone(), false);\n      Time time = converter.toTime(j);\n      String tsStr = converter.toString(j);\n      if (tsStr != null) {\n        assertFalse(converter.isNull(j));\n      } else {\n        assertTrue(converter.isNull(j));\n      }\n\n      if (nullValIndex.contains(j)) {\n        assertThat(ts, is(nullValue()));\n        assertThat(date, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(j)));\n        assertThat(converter.toBytes(j), is(nullValue()));\n      } else {\n        SFTimestamp sfTimestamp =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        Timestamp oldTs = sfTimestamp.getTimestamp();\n        oldTs = ResultUtil.adjustTimestamp(oldTs);\n        Date oldDate = new Date((oldTs).getTime());\n        SFTimestamp sfTS =\n            ResultUtil.getSFTimestamp(\n                testTimesJson[i],\n                oldScale,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                getResultVersion(),\n                getTimeZone(),\n                getSession());\n        String timestampStr =\n            ResultUtil.getSFTimestampAsString(\n                sfTS,\n                SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ,\n                oldScale,\n                getTimestampNTZFormatter(),\n                getTimestampLTZFormatter(),\n                getTimestampTZFormatter(),\n                getSession());\n        Time oldTime = new Time(oldTs.getTime());\n        assertThat(oldDate, is(date));\n        assertThat(oldTs, is(ts));\n        assertThat(oldTime, is(time));\n        assertThat(timestampStr, is(tsStr));\n        i++;\n        if (i < testScales.length) {\n          this.setScale(testScales[i]);\n        }\n        final int x = j;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBytes(x));\n      }\n      j++;\n    }\n    structVector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/VarBinaryToBinaryConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.VarBinaryVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.jupiter.api.Test;\n\npublic class VarBinaryToBinaryConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  @Test\n  public void testConvertToString() throws SFException {\n    final int rowCount = 1000;\n    List<byte[]> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(RandomStringUtils.random(20).getBytes());\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"BINARY\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.VARBINARY.getType(), null, customFieldMeta);\n\n    VarBinaryVector vector = new VarBinaryVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i));\n      }\n    }\n\n    ArrowVectorConverter converter = new VarBinaryToBinaryConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      String stringVal = converter.toString(i);\n      Object objectVal = converter.toObject(i);\n      byte[] bytesVal = converter.toBytes(i);\n      if (stringVal != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(stringVal, is(nullValue()));\n        assertThat(objectVal, is(nullValue()));\n        assertThat(bytesVal, is(nullValue()));\n        assertThat(false, is(converter.toBoolean(i)));\n      } else {\n        String base64Expected = Base64.getEncoder().encodeToString(expectedValues.get(i));\n        assertThat(stringVal, is(base64Expected));\n        assertThat(bytesVal, is(expectedValues.get(i)));\n        assertThat(objectVal, is(expectedValues.get(i)));\n        final int x = i;\n        TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(x));\n      }\n    }\n    vector.clear();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/arrow/VarCharConverterTest.java",
    "content": "package net.snowflake.client.internal.core.arrow;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Date;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.SFException;\nimport org.apache.arrow.memory.BufferAllocator;\nimport org.apache.arrow.memory.RootAllocator;\nimport org.apache.arrow.vector.VarCharVector;\nimport org.apache.arrow.vector.types.Types;\nimport org.apache.arrow.vector.types.pojo.FieldType;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.jupiter.api.Test;\n\npublic class VarCharConverterTest extends BaseConverterTest {\n  /** allocator for arrow */\n  private BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);\n\n  private Random random = new Random();\n\n  @Test\n  public void testConvertToString() throws SFException {\n    final int rowCount = 1000;\n    List<String> expectedValues = new ArrayList<>();\n    Set<Integer> nullValIndex = new HashSet<>();\n    for (int i = 0; i < rowCount; i++) {\n      expectedValues.add(RandomStringUtils.random(20));\n    }\n\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.VARCHAR.getType(), null, customFieldMeta);\n\n    VarCharVector vector = new VarCharVector(\"col_one\", fieldType, allocator);\n    for (int i = 0; i < rowCount; i++) {\n      boolean isNull = random.nextBoolean();\n      if (isNull) {\n        vector.setNull(i);\n        nullValIndex.add(i);\n      } else {\n        vector.setSafe(i, expectedValues.get(i).getBytes(StandardCharsets.UTF_8));\n      }\n    }\n\n    ArrowVectorConverter converter = new VarCharConverter(vector, 0, this);\n\n    for (int i = 0; i < rowCount; i++) {\n      String stringVal = converter.toString(i);\n      Object objectVal = converter.toObject(i);\n      byte[] bytesVal = converter.toBytes(i);\n      if (stringVal != null) {\n        assertFalse(converter.isNull(i));\n      } else {\n        assertTrue(converter.isNull(i));\n      }\n\n      if (nullValIndex.contains(i)) {\n        assertThat(stringVal, is(nullValue()));\n        assertThat(objectVal, is(nullValue()));\n        assertThat(bytesVal, is(nullValue()));\n      } else {\n        assertThat(stringVal, is(expectedValues.get(i)));\n        assertThat(objectVal, is(expectedValues.get(i)));\n        assertThat(bytesVal, is(expectedValues.get(i).getBytes(StandardCharsets.UTF_8)));\n      }\n    }\n    vector.clear();\n  }\n\n  @Test\n  public void testGetBoolean() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.VARCHAR.getType(), null, customFieldMeta);\n\n    VarCharVector vector = new VarCharVector(\"col_one\", fieldType, allocator);\n    vector.setSafe(0, \"0\".getBytes(StandardCharsets.UTF_8));\n    vector.setSafe(1, \"1\".getBytes(StandardCharsets.UTF_8));\n    vector.setNull(2);\n    vector.setSafe(3, \"5\".getBytes(StandardCharsets.UTF_8));\n\n    ArrowVectorConverter converter = new VarCharConverter(vector, 0, this);\n\n    assertThat(false, is(converter.toBoolean(0)));\n    assertThat(true, is(converter.toBoolean(1)));\n    assertThat(false, is(converter.toBoolean(2)));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toBoolean(3));\n\n    vector.close();\n  }\n\n  @Test\n  public void testGetDate() throws SFException {\n    Map<String, String> customFieldMeta = new HashMap<>();\n    customFieldMeta.put(\"logicalType\", \"FIXED\");\n\n    FieldType fieldType =\n        new FieldType(true, Types.MinorType.VARCHAR.getType(), null, customFieldMeta);\n\n    VarCharVector vector = new VarCharVector(\"col_one\", fieldType, allocator);\n    vector.setNull(0);\n    vector.setSafe(1, \"2023-10-26\".getBytes(StandardCharsets.UTF_8));\n    vector.setSafe(2, \"abc\".getBytes(StandardCharsets.UTF_8));\n\n    ArrowVectorConverter converter = new VarCharConverter(vector, 0, this);\n    Date expectedDate = new Date(123, 9, 26);\n    Date actualDate = converter.toDate(1, TimeZone.getDefault(), false);\n\n    assertThat(null, is(converter.toDate(0, null, false)));\n    assertThat(actualDate, is(expectedDate));\n    TestUtil.assertSFException(invalidConversionErrorCode, () -> converter.toDate(2, null, false));\n\n    vector.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/oauth/AuthorizationCodeRedirectRequestHandlerTest.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.nimbusds.oauth2.sdk.id.State;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport net.snowflake.client.internal.core.SFException;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\npublic class AuthorizationCodeRedirectRequestHandlerTest {\n\n  CompletableFuture<String> authorizationCodeFutureMock = Mockito.mock(CompletableFuture.class);\n\n  @Test\n  public void shouldReturnSuccessResponse() {\n    Map<String, String> params = new HashMap<>();\n    params.put(\"code\", \"some authorization code\");\n    params.put(\"state\", \"abc\");\n\n    String response =\n        AuthorizationCodeRedirectRequestHandler.handleRedirectRequest(\n            params, authorizationCodeFutureMock, new State(\"abc\"));\n    Mockito.verify(authorizationCodeFutureMock).complete(\"some authorization code\");\n    Assertions.assertEquals(\"Authorization completed successfully.\", response);\n  }\n\n  @Test\n  public void shouldReturnRandomErrorResponse() {\n    Map<String, String> params = new HashMap<>();\n    params.put(\"error\", \"some random error\");\n\n    String response =\n        AuthorizationCodeRedirectRequestHandler.handleRedirectRequest(\n            params, authorizationCodeFutureMock, new State(\"abc\"));\n    Mockito.verify(authorizationCodeFutureMock)\n        .completeExceptionally(Mockito.any(SFException.class));\n    Assertions.assertEquals(\"Authorization error: some random error\", response);\n  }\n\n  @Test\n  public void shouldReturnEscapedErrorResponse() {\n    Map<String, String> params = new HashMap<>();\n    params.put(\"error\", \"<script>some malicious script<script>\");\n\n    String response =\n        AuthorizationCodeRedirectRequestHandler.handleRedirectRequest(\n            params, authorizationCodeFutureMock, new State(\"abc\"));\n    Mockito.verify(authorizationCodeFutureMock)\n        .completeExceptionally(Mockito.any(SFException.class));\n    Assertions.assertEquals(\n        \"Authorization error: &lt;script&gt;some malicious script&lt;script&gt;\", response);\n  }\n\n  @Test\n  public void shouldReturnInvalidStateErrorResponse() {\n    Map<String, String> params = new HashMap<>();\n    params.put(\"authorization_code\", \"some authorization code\");\n    params.put(\"state\", \"invalid state\");\n\n    String response =\n        AuthorizationCodeRedirectRequestHandler.handleRedirectRequest(\n            params, authorizationCodeFutureMock, new State(\"abc\"));\n    Mockito.verify(authorizationCodeFutureMock)\n        .completeExceptionally(Mockito.any(SFException.class));\n    Assertions.assertEquals(\n        \"Authorization error: invalid authorization request redirection state\", response);\n  }\n\n  @Test\n  public void shouldReturnAuthorizationCodeAbsentErrorResponse() {\n    Map<String, String> params = new HashMap<>();\n    params.put(\"state\", \"abc\");\n    params.put(\"some-random-param\", \"some-value\");\n\n    String response =\n        AuthorizationCodeRedirectRequestHandler.handleRedirectRequest(\n            params, authorizationCodeFutureMock, new State(\"abc\"));\n    Mockito.verify(authorizationCodeFutureMock)\n        .completeExceptionally(Mockito.any(SFException.class));\n    Assertions.assertEquals(\n        \"Authorization error: authorization code has not been returned to the driver.\", response);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/oauth/OAuthAccessTokenProviderFactoryTest.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.core.SFOauthLoginInput;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\npublic class OAuthAccessTokenProviderFactoryTest {\n\n  private final OAuthAccessTokenProviderFactory providerFactory =\n      new OAuthAccessTokenProviderFactory();\n\n  @Test\n  public void shouldProperlyReturnIfAuthenticatorIsEligible() {\n    Arrays.stream(AuthenticatorType.values())\n        .forEach(\n            authenticatorType -> {\n              if (authenticatorType == AuthenticatorType.OAUTH_CLIENT_CREDENTIALS\n                  || authenticatorType.equals(AuthenticatorType.OAUTH_AUTHORIZATION_CODE)) {\n                Assertions.assertTrue(\n                    OAuthAccessTokenProviderFactory.isEligible(authenticatorType));\n              } else {\n                Assertions.assertFalse(\n                    OAuthAccessTokenProviderFactory.isEligible(authenticatorType));\n              }\n            });\n  }\n\n  @Test\n  public void shouldProperlyCreateClientCredentialsAccessTokenProvider() throws SFException {\n    SFLoginInput loginInput = createLoginInputStub(\"123\", \"123\", null, \"some/url\", null);\n    AccessTokenProvider provider =\n        providerFactory.createAccessTokenProvider(\n            AuthenticatorType.OAUTH_CLIENT_CREDENTIALS, loginInput);\n    Assertions.assertNotNull(provider);\n    Assertions.assertInstanceOf(OAuthClientCredentialsAccessTokenProvider.class, provider);\n  }\n\n  @Test\n  public void shouldProperlyCreateAuthzCodeAccessTokenProvider() throws SFException {\n    SFLoginInput loginInput = createLoginInputStub(\"123\", \"123\", null, null, null);\n    AccessTokenProvider provider =\n        providerFactory.createAccessTokenProvider(\n            AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput);\n    Assertions.assertNotNull(provider);\n    Assertions.assertInstanceOf(OAuthAuthorizationCodeAccessTokenProvider.class, provider);\n  }\n\n  @Test\n  public void shouldProperlyCreateAuthzCodeAccessTokenProviderWithDefaultClientCredentials()\n      throws SFException {\n    SFLoginInput loginInput = createLoginInputStub(null, null, null, null, null);\n    AccessTokenProvider provider =\n        providerFactory.createAccessTokenProvider(\n            AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput);\n    Assertions.assertNotNull(provider);\n    Assertions.assertInstanceOf(OAuthAuthorizationCodeAccessTokenProvider.class, provider);\n    Assertions.assertEquals(\"LOCAL_APPLICATION\", loginInput.getOauthLoginInput().getClientId());\n    Assertions.assertEquals(\"LOCAL_APPLICATION\", loginInput.getOauthLoginInput().getClientSecret());\n  }\n\n  @Test\n  public void shouldProperlyCreateAuthzCodeAccessTokenProviderForExternalIdp() throws SFException {\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"123\",\n            \"123\",\n            \"https://some.ext.idp.com/authz\",\n            \"https://some.ext.idp.com/token\",\n            \"http://localhost:12345/authz-code\");\n    AccessTokenProvider provider =\n        providerFactory.createAccessTokenProvider(\n            AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput);\n    Assertions.assertNotNull(provider);\n    Assertions.assertInstanceOf(OAuthAuthorizationCodeAccessTokenProvider.class, provider);\n  }\n\n  @Test\n  public void shouldFailToCreateClientCredentialsAccessTokenProviderWithoutClientId() {\n    SFLoginInput loginInput = createLoginInputStub(null, \"123\", null, \"some/url\", null);\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_CLIENT_CREDENTIALS, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"passing oauthClientId is required for OAUTH_CLIENT_CREDENTIALS authentication.\"));\n  }\n\n  @Test\n  public void shouldFailToCreateClientCredentialsAccessTokenProviderWithoutClientSecret() {\n    SFLoginInput loginInput = createLoginInputStub(\"123\", null, null, \"some/url\", null);\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_CLIENT_CREDENTIALS, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"passing oauthClientSecret is required for OAUTH_CLIENT_CREDENTIALS authentication.\"));\n  }\n\n  @Test\n  public void shouldFailToCreateClientCredentialsAccessTokenProviderWithoutExtTokenUrl() {\n    SFLoginInput loginInput = createLoginInputStub(\"123\", \"123\", null, null, null);\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_CLIENT_CREDENTIALS, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"passing oauthTokenRequestUrl is required for OAUTH_CLIENT_CREDENTIALS authentication.\"));\n  }\n\n  @Test\n  public void shouldProperlyCreateAuthorizationCodeAccessTokenProvider() throws SFException {\n    SFLoginInput loginInput = createLoginInputStub(\"123\", \"123\", null, null, null);\n    AccessTokenProvider provider =\n        providerFactory.createAccessTokenProvider(\n            AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput);\n    Assertions.assertNotNull(provider);\n    Assertions.assertInstanceOf(OAuthAuthorizationCodeAccessTokenProvider.class, provider);\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithoutClientId() {\n    SFLoginInput loginInput = createLoginInputStub(null, \"123\", \"some/url\", \"some/url\", null);\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"passing oauthClientId is required for OAUTH_AUTHORIZATION_CODE authentication.\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithoutClientSecret() {\n    SFLoginInput loginInput = createLoginInputStub(\"123\", null, null, null, null);\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"passing oauthClientSecret is required for OAUTH_AUTHORIZATION_CODE authentication.\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithHttpsRedirectUri() {\n    SFLoginInput loginInput =\n        createLoginInputStub(\"123\", \"123\", null, null, \"https://localhost:1234/\");\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage().contains(\"provided redirect URI should start with \\\"http\\\", not \\\"https\\\"\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithJustExtAuthzUrl() {\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"123\", \"123\", \"https://some.ext.idp.com/authz\", null, \"http://localhost:1234/\");\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Error during OAuth Authorization Code authentication: For OAUTH_AUTHORIZATION_CODE authentication with external IdP, both oauthAuthorizationUrl and oauthTokenRequestUrl must be specified\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithJustExtTokenUrl() {\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"123\", \"123\", null, \"https://some.ext.idp.com/token\", \"http://localhost:1234/\");\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Error during OAuth Authorization Code authentication: For OAUTH_AUTHORIZATION_CODE authentication with external IdP, both oauthAuthorizationUrl and oauthTokenRequestUrl must be specified\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithInvalidAuthzUrl() {\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"123\",\n            \"123\",\n            \"invalid/url/format\",\n            \"https://some.ext.idp.com/token\",\n            \"http://localhost:1234/\");\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Error during OAuth Authorization Code authentication: OAuth authorization URL and token URL must be specified in proper format; oauthAuthorizationUrl=invalid/url/format oauthTokenRequestUrl=https://some.ext.idp.com/token\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithInvalidTokenUrl() {\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"123\",\n            \"123\",\n            \"https://some.ext.idp.com/authz\",\n            \"invalid-token-format\",\n            \"http://localhost:1234/\");\n    SFException e =\n        Assertions.assertThrows(\n            SFException.class,\n            () ->\n                providerFactory.createAccessTokenProvider(\n                    AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput));\n    Assertions.assertTrue(\n        e.getMessage()\n            .contains(\n                \"Error during OAuth Authorization Code authentication: OAuth authorization URL and token URL must be specified in proper format; oauthAuthorizationUrl=https://some.ext.idp.com/authz oauthTokenRequestUrl=invalid-token-format\"));\n  }\n\n  @Test\n  public void shouldFailToCreateAuthzCodeAccessTokenProviderWithDifferentUrlDomains()\n      throws SFException {\n    SFLoginInput loginInput =\n        createLoginInputStub(\n            \"123\",\n            \"123\",\n            \"https://malicious.ext.idp.com/authz-url\",\n            \"https://some.ext.idp.com/token-url\",\n            \"http://localhost:1234/\");\n    SFLogger loggerMock = Mockito.mock(SFLogger.class);\n    try (MockedStatic<SFLoggerFactory> loggerFactoryMockedStatic =\n        Mockito.mockStatic(SFLoggerFactory.class)) {\n      loggerFactoryMockedStatic\n          .when(() -> SFLoggerFactory.getLogger(OAuthAccessTokenProviderFactory.class))\n          .thenReturn(loggerMock);\n      new OAuthAccessTokenProviderFactory()\n          .createAccessTokenProvider(AuthenticatorType.OAUTH_AUTHORIZATION_CODE, loginInput);\n      Mockito.verify(loggerMock)\n          .warn(\n              \"Both oauthAuthorizationUrl and oauthTokenRequestUrl should belong to the same host; oauthAuthorizationUrl=https://malicious.ext.idp.com/authz-url oauthTokenRequestUrl=https://some.ext.idp.com/token-url\");\n    }\n  }\n\n  @Test\n  public void shouldProperlyCheckIfIsEligibleForDefaultClientCredentials() {\n    SFOauthLoginInput oauthLoginInput = createOauthLoginInputStub(null, null, null, null, null);\n    Assertions.assertTrue(\n        OAuthAccessTokenProviderFactory.isEligibleForDefaultClientCredentials(oauthLoginInput));\n\n    oauthLoginInput =\n        createOauthLoginInputStub(\"some-client-id\", \"some-client-secret\", null, null, null);\n    Assertions.assertFalse(\n        OAuthAccessTokenProviderFactory.isEligibleForDefaultClientCredentials(oauthLoginInput));\n\n    oauthLoginInput =\n        createOauthLoginInputStub(\n            null, null, null, \"some.host.snowflakecomputing.com/oauth/authorize\", null);\n    Assertions.assertTrue(\n        OAuthAccessTokenProviderFactory.isEligibleForDefaultClientCredentials(oauthLoginInput));\n\n    oauthLoginInput =\n        createOauthLoginInputStub(\n            null,\n            null,\n            null,\n            \"some.host.snowflakecomputing.com/oauth/authorize\",\n            \"some.host.snowflakecomputing.com/oauth/token\");\n    Assertions.assertTrue(\n        OAuthAccessTokenProviderFactory.isEligibleForDefaultClientCredentials(oauthLoginInput));\n\n    oauthLoginInput =\n        createOauthLoginInputStub(\n            \"some-client-id\",\n            null,\n            null,\n            \"some.host.snowflakecomputing.com/oauth/authorize\",\n            \"some.host.snowflakecomputing.com/oauth/token\");\n    Assertions.assertFalse(\n        OAuthAccessTokenProviderFactory.isEligibleForDefaultClientCredentials(oauthLoginInput));\n  }\n\n  private SFLoginInput createLoginInputStub(\n      String clientId,\n      String clientSecret,\n      String authorizationUrl,\n      String tokenUrl,\n      String redirectUri) {\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setOauthLoginInput(\n        createOauthLoginInputStub(clientId, clientSecret, redirectUri, authorizationUrl, tokenUrl));\n    loginInput.setBrowserResponseTimeout(Duration.ofSeconds(20));\n    return loginInput;\n  }\n\n  private SFOauthLoginInput createOauthLoginInputStub(\n      String clientId,\n      String clientSecret,\n      String redirectUri,\n      String authorizationUrl,\n      String tokenUrl) {\n    return new SFOauthLoginInput(\n        clientId, clientSecret, redirectUri, authorizationUrl, tokenUrl, null);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/oauth/OAuthUtilTest.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport com.nimbusds.oauth2.sdk.http.HTTPRequest;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.StringWriter;\nimport java.net.URI;\nimport net.snowflake.client.internal.core.SFOauthLoginInput;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.Header;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\npublic class OAuthUtilTest {\n\n  private static final String BASE_SERVER_URL_FROM_LOGIN_INPUT = \"http://some.snowflake.server.com\";\n  public static final String ROLE_FROM_LOGIN_INPUT = \"ANALYST\";\n\n  @Test\n  public void shouldCreateDefaultAuthorizationUrl() {\n    SFOauthLoginInput loginInput = createLoginInputStub(null, null, null, null);\n    URI authorizationUrl =\n        OAuthUtil.getAuthorizationUrl(loginInput, BASE_SERVER_URL_FROM_LOGIN_INPUT);\n    Assertions.assertNotNull(authorizationUrl);\n    Assertions.assertEquals(\n        \"http://some.snowflake.server.com/oauth/authorize\", authorizationUrl.toString());\n  }\n\n  @Test\n  public void shouldCreateUserSuppliedAuthorizationUrl() {\n    SFOauthLoginInput loginInput =\n        createLoginInputStub(\"http://some.external.authorization.url.com/authz\", null, null, null);\n    URI tokenRequestUrl =\n        OAuthUtil.getAuthorizationUrl(loginInput, BASE_SERVER_URL_FROM_LOGIN_INPUT);\n    Assertions.assertNotNull(tokenRequestUrl);\n    Assertions.assertEquals(\n        \"http://some.external.authorization.url.com/authz\", tokenRequestUrl.toString());\n  }\n\n  @Test\n  public void shouldCreateDefaultTokenRequestUrl() {\n    SFOauthLoginInput loginInput = createLoginInputStub(null, null, null, null);\n    URI tokenRequestUrl =\n        OAuthUtil.getTokenRequestUrl(loginInput, BASE_SERVER_URL_FROM_LOGIN_INPUT);\n    Assertions.assertNotNull(tokenRequestUrl);\n    Assertions.assertEquals(\n        \"http://some.snowflake.server.com/oauth/token-request\", tokenRequestUrl.toString());\n  }\n\n  @Test\n  public void shouldCreateUserSuppliedTokenRequestUrl() {\n    SFOauthLoginInput loginInput =\n        createLoginInputStub(\n            null, \"http://some.external.authorization.url.com/token-request\", null, null);\n    URI tokenRequestUrl =\n        OAuthUtil.getTokenRequestUrl(loginInput, BASE_SERVER_URL_FROM_LOGIN_INPUT);\n    Assertions.assertNotNull(tokenRequestUrl);\n    Assertions.assertEquals(\n        \"http://some.external.authorization.url.com/token-request\", tokenRequestUrl.toString());\n  }\n\n  @Test\n  public void shouldCreateDefaultScope() {\n    SFOauthLoginInput loginInput = createLoginInputStub(null, null, null, null);\n    String scope = OAuthUtil.getScope(loginInput, ROLE_FROM_LOGIN_INPUT);\n    Assertions.assertNotNull(scope);\n    Assertions.assertEquals(\"session:role:ANALYST\", scope);\n  }\n\n  @Test\n  public void shouldCreateUserSuppliedScope() {\n    SFOauthLoginInput loginInput = createLoginInputStub(null, null, \"some:custom:SCOPE\", null);\n    String scope = OAuthUtil.getScope(loginInput, ROLE_FROM_LOGIN_INPUT);\n    Assertions.assertNotNull(scope);\n    Assertions.assertEquals(\"some:custom:SCOPE\", scope);\n  }\n\n  @Test\n  public void shouldCreateDefaultRedirectUri() throws IOException {\n    SFOauthLoginInput loginInput = createLoginInputStub(null, null, null, null);\n    URI redirectUri = OAuthUtil.buildRedirectUri(loginInput);\n    Assertions.assertNotNull(redirectUri);\n    Assertions.assertTrue(\n        redirectUri.toString().matches(\"^http://127.0.0.1:([0-9]*)\"),\n        \"Invalid redirect URI: \" + redirectUri);\n  }\n\n  @Test\n  public void shouldCreateCustomRedirectUri() throws IOException {\n    SFOauthLoginInput loginInput =\n        createLoginInputStub(null, null, null, \"http://localhost:8989/some-endpoint\");\n    URI redirectUri = OAuthUtil.buildRedirectUri(loginInput);\n    Assertions.assertNotNull(redirectUri);\n    Assertions.assertEquals(\"http://localhost:8989/some-endpoint\", redirectUri.toString());\n  }\n\n  @Test\n  public void shouldConvertToBaseAuthorizationRequest() throws IOException {\n    HTTPRequest httpRequest =\n        new HTTPRequest(\n            HTTPRequest.Method.POST, URI.create(\"https://some.snowflake.server.com/oauth/token\"));\n    httpRequest.setAccept(\"application/json\");\n    httpRequest.setAuthorization(\"Bearer some-token\");\n    httpRequest.setBody(\"{\\\"grant_type\\\":\\\"authorization_code\\\",\\\"code\\\":\\\"some-code\\\"}\");\n\n    HttpRequestBase httpRequestBase = OAuthUtil.convertToBaseRequest(httpRequest);\n    Assertions.assertNotNull(httpRequestBase);\n    Assertions.assertEquals(HttpPost.class, httpRequestBase.getClass());\n    Assertions.assertEquals(\n        \"https://some.snowflake.server.com/oauth/token\", httpRequestBase.getURI().toString());\n    Header[] allHeaders = httpRequestBase.getAllHeaders();\n    Assertions.assertNotNull(allHeaders);\n    Assertions.assertEquals(2, allHeaders.length);\n    Assertions.assertEquals(\"Accept\", allHeaders[0].getName());\n    Assertions.assertEquals(\"application/json\", allHeaders[0].getValue());\n    Assertions.assertEquals(\"Authorization\", allHeaders[1].getName());\n    Assertions.assertEquals(\"Bearer some-token\", allHeaders[1].getValue());\n\n    StringWriter writer = new StringWriter();\n    try (InputStream ins = ((HttpPost) httpRequestBase).getEntity().getContent()) {\n      IOUtils.copy(ins, writer, \"UTF-8\");\n    }\n    Assertions.assertEquals(\n        \"{\\\"grant_type\\\":\\\"authorization_code\\\",\\\"code\\\":\\\"some-code\\\"}\", writer.toString());\n  }\n\n  private SFOauthLoginInput createLoginInputStub(\n      String oauthAuthorizationUrl, String oauthTokenRequestUrl, String scope, String redirectUri) {\n    return new SFOauthLoginInput(\n        null, null, redirectUri, oauthAuthorizationUrl, oauthTokenRequestUrl, scope);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/oauth/RandomStateProviderTest.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class RandomStateProviderTest {\n\n  private RandomStateProvider randomStateProvider;\n\n  @BeforeEach\n  public void setUp() {\n    // Initialize the RandomStateProvider before each test\n    randomStateProvider = new RandomStateProvider();\n  }\n\n  @Test\n  public void testGetState_ShouldReturnNonNullState() {\n    // Call getState() and verify that it returns a non-null value\n    String state = randomStateProvider.getState();\n    assertNotNull(state, \"State should not be null\");\n  }\n\n  @Test\n  public void testGetState_ShouldReturnUniqueValues() {\n    // Ensure that the state is random and different each time\n    String state1 = randomStateProvider.getState();\n    String state2 = randomStateProvider.getState();\n\n    // Assert that the two generated states are different\n    assertNotEquals(state1, state2, \"State should be different on subsequent calls\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/oauth/TokenResponseDTOTest.java",
    "content": "package net.snowflake.client.internal.core.auth.oauth;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.junit.jupiter.api.Test;\n\npublic class TokenResponseDTOTest {\n\n  private static final String JSON_SAMPLE =\n      \"{\"\n          + \"\\\"access_token\\\":\\\"abc123\\\",\"\n          + \"\\\"refresh_token\\\":\\\"refresh123\\\",\"\n          + \"\\\"token_type\\\":\\\"bearer\\\",\"\n          + \"\\\"scope\\\":\\\"read write\\\",\"\n          + \"\\\"username\\\":\\\"testUser\\\",\"\n          + \"\\\"idp_initiated\\\":true,\"\n          + \"\\\"expires_in\\\":3600,\"\n          + \"\\\"refresh_token_expires_in\\\":7200\"\n          + \"}\";\n\n  @Test\n  public void testConstructorAndGetters() {\n    TokenResponseDTO tokenResponseDTO =\n        new TokenResponseDTO(\n            \"abc123\", \"refresh123\", \"bearer\", \"read write\", \"testUser\", true, 3600, 7200);\n    // Assert that the constructor has correctly initialized the object\n    assertEquals(\"abc123\", tokenResponseDTO.getAccessToken());\n    assertEquals(\"bearer\", tokenResponseDTO.getTokenType());\n    assertEquals(\"refresh123\", tokenResponseDTO.getRefreshToken());\n    assertEquals(\"read write\", tokenResponseDTO.getScope());\n    assertEquals(\"testUser\", tokenResponseDTO.getUsername());\n    assertTrue(tokenResponseDTO.isIdpInitiated());\n    assertEquals(3600, tokenResponseDTO.getExpiresIn());\n    assertEquals(7200, tokenResponseDTO.getRefreshTokenExpiresIn());\n  }\n\n  @Test\n  public void testJsonDeserialization() throws Exception {\n    // Create an ObjectMapper to convert JSON to TokenResponseDTO\n    ObjectMapper objectMapper = new ObjectMapper();\n\n    // Deserialize the JSON string into a TokenResponseDTO object\n    TokenResponseDTO deserializedTokenResponseDTO =\n        objectMapper.readValue(JSON_SAMPLE, TokenResponseDTO.class);\n\n    // Assert that the deserialized object matches the expected values\n    assertEquals(\"abc123\", deserializedTokenResponseDTO.getAccessToken());\n    assertEquals(\"bearer\", deserializedTokenResponseDTO.getTokenType());\n    assertEquals(\"refresh123\", deserializedTokenResponseDTO.getRefreshToken());\n    assertEquals(\"read write\", deserializedTokenResponseDTO.getScope());\n    assertEquals(\"testUser\", deserializedTokenResponseDTO.getUsername());\n    assertTrue(deserializedTokenResponseDTO.isIdpInitiated());\n    assertEquals(3600, deserializedTokenResponseDTO.getExpiresIn());\n    assertEquals(7200, deserializedTokenResponseDTO.getRefreshTokenExpiresIn());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/wif/AwsIdentityAttestationCreatorTest.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.net.URI;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.AwsSessionCredentials;\nimport software.amazon.awssdk.http.SdkHttpMethod;\nimport software.amazon.awssdk.http.SdkHttpRequest;\nimport software.amazon.awssdk.regions.Region;\n\npublic class AwsIdentityAttestationCreatorTest {\n\n  @Test\n  public void shouldThrowExceptionWhenNoCredentialsFound() {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    Mockito.when(attestationServiceMock.getAWSCredentials()).thenReturn(null);\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(Region.US_EAST_1);\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, new SFLoginInput());\n    assertThrows(SFException.class, attestationCreator::createAttestation);\n  }\n\n  @Test\n  public void shouldThrowExceptionWhenNoRegion() {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    Mockito.when(attestationServiceMock.getAWSCredentials())\n        .thenReturn(AwsBasicCredentials.create(\"abc\", \"abc\"));\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(null);\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, new SFLoginInput());\n    assertThrows(SFException.class, attestationCreator::createAttestation);\n  }\n\n  @Test\n  public void shouldReturnProperAttestationWithStandardRegion()\n      throws JsonProcessingException, SFException {\n    shouldReturnProperAttestationWithSignedRequestCredential(\n        Region.US_EAST_1, \"sts.us-east-1.amazonaws.com\");\n  }\n\n  @Test\n  public void shouldReturnProperAttestationWithCnRegion()\n      throws JsonProcessingException, SFException {\n    shouldReturnProperAttestationWithSignedRequestCredential(\n        Region.CN_NORTHWEST_1, \"sts.cn-northwest-1.amazonaws.com.cn\");\n  }\n\n  private void shouldReturnProperAttestationWithSignedRequestCredential(\n      Region region, String expectedStsUrl) throws JsonProcessingException, SFException {\n    AwsAttestationService attestationServiceSpy = Mockito.spy(AwsAttestationService.class);\n    Mockito.doReturn(AwsSessionCredentials.create(\"abc\", \"abc\", \"aws-session-token\"))\n        .when(attestationServiceSpy)\n        .getAWSCredentials();\n    Mockito.doNothing().when(attestationServiceSpy).initializeSignerRegion();\n    Mockito.doReturn(region).when(attestationServiceSpy).getAWSRegion();\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceSpy, new SFLoginInput());\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.AWS, attestation.getProvider());\n    assertNotNull(attestation.getCredential());\n    Base64.Decoder decoder = Base64.getDecoder();\n    String json = new String(decoder.decode(attestation.getCredential()));\n    Map<String, Object> credentialMap = new ObjectMapper().readValue(json, HashMap.class);\n    assertEquals(3, credentialMap.size());\n    assertEquals(\n        String.format(\"https://%s?Action=GetCallerIdentity&Version=2011-06-15\", expectedStsUrl),\n        credentialMap.get(\"url\"));\n    assertEquals(\"POST\", credentialMap.get(\"method\"));\n    assertNotNull(credentialMap.get(\"headers\"));\n    Map<String, String> headersMap = (Map<String, String>) credentialMap.get(\"headers\");\n    assertEquals(6, headersMap.size());\n    assertEquals(expectedStsUrl, headersMap.get(\"Host\"));\n    assertEquals(\"snowflakecomputing.com\", headersMap.get(\"X-Snowflake-Audience\"));\n    assertNotNull(headersMap.get(\"X-Amz-Date\"));\n    assertTrue(headersMap.get(\"Authorization\").matches(\"^AWS4-HMAC-SHA256 Credential=.*\"));\n    assertEquals(\"aws-session-token\", headersMap.get(\"X-Amz-Security-Token\"));\n    assertNotNull(headersMap.get(\"x-amz-content-sha256\"));\n  }\n\n  @Test\n  public void shouldCreateAttestationWithoutImpersonationWhenImpersonationPathIsEmpty()\n      throws SFException {\n    AwsAttestationService attestationServiceSpy = Mockito.spy(AwsAttestationService.class);\n    Mockito.doReturn(AwsSessionCredentials.create(\"abc\", \"abc\", \"aws-session-token\"))\n        .when(attestationServiceSpy)\n        .getAWSCredentials();\n    Mockito.doNothing().when(attestationServiceSpy).initializeSignerRegion();\n    Mockito.doReturn(Region.US_EAST_1).when(attestationServiceSpy).getAWSRegion();\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setWorkloadIdentityImpersonationPath(\"\"); // Empty path\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceSpy, loginInput);\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.AWS, attestation.getProvider());\n    assertNotNull(attestation.getCredential());\n  }\n\n  @Test\n  public void shouldThrowExceptionWhenImpersonationFailsWithNoInitialCredentials() {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    Mockito.when(attestationServiceMock.getAWSCredentials()).thenReturn(null);\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(Region.US_EAST_1);\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setWorkloadIdentityImpersonationPath(\"arn:aws:iam::123456789012:role/TestRole\");\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, loginInput);\n\n    assertThrows(SFException.class, attestationCreator::createAttestation);\n  }\n\n  @Test\n  public void shouldCreateAttestationWithImpersonationPath() throws SFException {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    SdkHttpRequest requestMock = mock(SdkHttpRequest.class);\n    Mockito.when(requestMock.getUri()).thenReturn(URI.create(\"https://snowflakecomputing.com\"));\n    Mockito.when(requestMock.method()).thenReturn(SdkHttpMethod.GET);\n\n    AwsSessionCredentials initialCredentials =\n        AwsSessionCredentials.create(\"initial-key\", \"initial-secret\", \"initial-token\");\n    Mockito.when(attestationServiceMock.getAWSCredentials()).thenReturn(initialCredentials);\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(Region.US_EAST_1);\n    Mockito.when(attestationServiceMock.getCredentialsViaRoleChaining(any())).thenCallRealMethod();\n\n    AwsSessionCredentials assumedCredentials =\n        AwsSessionCredentials.create(\"assumed-key\", \"assumed-secret\", \"assumed-token\");\n    Mockito.when(\n            attestationServiceMock.assumeRole(\n                initialCredentials, \"arn:aws:iam::123456789012:role/TestRole\", null))\n        .thenReturn(assumedCredentials);\n\n    Mockito.doNothing().when(attestationServiceMock).initializeSignerRegion();\n    Mockito.when(attestationServiceMock.signRequestWithSigV4(any(), Mockito.eq(assumedCredentials)))\n        .thenReturn(requestMock);\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setWorkloadIdentityImpersonationPath(\"arn:aws:iam::123456789012:role/TestRole\");\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, loginInput);\n\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.AWS, attestation.getProvider());\n    assertNotNull(attestation.getCredential());\n\n    Mockito.verify(attestationServiceMock)\n        .assumeRole(initialCredentials, \"arn:aws:iam::123456789012:role/TestRole\", null);\n  }\n\n  @Test\n  public void shouldPassExternalIdToAssumeRoleWhenProvided() throws SFException {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    SdkHttpRequest requestMock = mock(SdkHttpRequest.class);\n    Mockito.when(requestMock.getUri()).thenReturn(URI.create(\"https://snowflakecomputing.com\"));\n    Mockito.when(requestMock.method()).thenReturn(SdkHttpMethod.GET);\n\n    AwsSessionCredentials initialCredentials =\n        AwsSessionCredentials.create(\"initial-key\", \"initial-secret\", \"initial-token\");\n    AwsSessionCredentials assumedCredentials =\n        AwsSessionCredentials.create(\"assumed-key\", \"assumed-secret\", \"assumed-token\");\n\n    Mockito.when(attestationServiceMock.getAWSCredentials()).thenReturn(initialCredentials);\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(Region.US_EAST_1);\n    Mockito.when(attestationServiceMock.getCredentialsViaRoleChaining(any())).thenCallRealMethod();\n    Mockito.when(\n            attestationServiceMock.assumeRole(\n                initialCredentials, \"arn:aws:iam::123456789012:role/TestRole\", \"my-external-id\"))\n        .thenReturn(assumedCredentials);\n    Mockito.doNothing().when(attestationServiceMock).initializeSignerRegion();\n    Mockito.when(attestationServiceMock.signRequestWithSigV4(any(), Mockito.eq(assumedCredentials)))\n        .thenReturn(requestMock);\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setWorkloadIdentityImpersonationPath(\"arn:aws:iam::123456789012:role/TestRole\");\n    loginInput.setWorkloadIdentityAwsExternalId(\"my-external-id\");\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, loginInput);\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.AWS, attestation.getProvider());\n    assertNotNull(attestation.getCredential());\n    Mockito.verify(attestationServiceMock)\n        .assumeRole(\n            initialCredentials, \"arn:aws:iam::123456789012:role/TestRole\", \"my-external-id\");\n  }\n\n  @Test\n  public void shouldPassEmptyExternalIdToAssumeRoleWhenSetToEmptyString() throws SFException {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    SdkHttpRequest requestMock = mock(SdkHttpRequest.class);\n    Mockito.when(requestMock.getUri()).thenReturn(URI.create(\"https://snowflakecomputing.com\"));\n    Mockito.when(requestMock.method()).thenReturn(SdkHttpMethod.GET);\n\n    AwsSessionCredentials initialCredentials =\n        AwsSessionCredentials.create(\"initial-key\", \"initial-secret\", \"initial-token\");\n    AwsSessionCredentials assumedCredentials =\n        AwsSessionCredentials.create(\"assumed-key\", \"assumed-secret\", \"assumed-token\");\n\n    Mockito.when(attestationServiceMock.getAWSCredentials()).thenReturn(initialCredentials);\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(Region.US_EAST_1);\n    Mockito.when(attestationServiceMock.getCredentialsViaRoleChaining(any())).thenCallRealMethod();\n    Mockito.when(\n            attestationServiceMock.assumeRole(\n                initialCredentials, \"arn:aws:iam::123456789012:role/TestRole\", \"\"))\n        .thenReturn(assumedCredentials);\n    Mockito.doNothing().when(attestationServiceMock).initializeSignerRegion();\n    Mockito.when(attestationServiceMock.signRequestWithSigV4(any(), Mockito.eq(assumedCredentials)))\n        .thenReturn(requestMock);\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setWorkloadIdentityImpersonationPath(\"arn:aws:iam::123456789012:role/TestRole\");\n    loginInput.setWorkloadIdentityAwsExternalId(\"\");\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, loginInput);\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    assertNotNull(attestation);\n    // Empty string is passed through as-is; assumeRole handles it by not setting externalId\n    // in the STS AssumeRole request (same effective behaviour as null)\n    Mockito.verify(attestationServiceMock)\n        .assumeRole(initialCredentials, \"arn:aws:iam::123456789012:role/TestRole\", \"\");\n  }\n\n  @Test\n  public void shouldOnlyPassExternalIdToLastHopInMultiHopChain() throws SFException {\n    AwsAttestationService attestationServiceMock = mock(AwsAttestationService.class);\n    SdkHttpRequest requestMock = mock(SdkHttpRequest.class);\n    Mockito.when(requestMock.getUri()).thenReturn(URI.create(\"https://snowflakecomputing.com\"));\n    Mockito.when(requestMock.method()).thenReturn(SdkHttpMethod.GET);\n\n    AwsSessionCredentials initialCredentials =\n        AwsSessionCredentials.create(\"initial-key\", \"initial-secret\", \"initial-token\");\n    AwsSessionCredentials intermediateCredentials =\n        AwsSessionCredentials.create(\n            \"intermediate-key\", \"intermediate-secret\", \"intermediate-token\");\n    AwsSessionCredentials finalCredentials =\n        AwsSessionCredentials.create(\"final-key\", \"final-secret\", \"final-token\");\n\n    Mockito.when(attestationServiceMock.getAWSCredentials()).thenReturn(initialCredentials);\n    Mockito.when(attestationServiceMock.getAWSRegion()).thenReturn(Region.US_EAST_1);\n    Mockito.when(attestationServiceMock.getCredentialsViaRoleChaining(any())).thenCallRealMethod();\n    // First hop: no external ID\n    Mockito.when(\n            attestationServiceMock.assumeRole(\n                initialCredentials, \"arn:aws:iam::111111111111:role/RoleA\", null))\n        .thenReturn(intermediateCredentials);\n    // Last hop: external ID applied\n    Mockito.when(\n            attestationServiceMock.assumeRole(\n                intermediateCredentials, \"arn:aws:iam::222222222222:role/RoleB\", \"my-external-id\"))\n        .thenReturn(finalCredentials);\n    Mockito.doNothing().when(attestationServiceMock).initializeSignerRegion();\n    Mockito.when(attestationServiceMock.signRequestWithSigV4(any(), Mockito.eq(finalCredentials)))\n        .thenReturn(requestMock);\n\n    SFLoginInput loginInput = new SFLoginInput();\n    loginInput.setWorkloadIdentityImpersonationPath(\n        \"arn:aws:iam::111111111111:role/RoleA,arn:aws:iam::222222222222:role/RoleB\");\n    loginInput.setWorkloadIdentityAwsExternalId(\"my-external-id\");\n\n    AwsIdentityAttestationCreator attestationCreator =\n        new AwsIdentityAttestationCreator(attestationServiceMock, loginInput);\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    assertNotNull(attestation);\n    Mockito.verify(attestationServiceMock)\n        .assumeRole(initialCredentials, \"arn:aws:iam::111111111111:role/RoleA\", null);\n    Mockito.verify(attestationServiceMock)\n        .assumeRole(\n            intermediateCredentials, \"arn:aws:iam::222222222222:role/RoleB\", \"my-external-id\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/wif/AzureIdentityAttestationCreatorLatestIT.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.time.Duration;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\n@Tag(TestTags.AUTHENTICATION)\npublic class AzureIdentityAttestationCreatorLatestIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/wif/azure\";\n\n  /*\n   * {\n   *     \"iss\": \"https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/\",\n   *     \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\"\n   * }\n   */\n  private static final String SUCCESSFUL_FLOW_BASIC_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow_basic.json\";\n\n  /*\n   * {\n   *     \"iss\": \"https://login.microsoftonline.com/fa15d692-e9c7-4460-a743-29f29522229/\",\n   *     \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\"\n   * }\n   */\n  private static final String SUCCESSFUL_FLOW_V2_ISSUER_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow_v2_issuer.json\";\n\n  /*\n   * {\n   *     \"iss\": \"https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/\",\n   *     \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\"\n   * }\n   */\n  private static final String SUCCESSFUL_FLOW_AZURE_FUNCTIONS_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow_azure_functions.json\";\n\n  /*\n   * {\n   *     \"iss\": \"https://login.microsoftonline.com/fa15d692-e9c7-4460-a743-29f29522229/\",\n   *     \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\"\n   * }\n   */\n  private static final String SUCCESSFUL_FLOW_AZURE_FUNCTIONS_V2_ISSUER_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow_azure_functions_v2_issuer.json\";\n\n  /*\n   * {\n   *     \"iss\": \"https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/\",\n   *     \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\"\n   * }\n   */\n  private static final String SUCCESSFUL_FLOW_AZURE_FUNCTIONS_NO_CLIENT_ID_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow_azure_functions_no_client_id.json\";\n\n  /*\n   * {\n   *     \"iss\": \"https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/\",\n   *     \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\"\n   * }\n   */\n  private static final String\n      SUCCESSFUL_FLOW_AZURE_FUNCTIONS_CUSTOM_ENTRA_RESOURCE_SCENARIO_MAPPINGS =\n          SCENARIOS_BASE_DIR + \"/successful_flow_azure_functions_custom_entra_resource.json\";\n\n  /*\n   * {\n   *   \"sub\": \"77213E30-E8CB-4595-B1B6-5F050E8308FD\",\n   * }\n   */\n  private static final String MISSING_ISSUER_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/missing_issuer_claim.json\";\n\n  /*\n   * {\n   *   \"iss\": \"https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/\",\n   * }\n   */\n  private static final String MISSING_SUB_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/missing_sub_claim.json\";\n\n  // HTTP response that is not in JSON format\n  private static final String JSON_PARSE_ERROR_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/non_json_response.json\";\n\n  // token equal to \"unparsable.token\"\n  private static final String TOKEN_PARSE_ERROR_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/unparsable_token.json\";\n\n  // 400 Bad Request\n  private static final String HTTP_ERROR_MAPPINGS = SCENARIOS_BASE_DIR + \"/http_error.json\";\n\n  @Test\n  public void successfulFlowBasicScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_BASIC_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceMock = createAttestationServiceSpyForBasicFLow();\n    executeAndAssertCorrectAttestation(attestationServiceMock, loginInput);\n  }\n\n  @Test\n  public void successfulFlowV2IssuerScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_V2_ISSUER_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceMock = createAttestationServiceSpyForBasicFLow();\n    executeAndAssertCorrectAttestationWithIssuer(\n        attestationServiceMock,\n        loginInput,\n        \"https://login.microsoftonline.com/fa15d692-e9c7-4460-a743-29f29522229/\");\n  }\n\n  @Test\n  public void successfulFlowAzureFunctionsScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_AZURE_FUNCTIONS_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceSpy = Mockito.spy(AzureAttestationService.class);\n    Mockito.when(attestationServiceSpy.getIdentityEndpoint())\n        .thenReturn(getBaseUrl() + \"metadata/identity/endpoint/from/env\");\n    Mockito.when(attestationServiceSpy.getIdentityHeader())\n        .thenReturn(\"some-identity-header-from-env\");\n    Mockito.when(attestationServiceSpy.getClientId()).thenReturn(\"managed-client-id-from-env\");\n\n    executeAndAssertCorrectAttestation(attestationServiceSpy, loginInput);\n  }\n\n  @Test\n  public void successfulFlowAzureFunctionsWithV2IssuerScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_AZURE_FUNCTIONS_V2_ISSUER_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceSpy = Mockito.spy(AzureAttestationService.class);\n    Mockito.when(attestationServiceSpy.getIdentityEndpoint())\n        .thenReturn(getBaseUrl() + \"metadata/identity/endpoint/from/env\");\n    Mockito.when(attestationServiceSpy.getIdentityHeader())\n        .thenReturn(\"some-identity-header-from-env\");\n    Mockito.when(attestationServiceSpy.getClientId()).thenReturn(\"managed-client-id-from-env\");\n\n    executeAndAssertCorrectAttestationWithIssuer(\n        attestationServiceSpy,\n        loginInput,\n        \"https://login.microsoftonline.com/fa15d692-e9c7-4460-a743-29f29522229/\");\n  }\n\n  @Test\n  public void successfulFlowAzureFunctionsNoClientIdScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_AZURE_FUNCTIONS_NO_CLIENT_ID_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceSpy = Mockito.spy(AzureAttestationService.class);\n    Mockito.when(attestationServiceSpy.getIdentityEndpoint())\n        .thenReturn(getBaseUrl() + \"metadata/identity/endpoint/from/env\");\n    Mockito.when(attestationServiceSpy.getIdentityHeader())\n        .thenReturn(\"some-identity-header-from-env\");\n    Mockito.when(attestationServiceSpy.getClientId()).thenReturn(null);\n\n    executeAndAssertCorrectAttestation(attestationServiceSpy, loginInput);\n  }\n\n  @Test\n  public void successfulFlowAzureFunctionsCustomEntraResourceScenario() throws SFException {\n    importMappingFromResources(\n        SUCCESSFUL_FLOW_AZURE_FUNCTIONS_CUSTOM_ENTRA_RESOURCE_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    loginInput.setWorkloadIdentityEntraResource(\"api://1111111-2222-3333-44444-55555555\");\n    AzureAttestationService attestationServiceSpy = Mockito.spy(AzureAttestationService.class);\n    Mockito.when(attestationServiceSpy.getIdentityEndpoint())\n        .thenReturn(getBaseUrl() + \"metadata/identity/endpoint/from/env\");\n    Mockito.when(attestationServiceSpy.getIdentityHeader())\n        .thenReturn(\"some-identity-header-from-env\");\n    Mockito.when(attestationServiceSpy.getClientId()).thenReturn(\"managed-client-id-from-env\");\n\n    executeAndAssertCorrectAttestation(attestationServiceSpy, loginInput);\n  }\n\n  @Test\n  public void azureFunctionsFlowErrorNoIdentityHeader() {\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceMock = Mockito.mock(AzureAttestationService.class);\n    Mockito.when(attestationServiceMock.getIdentityEndpoint())\n        .thenReturn(getBaseUrl() + \"metadata/identity/endpoint/from/env\");\n    Mockito.when(attestationServiceMock.getIdentityHeader()).thenReturn(null);\n    Mockito.when(attestationServiceMock.getClientId()).thenReturn(null);\n\n    executeAndAssertExceptionThrown(attestationServiceMock, loginInput);\n  }\n\n  @Test\n  public void basicFlowErrorMissingIssuer() {\n    executeErrorScenarioAndAssertExceptionThrown(MISSING_ISSUER_SCENARIO_MAPPINGS);\n  }\n\n  @Test\n  public void basicFlowErrorMissingSub() {\n    executeErrorScenarioAndAssertExceptionThrown(MISSING_SUB_SCENARIO_MAPPINGS);\n  }\n\n  @Test\n  public void basicFlowErrorUnparsableToken() {\n    executeErrorScenarioAndAssertExceptionThrown(TOKEN_PARSE_ERROR_SCENARIO_MAPPINGS);\n  }\n\n  @Test\n  public void basicFlowUnparsableJsonError() {\n    executeErrorScenarioAndAssertExceptionThrown(JSON_PARSE_ERROR_SCENARIO_MAPPINGS);\n  }\n\n  @Test\n  public void basicFlowHttpError() {\n    executeErrorScenarioAndAssertExceptionThrown(HTTP_ERROR_MAPPINGS);\n  }\n\n  @Test\n  public void azureCreatorDoesNotSupportImpersonationPath() {\n    SFLoginInput loginInput = createLoginInputStub();\n    loginInput.setWorkloadIdentityImpersonationPath(\"notEmpty\");\n    AzureAttestationService attestationServiceMock = Mockito.mock(AzureAttestationService.class);\n    executeAndAssertExceptionThrown(attestationServiceMock, loginInput);\n  }\n\n  private void executeErrorScenarioAndAssertExceptionThrown(\n      String tokenParseErrorScenarioMappings) {\n    importMappingFromResources(tokenParseErrorScenarioMappings);\n    SFLoginInput loginInput = createLoginInputStub();\n    AzureAttestationService attestationServiceSpy = createAttestationServiceSpyForBasicFLow();\n    executeAndAssertExceptionThrown(attestationServiceSpy, loginInput);\n  }\n\n  private static AzureAttestationService createAttestationServiceSpyForBasicFLow() {\n    AzureAttestationService attestationServiceMock = Mockito.spy(AzureAttestationService.class);\n    Mockito.when(attestationServiceMock.getIdentityEndpoint()).thenReturn(null);\n    Mockito.when(attestationServiceMock.getIdentityHeader()).thenReturn(null);\n    Mockito.when(attestationServiceMock.getClientId()).thenReturn(null);\n    return attestationServiceMock;\n  }\n\n  private void executeAndAssertCorrectAttestation(\n      AzureAttestationService attestationServiceMock, SFLoginInput loginInput) throws SFException {\n    executeAndAssertCorrectAttestationWithIssuer(\n        attestationServiceMock,\n        loginInput,\n        \"https://sts.windows.net/fa15d692-e9c7-4460-a743-29f29522229/\");\n  }\n\n  private void executeAndAssertCorrectAttestationWithIssuer(\n      AzureAttestationService attestationServiceMock,\n      SFLoginInput loginInput,\n      String expectedIssuer)\n      throws SFException {\n    AzureIdentityAttestationCreator attestationCreator =\n        new AzureIdentityAttestationCreator(attestationServiceMock, loginInput, getBaseUrl());\n\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.AZURE, attestation.getProvider());\n    assertEquals(\n        \"77213E30-E8CB-4595-B1B6-5F050E8308FD\",\n        attestation.getUserIdentifierComponents().get(\"sub\"));\n    assertEquals(expectedIssuer, attestation.getUserIdentifierComponents().get(\"iss\"));\n    assertNotNull(attestation.getCredential());\n  }\n\n  private void executeAndAssertExceptionThrown(\n      AzureAttestationService attestationServiceMock, SFLoginInput loginInput) {\n    AzureIdentityAttestationCreator attestationCreator =\n        new AzureIdentityAttestationCreator(attestationServiceMock, loginInput, getBaseUrl());\n\n    assertThrows(SFException.class, attestationCreator::createAttestation);\n  }\n\n  private String getBaseUrl() {\n    return String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort);\n  }\n\n  private SFLoginInput createLoginInputStub() {\n    SFLoginInput loginInputStub = new SFLoginInput();\n    loginInputStub.setSocketTimeout(Duration.ofMinutes(5));\n    loginInputStub.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    return loginInputStub;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/wif/GcpIdentityAttestationCreatorLatestIT.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.time.Duration;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.AUTHENTICATION)\nclass GcpIdentityAttestationCreatorLatestIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = MAPPINGS_BASE_DIR + \"/wif/gcp\";\n\n  /*\n   * {\n   *     \"iss\": \"https://accounts.google.com\",\n   *     \"iat\": 1743692017,\n   *     \"exp\": 1775228014,\n   *     \"aud\": \"www.example.com\",\n   *     \"sub\": \"some-subject\"\n   * }\n   */\n  private static final String SUCCESSFUL_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_flow.json\";\n\n  private static final String SUCCESSFUL_IMPERSONATION_FLOW_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/successful_impersonation_flow.json\";\n\n  /*\n   * {\n   *   \"sub\": \"some-subject\",\n   *   \"iat\": 1743761213,\n   *   \"exp\": 1743764813,\n   *   \"aud\": \"www.example.com\"\n   * }\n   */\n  private static final String MISSING_ISSUER_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/missing_issuer_claim.json\";\n\n  /*\n   * {\n   *   \"iss\": \"https://accounts.google.com\",\n   *   \"iat\": 1743761213,\n   *   \"exp\": 1743764813,\n   *   \"aud\": \"www.example.com\"\n   * }\n   */\n  private static final String MISSING_SUB_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/missing_sub_claim.json\";\n\n  // token equal to \"unparsable.token\"\n  private static final String TOKEN_PARSE_ERROR_SCENARIO_MAPPINGS =\n      SCENARIOS_BASE_DIR + \"/unparsable_token.json\";\n\n  // 400 Bad Request\n  private static final String HTTP_ERROR_MAPPINGS = SCENARIOS_BASE_DIR + \"/http_error.json\";\n\n  @Test\n  public void successfulFlowScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n\n    GcpIdentityAttestationCreator attestationCreator =\n        new GcpIdentityAttestationCreator(loginInput, getBaseUrl(), getBaseUrl());\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.GCP, attestation.getProvider());\n    assertEquals(\"some-subject\", attestation.getUserIdentifierComponents().get(\"sub\"));\n    assertNotNull(attestation.getCredential());\n  }\n\n  @Test\n  public void missingIssuerScenario() {\n    importMappingFromResources(MISSING_ISSUER_SCENARIO_MAPPINGS);\n    createAttestationAndAssertExceptionThrown();\n  }\n\n  @Test\n  public void missingSubScenario() {\n    importMappingFromResources(MISSING_SUB_SCENARIO_MAPPINGS);\n    createAttestationAndAssertExceptionThrown();\n  }\n\n  @Test\n  public void unparsableTokenScenario() {\n    importMappingFromResources(TOKEN_PARSE_ERROR_SCENARIO_MAPPINGS);\n    createAttestationAndAssertExceptionThrown();\n  }\n\n  @Test\n  public void httpErrorScenario() {\n    importMappingFromResources(HTTP_ERROR_MAPPINGS);\n    createAttestationAndAssertExceptionThrown();\n  }\n\n  @Test\n  public void successfulImpersonationFlowScenario() throws SFException {\n    importMappingFromResources(SUCCESSFUL_IMPERSONATION_FLOW_SCENARIO_MAPPINGS);\n    SFLoginInput loginInput = createLoginInputStub();\n    loginInput.setWorkloadIdentityImpersonationPath(\"delegate1,delegate2,targetServiceAccount\");\n\n    GcpIdentityAttestationCreator attestationCreator =\n        new GcpIdentityAttestationCreator(loginInput, getBaseUrl(), getBaseUrl());\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n    assertNotNull(attestation);\n    assertEquals(WorkloadIdentityProviderType.GCP, attestation.getProvider());\n    assertEquals(\"some-subject\", attestation.getUserIdentifierComponents().get(\"sub\"));\n    assertNotNull(attestation.getCredential());\n  }\n\n  private void createAttestationAndAssertExceptionThrown() {\n    SFLoginInput loginInput = createLoginInputStub();\n    GcpIdentityAttestationCreator attestationCreator =\n        new GcpIdentityAttestationCreator(loginInput, getBaseUrl(), getBaseUrl());\n    assertThrows(SFException.class, attestationCreator::createAttestation);\n  }\n\n  private String getBaseUrl() {\n    return String.format(\"http://%s:%d/\", WIREMOCK_HOST, wiremockHttpPort);\n  }\n\n  private SFLoginInput createLoginInputStub() {\n    SFLoginInput loginInputStub = new SFLoginInput();\n    loginInputStub.setSocketTimeout(Duration.ofMinutes(5));\n    loginInputStub.setHttpClientSettingsKey(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN));\n    return loginInputStub;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/wif/OidcIdentityAttestationCreatorTest.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport net.snowflake.client.internal.core.SFException;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\n\nclass OidcIdentityAttestationCreatorTest {\n\n  private static final String UNPARSABLE_TOKEN = \"unparsable_token\";\n\n  /*\n   * {\n   *   \"sub\": \"some-subject\",\n   *   \"iat\": 1743761213,\n   *   \"exp\": 1743764813,\n   *   \"aud\": \"www.example.com\"\n   * }\n   */\n  private static final String MISSING_ISSUER_TOKEN =\n      \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImU2M2I5NzA1OTRiY2NmZTAxMDlkOTg4OWM2MDk3OWEwIn0.eyJzdWIiOiJzb21lLXN1YmplY3QiLCJpYXQiOjE3NDM3NjEyMTMsImV4cCI6MTc0Mzc2NDgxMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIn0.H6sN6kjA82EuijFcv-yCJTqau5qvVTCsk0ZQ4gvFQMkB7c71XPs4lkwTa7ZlNNlx9e6TpN1CVGnpCIRDDAZaDw\";\n\n  /*\n   * {\n   *   \"iss\": \"https://accounts.google.com\",\n   *   \"iat\": 1743761213,\n   *   \"exp\": 1743764813,\n   *   \"aud\": \"www.example.com\"\n   * }\n   */\n  private static final String MISSING_SUB_TOKEN =\n      \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImU2M2I5NzA1OTRiY2NmZTAxMDlkOTg4OWM2MDk3OWEwIn0.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJpYXQiOjE3NDM3NjEyMTMsImV4cCI6MTc0Mzc2NDgxMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIn0.w0njdpfWFETVK8Ktq9GdvuKRQJjvhOplcSyvQ_zHHwBUSMapqO1bjEWBx5VhGkdECZIGS1VY7db_IOqT45yOMA\";\n\n  /*\n   * {\n   *     \"iss\": \"https://oidc.eks.us-east-2.amazonaws.com/id/3B869BC5D12CEB5515358621D8085D58\",\n   *     \"iat\": 1743692017,\n   *     \"exp\": 1775228014,\n   *     \"aud\": \"www.example.com\",\n   *     \"sub\": \"system:serviceaccount:poc-namespace:oidc-sa\"\n   * }\n   */\n  private static final String VALID_TOKEN =\n      \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL29pZGMuZWtzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL2lkLzNCODY5QkM1RDEyQ0VCNTUxNTM1ODYyMUQ4MDg1RDU4IiwiaWF0IjoxNzQ0Mjg3ODc4LCJleHAiOjE3NzU4MjM4NzgsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpwb2MtbmFtZXNwYWNlOm9pZGMtc2EifQ.a8H6KRIF1XmM8lkqL6kR8ccInr7wAzQrbKd3ZHFgiEg\";\n\n  @Test\n  public void shouldReturnProperAttestation() throws SFException {\n    OidcIdentityAttestationCreator attestationCreator =\n        new OidcIdentityAttestationCreator(VALID_TOKEN);\n    WorkloadIdentityAttestation attestation = attestationCreator.createAttestation();\n\n    Assertions.assertEquals(WorkloadIdentityProviderType.OIDC, attestation.getProvider());\n    Assertions.assertEquals(VALID_TOKEN, attestation.getCredential());\n    assertEquals(\n        \"https://oidc.eks.us-east-2.amazonaws.com/id/3B869BC5D12CEB5515358621D8085D58\",\n        attestation.getUserIdentifierComponents().get(\"iss\"));\n    assertEquals(\n        \"system:serviceaccount:poc-namespace:oidc-sa\",\n        attestation.getUserIdentifierComponents().get(\"sub\"));\n  }\n\n  @Test\n  public void missingIssuerScenario() {\n    createAttestationAndAssertNull(MISSING_ISSUER_TOKEN);\n  }\n\n  @Test\n  public void missingSubScenario() {\n    createAttestationAndAssertNull(MISSING_SUB_TOKEN);\n  }\n\n  @Test\n  public void unparsableTokenScenario() {\n    createAttestationAndAssertNull(UNPARSABLE_TOKEN);\n  }\n\n  private void createAttestationAndAssertNull(String token) {\n    OidcIdentityAttestationCreator attestationCreator = new OidcIdentityAttestationCreator(token);\n    assertThrows(SFException.class, attestationCreator::createAttestation);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/wif/PlatformDetectionUtilTest.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.AwsCredentials;\n\npublic class PlatformDetectionUtilTest {\n\n  private AwsAttestationService mockAttestationService;\n  private AwsCredentials mockCredentials;\n\n  @BeforeEach\n  public void setUp() {\n    mockAttestationService = mock(AwsAttestationService.class);\n    mockCredentials = mock(AwsCredentials.class);\n  }\n\n  @Nested\n  @DisplayName(\"hasValidAwsIdentityForWif Tests\")\n  class HasValidAwsIdentityForWifTests {\n\n    @Test\n    @DisplayName(\"Should return false when credentials are null\")\n    public void testNullCredentials() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials()).thenReturn(null);\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when credentials are null\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when access key is null\")\n    public void testNullAccessKey() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials()).thenReturn(mockCredentials);\n      when(mockCredentials.accessKeyId()).thenReturn(null);\n      when(mockCredentials.secretAccessKey())\n          .thenReturn(\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"); // pragma: allowlist secret\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when access key is null\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when secret key is null\")\n    public void testNullsecretAccessKey() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials()).thenReturn(mockCredentials);\n      when(mockCredentials.accessKeyId())\n          .thenReturn(\"AKIAIOSFODNN7EXAMPLE\"); // pragma: allowlist secret\n      when(mockCredentials.secretAccessKey()).thenReturn(null);\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when secret key is null\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when access key is empty\")\n    public void testEmptyAccessKey() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials()).thenReturn(mockCredentials);\n      when(mockCredentials.accessKeyId()).thenReturn(\"\");\n      when(mockCredentials.secretAccessKey())\n          .thenReturn(\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"); // pragma: allowlist secret\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when access key is empty\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when secret key is empty\")\n    public void testEmptysecretAccessKey() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials()).thenReturn(mockCredentials);\n      when(mockCredentials.accessKeyId())\n          .thenReturn(\"AKIAIOSFODNN7EXAMPLE\"); // pragma: allowlist secret\n      when(mockCredentials.secretAccessKey()).thenReturn(\"\");\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when secret key is empty\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when access key is whitespace only\")\n    public void testWhitespaceAccessKey() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials()).thenReturn(mockCredentials);\n      when(mockCredentials.accessKeyId()).thenReturn(\"   \");\n      when(mockCredentials.secretAccessKey())\n          .thenReturn(\"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"); // pragma: allowlist secret\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when access key is whitespace only\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when getAWSCredentials throws exception\")\n    public void testGetCredentialsException() {\n      // Arrange\n      when(mockAttestationService.getAWSCredentials())\n          .thenThrow(new RuntimeException(\"Credentials error\"));\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when getAWSCredentials throws exception\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when region is null\")\n    public void testNullRegion() {\n      // Arrange\n      AwsBasicCredentials basicCredentials =\n          AwsBasicCredentials.create(\n              // pragma: allowlist nextline secret\n              \"AKIAIOSFODNN7EXAMPLE\", \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\");\n      when(mockAttestationService.getAWSCredentials()).thenReturn(basicCredentials);\n      when(mockAttestationService.getAWSRegion()).thenReturn(null);\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when region is null\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false when region is empty\")\n    public void testEmptyRegion() {\n      // Arrange\n      AwsBasicCredentials basicCredentials =\n          AwsBasicCredentials.create(\n              // pragma: allowlist nextline secret\n              \"AKIAIOSFODNN7EXAMPLE\", \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\");\n      when(mockAttestationService.getAWSCredentials()).thenReturn(basicCredentials);\n      when(mockAttestationService.getAWSRegion()).thenReturn(null);\n\n      // Act\n      boolean result =\n          PlatformDetectionUtil.hasValidAwsIdentityForWif(mockAttestationService, 1000);\n\n      // Assert\n      assertFalse(result, \"Should return false when region is empty\");\n    }\n  }\n\n  @Nested\n  @DisplayName(\"isValidArnForWif Tests\")\n  class IsValidArnForWifTests {\n\n    @Test\n    @DisplayName(\"Should return true for valid IAM user ARN\")\n    public void testValidIamUserArn() {\n      assertTrue(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:iam::123456789012:user/testuser\"),\n          \"Should accept IAM user ARN\");\n      assertTrue(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:iam::123456789012:user/path/to/user\"),\n          \"Should accept IAM user ARN with path\");\n      assertTrue(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws-cn:iam::123456789012:user/testuser\"),\n          \"Should accept IAM user ARN in China partition\");\n    }\n\n    @Test\n    @DisplayName(\"Should return true for valid assumed role ARN\")\n    public void testValidAssumedRoleArn() {\n      assertTrue(\n          PlatformDetectionUtil.isValidArnForWif(\n              \"arn:aws:sts::123456789012:assumed-role/role-name/session-name\"),\n          \"Should accept assumed role ARN\");\n      assertTrue(\n          PlatformDetectionUtil.isValidArnForWif(\n              \"arn:aws:sts::123456789012:assumed-role/path/to/role/session\"),\n          \"Should accept assumed role ARN with path\");\n      assertTrue(\n          PlatformDetectionUtil.isValidArnForWif(\n              \"arn:aws-cn:sts::123456789012:assumed-role/role-name/session-name\"),\n          \"Should accept assumed role ARN in China partition\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false for invalid ARN patterns\")\n    public void testInvalidArnPatterns() {\n      assertFalse(PlatformDetectionUtil.isValidArnForWif(null), \"Should reject null ARN\");\n      assertFalse(PlatformDetectionUtil.isValidArnForWif(\"\"), \"Should reject empty ARN\");\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"   \"), \"Should reject whitespace-only ARN\");\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"not-an-arn\"), \"Should reject non-ARN string\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false for non-WIF ARN types\")\n    public void testNonWifArnTypes() {\n      // IAM role (not assumed-role)\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:iam::123456789012:role/test-role\"),\n          \"Should reject IAM role ARN (not assumed-role)\");\n\n      // IAM group\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:iam::123456789012:group/test-group\"),\n          \"Should reject IAM group ARN\");\n\n      // S3 bucket\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:s3:::my-bucket\"),\n          \"Should reject S3 bucket ARN\");\n\n      // EC2 instance\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\n              \"arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0\"),\n          \"Should reject EC2 instance ARN\");\n\n      // Lambda function\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\n              \"arn:aws:lambda:us-east-1:123456789012:function:my-function\"),\n          \"Should reject Lambda function ARN\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false for malformed IAM user ARNs\")\n    public void testMalformedIamUserArns() {\n      // Missing user name\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:iam::123456789012:user/\"),\n          \"Should reject IAM user ARN without username\");\n\n      // Missing account ID\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:iam:::user/testuser\"),\n          \"Should reject IAM user ARN without account ID\");\n    }\n\n    @Test\n    @DisplayName(\"Should return false for malformed assumed role ARNs\")\n    public void testMalformedAssumedRoleArns() {\n      // Missing everything after assumed-role/\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:sts::123456789012:assumed-role/\"),\n          \"Should reject assumed role ARN with nothing after assumed-role/\");\n\n      // Missing account ID\n      assertFalse(\n          PlatformDetectionUtil.isValidArnForWif(\"arn:aws:sts:::assumed-role/role-name/session\"),\n          \"Should reject assumed role ARN without account ID\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/auth/wif/WorkloadIdentityAttestationProviderTest.java",
    "content": "package net.snowflake.client.internal.core.auth.wif;\n\nimport static net.snowflake.client.internal.core.auth.wif.WorkloadIdentityProviderType.AWS;\n\nimport java.util.HashMap;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\nclass WorkloadIdentityAttestationProviderTest {\n\n  @Test\n  public void shouldCreateAttestationWithExplicitAWSProvider() throws SFException {\n    AwsIdentityAttestationCreator awsCreatorMock =\n        Mockito.mock(AwsIdentityAttestationCreator.class);\n    Mockito.when(awsCreatorMock.createAttestation())\n        .thenReturn(new WorkloadIdentityAttestation(AWS, \"credential_abc\", new HashMap<>()));\n    WorkloadIdentityAttestationProvider provider =\n        new WorkloadIdentityAttestationProvider(awsCreatorMock, null, null, null);\n\n    WorkloadIdentityAttestation attestation = provider.getAttestation(AWS.name());\n    Assertions.assertNotNull(attestation);\n    Assertions.assertEquals(AWS, attestation.getProvider());\n    Assertions.assertEquals(\"credential_abc\", attestation.getCredential());\n    Assertions.assertEquals(new HashMap<>(), attestation.getUserIdentifierComponents());\n  }\n\n  @Test\n  public void shouldCreateProperAttestationCreatorByType() throws SFException {\n    WorkloadIdentityAttestationProvider provider =\n        new WorkloadIdentityAttestationProvider(\n            new AwsIdentityAttestationCreator(null, null),\n            new GcpIdentityAttestationCreator(null),\n            new AzureIdentityAttestationCreator(null, new SFLoginInput()),\n            new OidcIdentityAttestationCreator(null));\n    WorkloadIdentityAttestationCreator attestationCreator = provider.getCreator(AWS.name());\n    Assertions.assertInstanceOf(AwsIdentityAttestationCreator.class, attestationCreator);\n\n    attestationCreator = provider.getCreator(WorkloadIdentityProviderType.AZURE.name());\n    Assertions.assertInstanceOf(AzureIdentityAttestationCreator.class, attestationCreator);\n\n    attestationCreator = provider.getCreator(WorkloadIdentityProviderType.GCP.name());\n    Assertions.assertInstanceOf(GcpIdentityAttestationCreator.class, attestationCreator);\n\n    attestationCreator = provider.getCreator(WorkloadIdentityProviderType.OIDC.name());\n    Assertions.assertInstanceOf(OidcIdentityAttestationCreator.class, attestationCreator);\n\n    Assertions.assertThrows(\n        SFException.class, () -> provider.getCreator(\"UNKNOWN_IDENTITY_PROVIDER\"));\n\n    Assertions.assertThrows(SFException.class, () -> provider.getCreator(null));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/bind/BindExceptionTest.java",
    "content": "package net.snowflake.client.internal.core.bind;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport org.junit.jupiter.api.Test;\n\npublic class BindExceptionTest {\n\n  @Test\n  public void testBindExceptionType() {\n    assertEquals(BindException.Type.SERIALIZATION.field, TelemetryField.FAILED_BIND_SERIALIZATION);\n    assertEquals(BindException.Type.UPLOAD.field, TelemetryField.FAILED_BIND_UPLOAD);\n    assertEquals(BindException.Type.OTHER.field, TelemetryField.FAILED_BIND_OTHER);\n  }\n\n  @Test\n  public void testBindExceptionConstructor() {\n    BindException exception = new BindException(\"testException\", BindException.Type.SERIALIZATION);\n    assertEquals(exception.getMessage(), \"testException\");\n    assertEquals(exception.type.field, TelemetryField.FAILED_BIND_SERIALIZATION);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLCacheManagerLatestIT.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Date;\nimport java.util.stream.Stream;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n@Tag(TestTags.CORE)\npublic class CRLCacheManagerLatestIT {\n\n  private static final String TEST_CRL_URL = \"http://snowflake.com/test.crl\";\n  private static final String TEST_CRL_URL_2 = \"http://snowflake.com/test2.crl\";\n  private static final CertificateGeneratorUtil certGen = new CertificateGeneratorUtil();\n\n  @TempDir private Path tempCacheDir;\n\n  private CRLCacheManager cacheManager;\n  private CRLFileCache crlFileCache;\n  private CRLInMemoryCache crlInMemoryCache;\n  private X509CRL testCRL;\n  private X509CRL testCRL2;\n  private CRLCacheEntry cacheEntry;\n  private Instant downloadTime;\n\n  @BeforeEach\n  void setUp() throws Exception {\n    downloadTime = Instant.now();\n    testCRL = createTestCrl();\n    testCRL2 = createTestCrl();\n    crlInMemoryCache = new CRLInMemoryCache(Duration.ofSeconds(10));\n    crlFileCache = new CRLFileCache(tempCacheDir, Duration.ofSeconds(10));\n    cacheManager =\n        new CRLCacheManager(\n            crlInMemoryCache, crlFileCache, Duration.ofSeconds(10), Duration.ofSeconds(10));\n    cacheEntry = new CRLCacheEntry(testCRL, downloadTime);\n  }\n\n  @AfterEach\n  void tearDown() throws Exception {\n    if (cacheManager != null) {\n      cleanupTempFiles();\n    }\n  }\n\n  @Test\n  void testFileCacheStoreAndRetrieve() throws Exception {\n    crlFileCache.put(TEST_CRL_URL, cacheEntry);\n\n    CRLCacheEntry entry = crlFileCache.get(TEST_CRL_URL);\n\n    assertNotNull(entry);\n    assertEquals(testCRL.getEncoded().length, entry.getCrl().getEncoded().length);\n    assertTrue(\n        Math.abs(downloadTime.toEpochMilli() - entry.getDownloadTime().toEpochMilli()) < 1000);\n  }\n\n  @Test\n  @DontRunOnWindows\n  void testFileCacheFilePermissions() throws Exception {\n    crlFileCache.put(TEST_CRL_URL, cacheEntry);\n    try (Stream<Path> files = Files.list(tempCacheDir)) {\n      Path crlFile = files.filter(Files::isRegularFile).findFirst().orElse(null);\n      assertNotNull(crlFile);\n      String permissions = PosixFilePermissions.toString(Files.getPosixFilePermissions(crlFile));\n      assertEquals(\"rw-------\", permissions);\n    }\n  }\n\n  @Test\n  void testFileCacheCorruptedFileHandling() throws Exception {\n    crlFileCache.put(TEST_CRL_URL, cacheEntry);\n    Path corruptedFile = tempCacheDir.resolve(\"corrupted.crl\");\n    Files.write(corruptedFile, \"This is not a valid CRL\".getBytes());\n\n    assertEquals(2, countFilesInCache());\n\n    crlFileCache.cleanup();\n\n    assertEquals(1, countFilesInCache());\n    assertFalse(Files.exists(corruptedFile));\n    assertNotNull(crlFileCache.get(TEST_CRL_URL));\n  }\n\n  @Test\n  void testFileCacheExpiredCrlRemoval() throws Exception {\n    CRLCacheEntry expiredEntry =\n        new CRLCacheEntry(createExpiredCrl(), downloadTime.minus(1, ChronoUnit.DAYS));\n    crlFileCache.put(TEST_CRL_URL, expiredEntry);\n    crlFileCache.put(TEST_CRL_URL_2, cacheEntry);\n\n    assertEquals(2, countFilesInCache());\n\n    crlFileCache.cleanup();\n\n    assertEquals(1, countFilesInCache());\n    assertNull(crlFileCache.get(TEST_CRL_URL));\n    assertNotNull(crlFileCache.get(TEST_CRL_URL_2));\n  }\n\n  @Test\n  void testFileCacheRemovalDelay() throws Exception {\n    Duration removalDelay = Duration.ofHours(1);\n    CRLFileCache delayedCache = new CRLFileCache(tempCacheDir, removalDelay);\n    CRLCacheEntry oldCacheEntry =\n        new CRLCacheEntry(testCRL, downloadTime.minus(30, ChronoUnit.MINUTES));\n    delayedCache.put(TEST_CRL_URL, oldCacheEntry);\n\n    assertEquals(1, countFilesInCache());\n\n    delayedCache.cleanup();\n\n    assertEquals(1, countFilesInCache());\n    assertNotNull(delayedCache.get(TEST_CRL_URL));\n  }\n\n  @Test\n  void testFileCachePromotionToMemoryCache() throws Exception {\n    crlFileCache.put(TEST_CRL_URL, cacheEntry);\n\n    assertNull(crlInMemoryCache.get(TEST_CRL_URL));\n    assertNotNull(crlFileCache.get(TEST_CRL_URL));\n\n    // should promote to memory cache\n    cacheManager.get(TEST_CRL_URL);\n\n    assertNotNull(crlFileCache.get(TEST_CRL_URL));\n    assertNotNull(crlInMemoryCache.get(TEST_CRL_URL));\n  }\n\n  @Test\n  void testCacheManagerPeriodicCleanup() throws Exception {\n    CRLCacheManager managerWithCleanup =\n        CRLCacheManager.build(\n            true, true, tempCacheDir, Duration.ofSeconds(1), Duration.ofMillis(10));\n    managerWithCleanup.put(TEST_CRL_URL, testCRL, downloadTime.minus(2, ChronoUnit.SECONDS));\n    managerWithCleanup.put(TEST_CRL_URL_2, testCRL2, downloadTime.minus(2, ChronoUnit.SECONDS));\n\n    assertNotNull(managerWithCleanup.get(TEST_CRL_URL));\n    assertNotNull(managerWithCleanup.get(TEST_CRL_URL_2));\n\n    await()\n        .atMost(Duration.ofSeconds(5))\n        .untilAsserted(\n            () -> {\n              assertNull(managerWithCleanup.get(TEST_CRL_URL));\n              assertNull(managerWithCleanup.get(TEST_CRL_URL_2));\n            });\n  }\n\n  @Test\n  void testCrlUpdateScenario() throws Exception {\n    Instant firstDownload = downloadTime.minus(1, ChronoUnit.HOURS);\n    Instant secondDownload = downloadTime;\n    cacheManager.put(TEST_CRL_URL, testCRL, firstDownload);\n    cacheManager.put(TEST_CRL_URL, testCRL2, secondDownload);\n\n    CRLCacheEntry entry = cacheManager.get(TEST_CRL_URL);\n\n    assertNotNull(entry);\n    assertEquals(testCRL2.getEncoded().length, entry.getCrl().getEncoded().length);\n    assertEquals(secondDownload, entry.getDownloadTime());\n  }\n\n  private X509CRL createTestCrl() throws Exception {\n    Date futureDate = Date.from(downloadTime.plus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(futureDate);\n  }\n\n  private X509CRL createExpiredCrl() throws Exception {\n    Date pastDate = Date.from(downloadTime.minus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(pastDate);\n  }\n\n  private void cleanupTempFiles() throws Exception {\n    if (Files.exists(tempCacheDir)) {\n      try (Stream<Path> paths = Files.walk(tempCacheDir)) {\n        paths\n            .filter(Files::isRegularFile)\n            .forEach(\n                file -> {\n                  try {\n                    Files.deleteIfExists(file);\n                  } catch (IOException ignored) {\n                  }\n                });\n      }\n    }\n  }\n\n  private int countFilesInCache() throws IOException {\n    try (Stream<Path> files = Files.list(tempCacheDir)) {\n      return (int) files.filter(Files::isRegularFile).count();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLCacheManagerTest.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Date;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\nclass CRLCacheManagerTest {\n  private static final String TEST_CRL_URL = \"http://snowflake.com/test.crl\";\n\n  private static CertificateGeneratorUtil certGen;\n\n  private CRLInMemoryCache mockMemoryCache;\n  private CRLFileCache mockFileCache;\n  private CRLCacheManager cacheManager;\n  private X509CRL testCrl;\n  private Instant testDownloadTime;\n  private CRLCacheEntry testCacheEntry;\n\n  @BeforeAll\n  static void setUpClass() {\n    certGen = new CertificateGeneratorUtil();\n  }\n\n  @BeforeEach\n  void setUp() throws Exception {\n    mockMemoryCache = mock(CRLInMemoryCache.class);\n    mockFileCache = mock(CRLFileCache.class);\n\n    testCrl = createTestCrl();\n    testDownloadTime = Instant.now().minus(30, ChronoUnit.MINUTES);\n    testCacheEntry = new CRLCacheEntry(testCrl, testDownloadTime);\n\n    cacheManager =\n        new CRLCacheManager(mockMemoryCache, mockFileCache, Duration.ZERO, Duration.ZERO);\n  }\n\n  @Test\n  void shouldReturnCacheEntryWhenMemoryCacheHit() {\n    when(mockMemoryCache.get(TEST_CRL_URL)).thenReturn(testCacheEntry);\n\n    CRLCacheEntry result = cacheManager.get(TEST_CRL_URL);\n\n    assertNotNull(result);\n    assertEquals(testCrl, result.getCrl());\n    assertEquals(testDownloadTime, result.getDownloadTime());\n    verify(mockMemoryCache).get(TEST_CRL_URL);\n    verify(mockFileCache, never()).get(TEST_CRL_URL);\n  }\n\n  @Test\n  void shouldPromoteFileCacheHitToMemoryCache() {\n    when(mockMemoryCache.get(TEST_CRL_URL)).thenReturn(null);\n    when(mockFileCache.get(TEST_CRL_URL)).thenReturn(testCacheEntry);\n\n    CRLCacheEntry result = cacheManager.get(TEST_CRL_URL);\n\n    assertNotNull(result);\n    assertEquals(testCrl, result.getCrl());\n    assertEquals(testDownloadTime, result.getDownloadTime());\n    verify(mockMemoryCache).get(TEST_CRL_URL);\n    verify(mockFileCache).get(TEST_CRL_URL);\n    verify(mockMemoryCache).put(TEST_CRL_URL, testCacheEntry);\n  }\n\n  @Test\n  void shouldReturnNullWhenBothCachesMiss() {\n    when(mockMemoryCache.get(TEST_CRL_URL)).thenReturn(null);\n    when(mockFileCache.get(TEST_CRL_URL)).thenReturn(null);\n\n    CRLCacheEntry result = cacheManager.get(TEST_CRL_URL);\n\n    assertNull(result);\n    verify(mockMemoryCache).get(TEST_CRL_URL);\n    verify(mockFileCache).get(TEST_CRL_URL);\n    verify(mockMemoryCache, never()).put(any(), any());\n  }\n\n  @Test\n  void shouldPutToBothMemoryAndFileCache() {\n    Instant putTime = Instant.now();\n\n    cacheManager.put(TEST_CRL_URL, testCrl, putTime);\n\n    verify(mockMemoryCache).put(eq(TEST_CRL_URL), any(CRLCacheEntry.class));\n    verify(mockFileCache).put(eq(TEST_CRL_URL), any(CRLCacheEntry.class));\n  }\n\n  @Test\n  void shouldNotPromoteToMemoryCacheWhenFileCacheReturnsNull() {\n    when(mockMemoryCache.get(TEST_CRL_URL)).thenReturn(null);\n    when(mockFileCache.get(TEST_CRL_URL)).thenReturn(null);\n\n    CRLCacheEntry result = cacheManager.get(TEST_CRL_URL);\n\n    assertNull(result);\n    verify(mockMemoryCache).get(TEST_CRL_URL);\n    verify(mockFileCache).get(TEST_CRL_URL);\n    verify(mockMemoryCache, never()).put(any(), any());\n  }\n\n  @Test\n  void shouldCreateDifferentCacheEntriesForSameCrlWithDifferentDownloadTimes() {\n    Instant firstPutTime = Instant.now().minus(1, ChronoUnit.HOURS);\n    Instant secondPutTime = Instant.now();\n\n    cacheManager.put(TEST_CRL_URL, testCrl, firstPutTime);\n    cacheManager.put(TEST_CRL_URL, testCrl, secondPutTime);\n\n    verify(mockMemoryCache, times(2)).put(eq(TEST_CRL_URL), any(CRLCacheEntry.class));\n    verify(mockFileCache)\n        .put(\n            eq(TEST_CRL_URL),\n            argThat(entry -> entry.getCrl() == testCrl && entry.getDownloadTime() == firstPutTime));\n    verify(mockFileCache)\n        .put(\n            eq(TEST_CRL_URL),\n            argThat(\n                entry -> entry.getCrl() == testCrl && entry.getDownloadTime() == secondPutTime));\n  }\n\n  private X509CRL createTestCrl() throws Exception {\n    Date futureDate = Date.from(Instant.now().plus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(futureDate);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLFileCacheTest.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Date;\nimport java.util.stream.Stream;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.Constants;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n@Tag(TestTags.CORE)\nclass CRLFileCacheTest {\n  private static final String TEST_CRL_URL = \"http://snowflake.com/test.crl\";\n  private static final String TEST_CRL_URL_2 = \"http://snowflake.com/test2.crl\";\n  private static CertificateGeneratorUtil certGen;\n\n  @TempDir private Path tempCacheDir;\n\n  private CRLFileCache cache;\n  private X509CRL testCRL;\n  private X509CRL testCRL2;\n  private Instant downloadTime;\n  private CRLCacheEntry cacheEntry;\n\n  @BeforeAll\n  static void setUpClass() {\n    certGen = new CertificateGeneratorUtil();\n  }\n\n  @BeforeEach\n  void setUp() throws Exception {\n    downloadTime = Instant.now();\n    testCRL = createValidCrl();\n    testCRL2 = createValidCrl();\n    cache = new CRLFileCache(tempCacheDir, Duration.ofMinutes(10));\n    cacheEntry = new CRLCacheEntry(testCRL, downloadTime);\n  }\n\n  @AfterEach\n  void tearDown() throws Exception {\n    cleanupTempFiles();\n  }\n\n  @Test\n  void testEnabledCacheCreatesDirectory() throws Exception {\n    Path newCacheDir = tempCacheDir.resolve(\"new-cache\");\n    assertFalse(Files.exists(newCacheDir));\n\n    cache = new CRLFileCache(newCacheDir, Duration.ofMinutes(10));\n\n    assertTrue(Files.exists(newCacheDir));\n    assertTrue(Files.isDirectory(newCacheDir));\n    if ((Constants.getOS() != Constants.OS.WINDOWS)) {\n      String permissions =\n          PosixFilePermissions.toString(Files.getPosixFilePermissions(newCacheDir));\n      assertEquals(\"rwx------\", permissions);\n    }\n  }\n\n  @Test\n  void testBasicPutAndGet() throws Exception {\n    cache.put(TEST_CRL_URL, cacheEntry);\n\n    assertEquals(1, countFilesInCache());\n\n    CRLCacheEntry retrieved = cache.get(TEST_CRL_URL);\n    assertNotNull(retrieved);\n    assertEquals(testCRL.getEncoded().length, retrieved.getCrl().getEncoded().length);\n    assertTrue(\n        Math.abs(downloadTime.toEpochMilli() - retrieved.getDownloadTime().toEpochMilli()) < 1000);\n  }\n\n  @Test\n  void testGetNonExistentEntry() throws Exception {\n    CRLCacheEntry result = cache.get(\"http://nonexistent.com/crl\");\n\n    assertNull(result);\n  }\n\n  @Test\n  void testOverwriteExistingEntry() throws Exception {\n    Instant firstTime = downloadTime.minus(1, ChronoUnit.HOURS);\n    Instant secondTime = downloadTime;\n\n    cache.put(TEST_CRL_URL, new CRLCacheEntry(testCRL, firstTime));\n    assertEquals(1, countFilesInCache());\n\n    cache.put(TEST_CRL_URL, new CRLCacheEntry(testCRL2, secondTime));\n    assertEquals(1, countFilesInCache()); // Should still be one file\n\n    CRLCacheEntry retrieved = cache.get(TEST_CRL_URL);\n    assertNotNull(retrieved);\n    assertEquals(testCRL2.getEncoded().length, retrieved.getCrl().getEncoded().length);\n    assertTrue(\n        Math.abs(secondTime.toEpochMilli() - retrieved.getDownloadTime().toEpochMilli()) < 1000);\n  }\n\n  @Test\n  void testMultipleEntries() throws Exception {\n    Instant downloadTime1 = downloadTime.minus(30, ChronoUnit.MINUTES);\n    Instant downloadTime2 = downloadTime.minus(15, ChronoUnit.MINUTES);\n\n    cache.put(TEST_CRL_URL, new CRLCacheEntry(testCRL, downloadTime1));\n    cache.put(TEST_CRL_URL_2, new CRLCacheEntry(testCRL2, downloadTime2));\n\n    assertEquals(2, countFilesInCache());\n\n    CRLCacheEntry retrieved1 = cache.get(TEST_CRL_URL);\n    CRLCacheEntry retrieved2 = cache.get(TEST_CRL_URL_2);\n\n    assertNotNull(retrieved1);\n    assertNotNull(retrieved2);\n    assertEquals(testCRL.getEncoded().length, retrieved1.getCrl().getEncoded().length);\n    assertEquals(testCRL2.getEncoded().length, retrieved2.getCrl().getEncoded().length);\n  }\n\n  @Test\n  @DontRunOnWindows\n  void testFilePermissions() throws Exception {\n    cache = new CRLFileCache(tempCacheDir, Duration.ofMinutes(10));\n\n    cache.put(TEST_CRL_URL, cacheEntry);\n\n    try (Stream<Path> files = Files.list(tempCacheDir)) {\n      Path crlFile = files.filter(Files::isRegularFile).findFirst().orElse(null);\n      assertNotNull(crlFile);\n      String permissions = PosixFilePermissions.toString(Files.getPosixFilePermissions(crlFile));\n      assertEquals(\"rw-------\", permissions);\n    }\n  }\n\n  @Test\n  void testCleanupExpiredCrl() throws Exception {\n    cache.put(TEST_CRL_URL, cacheEntry);\n    cache.put(TEST_CRL_URL_2, new CRLCacheEntry(createExpiredCrl(), downloadTime));\n\n    assertEquals(2, countFilesInCache());\n\n    cache.cleanup();\n\n    assertEquals(1, countFilesInCache());\n    assertNotNull(cache.get(TEST_CRL_URL));\n    assertNull(cache.get(TEST_CRL_URL_2));\n  }\n\n  @Test\n  void testCleanupEvictedEntries() throws Exception {\n    cache.put(TEST_CRL_URL, new CRLCacheEntry(testCRL, downloadTime.minus(20, ChronoUnit.MINUTES)));\n    cache.put(\n        TEST_CRL_URL_2, new CRLCacheEntry(testCRL2, downloadTime.minus(5, ChronoUnit.MINUTES)));\n\n    assertEquals(2, countFilesInCache());\n\n    cache.cleanup();\n\n    assertEquals(1, countFilesInCache());\n    assertNull(cache.get(TEST_CRL_URL));\n    assertNotNull(cache.get(TEST_CRL_URL_2));\n  }\n\n  @Test\n  void testCleanupCorruptedFiles() throws Exception {\n    // Create a corrupted file\n    Path corruptedFile = tempCacheDir.resolve(\"corrupted.crl\");\n    Files.write(corruptedFile, \"This is not a valid CRL\".getBytes());\n\n    assertEquals(1, countFilesInCache());\n\n    cache.cleanup();\n\n    // Corrupted file should be removed\n    assertEquals(0, countFilesInCache());\n    assertFalse(Files.exists(corruptedFile));\n  }\n\n  private X509CRL createValidCrl() throws Exception {\n    Date futureDate = Date.from(downloadTime.plus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(futureDate);\n  }\n\n  private X509CRL createExpiredCrl() throws Exception {\n    Date pastDate = Date.from(downloadTime.minus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(pastDate);\n  }\n\n  private int countFilesInCache() throws IOException {\n    try (Stream<Path> files = Files.list(tempCacheDir)) {\n      return (int) files.filter(Files::isRegularFile).count();\n    }\n  }\n\n  private void cleanupTempFiles() throws Exception {\n    if (Files.exists(tempCacheDir)) {\n      try (Stream<Path> paths = Files.walk(tempCacheDir)) {\n        paths\n            .filter(Files::isRegularFile)\n            .forEach(\n                file -> {\n                  try {\n                    Files.deleteIfExists(file);\n                  } catch (IOException ignored) {\n                    // Ignore cleanup errors\n                  }\n                });\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLInMemoryCacheTest.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.security.cert.X509CRL;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Date;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\nclass CRLInMemoryCacheTest {\n  private static final String TEST_CRL_URL = \"http://snowflake.com/test.crl\";\n  private static final String TEST_CRL_URL_2 = \"http://snowflake.com/test2.crl\";\n  private static CertificateGeneratorUtil certGen;\n\n  private CRLInMemoryCache cache;\n  private X509CRL testCRL;\n  private X509CRL testCRL2;\n\n  @BeforeAll\n  static void setUpClass() {\n    certGen = new CertificateGeneratorUtil();\n  }\n\n  @BeforeEach\n  void setUp() throws Exception {\n    testCRL = createValidCrl();\n    testCRL2 = createValidCrl();\n    cache = new CRLInMemoryCache(Duration.ofMinutes(10));\n  }\n\n  @Test\n  void testBasicPutAndGet() {\n    Instant downloadTime = Instant.now();\n    CRLCacheEntry entry = new CRLCacheEntry(testCRL, downloadTime);\n\n    cache.put(TEST_CRL_URL, entry);\n    CRLCacheEntry retrieved = cache.get(TEST_CRL_URL);\n\n    assertNotNull(retrieved);\n    assertEquals(testCRL, retrieved.getCrl());\n    assertEquals(downloadTime, retrieved.getDownloadTime());\n  }\n\n  @Test\n  void testGetNonExistentEntry() {\n    CRLCacheEntry result = cache.get(\"http://nonexistent.com/crl\");\n\n    assertNull(result);\n  }\n\n  @Test\n  void testOverwriteExistingEntry() {\n    Instant firstTime = Instant.now().minus(1, ChronoUnit.HOURS);\n    Instant secondTime = Instant.now();\n\n    CRLCacheEntry firstEntry = new CRLCacheEntry(testCRL, firstTime);\n    CRLCacheEntry secondEntry = new CRLCacheEntry(testCRL2, secondTime);\n\n    cache.put(TEST_CRL_URL, firstEntry);\n    cache.put(TEST_CRL_URL, secondEntry);\n\n    CRLCacheEntry retrieved = cache.get(TEST_CRL_URL);\n    assertNotNull(retrieved);\n    assertEquals(testCRL2, retrieved.getCrl());\n    assertEquals(secondTime, retrieved.getDownloadTime());\n  }\n\n  @Test\n  void testCleanupExpiredCrl() throws Exception {\n    CRLCacheEntry expiredEntry = new CRLCacheEntry(createExpiredCrl(), Instant.now());\n    CRLCacheEntry validEntry = new CRLCacheEntry(createValidCrl(), Instant.now());\n\n    cache.put(TEST_CRL_URL, expiredEntry);\n    cache.put(TEST_CRL_URL_2, validEntry);\n\n    // Both entries should be present before cleanup\n    assertNotNull(cache.get(TEST_CRL_URL));\n    assertNotNull(cache.get(TEST_CRL_URL_2));\n\n    cache.cleanup();\n\n    // Expired CRL should be removed, valid one should remain\n    assertNull(cache.get(TEST_CRL_URL));\n    assertNotNull(cache.get(TEST_CRL_URL_2));\n  }\n\n  @Test\n  void testCleanupEvictedEntries() {\n    CRLCacheEntry oldEntry =\n        new CRLCacheEntry(testCRL, Instant.now().minus(20, ChronoUnit.MINUTES));\n    CRLCacheEntry recentEntry =\n        new CRLCacheEntry(testCRL2, Instant.now().minus(5, ChronoUnit.MINUTES));\n\n    cache.put(TEST_CRL_URL, oldEntry);\n    cache.put(TEST_CRL_URL_2, recentEntry);\n\n    // Both entries should be present before cleanup\n    assertNotNull(cache.get(TEST_CRL_URL));\n    assertNotNull(cache.get(TEST_CRL_URL_2));\n\n    cache.cleanup();\n\n    // Old entry should be evicted, recent one should remain\n    assertNull(cache.get(TEST_CRL_URL));\n    assertNotNull(cache.get(TEST_CRL_URL_2));\n  }\n\n  private X509CRL createValidCrl() throws Exception {\n    Date futureDate = Date.from(Instant.now().plus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(futureDate);\n  }\n\n  private X509CRL createExpiredCrl() throws Exception {\n    Date pastDate = Date.from(Instant.now().minus(1, ChronoUnit.DAYS));\n    return certGen.generateCRL(pastDate);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLValidationUtilsTest.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.extractCRLDistributionPoints;\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.isShortLived;\nimport static net.snowflake.client.internal.core.crl.CRLValidationUtils.verifyIssuingDistributionPoint;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509Certificate;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.crl.CertificateGeneratorUtil.CertificateChain;\nimport org.bouncycastle.asn1.x509.IssuingDistributionPoint;\nimport org.bouncycastle.asn1.x509.ReasonFlags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\nclass CRLValidationUtilsTest {\n  private CertificateGeneratorUtil certGen;\n\n  @BeforeEach\n  void setUp() {\n    certGen = new CertificateGeneratorUtil();\n  }\n\n  @Nested\n  class ExtractCRLDistributionPointsTests {\n\n    @Test\n    void shouldExtractMultipleCRLDistributionPoints() throws Exception {\n      List<String> expectedUrls =\n          Arrays.asList(\n              \"http://crl.snowflake.com/test.crl\", \"https://backup-crl.snowflake.com/test.crl\");\n\n      X509Certificate cert =\n          certGen.createCertificateWithCRLDistributionPoints(\"CN=Test Certificate\", expectedUrls);\n      List<String> extractedUrls = extractCRLDistributionPoints(cert);\n\n      assertEquals(2, extractedUrls.size(), \"Should extract 2 CRL URLs\");\n      assertTrue(extractedUrls.contains(\"http://crl.snowflake.com/test.crl\"));\n      assertTrue(extractedUrls.contains(\"https://backup-crl.snowflake.com/test.crl\"));\n    }\n\n    @Test\n    void shouldReturnEmptyListForCertificateWithoutCRLDistributionPoints() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      List<String> extractedUrls = extractCRLDistributionPoints(chain.leafCert);\n\n      assertTrue(extractedUrls.isEmpty());\n    }\n\n    @Test\n    void shouldFilterOnlyHttpAndHttpsUrls() throws Exception {\n      List<String> mixedUrls =\n          Arrays.asList(\n              \"http://crl.snowflake.com/test.crl\",\n              \"https://secure-crl.snowflake.com/test.crl\",\n              \"HTTP://crl2.snowflake.com/test.crl\",\n              \"HTTPS://secure-crl2.snowflake.com/test.crl\",\n              \"ftp://ftp.snowflake.com/test.crl\", // Should be filtered out\n              \"FTP://ftp2.snowflake.com/test.crl\", // Should be filtered out\n              \"ldap://ldap.snowflake.com/test.crl\"); // Should be filtered out\n\n      X509Certificate cert =\n          certGen.createCertificateWithCRLDistributionPoints(\"CN=Test Certificate\", mixedUrls);\n      List<String> extractedUrls = extractCRLDistributionPoints(cert);\n\n      assertEquals(4, extractedUrls.size(), \"Should extract only HTTP/HTTPS URLs\");\n      assertTrue(extractedUrls.contains(\"http://crl.snowflake.com/test.crl\"));\n      assertTrue(extractedUrls.contains(\"HTTP://crl2.snowflake.com/test.crl\"));\n      assertTrue(extractedUrls.contains(\"https://secure-crl.snowflake.com/test.crl\"));\n      assertTrue(extractedUrls.contains(\"HTTPS://secure-crl2.snowflake.com/test.crl\"));\n      assertFalse(extractedUrls.contains(\"ftp://ftp.snowflake.com/test.crl\"));\n      assertFalse(extractedUrls.contains(\"FTP://ftp2.snowflake.com/test.crl\"));\n      assertFalse(extractedUrls.contains(\"ldap://ldap.snowflake.com/test.crl\"));\n    }\n\n    @Test\n    void shouldHandleEmptyCRLDistributionPointsList() throws Exception {\n      X509Certificate cert =\n          certGen.createCertificateWithCRLDistributionPoints(\n              \"CN=Test Certificate\", Arrays.asList());\n      List<String> extractedUrls = extractCRLDistributionPoints(cert);\n\n      assertTrue(extractedUrls.isEmpty());\n    }\n  }\n\n  @Nested\n  class IsShortLivedTests {\n    @Test\n    void shouldNotConsiderCertificatesIssuedBeforeMarch2024AsShortLived() throws Exception {\n      Date feb2024Date =\n          Date.from(LocalDate.of(2024, 2, 1).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n      X509Certificate cert = certGen.createShortLivedCertificate(5, feb2024Date);\n\n      assertFalse(isShortLived(cert));\n    }\n\n    @Test\n    void shouldConsiderShortCertificatesIssuedAfterMarch2024AsShortLived() throws Exception {\n      Date april2024Date =\n          Date.from(LocalDate.of(2024, 4, 1).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n      X509Certificate cert = certGen.createShortLivedCertificate(5, april2024Date);\n\n      assertTrue(isShortLived(cert));\n    }\n\n    @Test\n    void shouldApply10DayThresholdBetweenMarch2024AndMarch2026() throws Exception {\n      Date april2024Date =\n          Date.from(LocalDate.of(2024, 4, 1).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n\n      assertTrue(isShortLived(certGen.createShortLivedCertificate(10, april2024Date)));\n      assertFalse(isShortLived(certGen.createShortLivedCertificate(15, april2024Date)));\n    }\n\n    @Test\n    void shouldApply7DayThresholdAfterMarch2026() throws Exception {\n      Date april2026Date =\n          Date.from(LocalDate.of(2026, 4, 1).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n\n      X509Certificate shortCert = certGen.createShortLivedCertificate(6, april2026Date);\n      assertTrue(isShortLived(shortCert));\n\n      X509Certificate longCert = certGen.createShortLivedCertificate(8, april2026Date);\n      assertFalse(isShortLived(longCert));\n    }\n\n    @Test\n    void shouldHandleEdgeCaseWithExact7DaysAfter2026() throws Exception {\n      Date april2026Date =\n          Date.from(LocalDate.of(2026, 4, 1).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n      X509Certificate cert = certGen.createShortLivedCertificate(7, april2026Date);\n\n      assertTrue(isShortLived(cert));\n    }\n  }\n\n  @Nested\n  class VerifyIssuingDistributionPointTests {\n    @Test\n    void shouldReturnTrueForCRLWithoutIDPExtension() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      X509CRL crl = certGen.createCRLWithIDP(chain.rootCert, null);\n\n      assertTrue(\n          verifyIssuingDistributionPoint(crl, chain.leafCert, \"http://snowflake.com/test.crl\"));\n    }\n\n    @Test\n    void shouldRejectUserCertificateWhenCRLOnlyCoversUserCerts() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      IssuingDistributionPoint idp =\n          new IssuingDistributionPoint(null, true, false, null, false, false);\n      X509CRL crl = certGen.createCRLWithIDP(chain.rootCert, idp);\n\n      assertFalse(\n          verifyIssuingDistributionPoint(\n              crl, chain.intermediateCert, \"http://snowflake.com/test.crl\"),\n          \"Should reject CA certificate when CRL only covers user certificates\");\n\n      assertTrue(\n          verifyIssuingDistributionPoint(crl, chain.leafCert, \"http://snowflake.com/test.crl\"),\n          \"Should accept end-entity certificate when CRL only covers user certificates\");\n    }\n\n    @Test\n    void shouldRejectEndEntityCertificateWhenCRLOnlyCoversCACs() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      IssuingDistributionPoint idp =\n          new IssuingDistributionPoint(null, false, true, null, false, false);\n      X509CRL crl = certGen.createCRLWithIDP(chain.rootCert, idp);\n\n      assertFalse(\n          verifyIssuingDistributionPoint(crl, chain.leafCert, \"http://snowflake.com/test.crl\"),\n          \"Should reject end-entity certificate when CRL only covers CA certificates\");\n\n      assertTrue(\n          verifyIssuingDistributionPoint(\n              crl, chain.intermediateCert, \"http://snowflake.com/test.crl\"),\n          \"Should accept CA certificate when CRL only covers CA certificates\");\n    }\n\n    @Test\n    void shouldValidateURLMatchingInIDPDistributionPoints() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      String crlUrl = \"http://crl.snowflake.com/test.crl\";\n      List<String> idpUrls = Arrays.asList(crlUrl, \"http://backup.snowflake.com/test.crl\");\n\n      X509CRL crl = certGen.createCRLWithIDPDistributionPoints(chain.rootCert, idpUrls);\n\n      assertTrue(verifyIssuingDistributionPoint(crl, chain.leafCert, crlUrl));\n    }\n\n    @Test\n    void shouldRejectURLNotMatchingIDPDistributionPoints() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      String unauthorizedUrl = \"http://unauthorized.snowflake.com/test.crl\";\n      List<String> idpUrls =\n          Arrays.asList(\n              \"http://authorized.snowflake.com/test.crl\", \"http://backup.snowflake.com/test.crl\");\n\n      X509CRL crl = certGen.createCRLWithIDPDistributionPoints(chain.rootCert, idpUrls);\n\n      assertFalse(verifyIssuingDistributionPoint(crl, chain.leafCert, unauthorizedUrl));\n    }\n\n    @Test\n    void shouldRejectWhenIDPHasEmptyDistributionPointsList() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      X509CRL crl = certGen.createCRLWithIDPDistributionPoints(chain.rootCert, Arrays.asList());\n\n      assertFalse(\n          verifyIssuingDistributionPoint(crl, chain.leafCert, \"http://snowflake.com/test.crl\"));\n    }\n\n    @Test\n    void shouldHandleIDPWithoutDistributionPoints() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      IssuingDistributionPoint idp =\n          new IssuingDistributionPoint(null, false, false, null, false, false);\n      X509CRL crl = certGen.createCRLWithIDP(chain.rootCert, idp);\n\n      assertTrue(\n          verifyIssuingDistributionPoint(crl, chain.leafCert, \"http://snowflake.com/test.crl\"));\n    }\n\n    @Test\n    void shouldRejectCRLWithOnlySomeReasons() throws Exception {\n      CertificateChain chain = certGen.createSimpleChain();\n      ReasonFlags reasons = new ReasonFlags(ReasonFlags.keyCompromise);\n      IssuingDistributionPoint idp =\n          new IssuingDistributionPoint(null, false, false, reasons, false, false);\n      X509CRL crl = certGen.createCRLWithIDP(chain.rootCert, idp);\n\n      assertFalse(\n          verifyIssuingDistributionPoint(crl, chain.leafCert, \"http://snowflake.com/test.crl\"),\n          \"Should reject CRL that only covers specific revocation reasons\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLValidatorTest.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\n\nimport java.security.cert.X509Certificate;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.crl.CertificateGeneratorUtil.CertificateChain;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\nclass CRLValidatorTest {\n  private CertificateGeneratorUtil certGen;\n  private CloseableHttpClient mockHttpClient;\n  private CRLCacheManager mockCacheManager;\n  private Telemetry mockTelemetry;\n\n  @BeforeEach\n  void setUp() {\n    certGen = new CertificateGeneratorUtil();\n    mockHttpClient = mock(CloseableHttpClient.class);\n    mockCacheManager = mock(CRLCacheManager.class);\n    mockTelemetry = mock(Telemetry.class);\n  }\n\n  @Test\n  void shouldAllowConnectionWhenCRLValidationDisabled() throws Exception {\n    CertificateChain chain = certGen.createSimpleChain();\n    List<X509Certificate[]> chains = new ArrayList<>();\n    chains.add(new X509Certificate[] {chain.leafCert, chain.intermediateCert, chain.rootCert});\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.DISABLED,\n            false,\n            mockHttpClient,\n            mockCacheManager,\n            mockTelemetry);\n\n    assertTrue(validator.validateCertificateChains(chains));\n  }\n\n  @Test\n  void shouldFailWithNullOrEmptyCertificateChains() {\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, true, mockHttpClient, mockCacheManager, mockTelemetry);\n\n    assertThrows(IllegalArgumentException.class, () -> validator.validateCertificateChains(null));\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> validator.validateCertificateChains(new ArrayList<>()));\n  }\n\n  @Test\n  void shouldHandleCertificatesWithoutCRLUrlsInEnabledMode() throws Exception {\n    CertificateChain chain = certGen.createSimpleChain();\n    List<X509Certificate[]> chains = new ArrayList<>();\n    chains.add(new X509Certificate[] {chain.leafCert, chain.intermediateCert, chain.rootCert});\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED,\n            false,\n            mockHttpClient,\n            mockCacheManager,\n            mockTelemetry);\n\n    assertFalse(validator.validateCertificateChains(chains));\n  }\n\n  @Test\n  void shouldAllowCertificatesWithoutCRLUrlsWhenConfigured() throws Exception {\n    CertificateChain chain = certGen.createSimpleChain();\n    List<X509Certificate[]> chains = new ArrayList<>();\n    chains.add(new X509Certificate[] {chain.leafCert, chain.intermediateCert, chain.rootCert});\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, true, mockHttpClient, mockCacheManager, mockTelemetry);\n\n    assertTrue(validator.validateCertificateChains(chains));\n  }\n\n  @Test\n  void shouldPassInAdvisoryModeEvenWithErrors() throws Exception {\n    CertificateChain chain = certGen.createSimpleChain();\n    List<X509Certificate[]> chains = new ArrayList<>();\n    chains.add(new X509Certificate[] {chain.leafCert, chain.intermediateCert, chain.rootCert});\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ADVISORY,\n            false,\n            mockHttpClient,\n            mockCacheManager,\n            mockTelemetry);\n\n    assertTrue(validator.validateCertificateChains(chains));\n  }\n\n  @Test\n  void shouldValidateMultipleChainsAndReturnFirstValid() throws Exception {\n    Date beforeMarch2024 =\n        Date.from(LocalDate.of(2024, 2, 1).atStartOfDay(ZoneId.of(\"UTC\")).toInstant());\n    X509Certificate invalidCert =\n        certGen.createShortLivedCertificate(5, beforeMarch2024); // Not considered short-lived\n    CertificateChain validChain = certGen.createSimpleChain();\n\n    List<X509Certificate[]> chains = new ArrayList<>();\n    chains.add(\n        new X509Certificate[] {invalidCert, validChain.intermediateCert, validChain.rootCert});\n    chains.add(\n        new X509Certificate[] {\n          validChain.leafCert, validChain.intermediateCert, validChain.rootCert\n        });\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, true, mockHttpClient, mockCacheManager, mockTelemetry);\n\n    assertTrue(\n        validator.validateCertificateChains(chains),\n        \"Should return true when at least one valid chain is found\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CRLValidatorWiremockIT.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.io.File;\nimport java.nio.file.Paths;\nimport java.security.cert.X509Certificate;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Mockito;\n\n@Tag(TestTags.CORE)\npublic class CRLValidatorWiremockIT extends BaseWiremockTest {\n  private CertificateGeneratorUtil certGen;\n  private CloseableHttpClient httpClient;\n  private CRLCacheManager cacheManager;\n  private Telemetry telemetry;\n  @TempDir private File cacheDir;\n\n  @BeforeEach\n  public void setUpTest() throws SnowflakeSQLLoggedException {\n    certGen = new CertificateGeneratorUtil();\n    httpClient = HttpClients.createDefault();\n    cacheManager =\n        CRLCacheManager.build(\n            CRLCacheConfig.getInMemoryCacheEnabled(),\n            CRLCacheConfig.getOnDiskCacheEnabled(),\n            Paths.get(cacheDir.getAbsolutePath()),\n            CRLCacheConfig.getCrlOnDiskCacheRemovalDelay(),\n            CRLCacheConfig.getCacheValidityTime());\n    telemetry = Mockito.mock(Telemetry.class);\n    resetWiremock();\n  }\n\n  @Test\n  void shouldValidateNonRevokedCertificateSuccessfully() throws Exception {\n    setupCRLMapping(\"/test-ca.crl\", certGen.generateValidCRL(), 200);\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Test Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/test-ca.crl\"));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldFailForRevokedCertificate() throws Exception {\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Revoked Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/test-ca.crl\"));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n    setupCRLMapping(\n        \"/test-ca.crl\", certGen.generateCRLWithRevokedCertificate(cert.getSerialNumber()), 200);\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertFalse(\n        validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldAllowRevokedCertificateWhenCRLValidationDisabled() throws Exception {\n    X509Certificate revokedCert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Revoked Server (Disabled Mode)\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/test-ca.crl\"));\n    X509Certificate[] chain = {revokedCert, certGen.getCACertificate()};\n    setupCRLMapping(\n        \"/test-ca.crl\",\n        certGen.generateCRLWithRevokedCertificate(revokedCert.getSerialNumber()),\n        200);\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.DISABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldPassInAdvisoryModeWithCRLErrors() throws Exception {\n    setup404CRLMapping(\"/test-ca.crl\");\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Test Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/test-ca.crl\"));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.DISABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldFailInEnabledModeWithCRLErrors() throws Exception {\n    setup404CRLMapping(\"/test-ca.crl\");\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Test Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/test-ca.crl\"));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertFalse(\n        validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldValidateMultipleChainsAndReturnFirstValid() throws Exception {\n    byte[] validCrlContent = certGen.generateValidCRL();\n    setupCRLMapping(\"/valid-ca.crl\", validCrlContent, 200);\n    setup404CRLMapping(\"/invalid-ca.crl\");\n\n    X509Certificate invalidCert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Invalid Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/invalid-ca.crl\"));\n    X509Certificate[] invalidChain = {invalidCert, certGen.getCACertificate()};\n\n    X509Certificate validCert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Valid Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/valid-ca.crl\"));\n    X509Certificate[] validChain = {validCert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(invalidChain, validChain)));\n  }\n\n  @Test\n  void shouldRejectExpiredCRL() throws Exception {\n    byte[] expiredCrlContent = certGen.generateExpiredCRL();\n    setupCRLMapping(\"/expired-ca.crl\", expiredCrlContent, 200);\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Test Server\",\n            Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/expired-ca.crl\"));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    List<X509Certificate[]> chains = Arrays.asList(new X509Certificate[][] {chain});\n\n    assertFalse(validator.validateCertificateChains(chains));\n  }\n\n  @Test\n  void shouldSkipShortLivedCertificates() throws Exception {\n    X509Certificate shortLivedCert = certGen.createShortLivedCertificate(5, new Date());\n    X509Certificate[] chain = {shortLivedCert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldHandleMultipleCRLDistributionPoints() throws Exception {\n    byte[] crlContent = certGen.generateValidCRL();\n    setupCRLMapping(\"/primary-ca.crl\", crlContent, 200);\n    setupCRLMapping(\"/backup-ca.crl\", crlContent, 200);\n\n    List<String> crlUrls =\n        Arrays.asList(\n            \"http://localhost:\" + wiremockHttpPort + \"/primary-ca.crl\",\n            \"http://localhost:\" + wiremockHttpPort + \"/backup-ca.crl\");\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\"CN=Multi-CRL Server\", crlUrls);\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldCacheCRLOnFirstRequestAndReuseOnSecond() throws Exception {\n    setupCRLMapping(\"/cached-ca.crl\", certGen.generateValidCRL(), 200);\n    String crlUrl = \"http://localhost:\" + wiremockHttpPort + \"/cached-ca.crl\";\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Cached Server\", Arrays.asList(crlUrl));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n\n    // Remove the WireMock mapping to ensure second request uses cache\n    resetWiremock();\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n  }\n\n  @Test\n  void shouldFailWhenCrlExceedsMaxSizeLimit() throws Exception {\n    // Override max size to a small value for testing\n    String previousValue = System.getProperty(CRLCacheConfig.CRL_DOWNLOAD_MAX_SIZE_BYTES);\n    try {\n      System.setProperty(CRLCacheConfig.CRL_DOWNLOAD_MAX_SIZE_BYTES, \"1024\");\n      // Generate a valid CRL with many revoked entries so its encoded size exceeds 1 KB\n      for (int i = 0; i < 50; i++) {\n        certGen.generateCRLWithRevokedCertificate(java.math.BigInteger.valueOf(1000 + i));\n      }\n      byte[] largeCrl = certGen.generateValidCRL();\n      assertTrue(\n          largeCrl.length > 1024,\n          \"Test precondition: CRL should be larger than 1024 bytes, but was \" + largeCrl.length);\n      setupCRLMapping(\"/oversized-ca.crl\", largeCrl, 200);\n\n      X509Certificate cert =\n          certGen.createCertificateWithCRLDistributionPoints(\n              \"CN=Oversized CRL Server\",\n              Arrays.asList(\"http://localhost:\" + wiremockHttpPort + \"/oversized-ca.crl\"));\n      X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n      CRLValidator validator =\n          new CRLValidator(\n              CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n      assertFalse(\n          validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})),\n          \"Should fail validation when CRL exceeds max download size\");\n    } finally {\n      if (previousValue != null) {\n        System.setProperty(CRLCacheConfig.CRL_DOWNLOAD_MAX_SIZE_BYTES, previousValue);\n      } else {\n        System.clearProperty(CRLCacheConfig.CRL_DOWNLOAD_MAX_SIZE_BYTES);\n      }\n    }\n  }\n\n  @Test\n  void shouldEmitCrlTelemetry() throws Exception {\n    byte[] crlContent = certGen.generateValidCRL();\n    String crlUrl = \"http://localhost:\" + wiremockHttpPort + \"/test-ca.crl\";\n    setupCRLMapping(\"/test-ca.crl\", crlContent, 200);\n    X509Certificate cert =\n        certGen.createCertificateWithCRLDistributionPoints(\n            \"CN=Test Server\", Collections.singletonList(crlUrl));\n    X509Certificate[] chain = {cert, certGen.getCACertificate()};\n\n    CRLValidator validator =\n        new CRLValidator(\n            CertRevocationCheckMode.ENABLED, false, httpClient, cacheManager, telemetry);\n\n    assertTrue(validator.validateCertificateChains(Arrays.asList(new X509Certificate[][] {chain})));\n    ArgumentCaptor<TelemetryData> captor = ArgumentCaptor.forClass(TelemetryData.class);\n    Mockito.verify(telemetry).addLogToBatch(captor.capture());\n    JsonNode telemetryData = captor.getValue().getMessage();\n    assertEquals(TelemetryField.CLIENT_CRL_STATS.toString(), telemetryData.get(\"type\").asText());\n    assertEquals(crlUrl, telemetryData.get(\"client_crl_url\").asText());\n    assertEquals(crlContent.length, telemetryData.get(\"client_crl_bytes\").asInt());\n    assertEquals(0, telemetryData.get(\"client_revoked_certificates\").asInt());\n    assertTrue(telemetryData.get(\"client_time_downloading_crl\").asInt() >= 0);\n    assertTrue(telemetryData.get(\"client_time_parsing_crl\").asInt() >= 0);\n  }\n\n  private void setupCRLMapping(String urlPath, byte[] crlContent, int statusCode) {\n    String mappingBody;\n    if (statusCode == 200) {\n      mappingBody =\n          String.format(\n              \"{\\n\"\n                  + \"  \\\"mappings\\\": [{\\n\"\n                  + \"    \\\"request\\\": {\\n\"\n                  + \"      \\\"method\\\": \\\"GET\\\",\\n\"\n                  + \"      \\\"urlPath\\\": \\\"%s\\\"\\n\"\n                  + \"    },\\n\"\n                  + \"    \\\"response\\\": {\\n\"\n                  + \"      \\\"status\\\": %d,\\n\"\n                  + \"      \\\"headers\\\": {\\n\"\n                  + \"        \\\"Content-Type\\\": \\\"application/pkcs7-mime\\\"\\n\"\n                  + \"      },\\n\"\n                  + \"      \\\"base64Body\\\": \\\"%s\\\"\\n\"\n                  + \"    }\\n\"\n                  + \"  }]\\n\"\n                  + \"}\",\n              urlPath, statusCode, java.util.Base64.getEncoder().encodeToString(crlContent));\n    } else {\n      mappingBody =\n          String.format(\n              \"{\\n\"\n                  + \"  \\\"mappings\\\": [{\\n\"\n                  + \"    \\\"request\\\": {\\n\"\n                  + \"      \\\"method\\\": \\\"GET\\\",\\n\"\n                  + \"      \\\"urlPath\\\": \\\"%s\\\"\\n\"\n                  + \"    },\\n\"\n                  + \"    \\\"response\\\": {\\n\"\n                  + \"      \\\"status\\\": %d\\n\"\n                  + \"    }\\n\"\n                  + \"  }]\\n\"\n                  + \"}\",\n              urlPath, statusCode);\n    }\n\n    importMapping(mappingBody);\n  }\n\n  private void setup404CRLMapping(String urlPath) {\n    setupCRLMapping(urlPath, new byte[0], 404);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/CertificateGeneratorUtil.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport java.math.BigInteger;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.SecureRandom;\nimport java.security.Security;\nimport java.security.cert.X509CRL;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport org.bouncycastle.asn1.x500.X500Name;\nimport org.bouncycastle.asn1.x509.BasicConstraints;\nimport org.bouncycastle.asn1.x509.CRLDistPoint;\nimport org.bouncycastle.asn1.x509.DistributionPoint;\nimport org.bouncycastle.asn1.x509.DistributionPointName;\nimport org.bouncycastle.asn1.x509.Extension;\nimport org.bouncycastle.asn1.x509.GeneralName;\nimport org.bouncycastle.asn1.x509.GeneralNames;\nimport org.bouncycastle.asn1.x509.IssuingDistributionPoint;\nimport org.bouncycastle.asn1.x509.KeyUsage;\nimport org.bouncycastle.cert.CertIOException;\nimport org.bouncycastle.cert.X509CRLHolder;\nimport org.bouncycastle.cert.X509CertificateHolder;\nimport org.bouncycastle.cert.X509v2CRLBuilder;\nimport org.bouncycastle.cert.X509v3CertificateBuilder;\nimport org.bouncycastle.cert.jcajce.JcaX509CRLConverter;\nimport org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;\nimport org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;\nimport org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;\nimport org.bouncycastle.jce.provider.BouncyCastleProvider;\nimport org.bouncycastle.operator.ContentSigner;\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\n\npublic class CertificateGeneratorUtil {\n  private static final String SIGNATURE_ALGORITHM = \"SHA256WithRSA\";\n  private static final String BOUNCY_CASTLE_PROVIDER = \"BC\";\n  private static final long ONE_YEAR_MS = 365L * 24 * 60 * 60 * 1000;\n  private static final long ONE_DAY_MS = 24L * 60 * 60 * 1000;\n\n  private final SecureRandom random = new SecureRandom();\n  private KeyPair caKeyPair;\n  private X509Certificate caCertificate;\n  private final List<BigInteger> revokedSerialNumbers = new ArrayList<>();\n\n  public CertificateGeneratorUtil() {\n    try {\n      Security.addProvider(new BouncyCastleProvider());\n      this.caKeyPair = generateKeyPair();\n      this.caCertificate = createCACertificate();\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  X509Certificate getCACertificate() {\n    return caCertificate;\n  }\n\n  private X509Certificate createCertificate(\n      String subjectDN, boolean isCA, List<String> crlUrls, Date notBefore, Date notAfter)\n      throws Exception {\n    KeyPair keyPair = generateKeyPair();\n\n    X509v3CertificateBuilder certBuilder =\n        createBasicCertBuilder(\n            keyPair.getPublic(),\n            subjectDN,\n            caCertificate.getSubjectX500Principal().getName(),\n            isCA,\n            notBefore,\n            notAfter);\n\n    if (crlUrls != null && !crlUrls.isEmpty()) {\n      GeneralName[] generalNames =\n          crlUrls.stream()\n              .map(url -> new GeneralName(GeneralName.uniformResourceIdentifier, url))\n              .toArray(GeneralName[]::new);\n\n      DistributionPointName dpName =\n          new DistributionPointName(\n              DistributionPointName.FULL_NAME, new GeneralNames(generalNames));\n\n      CRLDistPoint crlDistPoint =\n          new CRLDistPoint(new DistributionPoint[] {new DistributionPoint(dpName, null, null)});\n      certBuilder.addExtension(Extension.cRLDistributionPoints, true, crlDistPoint);\n    }\n\n    return buildCertificate(certBuilder, keyPair.getPrivate());\n  }\n\n  CertificateChain createSimpleChain() throws Exception {\n    X509Certificate rootCert =\n        createCertificate(\"CN=Test Root CA \" + random.nextInt(10000), true, null, null, null);\n\n    X509Certificate intermediateCert =\n        createCertificate(\n            \"CN=Test Intermediate CA \" + random.nextInt(10000), true, null, null, null);\n\n    X509Certificate leafCert =\n        createCertificate(\"CN=Test Leaf \" + random.nextInt(10000), false, null, null, null);\n\n    return new CertificateChain(rootCert, intermediateCert, leafCert);\n  }\n\n  X509Certificate createShortLivedCertificate(int validityDays, Date issuanceDate)\n      throws Exception {\n    Date notAfter = new Date(issuanceDate.getTime() + validityDays * ONE_DAY_MS);\n    return createCertificate(\n        \"CN=Test Short-Lived \" + random.nextInt(10000), false, null, issuanceDate, notAfter);\n  }\n\n  X509Certificate createCertificateWithCRLDistributionPoints(String subjectDN, List<String> crlUrls)\n      throws Exception {\n    return createCertificate(subjectDN, false, crlUrls, null, null);\n  }\n\n  public X509Certificate createWithIssuer(String issuerDN) throws Exception {\n    KeyPair keyPair = generateKeyPair();\n    X509v3CertificateBuilder certBuilder =\n        createBasicCertBuilder(\n            keyPair.getPublic(),\n            \"CN=Test Leaf \" + random.nextInt(10000),\n            issuerDN,\n            false,\n            null,\n            null);\n    return buildCertificate(certBuilder, caKeyPair.getPrivate());\n  }\n\n  X509CRL createCRLWithIDPDistributionPoints(\n      X509Certificate issuerCert, List<String> distributionPointUrls) throws Exception {\n    GeneralName[] generalNames =\n        distributionPointUrls.stream()\n            .map(url -> new GeneralName(GeneralName.uniformResourceIdentifier, url))\n            .toArray(GeneralName[]::new);\n\n    DistributionPointName dpName =\n        new DistributionPointName(DistributionPointName.FULL_NAME, new GeneralNames(generalNames));\n\n    IssuingDistributionPoint idp =\n        new IssuingDistributionPoint(dpName, false, false, null, false, false);\n    return createCRLWithIDP(issuerCert, idp);\n  }\n\n  byte[] generateValidCRL() throws Exception {\n    return generateCRL(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)).getEncoded();\n  }\n\n  byte[] generateCRLWithRevokedCertificate(BigInteger serialNumber) throws Exception {\n    revokedSerialNumbers.add(serialNumber);\n    return generateValidCRL();\n  }\n\n  byte[] generateExpiredCRL() throws Exception {\n    return generateCRL(new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000)).getEncoded();\n  }\n\n  X509CRL generateCRL(Date nextUpdate) throws Exception {\n    Date now = new Date();\n    X509v2CRLBuilder crlBuilder =\n        new X509v2CRLBuilder(new X500Name(caCertificate.getSubjectX500Principal().getName()), now);\n    crlBuilder.setNextUpdate(nextUpdate);\n\n    for (BigInteger serialNumber : revokedSerialNumbers) {\n      crlBuilder.addCRLEntry(serialNumber, now, 0);\n    }\n\n    ContentSigner contentSigner =\n        new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(caKeyPair.getPrivate());\n    X509CRLHolder crlHolder = crlBuilder.build(contentSigner);\n    return new JcaX509CRLConverter().setProvider(BOUNCY_CASTLE_PROVIDER).getCRL(crlHolder);\n  }\n\n  private KeyPair generateKeyPair() throws Exception {\n    KeyPairGenerator keyGen = KeyPairGenerator.getInstance(\"RSA\");\n    keyGen.initialize(2048);\n    return keyGen.generateKeyPair();\n  }\n\n  private X509Certificate createCACertificate() throws Exception {\n    long now = System.currentTimeMillis();\n    Date notBefore = new Date(now);\n    Date notAfter = new Date(now + 10L * 365 * 24 * 60 * 60 * 1000); // 10 years\n\n    X509v3CertificateBuilder certBuilder =\n        new JcaX509v3CertificateBuilder(\n            new X500Name(\"CN=Test CA \" + random.nextInt(10000)),\n            BigInteger.valueOf(random.nextLong()),\n            notBefore,\n            notAfter,\n            new X500Name(\"CN=Test CA \" + random.nextInt(10000)),\n            caKeyPair.getPublic());\n\n    certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));\n    certBuilder.addExtension(\n        Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));\n\n    ContentSigner contentSigner =\n        new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(caKeyPair.getPrivate());\n    X509CertificateHolder certHolder = certBuilder.build(contentSigner);\n\n    return new JcaX509CertificateConverter()\n        .setProvider(BOUNCY_CASTLE_PROVIDER)\n        .getCertificate(certHolder);\n  }\n\n  private X509v3CertificateBuilder createBasicCertBuilder(\n      PublicKey publicKey,\n      String subjectDN,\n      String issuerDN,\n      boolean isCA,\n      Date notBefore,\n      Date notAfter)\n      throws CertIOException {\n\n    if (notBefore == null) {\n      notBefore = new Date();\n    }\n    if (notAfter == null) {\n      notAfter = new Date(notBefore.getTime() + ONE_YEAR_MS);\n    }\n\n    X509v3CertificateBuilder certBuilder =\n        new JcaX509v3CertificateBuilder(\n            new X500Name(issuerDN),\n            BigInteger.valueOf(random.nextLong()),\n            notBefore,\n            notAfter,\n            new X500Name(subjectDN),\n            publicKey);\n\n    certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(isCA));\n\n    KeyUsage keyUsage =\n        isCA\n            ? new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)\n            : new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment);\n    certBuilder.addExtension(Extension.keyUsage, true, keyUsage);\n\n    return certBuilder;\n  }\n\n  private X509Certificate buildCertificate(\n      X509v3CertificateBuilder certBuilder, PrivateKey signerPrivateKey) throws Exception {\n    ContentSigner contentSigner =\n        new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(signerPrivateKey);\n    X509CertificateHolder certHolder = certBuilder.build(contentSigner);\n    return new JcaX509CertificateConverter()\n        .setProvider(BOUNCY_CASTLE_PROVIDER)\n        .getCertificate(certHolder);\n  }\n\n  X509CRL createCRLWithIDP(X509Certificate issuerCert, IssuingDistributionPoint idp)\n      throws Exception {\n    KeyPair issuerKeyPair = generateKeyPair();\n    X509v2CRLBuilder crlBuilder =\n        new JcaX509v2CRLBuilder(issuerCert.getSubjectX500Principal(), new Date());\n    crlBuilder.setNextUpdate(new Date(System.currentTimeMillis() + ONE_DAY_MS));\n\n    if (idp != null) {\n      crlBuilder.addExtension(Extension.issuingDistributionPoint, true, idp);\n    }\n\n    return buildCRL(crlBuilder, issuerKeyPair.getPrivate());\n  }\n\n  private X509CRL buildCRL(X509v2CRLBuilder crlBuilder, PrivateKey signerPrivateKey)\n      throws Exception {\n    ContentSigner contentSigner =\n        new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(signerPrivateKey);\n    X509CRLHolder crlHolder = crlBuilder.build(contentSigner);\n    return new JcaX509CRLConverter().setProvider(BOUNCY_CASTLE_PROVIDER).getCRL(crlHolder);\n  }\n\n  static class CertificateChain {\n    final X509Certificate rootCert;\n    final X509Certificate intermediateCert;\n    final X509Certificate leafCert;\n\n    CertificateChain(\n        X509Certificate rootCert, X509Certificate intermediateCert, X509Certificate leafCert) {\n      this.rootCert = rootCert;\n      this.intermediateCert = intermediateCert;\n      this.leafCert = leafCert;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/crl/VerifiedCertPathBuilderTest.java",
    "content": "package net.snowflake.client.internal.core.crl;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.math.BigInteger;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.KeyStore;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.SecureRandom;\nimport java.security.cert.X509Certificate;\nimport java.util.Date;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\nimport net.snowflake.client.category.TestTags;\nimport org.bouncycastle.asn1.x500.X500Name;\nimport org.bouncycastle.asn1.x509.BasicConstraints;\nimport org.bouncycastle.asn1.x509.Extension;\nimport org.bouncycastle.asn1.x509.KeyUsage;\nimport org.bouncycastle.cert.X509CertificateHolder;\nimport org.bouncycastle.cert.X509v3CertificateBuilder;\nimport org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;\nimport org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;\nimport org.bouncycastle.operator.ContentSigner;\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\nclass VerifiedCertPathBuilderTest {\n\n  // Cross-signed scenario path identifiers\n  private static final String PATH_INTERMEDIATE_TO_ROOT =\n      \"intermediateCrossSignedByRoot->rootCASelfSigned\";\n  private static final String PATH_INTERMEDIATE_TO_OLD_ROOT =\n      \"intermediateCrossSignedByOldRoot->oldRootCA\";\n\n  // Certificate generation constants\n  private static final String RSA_ALGORITHM = \"RSA\";\n  private static final String SIGNATURE_ALGORITHM = \"SHA256WithRSA\";\n  private static final String BOUNCY_CASTLE_PROVIDER = \"BC\";\n\n  private TestCertificateGenerator certGen;\n\n  @BeforeEach\n  void setUp() {\n    certGen = new TestCertificateGenerator();\n  }\n\n  @Test\n  void shouldValidateSimpleCertificateChain() throws Exception {\n    CertificateChain chain = certGen.createSimpleChain();\n    KeyStore trustStore = createInMemoryTrustStore(chain.rootCert);\n\n    VerifiedCertPathBuilder builder = new VerifiedCertPathBuilder(createTrustManager(trustStore));\n    X509Certificate[] certChain = {chain.leafCert, chain.intermediateCert, chain.rootCert};\n    List<X509Certificate[]> paths = builder.buildAllVerifiedPaths(certChain, \"RSA\");\n\n    assertFalse(paths.isEmpty(), \"Should find at least one valid path\");\n    assertEquals(1, paths.size(), \"Should find exactly one path for simple chain\");\n\n    X509Certificate[] path = paths.get(0);\n    assertEquals(3, path.length, \"Expected path length of 3 (including trust anchor)\");\n\n    // Verify path structure: [leaf, intermediate, trust anchor]\n    assertEquals(chain.leafCert.getSubjectX500Principal(), path[0].getSubjectX500Principal());\n    assertEquals(\n        chain.intermediateCert.getSubjectX500Principal(), path[1].getSubjectX500Principal());\n    assertEquals(chain.rootCert.getSubjectX500Principal(), path[2].getSubjectX500Principal());\n\n    validateCertificateChainOrder(path);\n    assertTrustAnchorInPath(path, chain.rootCert);\n  }\n\n  @Test\n  void shouldReturnEmptyForEmptyChain() throws Exception {\n    // Create a minimal trust store for validation testing\n    CertificateChain chain = certGen.createSimpleChain();\n    KeyStore trustStore = createInMemoryTrustStore(chain.rootCert);\n    VerifiedCertPathBuilder builder = new VerifiedCertPathBuilder(createTrustManager(trustStore));\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> builder.buildAllVerifiedPaths(new X509Certificate[] {}, \"RSA\"),\n        \"Empty certificate chain should throw IllegalArgumentException\");\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () -> builder.buildAllVerifiedPaths(null, \"RSA\"),\n        \"Null certificate chain should throw IllegalArgumentException\");\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            builder.buildAllVerifiedPaths(\n                new X509Certificate[] {certGen.createSimpleChain().leafCert}, null),\n        \"Null authType should throw IllegalArgumentException\");\n\n    assertThrows(\n        IllegalArgumentException.class,\n        () ->\n            builder.buildAllVerifiedPaths(\n                new X509Certificate[] {certGen.createSimpleChain().leafCert}, \"\"),\n        \"Empty authType should throw IllegalArgumentException\");\n  }\n\n  @Test\n  void shouldFindAllValidPathsInCrossSignedScenario() throws Exception {\n    CrossSignedCertificates certs = certGen.createCrossSignedCertificates();\n    KeyStore trustStore = createInMemoryTrustStore(certs.oldRootCA, certs.rootCASelfSigned);\n    VerifiedCertPathBuilder builder = new VerifiedCertPathBuilder(createTrustManager(trustStore));\n    X509Certificate[] chain = {\n      certs.leafCert,\n      certs.intermediateCrossSignedByRoot,\n      certs.intermediateCrossSignedByOldRoot,\n      certs.oldRootCA,\n      certs.rootCASelfSigned\n    };\n\n    List<X509Certificate[]> paths = builder.buildAllVerifiedPaths(chain, \"RSA\");\n\n    assertFalse(paths.isEmpty(), \"Should find valid paths\");\n    assertEquals(2, paths.size(), \"Should find exactly 2 paths in cross-signed scenario\");\n\n    Set<String> foundPairings = validateCrossSignedPaths(paths, certs);\n    assertEquals(\n        2, foundPairings.size(), \"Should find 2 distinct intermediate-trust anchor pairings\");\n    assertTrue(foundPairings.contains(PATH_INTERMEDIATE_TO_ROOT));\n    assertTrue(foundPairings.contains(PATH_INTERMEDIATE_TO_OLD_ROOT));\n  }\n\n  @Test\n  void shouldValidateComplexCrossSignedChain() throws Exception {\n    ComplexCrossSignedCertificates certs = certGen.createComplexCrossSignedChain();\n    KeyStore trustStore = createInMemoryTrustStore(certs.selfSignedRoot);\n\n    VerifiedCertPathBuilder builder = new VerifiedCertPathBuilder(createTrustManager(trustStore));\n    X509Certificate[] chain = {\n      certs.leafCert, certs.intermediate, certs.crossSignedRoot, certs.intermediateRoot\n    };\n\n    List<X509Certificate[]> paths = builder.buildAllVerifiedPaths(chain, \"RSA\");\n\n    assertFalse(paths.isEmpty(), \"Should find valid path for complex cross-signed scenario\");\n    assertEquals(1, paths.size(), \"Should find exactly one path\");\n\n    X509Certificate[] path = paths.get(0);\n    assertEquals(3, path.length, \"Expected path length of 3 (including trust anchor)\");\n\n    // Verify path structure\n    assertEquals(certs.leafCert.getSubjectX500Principal(), path[0].getSubjectX500Principal());\n    assertEquals(certs.intermediate.getSubjectX500Principal(), path[1].getSubjectX500Principal());\n    assertEquals(certs.selfSignedRoot.getSubjectX500Principal(), path[2].getSubjectX500Principal());\n\n    validateCertificateChainOrder(path);\n    assertTrustAnchorInPath(path, certs.selfSignedRoot);\n  }\n\n  @Test\n  void shouldValidateChainToUltimateRoot() throws Exception {\n    ComplexCrossSignedCertificates certs = certGen.createComplexCrossSignedChain();\n    KeyStore trustStore = createInMemoryTrustStore(certs.ultimateRoot);\n\n    VerifiedCertPathBuilder builder = new VerifiedCertPathBuilder(createTrustManager(trustStore));\n    X509Certificate[] chain = {\n      certs.leafCert, certs.intermediate, certs.crossSignedRoot, certs.intermediateRoot\n    };\n\n    List<X509Certificate[]> paths = builder.buildAllVerifiedPaths(chain, \"RSA\");\n\n    assertFalse(paths.isEmpty(), \"Should find valid path to ultimate root\");\n    assertEquals(1, paths.size(), \"Should find exactly one path\");\n\n    X509Certificate[] path = paths.get(0);\n    assertEquals(5, path.length, \"Expected path length of 5 (including trust anchor)\");\n\n    // Verify complete path structure\n    assertEquals(certs.leafCert.getSubjectX500Principal(), path[0].getSubjectX500Principal());\n    assertEquals(certs.intermediate.getSubjectX500Principal(), path[1].getSubjectX500Principal());\n    assertEquals(\n        certs.crossSignedRoot.getSubjectX500Principal(), path[2].getSubjectX500Principal());\n    assertEquals(\n        certs.intermediateRoot.getSubjectX500Principal(), path[3].getSubjectX500Principal());\n    assertEquals(certs.ultimateRoot.getSubjectX500Principal(), path[4].getSubjectX500Principal());\n\n    validateCertificateChainOrder(path);\n    assertTrustAnchorInPath(path, certs.ultimateRoot);\n  }\n\n  private Set<String> validateCrossSignedPaths(\n      List<X509Certificate[]> paths, CrossSignedCertificates certs) {\n    Set<String> foundPairings = new HashSet<>();\n\n    for (int i = 0; i < paths.size(); i++) {\n      X509Certificate[] path = paths.get(i);\n      assertEquals(3, path.length, \"Path \" + i + \" should have length 3\");\n      assertEquals(certs.leafCert.getSubjectX500Principal(), path[0].getSubjectX500Principal());\n\n      X509Certificate intermediate = path[1];\n      X509Certificate trustAnchor = path[2];\n\n      if (intermediate\n              .getIssuerX500Principal()\n              .equals(certs.rootCASelfSigned.getSubjectX500Principal())\n          && trustAnchor\n              .getSubjectX500Principal()\n              .equals(certs.rootCASelfSigned.getSubjectX500Principal())) {\n        foundPairings.add(PATH_INTERMEDIATE_TO_ROOT);\n      } else if (intermediate\n              .getIssuerX500Principal()\n              .equals(certs.oldRootCA.getSubjectX500Principal())\n          && trustAnchor\n              .getSubjectX500Principal()\n              .equals(certs.oldRootCA.getSubjectX500Principal())) {\n        foundPairings.add(PATH_INTERMEDIATE_TO_OLD_ROOT);\n      } else {\n        fail(\"Unexpected intermediate-trust anchor pairing in path \" + i);\n      }\n\n      validateCertificateChainOrder(path);\n    }\n\n    return foundPairings;\n  }\n\n  private void validateCertificateChainOrder(X509Certificate[] path) {\n    // Validate issuer-subject relationships (excluding the trust anchor which may be self-signed)\n    for (int i = 0; i < path.length - 2; i++) {\n      assertEquals(\n          path[i].getIssuerX500Principal(),\n          path[i + 1].getSubjectX500Principal(),\n          \"Certificate \" + i + \" issuer should match certificate \" + (i + 1) + \" subject\");\n    }\n\n    // Verify second-to-last certificate is issued by trust anchor\n    if (path.length >= 2) {\n      assertEquals(\n          path[path.length - 2].getIssuerX500Principal(),\n          path[path.length - 1].getSubjectX500Principal(),\n          \"Second-to-last certificate should be issued by the trust anchor\");\n    }\n  }\n\n  private void assertTrustAnchorInPath(X509Certificate[] path, X509Certificate trustAnchor) {\n    boolean found = false;\n    for (X509Certificate cert : path) {\n      if (trustAnchor.getSubjectX500Principal().equals(cert.getSubjectX500Principal())) {\n        found = true;\n        break;\n      }\n    }\n    assertTrue(\n        found, \"Trust anchor should be included in the certification path for CRL validation\");\n  }\n\n  /** Creates an in-memory KeyStore with the provided certificates as trusted CAs. */\n  private KeyStore createInMemoryTrustStore(X509Certificate... certificates) throws Exception {\n    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());\n    trustStore.load(null, null);\n\n    for (int i = 0; i < certificates.length; i++) {\n      trustStore.setCertificateEntry(\"testroot\" + i, certificates[i]);\n    }\n\n    return trustStore;\n  }\n\n  /** Creates an X509TrustManager using the provided KeyStore. */\n  private X509TrustManager createTrustManager(KeyStore trustStore) throws Exception {\n    TrustManagerFactory tmf =\n        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n    tmf.init(trustStore);\n\n    for (javax.net.ssl.TrustManager tm : tmf.getTrustManagers()) {\n      if (tm instanceof X509TrustManager) {\n        return (X509TrustManager) tm;\n      }\n    }\n\n    throw new RuntimeException(\"No X509TrustManager found\");\n  }\n\n  /** Certificate generator for test scenarios */\n  private static class TestCertificateGenerator {\n    private final SecureRandom random = new SecureRandom();\n\n    CertificateChain createSimpleChain() throws Exception {\n      KeyPair rootKeyPair = generateKeyPair();\n      KeyPair intermediateKeyPair = generateKeyPair();\n      KeyPair leafKeyPair = generateKeyPair();\n\n      X509Certificate rootCert =\n          createSelfSignedCertificate(\n              rootKeyPair, \"CN=Test Root CA \" + random.nextInt(10000), true);\n\n      X509Certificate intermediateCert =\n          createSignedCertificate(\n              intermediateKeyPair.getPublic(),\n              \"CN=Test Intermediate CA \" + random.nextInt(10000),\n              rootKeyPair.getPrivate(),\n              rootCert,\n              true);\n\n      X509Certificate leafCert =\n          createSignedCertificate(\n              leafKeyPair.getPublic(),\n              \"CN=Test Leaf \" + random.nextInt(10000),\n              intermediateKeyPair.getPrivate(),\n              intermediateCert,\n              false);\n\n      return new CertificateChain(rootCert, intermediateCert, leafCert);\n    }\n\n    CrossSignedCertificates createCrossSignedCertificates() throws Exception {\n      KeyPair rootCAKeyPair = generateKeyPair();\n      KeyPair oldRootCAKeyPair = generateKeyPair();\n      KeyPair intermediateKeyPair = generateKeyPair();\n      KeyPair leafKeyPair = generateKeyPair();\n\n      String rootSubject = \"CN=Test Root CA \" + random.nextInt(10000);\n      String oldRootSubject = \"CN=Test Old Root CA \" + random.nextInt(10000);\n      String intermediateSubject = \"CN=Test Cross-Signed Intermediate \" + random.nextInt(10000);\n\n      X509Certificate rootCASelfSigned =\n          createSelfSignedCertificate(rootCAKeyPair, rootSubject, true);\n      X509Certificate oldRootCA =\n          createSelfSignedCertificate(oldRootCAKeyPair, oldRootSubject, true);\n\n      // Create same intermediate signed by different roots\n      X509Certificate intermediateCrossSignedByRoot =\n          createSignedCertificate(\n              intermediateKeyPair.getPublic(),\n              intermediateSubject,\n              rootCAKeyPair.getPrivate(),\n              rootCASelfSigned,\n              true);\n\n      X509Certificate intermediateCrossSignedByOldRoot =\n          createSignedCertificate(\n              intermediateKeyPair.getPublic(),\n              intermediateSubject,\n              oldRootCAKeyPair.getPrivate(),\n              oldRootCA,\n              true);\n\n      X509Certificate leafCert =\n          createSignedCertificate(\n              leafKeyPair.getPublic(),\n              \"CN=Test Leaf \" + random.nextInt(10000),\n              intermediateKeyPair.getPrivate(),\n              intermediateCrossSignedByRoot,\n              false);\n\n      return new CrossSignedCertificates(\n          rootCASelfSigned,\n          oldRootCA,\n          intermediateCrossSignedByRoot,\n          intermediateCrossSignedByOldRoot,\n          leafCert);\n    }\n\n    ComplexCrossSignedCertificates createComplexCrossSignedChain() throws Exception {\n      KeyPair ultimateRootKeyPair = generateKeyPair();\n      KeyPair intermediateRootKeyPair = generateKeyPair();\n      KeyPair crossSignedRootKeyPair = generateKeyPair();\n      KeyPair intermediateKeyPair = generateKeyPair();\n      KeyPair leafKeyPair = generateKeyPair();\n\n      X509Certificate ultimateRoot =\n          createSelfSignedCertificate(\n              ultimateRootKeyPair, \"CN=Ultimate Root CA \" + random.nextInt(10000), true);\n\n      X509Certificate intermediateRoot =\n          createSignedCertificate(\n              intermediateRootKeyPair.getPublic(),\n              \"CN=Intermediate Root CA \" + random.nextInt(10000),\n              ultimateRootKeyPair.getPrivate(),\n              ultimateRoot,\n              true);\n\n      String crossSignedSubject = \"CN=Cross-Signed Root CA \" + random.nextInt(10000);\n      X509Certificate crossSignedRoot =\n          createSignedCertificate(\n              crossSignedRootKeyPair.getPublic(),\n              crossSignedSubject,\n              intermediateRootKeyPair.getPrivate(),\n              intermediateRoot,\n              true);\n\n      X509Certificate selfSignedRoot =\n          createSelfSignedCertificate(crossSignedRootKeyPair, crossSignedSubject, true);\n\n      X509Certificate intermediate =\n          createSignedCertificate(\n              intermediateKeyPair.getPublic(),\n              \"CN=Intermediate CA \" + random.nextInt(10000),\n              crossSignedRootKeyPair.getPrivate(),\n              crossSignedRoot,\n              true);\n\n      X509Certificate leafCert =\n          createSignedCertificate(\n              leafKeyPair.getPublic(),\n              \"CN=Test Leaf \" + random.nextInt(10000),\n              intermediateKeyPair.getPrivate(),\n              intermediate,\n              false);\n\n      return new ComplexCrossSignedCertificates(\n          ultimateRoot, intermediateRoot, crossSignedRoot, selfSignedRoot, intermediate, leafCert);\n    }\n\n    private KeyPair generateKeyPair() throws Exception {\n      KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);\n      keyGen.initialize(2048);\n      return keyGen.generateKeyPair();\n    }\n\n    private X509Certificate createSelfSignedCertificate(\n        KeyPair keyPair, String subjectDN, boolean isCA) throws Exception {\n      return createCertificate(\n          keyPair.getPublic(), subjectDN, keyPair.getPrivate(), subjectDN, isCA);\n    }\n\n    private X509Certificate createSignedCertificate(\n        PublicKey publicKey,\n        String subjectDN,\n        PrivateKey issuerPrivateKey,\n        X509Certificate issuerCert,\n        boolean isCA)\n        throws Exception {\n      return createCertificate(\n          publicKey,\n          subjectDN,\n          issuerPrivateKey,\n          issuerCert.getSubjectX500Principal().getName(),\n          isCA);\n    }\n\n    private X509Certificate createCertificate(\n        PublicKey publicKey,\n        String subjectDN,\n        PrivateKey signerPrivateKey,\n        String issuerDN,\n        boolean isCA)\n        throws Exception {\n      long now = System.currentTimeMillis();\n      Date notBefore = new Date(now);\n      Date notAfter = new Date(now + 365L * 24 * 60 * 60 * 1000);\n\n      X509v3CertificateBuilder certBuilder =\n          new JcaX509v3CertificateBuilder(\n              new X500Name(issuerDN),\n              BigInteger.valueOf(random.nextLong()),\n              notBefore,\n              notAfter,\n              new X500Name(subjectDN),\n              publicKey);\n\n      certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(isCA));\n\n      if (isCA) {\n        certBuilder.addExtension(\n            Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));\n      } else {\n        certBuilder.addExtension(\n            Extension.keyUsage,\n            true,\n            new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));\n      }\n\n      ContentSigner contentSigner =\n          new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(signerPrivateKey);\n      X509CertificateHolder certHolder = certBuilder.build(contentSigner);\n\n      return new JcaX509CertificateConverter()\n          .setProvider(BOUNCY_CASTLE_PROVIDER)\n          .getCertificate(certHolder);\n    }\n  }\n\n  /** Simple certificate chain holder */\n  private static class CertificateChain {\n    final X509Certificate rootCert;\n    final X509Certificate intermediateCert;\n    final X509Certificate leafCert;\n\n    CertificateChain(\n        X509Certificate rootCert, X509Certificate intermediateCert, X509Certificate leafCert) {\n      this.rootCert = rootCert;\n      this.intermediateCert = intermediateCert;\n      this.leafCert = leafCert;\n    }\n  }\n\n  /** Cross-signed certificate chain holder */\n  private static class CrossSignedCertificates {\n    final X509Certificate rootCASelfSigned;\n    final X509Certificate oldRootCA;\n    final X509Certificate intermediateCrossSignedByRoot;\n    final X509Certificate intermediateCrossSignedByOldRoot;\n    final X509Certificate leafCert;\n\n    CrossSignedCertificates(\n        X509Certificate rootCASelfSigned,\n        X509Certificate oldRootCA,\n        X509Certificate intermediateCrossSignedByRoot,\n        X509Certificate intermediateCrossSignedByOldRoot,\n        X509Certificate leafCert) {\n      this.rootCASelfSigned = rootCASelfSigned;\n      this.oldRootCA = oldRootCA;\n      this.intermediateCrossSignedByRoot = intermediateCrossSignedByRoot;\n      this.intermediateCrossSignedByOldRoot = intermediateCrossSignedByOldRoot;\n      this.leafCert = leafCert;\n    }\n  }\n\n  /** Complex cross-signed certificate chain holder */\n  private static class ComplexCrossSignedCertificates {\n    final X509Certificate ultimateRoot;\n    final X509Certificate intermediateRoot;\n    final X509Certificate crossSignedRoot;\n    final X509Certificate selfSignedRoot;\n    final X509Certificate intermediate;\n    final X509Certificate leafCert;\n\n    ComplexCrossSignedCertificates(\n        X509Certificate ultimateRoot,\n        X509Certificate intermediateRoot,\n        X509Certificate crossSignedRoot,\n        X509Certificate selfSignedRoot,\n        X509Certificate intermediate,\n        X509Certificate leafCert) {\n      this.ultimateRoot = ultimateRoot;\n      this.intermediateRoot = intermediateRoot;\n      this.crossSignedRoot = crossSignedRoot;\n      this.selfSignedRoot = selfSignedRoot;\n      this.intermediate = intermediate;\n      this.leafCert = leafCert;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/json/BooleanConverterTest.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.Types;\nimport net.snowflake.client.internal.core.SFException;\nimport org.junit.jupiter.api.Test;\n\npublic class BooleanConverterTest {\n  private final BooleanConverter booleanConverter = new BooleanConverter();\n\n  @Test\n  public void testConvertBoolean() throws SFException {\n    assertThat(booleanConverter.getBoolean(true, Types.BOOLEAN), equalTo(true));\n    assertThat(booleanConverter.getBoolean(false, Types.BOOLEAN), equalTo(false));\n  }\n\n  @Test\n  public void testConvertNumeric() throws SFException {\n    assertThat(booleanConverter.getBoolean(1, Types.INTEGER), equalTo(true));\n    assertThat(booleanConverter.getBoolean(1, Types.SMALLINT), equalTo(true));\n    assertThat(booleanConverter.getBoolean(1, Types.TINYINT), equalTo(true));\n    assertThat(booleanConverter.getBoolean(1, Types.BIGINT), equalTo(true));\n    assertThat(booleanConverter.getBoolean(1, Types.BIT), equalTo(true));\n    assertThat(booleanConverter.getBoolean(1, Types.DECIMAL), equalTo(true));\n    assertThat(booleanConverter.getBoolean(0, Types.INTEGER), equalTo(false));\n    assertThat(booleanConverter.getBoolean(0, Types.SMALLINT), equalTo(false));\n    assertThat(booleanConverter.getBoolean(0, Types.TINYINT), equalTo(false));\n    assertThat(booleanConverter.getBoolean(0, Types.BIGINT), equalTo(false));\n    assertThat(booleanConverter.getBoolean(0, Types.BIT), equalTo(false));\n    assertThat(booleanConverter.getBoolean(0, Types.DECIMAL), equalTo(false));\n  }\n\n  @Test\n  public void testConvertString() throws SFException {\n    assertThat(booleanConverter.getBoolean(\"1\", Types.VARCHAR), equalTo(true));\n    assertThat(booleanConverter.getBoolean(\"1\", Types.CHAR), equalTo(true));\n    assertThat(booleanConverter.getBoolean(\"true\", Types.VARCHAR), equalTo(true));\n    assertThat(booleanConverter.getBoolean(\"TRUE\", Types.CHAR), equalTo(true));\n    assertThat(booleanConverter.getBoolean(\"0\", Types.VARCHAR), equalTo(false));\n    assertThat(booleanConverter.getBoolean(\"0\", Types.CHAR), equalTo(false));\n    assertThat(booleanConverter.getBoolean(\"false\", Types.VARCHAR), equalTo(false));\n    assertThat(booleanConverter.getBoolean(\"FALSE\", Types.CHAR), equalTo(false));\n  }\n\n  @Test\n  public void testConvertOtherType() {\n    assertThrows(SFException.class, () -> booleanConverter.getBoolean(\"1\", Types.BINARY));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/json/BytesConverterTest.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\n\nimport java.math.BigInteger;\nimport java.nio.ByteBuffer;\nimport java.sql.Types;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport org.apache.arrow.vector.Float8Vector;\nimport org.junit.jupiter.api.Test;\n\npublic class BytesConverterTest {\n  private final Converters converters =\n      new Converters(\n          null, new SFSession(), 0, false, false, false, false, null, null, null, null, null, null);\n  private final BytesConverter bytesConverter = new BytesConverter(converters);\n\n  @Test\n  public void testConvertFloatingPointToBytes() throws SFException {\n    byte[] expected = ByteBuffer.allocate(Float8Vector.TYPE_WIDTH).putDouble(0, 12.5).array();\n    assertArrayEquals(expected, bytesConverter.getBytes(12.5f, Types.FLOAT, Types.FLOAT, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12.5f, Types.DOUBLE, Types.DOUBLE, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12.5, Types.FLOAT, Types.DOUBLE, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12.5, Types.DOUBLE, Types.DOUBLE, 0));\n  }\n\n  @Test\n  public void testConvertIntegerNumberToBytes() throws SFException {\n    byte[] expected = new BigInteger(\"12\").toByteArray();\n    assertArrayEquals(expected, bytesConverter.getBytes(12, Types.NUMERIC, Types.NUMERIC, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12, Types.INTEGER, Types.INTEGER, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12, Types.SMALLINT, Types.SMALLINT, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12, Types.TINYINT, Types.TINYINT, 0));\n    assertArrayEquals(expected, bytesConverter.getBytes(12, Types.BIGINT, Types.BIGINT, 0));\n  }\n\n  @Test\n  public void testString() throws SFException {\n    assertArrayEquals(\n        \"abc\".getBytes(), bytesConverter.getBytes(\"abc\", Types.VARCHAR, Types.VARCHAR, 0));\n  }\n\n  @Test\n  public void testConvertBooleanToBytes() throws SFException {\n    assertArrayEquals(\n        new byte[] {1}, bytesConverter.getBytes(true, Types.BOOLEAN, Types.BOOLEAN, 0));\n    assertArrayEquals(\n        new byte[] {0}, bytesConverter.getBytes(false, Types.BOOLEAN, Types.BOOLEAN, 0));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/json/DateTimeConverterTest.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.sql.Date;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.ZoneId;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport org.junit.jupiter.api.Test;\n\npublic class DateTimeConverterTest {\n  private final TimeZone honoluluTimeZone =\n      TimeZone.getTimeZone(ZoneId.of(\"Pacific/Honolulu\")); // session time zone\n  private final TimeZone nuukTimeZone = TimeZone.getTimeZone(ZoneId.of(\"America/Nuuk\"));\n  private final DateTimeConverter dateTimeConverter =\n      new DateTimeConverter(honoluluTimeZone, new SFSession(), 1, true, false, false, false);\n  private final DateTimeConverter dateTimeConverterWithUseSessionTimeZone =\n      new DateTimeConverter(honoluluTimeZone, new SFSession(), 1, true, false, true, false);\n  private final DateTimeConverter dateTimeConverterWithTreatNTZAsUTC =\n      new DateTimeConverter(honoluluTimeZone, new SFSession(), 1, true, true, false, false);\n\n  @Test\n  public void testGetVariousTypesWhenNullObjectGiven() throws SFException {\n    assertNull(dateTimeConverter.getTimestamp(null, Types.TIMESTAMP, Types.TIMESTAMP, null, 0));\n    assertNull(dateTimeConverter.getTime(null, Types.TIMESTAMP, Types.TIMESTAMP, null, 0));\n    assertNull(dateTimeConverter.getDate(null, Types.TIMESTAMP, Types.TIMESTAMP, null, 0));\n  }\n\n  @Test\n  public void testGetTimestampWithDefaultTimeZone() throws SFException {\n    assertEquals(\n        Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3)),\n        dateTimeConverter.getTimestamp(\"1691568123\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0));\n    assertEquals(\n        Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789000)),\n        dateTimeConverter.getTimestamp(\n            \"1691568123.456789\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0));\n    assertEquals(\n        Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789123)),\n        dateTimeConverter.getTimestamp(\n            \"1691568123.456789123\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0));\n  }\n\n  @Test\n  public void testGetTimestampWithSpecificTimeZone() throws SFException {\n    assertEquals(\n        Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3)).toString(),\n        dateTimeConverterWithTreatNTZAsUTC\n            .getTimestamp(\"1691568123\", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0)\n            .toString());\n    assertEquals(\n        Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789000)).toString(),\n        dateTimeConverterWithTreatNTZAsUTC\n            .getTimestamp(\"1691568123.456789\", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0)\n            .toString());\n    assertEquals(\n        Timestamp.valueOf(LocalDateTime.of(2023, 8, 9, 8, 2, 3, 456789123)).toString(),\n        dateTimeConverterWithTreatNTZAsUTC\n            .getTimestamp(\"1691568123.456789123\", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0)\n            .toString());\n  }\n\n  // TODO replace equality when SNOW-991418 is fixed\n  @Test\n  public void testGetTimeWithDefaultTimeZone() throws SFException {\n    assertEquals(\n        Time.valueOf(LocalTime.of(8, 2, 3)).toString(),\n        dateTimeConverter\n            .getTime(\"1691568123\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)\n            .toString());\n    assertEquals(\n        Time.valueOf(LocalTime.of(8, 2, 3)).toString(),\n        dateTimeConverter\n            .getTime(\"1691568123.456789\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)\n            .toString());\n    assertEquals(\n        Time.valueOf(LocalTime.of(8, 2, 3)).toString(),\n        dateTimeConverter\n            .getTime(\"1691568123.456789123\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)\n            .toString());\n  }\n\n  @Test\n  public void testGetTimeWithSessionTimeZone() throws SFException {\n    Time expected = Time.valueOf(LocalTime.of(22, 2, 3));\n    Time actual =\n        dateTimeConverterWithUseSessionTimeZone.getTime(\n            \"1691568123\", Types.TIMESTAMP, SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ, null, 0);\n    assertEquals(expected.toString(), actual.toString());\n  }\n\n  @Test\n  public void testGetDateWithDefaultTimeZone() throws SFException {\n    assertEquals(\n        Date.valueOf(LocalDate.of(2023, 8, 9)).toString(),\n        dateTimeConverter\n            .getDate(\"1691568123\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)\n            .toString());\n    assertEquals(\n        Date.valueOf(LocalDate.of(2023, 8, 9)).toString(),\n        dateTimeConverter\n            .getDate(\"1691568123.456789\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)\n            .toString());\n    assertEquals(\n        Date.valueOf(LocalDate.of(2023, 8, 9)).toString(),\n        dateTimeConverter\n            .getDate(\"1691568123.456789123\", Types.TIMESTAMP, Types.TIMESTAMP, null, 0)\n            .toString());\n  }\n\n  @Test\n  public void testGetDateWithSpecificTimeZone() throws SFException {\n    assertEquals(\n        Date.valueOf(LocalDate.of(2023, 8, 9)).toString(),\n        dateTimeConverter\n            .getDate(\"1691568123\", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0)\n            .toString());\n    assertEquals(\n        Date.valueOf(LocalDate.of(2023, 8, 9)).toString(),\n        dateTimeConverter\n            .getDate(\"1691568123.456789\", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0)\n            .toString());\n    assertEquals(\n        Date.valueOf(LocalDate.of(2023, 8, 9)).toString(),\n        dateTimeConverter\n            .getDate(\"1691568123.456789123\", Types.TIMESTAMP, Types.TIMESTAMP, nuukTimeZone, 0)\n            .toString());\n  }\n\n  @Test\n  public void testGetDateWithSessionTimeZone() throws SFException {\n    Date expected = Date.valueOf(LocalDate.of(2023, 8, 8));\n    Date actual =\n        dateTimeConverterWithUseSessionTimeZone.getDate(\n            \"1691568123\", Types.TIMESTAMP, SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ, null, 0);\n    assertEquals(expected.toString(), actual.toString());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/json/NumberConverterTest.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\n\nimport java.math.BigDecimal;\nimport java.sql.Types;\nimport net.snowflake.client.internal.core.SFException;\nimport org.junit.jupiter.api.Test;\n\npublic class NumberConverterTest {\n  private final NumberConverter numberConverter = new NumberConverter();\n\n  @Test\n  public void testByteFromNumber() {\n    assertThat(numberConverter.getByte(1), equalTo((byte) 1));\n    assertThat(numberConverter.getByte(258), equalTo((byte) 2));\n    assertThat(numberConverter.getByte(-1), equalTo((byte) -1));\n  }\n\n  @Test\n  public void testByteFromString() {\n    assertThat(numberConverter.getByte(\"1\"), equalTo((byte) 1));\n    assertThat(numberConverter.getByte(\"-1\"), equalTo((byte) -1));\n  }\n\n  @Test\n  public void testShortFromNumber() throws SFException {\n    assertThat(numberConverter.getShort(1, Types.INTEGER), equalTo((short) 1));\n    assertThat(numberConverter.getShort(2.5, Types.DOUBLE), equalTo((short) 2));\n    assertThat(numberConverter.getShort(3.4f, Types.FLOAT), equalTo((short) 3));\n  }\n\n  @Test\n  public void testShortFromString() throws SFException {\n    assertThat(numberConverter.getShort(\"1\", Types.INTEGER), equalTo((short) 1));\n    assertThat(numberConverter.getShort(\"2.5\", Types.DOUBLE), equalTo((short) 2));\n    assertThat(numberConverter.getShort(\"3.4\", Types.FLOAT), equalTo((short) 3));\n    assertThat(numberConverter.getShort(\"4.5.6\", Types.FLOAT), equalTo((short) 4));\n  }\n\n  @Test\n  public void testIntFromNumber() throws SFException {\n    assertThat(numberConverter.getInt(1, Types.INTEGER), equalTo(1));\n    assertThat(numberConverter.getInt(2.5, Types.DOUBLE), equalTo(2));\n    assertThat(numberConverter.getInt(3.4f, Types.FLOAT), equalTo(3));\n  }\n\n  @Test\n  public void testIntFromString() throws SFException {\n    assertThat(numberConverter.getInt(\"1\", Types.INTEGER), equalTo(1));\n    assertThat(numberConverter.getInt(\"2.5\", Types.DOUBLE), equalTo(2));\n    assertThat(numberConverter.getInt(\"3.4\", Types.FLOAT), equalTo(3));\n    assertThat(numberConverter.getInt(\"4.5.6\", Types.FLOAT), equalTo(4));\n  }\n\n  @Test\n  public void testLongFromNumber() throws SFException {\n    assertThat(numberConverter.getLong(1, Types.INTEGER), equalTo(1L));\n    assertThat(numberConverter.getLong(2.5, Types.DOUBLE), equalTo(2L));\n    assertThat(numberConverter.getLong(3.4f, Types.FLOAT), equalTo(3L));\n  }\n\n  @Test\n  public void testLongFromString() throws SFException {\n    assertThat(numberConverter.getLong(\"1\", Types.INTEGER), equalTo(1L));\n    assertThat(numberConverter.getLong(\"2.5\", Types.DOUBLE), equalTo(2L));\n    assertThat(numberConverter.getLong(\"3.4\", Types.FLOAT), equalTo(3L));\n    assertThat(numberConverter.getLong(\"4.5.6\", Types.FLOAT), equalTo(4L));\n  }\n\n  @Test\n  public void testBigDecimalFromNumber() throws SFException {\n    assertThat(numberConverter.getBigDecimal(1, Types.INTEGER), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(1, Types.BIGINT), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(1.5, Types.FLOAT), equalTo(new BigDecimal(\"1.5\")));\n  }\n\n  @Test\n  public void testBigDecimalFromString() throws SFException {\n    assertThat(numberConverter.getBigDecimal(\"1\", Types.INTEGER), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(\"1\", Types.BIGINT), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(\"1.5\", Types.FLOAT), equalTo(new BigDecimal(\"1.5\")));\n  }\n\n  @Test\n  public void testBigDecimalWithScaleFromNumber() throws SFException {\n    assertThat(numberConverter.getBigDecimal(1, Types.INTEGER), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(1, Types.BIGINT), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(1.5, Types.FLOAT), equalTo(new BigDecimal(\"1.5\")));\n    assertThat(\n        numberConverter.getBigDecimal(1.50001, Types.FLOAT, 1), equalTo(new BigDecimal(\"1.5\")));\n    assertThat(\n        numberConverter.getBigDecimal(1.50001, Types.FLOAT, 5), equalTo(new BigDecimal(\"1.50001\")));\n  }\n\n  @Test\n  public void testBigDecimalWithScaleFromString() throws SFException {\n    assertThat(numberConverter.getBigDecimal(\"1\", Types.INTEGER), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(\"1\", Types.BIGINT), equalTo(BigDecimal.ONE));\n    assertThat(numberConverter.getBigDecimal(\"1.5\", Types.FLOAT), equalTo(new BigDecimal(\"1.5\")));\n    assertThat(\n        numberConverter.getBigDecimal(\"1.50001\", Types.FLOAT, 1), equalTo(new BigDecimal(\"1.5\")));\n    assertThat(\n        numberConverter.getBigDecimal(\"1.50001\", Types.FLOAT, 5),\n        equalTo(new BigDecimal(\"1.50001\")));\n  }\n\n  @Test\n  public void testFloatFromNumber() throws SFException {\n    assertThat(numberConverter.getFloat(1, Types.INTEGER), equalTo(1.0f));\n    assertThat(numberConverter.getFloat(1, Types.BIGINT), equalTo(1.0f));\n    assertThat(numberConverter.getFloat(1.5, Types.FLOAT), equalTo(1.5f));\n    assertThat(numberConverter.getFloat(1.5, Types.DOUBLE), equalTo(1.5f));\n  }\n\n  @Test\n  public void testFloatFromString() throws SFException {\n    assertThat(numberConverter.getFloat(\"1\", Types.INTEGER), equalTo(1.0f));\n    assertThat(numberConverter.getFloat(\"1\", Types.BIGINT), equalTo(1.0f));\n    assertThat(numberConverter.getFloat(\"1.5\", Types.FLOAT), equalTo(1.5f));\n    assertThat(numberConverter.getFloat(\"1.5\", Types.DOUBLE), equalTo(1.5f));\n    assertThat(numberConverter.getFloat(\"inf\", Types.FLOAT), equalTo(Float.POSITIVE_INFINITY));\n    assertThat(numberConverter.getFloat(\"-inf\", Types.FLOAT), equalTo(Float.NEGATIVE_INFINITY));\n  }\n\n  @Test\n  public void testDoubleFromNumber() throws SFException {\n    assertThat(numberConverter.getDouble(1, Types.INTEGER), equalTo(1.0));\n    assertThat(numberConverter.getDouble(1, Types.BIGINT), equalTo(1.0));\n    assertThat(numberConverter.getDouble(1.5, Types.FLOAT), equalTo(1.5));\n    assertThat(numberConverter.getDouble(1.5, Types.DOUBLE), equalTo(1.5));\n  }\n\n  @Test\n  public void testDoubleFromString() throws SFException {\n    assertThat(numberConverter.getDouble(\"1\", Types.INTEGER), equalTo(1.0));\n    assertThat(numberConverter.getDouble(\"1\", Types.BIGINT), equalTo(1.0));\n    assertThat(numberConverter.getDouble(\"1.5\", Types.FLOAT), equalTo(1.5));\n    assertThat(numberConverter.getDouble(\"1.5\", Types.DOUBLE), equalTo(1.5));\n    assertThat(numberConverter.getDouble(\"inf\", Types.FLOAT), equalTo(Double.POSITIVE_INFINITY));\n    assertThat(numberConverter.getDouble(\"-inf\", Types.FLOAT), equalTo(Double.NEGATIVE_INFINITY));\n  }\n\n  @Test\n  public void testBigIntFromNumber() throws SFException {\n    assertThat(numberConverter.getBigInt(1, Types.BIGINT), equalTo(1L));\n    assertThat(\n        numberConverter.getBigInt(9_223_372_036_854_775_807L, Types.BIGINT),\n        equalTo(9_223_372_036_854_775_807L));\n  }\n\n  @Test\n  public void testBigIntFromString() throws SFException {\n    assertThat(numberConverter.getBigInt(\"1\", Types.BIGINT), equalTo(1L));\n    assertThat(\n        numberConverter.getBigInt(\"9223372036854775807\", Types.BIGINT),\n        equalTo(9_223_372_036_854_775_807L));\n    assertThat(\n        numberConverter.getBigInt(\"9223372036854775808\", Types.BIGINT),\n        equalTo(new BigDecimal(\"9223372036854775808\")));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/json/StringConverterTest.java",
    "content": "package net.snowflake.client.internal.core.json;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.mock;\n\nimport java.sql.Types;\nimport java.time.ZoneId;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class StringConverterTest {\n  private final TimeZone honoluluTimeZone =\n      TimeZone.getTimeZone(ZoneId.of(\"America/Nuuk\")); // session time zone\n  private final SnowflakeResultSetSerializableV1 resultSetSerializableV1 =\n      mock(SnowflakeResultSetSerializableV1.class);\n  private Converters converters;\n\n  private StringConverter stringConverter;\n\n  @BeforeEach\n  public void init() {\n    SnowflakeDateTimeFormat timestampNTZFormatter =\n        SnowflakeDateTimeFormat.fromSqlFormat(\"YYYY-MM-DD HH24:MI:SS.FF3\");\n    SnowflakeDateTimeFormat timestampLTZFormatter =\n        SnowflakeDateTimeFormat.fromSqlFormat(\"YYYY/DD/MM HH24:MI:SS.FF3\");\n    SnowflakeDateTimeFormat timestampTZFormatter =\n        SnowflakeDateTimeFormat.fromSqlFormat(\"YYYY MM DD HH24:MI:SS.FF3\");\n    SnowflakeDateTimeFormat dateFormatter = SnowflakeDateTimeFormat.fromSqlFormat(\"YYYY-MM-DD\");\n    SnowflakeDateTimeFormat timeFormatter = SnowflakeDateTimeFormat.fromSqlFormat(\"HH24:MI:SS.FF3\");\n    converters =\n        new Converters(\n            honoluluTimeZone,\n            new SFSession(),\n            1,\n            false,\n            false,\n            false,\n            false,\n            SFBinaryFormat.BASE64,\n            dateFormatter,\n            timeFormatter,\n            timestampNTZFormatter,\n            timestampLTZFormatter,\n            timestampTZFormatter);\n    stringConverter = converters.getStringConverter();\n  }\n\n  @Test\n  public void testConvertingString() throws SFException {\n    assertEquals(\"test\", stringConverter.getString(\"test\", Types.VARCHAR, Types.VARCHAR, 0));\n  }\n\n  @Test\n  public void testConvertingBoolean() throws SFException {\n    assertEquals(\"TRUE\", stringConverter.getString(true, Types.BOOLEAN, Types.BOOLEAN, 0));\n    assertEquals(\"TRUE\", stringConverter.getString(\"true\", Types.BOOLEAN, Types.BOOLEAN, 0));\n    assertEquals(\"FALSE\", stringConverter.getString(false, Types.BOOLEAN, Types.BOOLEAN, 0));\n    assertEquals(\"FALSE\", stringConverter.getString(\"false\", Types.BOOLEAN, Types.BOOLEAN, 0));\n  }\n\n  @Test\n  public void testConvertingNumbers() throws SFException {\n    assertEquals(\"12\", stringConverter.getString(12, Types.INTEGER, Types.INTEGER, 0));\n    assertEquals(\"12\", stringConverter.getString(12, Types.TINYINT, Types.TINYINT, 0));\n    assertEquals(\"12\", stringConverter.getString(12, Types.SMALLINT, Types.SMALLINT, 0));\n    assertEquals(\"12\", stringConverter.getString(12L, Types.BIGINT, Types.BIGINT, 0));\n    assertEquals(\"12.5\", stringConverter.getString(12.5, Types.DOUBLE, Types.DOUBLE, 0));\n    assertEquals(\"12.5\", stringConverter.getString(12.5F, Types.FLOAT, Types.FLOAT, 0));\n  }\n\n  @Test\n  public void testConvertingTimestamp() throws SFException {\n    assertEquals(\n        \"1988-03-21 22:33:15.000\",\n        stringConverter.getString(\"574986795\", Types.TIMESTAMP, Types.TIMESTAMP, 0));\n    assertEquals(\n        \"1988-03-21 19:33:15.000\",\n        stringConverter.getString(\n            \"574986795\", Types.TIMESTAMP, SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ, 0));\n    assertEquals(\n        \"1988-03-21 14:33:15.000\",\n        stringConverter.getString(\n            \"574986795 960\", Types.TIMESTAMP, SnowflakeType.EXTRA_TYPES_TIMESTAMP_TZ, 0));\n  }\n\n  @Test\n  public void testConvertingDate() throws SFException {\n    assertEquals(\"2023-12-18\", stringConverter.getString(\"19709\", Types.DATE, Types.DATE, 0));\n  }\n\n  @Test\n  public void testConvertingTime() throws SFException {\n    assertEquals(\n        \"00:13:18.000\", stringConverter.getString(\"798.838000000\", Types.TIME, Types.TIME, 0));\n  }\n\n  @Test\n  public void testConvertingBinary() throws SFException {\n    assertEquals(\"AQID\", stringConverter.getString(\"010203\", Types.BINARY, Types.BINARY, 0));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/minicore/MinicoreLoaderTest.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mockStatic;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport net.snowflake.client.internal.core.Constants;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\n\npublic class MinicoreLoaderTest {\n\n  @Test\n  public void testLoadLibraryIsCached() {\n    MinicoreLoader loader = new MinicoreLoader();\n    MinicoreLoadResult result1 = loader.loadLibrary();\n    MinicoreLoadResult result2 = loader.loadLibrary();\n\n    assertSame(result1, result2, \"Subsequent calls should return the same cached result\");\n  }\n\n  @Test\n  public void testLoadResultContainsAllFields() {\n    MinicoreLoader loader = new MinicoreLoader();\n    MinicoreLoadResult result = loader.loadLibrary();\n    MinicorePlatform platform = new MinicorePlatform();\n\n    assertNotNull(result.getLibraryFileName(), \"Library file name should not be null\");\n    String expectedFileName = platform.getLibraryFileName();\n    assertTrue(\n        result.getLibraryFileName().contains(\"sf_mini_core\"),\n        \"Library file name should contain base name\");\n    assertEquals(\n        result.getLibraryFileName(),\n        expectedFileName,\n        \"Library file name should match platform's expected file name\");\n\n    assertNotNull(result.getLogs(), \"Logs should not be null\");\n    assertFalse(result.getLogs().isEmpty(), \"Logs should contain entries\");\n\n    if (result.isSuccess()) {\n      assertNull(result.getErrorMessage(), \"Error message should be null on success\");\n      assertNull(result.getException(), \"Exception should be null on success\");\n    } else {\n      assertNotNull(result.getErrorMessage(), \"Error message should be set on failure\");\n    }\n  }\n\n  @Test\n  public void testHomeCacheDirectoryWindows() {\n    String home = System.getProperty(\"user.home\");\n    try (MockedStatic<Constants> constantsMock = mockStatic(Constants.class)) {\n      constantsMock.when(Constants::getOS).thenReturn(Constants.OS.WINDOWS);\n      constantsMock.when(Constants::getArchitecture).thenReturn(Constants.Architecture.X86_64);\n\n      MinicoreLoader loader = new MinicoreLoader();\n      Path cacheDir = loader.getHomeCacheDirectory();\n\n      Path expected = Paths.get(home, \"AppData\", \"Local\", \"Snowflake\", \"Caches\", \"minicore\");\n      assertEquals(expected, cacheDir);\n    }\n  }\n\n  @Test\n  public void testHomeCacheDirectoryMac() {\n    String home = System.getProperty(\"user.home\");\n    try (MockedStatic<Constants> constantsMock = mockStatic(Constants.class)) {\n      constantsMock.when(Constants::getOS).thenReturn(Constants.OS.MAC);\n      constantsMock.when(Constants::getArchitecture).thenReturn(Constants.Architecture.AARCH64);\n\n      MinicoreLoader loader = new MinicoreLoader();\n      Path cacheDir = loader.getHomeCacheDirectory();\n\n      Path expected = Paths.get(home, \"Library\", \"Caches\", \"Snowflake\", \"minicore\");\n      assertEquals(expected, cacheDir);\n    }\n  }\n\n  @Test\n  public void testHomeCacheDirectoryLinux() {\n    String home = System.getProperty(\"user.home\");\n    try (MockedStatic<Constants> constantsMock = mockStatic(Constants.class)) {\n      constantsMock.when(Constants::getOS).thenReturn(Constants.OS.LINUX);\n      constantsMock.when(Constants::getArchitecture).thenReturn(Constants.Architecture.X86_64);\n      constantsMock.when(Constants::isAix).thenReturn(false);\n\n      MinicoreLoader loader = new MinicoreLoader();\n      Path cacheDir = loader.getHomeCacheDirectory();\n\n      Path expected = Paths.get(home, \".cache\", \"Snowflake\", \"minicore\");\n      assertEquals(expected, cacheDir);\n    }\n  }\n\n  @Test\n  public void testHomeCacheDirectoryReturnsNullWhenHomeNotSet() {\n    String originalHome = System.getProperty(\"user.home\");\n    try {\n      System.clearProperty(\"user.home\");\n      MinicoreLoader loader = new MinicoreLoader();\n      Path cacheDir = loader.getHomeCacheDirectory();\n      assertNull(cacheDir, \"Cache directory should be null when home is not set\");\n    } finally {\n      if (originalHome != null) {\n        System.setProperty(\"user.home\", originalHome);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/minicore/MinicorePlatformTest.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.condition.OS.LINUX;\nimport static org.junit.jupiter.api.condition.OS.MAC;\nimport static org.junit.jupiter.api.condition.OS.WINDOWS;\nimport static org.mockito.Mockito.mockStatic;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.Constants.Architecture;\nimport net.snowflake.client.internal.core.Constants.OS;\nimport net.snowflake.client.internal.util.LibcDetails;\nimport net.snowflake.client.internal.util.LibcInfo;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.mockito.MockedStatic;\n\npublic class MinicorePlatformTest {\n\n  private static final List<TestPlatformConfig> SUPPORTED_PLATFORMS =\n      Arrays.asList(\n          // Linux glibc\n          new TestPlatformConfig(OS.LINUX, Architecture.X86_64, LibcDetails.GLIBC, false),\n          new TestPlatformConfig(OS.LINUX, Architecture.AARCH64, LibcDetails.GLIBC, false),\n          // Linux musl\n          new TestPlatformConfig(OS.LINUX, Architecture.X86_64, LibcDetails.MUSL, false),\n          new TestPlatformConfig(OS.LINUX, Architecture.AARCH64, LibcDetails.MUSL, false),\n          // macOS\n          new TestPlatformConfig(OS.MAC, Architecture.X86_64, null, false),\n          new TestPlatformConfig(OS.MAC, Architecture.AARCH64, null, false),\n          // Windows\n          new TestPlatformConfig(OS.WINDOWS, Architecture.X86_64, null, false),\n          new TestPlatformConfig(OS.WINDOWS, Architecture.AARCH64, null, false),\n          // AIX (detected as LINUX but isAix=true)\n          new TestPlatformConfig(OS.LINUX, Architecture.PPC64, null, true));\n\n  @Test\n  public void testPlatformDetection() {\n    MinicorePlatform platform = new MinicorePlatform();\n\n    assertNotNull(platform.getOsName(), \"OS name should not be null\");\n    assertNotNull(platform.getOsArch(), \"OS architecture should not be null\");\n  }\n\n  @Test\n  @EnabledOnOs(LINUX)\n  public void testLinuxPlatformFileName() {\n    MinicorePlatform platform = new MinicorePlatform();\n\n    String fileName = platform.getLibraryFileName();\n    assertNotNull(fileName, \"Library file name should not be null\");\n    assertTrue(\n        fileName.matches(\"libsf_mini_core_linux_(x86_64|aarch64)_(glibc|musl)\\\\.so\"),\n        \"Linux library should be 'libsf_mini_core_linux_{arch}_{libc}.so', got: \" + fileName);\n  }\n\n  @Test\n  @EnabledOnOs(MAC)\n  public void testMacOSPlatformFileName() {\n    MinicorePlatform platform = new MinicorePlatform();\n\n    String fileName = platform.getLibraryFileName();\n    assertNotNull(fileName, \"Library file name should not be null\");\n    assertTrue(\n        fileName.equals(\"libsf_mini_core_macos_x86_64.dylib\")\n            || fileName.equals(\"libsf_mini_core_macos_aarch64.dylib\"),\n        \"macOS library should be 'libsf_mini_core_macos_{x86_64|aarch64}.dylib', got: \" + fileName);\n  }\n\n  @Test\n  @EnabledOnOs(WINDOWS)\n  public void testWindowsPlatformFileName() {\n    MinicorePlatform platform = new MinicorePlatform();\n\n    String fileName = platform.getLibraryFileName();\n    assertNotNull(fileName, \"Library file name should not be null\");\n    assertTrue(\n        fileName.equals(\"libsf_mini_core_windows_x86_64.dll\")\n            || fileName.equals(\"libsf_mini_core_windows_aarch64.dll\"),\n        \"Windows library should be 'libsf_mini_core_windows_{x86_64|aarch64}.dll', got: \"\n            + fileName);\n  }\n\n  @Test\n  public void testIsSupportedOnCurrentPlatform() {\n    MinicorePlatform platform = new MinicorePlatform();\n    String platformId = platform.getPlatformIdentifier();\n\n    if (platformId != null\n        && (platformId.startsWith(\"linux-\")\n            || platformId.startsWith(\"macos-\")\n            || platformId.startsWith(\"windows-\"))) {\n      assertTrue(platform.isSupported(), \"Standard platforms should be supported: \" + platformId);\n    }\n  }\n\n  @Test\n  public void testAllSupportedPlatformsHaveMatchingResourceFiles()\n      throws IOException, URISyntaxException {\n    // Generate library paths from all supported platform configs using mocking\n    List<String> platformPaths = new ArrayList<>();\n    for (TestPlatformConfig config : SUPPORTED_PLATFORMS) {\n      String path = getLibraryPathForConfig(config);\n      platformPaths.add(path);\n    }\n\n    // Get actual files from minicore directory\n    URL resourceDir = MinicorePlatform.class.getResource(\"/minicore\");\n    assertNotNull(resourceDir, \"minicore resource directory should exist\");\n\n    Path minicorePath = Paths.get(resourceDir.toURI());\n    Set<String> actualFiles;\n    try (Stream<Path> paths = Files.list(minicorePath)) {\n      actualFiles =\n          paths\n              .map(p -> p.getFileName().toString())\n              .filter(f -> f.endsWith(\".so\") || f.endsWith(\".dylib\") || f.endsWith(\".dll\"))\n              .map(f -> \"/minicore/\" + f)\n              .collect(Collectors.toSet());\n    }\n\n    // Compare sizes\n    assertEquals(\n        SUPPORTED_PLATFORMS.size(),\n        actualFiles.size(),\n        String.format(\n            \"Number of supported platforms (%d) should match number of resource files (%d).\\n\"\n                + \"Platform paths: %s\\n\"\n                + \"Actual files: %s\",\n            SUPPORTED_PLATFORMS.size(), actualFiles.size(), platformPaths, actualFiles));\n\n    // Check each platform path has a matching file\n    for (String platformPath : platformPaths) {\n      assertTrue(\n          actualFiles.contains(platformPath),\n          String.format(\n              \"Platform path '%s' should exist in resource files.\\nActual files: %s\",\n              platformPath, actualFiles));\n    }\n  }\n\n  private String getLibraryPathForConfig(TestPlatformConfig config) {\n    try (MockedStatic<Constants> constantsMock = mockStatic(Constants.class);\n        MockedStatic<LibcDetails> libcMock = mockStatic(LibcDetails.class)) {\n\n      constantsMock.when(Constants::getOS).thenReturn(config.os);\n      constantsMock.when(Constants::getArchitecture).thenReturn(config.arch);\n      constantsMock.when(Constants::isAix).thenReturn(config.isAix);\n      libcMock.when(LibcDetails::load).thenReturn(new LibcInfo(config.libcFamily, null));\n\n      MinicorePlatform platform = new MinicorePlatform();\n      assertTrue(platform.isSupported(), \"Platform should be supported: \" + config);\n      return platform.getLibraryPath();\n    }\n  }\n\n  private static class TestPlatformConfig {\n    final OS os;\n    final Architecture arch;\n    final String libcFamily;\n    final boolean isAix;\n\n    TestPlatformConfig(OS os, Architecture arch, String libcFamily, boolean isAix) {\n      this.os = os;\n      this.arch = arch;\n      this.libcFamily = libcFamily;\n      this.isAix = isAix;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/minicore/MinicoreTelemetryTest.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests for MinicoreTelemetry.\n *\n * <p>These tests verify telemetry data generation from load results, including ISA detection,\n * version information, error reporting, and log aggregation. Uses mock MinicoreLoadResult objects\n * to test telemetry generation in isolation.\n */\npublic class MinicoreTelemetryTest {\n\n  private static final String TEST_LIBRARY_FILE = \"libsf_mini_core_test.so\";\n  private static final String TEST_VERSION = \"0.0.1-test\";\n\n  @AfterEach\n  public void tearDown() {\n    Minicore.resetForTesting();\n  }\n\n  @Test\n  public void testClientEnvTelemetryFromSuccessfulLoad() {\n    MinicoreLoadResult result =\n        MinicoreLoadResult.success(TEST_LIBRARY_FILE, null, TEST_VERSION, new ArrayList<>());\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(result);\n    Map<String, Object> map = telemetry.toClientEnvironmentTelemetryMap();\n\n    assertNotNull(telemetry, \"Telemetry should not be null\");\n    assertTrue(map.containsKey(\"ISA\"), \"Telemetry should contain ISA\");\n    assertNotNull(map.get(\"ISA\"), \"ISA should not be null\");\n    assertTrue(map.containsKey(\"CORE_VERSION\"), \"Telemetry should contain CORE_VERSION\");\n    assertEquals(TEST_VERSION, map.get(\"CORE_VERSION\"), \"CORE_VERSION should match\");\n    assertTrue(map.containsKey(\"CORE_FILE_NAME\"), \"Telemetry should contain CORE_FILE_NAME\");\n    assertEquals(TEST_LIBRARY_FILE, map.get(\"CORE_FILE_NAME\"), \"CORE_FILE_NAME should match\");\n    assertFalse(\n        map.containsKey(\"CORE_LOAD_ERROR\"), \"Should not contain CORE_LOAD_ERROR on success\");\n  }\n\n  @Test\n  public void testClientEnvTelemetryFromFailedLoad() {\n    String errorMessage = \"Failed to load library: symbol not found\";\n    MinicoreLoadResult result =\n        MinicoreLoadResult.failure(\n            errorMessage, TEST_LIBRARY_FILE, new UnsatisfiedLinkError(\"test\"), new ArrayList<>());\n\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(result);\n    Map<String, Object> map = telemetry.toClientEnvironmentTelemetryMap();\n\n    assertNotNull(telemetry, \"Telemetry should not be null\");\n    assertTrue(map.containsKey(\"ISA\"), \"Telemetry should contain ISA\");\n    assertTrue(map.containsKey(\"CORE_FILE_NAME\"), \"Telemetry should contain CORE_FILE_NAME\");\n    assertEquals(TEST_LIBRARY_FILE, map.get(\"CORE_FILE_NAME\"), \"CORE_FILE_NAME should match\");\n    assertFalse(map.containsKey(\"CORE_VERSION\"), \"Should not contain CORE_VERSION on failure\");\n    assertTrue(map.containsKey(\"CORE_LOAD_ERROR\"), \"Should contain CORE_LOAD_ERROR on failure\");\n    assertEquals(\n        MinicoreLoadError.FAILED_TO_LOAD.getMessage(),\n        map.get(\"CORE_LOAD_ERROR\"),\n        \"CORE_LOAD_ERROR should match enum message\");\n  }\n\n  @Test\n  public void testTelemetryWhenStillLoading() {\n    // Don't initialize minicore - simulates \"still loading\" state\n    Minicore.resetForTesting();\n\n    MinicoreTelemetry telemetry = MinicoreTelemetry.create();\n    Map<String, Object> map = telemetry.toClientEnvironmentTelemetryMap();\n\n    assertNotNull(telemetry, \"Telemetry should not be null\");\n    assertTrue(map.containsKey(\"ISA\"), \"Telemetry should contain ISA\");\n    assertTrue(map.containsKey(\"CORE_LOAD_ERROR\"), \"Should contain CORE_LOAD_ERROR\");\n    assertEquals(\n        MinicoreLoadError.STILL_LOADING.getMessage(),\n        map.get(\"CORE_LOAD_ERROR\"),\n        \"CORE_LOAD_ERROR should match enum message\");\n\n    // Check in-band telemetry has the correct error\n    ObjectNode node = telemetry.toInBandTelemetryNode();\n    assertEquals(MinicoreLoadError.STILL_LOADING.getMessage(), node.get(\"error\").asText());\n  }\n\n  @Test\n  public void testFailedLoadPreservesDetailedErrorInLogs() {\n    String detailedError = \"Library resource not found in JAR: /native/linux/libsf_mini_core.so\";\n    RuntimeException exception = new RuntimeException(\"File not found\");\n    List<String> originalLogs = Arrays.asList(\"Starting load\", \"Checking platform\");\n\n    MinicoreLoadResult result =\n        MinicoreLoadResult.failure(detailedError, TEST_LIBRARY_FILE, exception, originalLogs);\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(result);\n    ObjectNode node = telemetry.toInBandTelemetryNode();\n\n    // Error field should have the enum message\n    assertEquals(\n        MinicoreLoadError.FAILED_TO_LOAD.getMessage(),\n        node.get(\"error\").asText(),\n        \"Error should be the enum message\");\n\n    // Logs should contain the detailed error and exception\n    assertTrue(node.has(\"loadLogs\"), \"Should have loadLogs\");\n    List<String> logs = new ArrayList<>();\n    node.get(\"loadLogs\").forEach(n -> logs.add(n.asText()));\n\n    assertTrue(\n        logs.stream().anyMatch(log -> log.contains(detailedError)),\n        \"Logs should contain detailed error message\");\n    assertTrue(\n        logs.stream().anyMatch(log -> log.contains(\"RuntimeException\")),\n        \"Logs should contain exception class name\");\n  }\n\n  // Tests for toInBandTelemetryNode()\n\n  @Test\n  public void testInBandTelemetryNodeFromSuccessfulLoad() {\n    List<String> logs = Arrays.asList(\"log1\", \"log2\", \"log3\");\n    MinicoreLoadResult result =\n        MinicoreLoadResult.success(TEST_LIBRARY_FILE, null, TEST_VERSION, logs);\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(result);\n    ObjectNode node = telemetry.toInBandTelemetryNode();\n\n    assertNotNull(node, \"ObjectNode should not be null\");\n    assertEquals(\n        \"client_minicore_load\", node.get(\"type\").asText(), \"Type should be client_minicore_load\");\n    assertEquals(\"JDBC\", node.get(\"source\").asText(), \"Source should be JDBC\");\n    assertTrue(node.get(\"success\").asBoolean(), \"Success should be true\");\n    assertEquals(\n        TEST_LIBRARY_FILE, node.get(\"libraryFileName\").asText(), \"Library file name should match\");\n    assertEquals(TEST_VERSION, node.get(\"coreVersion\").asText(), \"Core version should match\");\n    assertNull(node.get(\"error\"), \"Error should not be present on success\");\n\n    assertTrue(node.has(\"loadLogs\"), \"Should have loadLogs array\");\n    assertEquals(3, node.get(\"loadLogs\").size(), \"Should have 3 log entries\");\n    assertEquals(\"log1\", node.get(\"loadLogs\").get(0).asText());\n    assertEquals(\"log2\", node.get(\"loadLogs\").get(1).asText());\n    assertEquals(\"log3\", node.get(\"loadLogs\").get(2).asText());\n  }\n\n  @Test\n  public void testInBandTelemetryNodeFromFailedLoad() {\n    String errorMessage = \"Failed to load library\";\n    List<String> logs = Arrays.asList(\"starting\", \"failed\");\n    MinicoreLoadResult result =\n        MinicoreLoadResult.failure(\n            errorMessage, TEST_LIBRARY_FILE, new RuntimeException(\"test\"), logs);\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(result);\n    ObjectNode node = telemetry.toInBandTelemetryNode();\n\n    assertNotNull(node, \"ObjectNode should not be null\");\n    assertEquals(\n        \"client_minicore_load\", node.get(\"type\").asText(), \"Type should be client_minicore_load\");\n    assertEquals(\"JDBC\", node.get(\"source\").asText(), \"Source should be JDBC\");\n    assertFalse(node.get(\"success\").asBoolean(), \"Success should be false\");\n    assertEquals(\n        TEST_LIBRARY_FILE, node.get(\"libraryFileName\").asText(), \"Library file name should match\");\n    assertNull(node.get(\"coreVersion\"), \"Core version should not be present on failure\");\n    assertEquals(\n        MinicoreLoadError.FAILED_TO_LOAD.getMessage(),\n        node.get(\"error\").asText(),\n        \"Error should be enum message\");\n\n    assertTrue(node.has(\"loadLogs\"), \"Should have loadLogs array\");\n  }\n\n  @Test\n  public void testInBandTelemetryNodeWhenStillLoading() {\n    Minicore.resetForTesting();\n    MinicoreTelemetry telemetry = MinicoreTelemetry.create();\n    ObjectNode node = telemetry.toInBandTelemetryNode();\n\n    assertNotNull(node, \"ObjectNode should not be null\");\n    assertEquals(\"client_minicore_load\", node.get(\"type\").asText());\n    assertEquals(\"JDBC\", node.get(\"source\").asText());\n    assertFalse(node.get(\"success\").asBoolean(), \"Success should be false\");\n    assertEquals(MinicoreLoadError.STILL_LOADING.getMessage(), node.get(\"error\").asText());\n  }\n\n  @Test\n  public void testInBandTelemetryNodeWithEmptyLogs() {\n    MinicoreLoadResult result =\n        MinicoreLoadResult.success(TEST_LIBRARY_FILE, null, TEST_VERSION, new ArrayList<>());\n    MinicoreTelemetry telemetry = MinicoreTelemetry.fromLoadResult(result);\n    ObjectNode node = telemetry.toInBandTelemetryNode();\n\n    assertNotNull(node, \"ObjectNode should not be null\");\n    assertFalse(node.has(\"loadLogs\"), \"Should not have loadLogs when empty\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/minicore/MinicoreTest.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.time.Duration;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class MinicoreTest {\n\n  private static final long MAX_MINICORE_INIT_TIME_MS = 5000;\n  private static final int NUM_TIMING_RUNS = 5;\n\n  @Test\n  public void testAsyncInitializationCompletesInReasonableTime() {\n    long[] times = new long[NUM_TIMING_RUNS];\n    MinicoreLoadResult[] results = new MinicoreLoadResult[NUM_TIMING_RUNS];\n\n    for (int run = 0; run < NUM_TIMING_RUNS; run++) {\n      Minicore.resetForTesting();\n      long startTime = System.currentTimeMillis();\n      Minicore.initializeAsync();\n\n      await()\n          .atMost(Duration.ofMillis(MAX_MINICORE_INIT_TIME_MS))\n          .until(() -> Minicore.getInstance() != null);\n\n      times[run] = System.currentTimeMillis() - startTime;\n      results[run] = Minicore.getInstance().getLoadResult();\n      assertTrue(results[run].isSuccess());\n    }\n\n    System.out.printf(\n        \"Minicore init (%d runs): firstRun=%dms, avg=%dms%n\",\n        NUM_TIMING_RUNS, times[0], average(times));\n\n    for (int run = 0; run < NUM_TIMING_RUNS; run++) {\n      System.out.printf(\"Run %d logs:%n\", run + 1);\n      for (String log : results[run].getLogs()) {\n        System.out.printf(\"  %s%n\", log);\n      }\n    }\n  }\n\n  private long average(long[] values) {\n    long sum = 0;\n    for (long v : values) {\n      sum += v;\n    }\n    return sum / values.length;\n  }\n\n  @Test\n  public void testMinicoreInitializesSuccessfully() {\n    Minicore.resetForTesting();\n\n    Minicore.initialize();\n    Minicore instance = Minicore.getInstance();\n    MinicorePlatform platform = new MinicorePlatform();\n\n    assertNotNull(instance, \"Minicore instance should not be null after initialization\");\n\n    MinicoreLoadResult result = instance.getLoadResult();\n    assertNotNull(result, \"Load result should not be null\");\n\n    // Build detailed error message in case of failure\n    StringBuilder errorMessage = new StringBuilder();\n    errorMessage\n        .append(\"Minicore should load successfully on platform: \")\n        .append(platform.getPlatformIdentifier());\n    errorMessage.append(\"\\nPlatform supported: \").append(platform.isSupported());\n    errorMessage.append(\"\\nOS: \").append(platform.getOsName());\n    errorMessage.append(\"\\nArch: \").append(platform.getOsArch());\n\n    if (!result.isSuccess()) {\n      errorMessage.append(\"\\n\\n=== LOAD FAILURE DETAILS ===\");\n      errorMessage.append(\"\\nError: \").append(result.getErrorMessage());\n      if (result.getException() != null) {\n        errorMessage.append(\"\\nException: \").append(result.getException().getClass().getName());\n        errorMessage.append(\": \").append(result.getException().getMessage());\n      }\n      errorMessage.append(\"\\nLibrary file: \").append(result.getLibraryFileName());\n      errorMessage.append(\"\\n\\nLoad Logs:\");\n      for (String log : result.getLogs()) {\n        errorMessage.append(\"\\n  \").append(log);\n      }\n    }\n\n    // Loading should succeed\n    assertTrue(result.isSuccess(), errorMessage.toString());\n    assertNull(result.getErrorMessage(), \"Error message should be null on success\");\n    assertNull(result.getException(), \"Exception should be null on success\");\n\n    MinicoreLibrary library = instance.getLibrary();\n    assertNotNull(library, \"Library should not be null after successful load\");\n\n    String version = library.sf_core_full_version();\n    assertNotNull(version, \"Version should not be null\");\n    assertFalse(version.isEmpty(), \"Version should not be empty\");\n    assertTrue(\n        version.contains(\"0.0.1\"),\n        \"Version should contain expected version number, got: \" + version);\n\n    assertEquals(version, result.getCoreVersion(), \"Version in result should match library call\");\n  }\n\n  @Test\n  public void testMinicoreInitializationIsIdempotent() {\n    // Reset to ensure clean state\n    Minicore.resetForTesting();\n\n    // GIVEN - Initialize minicore twice\n    Minicore.initialize();\n    Minicore instance1 = Minicore.getInstance();\n\n    Minicore.initialize();\n    Minicore instance2 = Minicore.getInstance();\n\n    // THEN - Should return the same singleton instance\n    assertSame(instance1, instance2, \"Multiple initializations should return the same instance\");\n\n    // AND - Load result should be the same\n    assertSame(\n        instance1.getLoadResult(),\n        instance2.getLoadResult(),\n        \"Load result should be the same across calls\");\n  }\n\n  @Test\n  public void testMinicoreLibraryFunctionConsistency() {\n    // Reset to ensure clean state\n    Minicore.resetForTesting();\n\n    // GIVEN - Initialized minicore\n    Minicore.initialize();\n    Minicore instance = Minicore.getInstance();\n\n    MinicoreLoadResult result = instance.getLoadResult();\n    assertTrue(result.isSuccess(), \"This test requires successful minicore load\");\n\n    MinicoreLibrary library = instance.getLibrary();\n    assertNotNull(library, \"Library should not be null\");\n\n    // WHEN - Calling the same function multiple times\n    String version1 = library.sf_core_full_version();\n    String version2 = library.sf_core_full_version();\n    String version3 = library.sf_core_full_version();\n\n    // THEN - Results should be consistent\n    assertEquals(version1, version2, \"Version should be consistent across calls\");\n    assertEquals(version2, version3, \"Version should be consistent across calls\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/core/minicore/MinicoreTestLatestIT.java",
    "content": "package net.snowflake.client.internal.core.minicore;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport org.junit.jupiter.api.Tag;\n\n@Tag(TestTags.CORE)\npublic class MinicoreTestLatestIT extends BaseJDBCTest {\n\n  private static final long MAX_MINICORE_INIT_TIME_MS = 1000;\n\n  // @Test\n  public void testExecuteSelectWithMinicorePerformance() throws SQLException {\n    int numRuns = 3;\n\n    // Cold start run (first connection with minicore - simulates customer cold JVM)\n    System.out.println(\"Cold start run (with minicore)...\");\n    Minicore.resetForTesting();\n    long coldStartTotal = System.currentTimeMillis();\n    Minicore.initialize();\n    long coldMinicoreInitTime = System.currentTimeMillis() - coldStartTotal;\n    long coldConnectionTime = measureConnectionTime();\n    long coldTotalTime = System.currentTimeMillis() - coldStartTotal;\n    for (String log : Minicore.getInstance().getLoadResult().getLogs()) {\n      System.out.printf(\"  %s%n\", log);\n    }\n    System.out.printf(\n        \"  Cold: minicore=%dms, connection=%dms, total=%dms%n\",\n        coldMinicoreInitTime, coldConnectionTime, coldTotalTime);\n\n    // Warm interleaved runs\n    long[] timesWithMinicore = new long[numRuns];\n    long[] timesWithoutMinicore = new long[numRuns];\n    long[] minicoreTimes = new long[numRuns];\n\n    System.out.println(\"\\nInterleaved measurement runs (warm JVM):\");\n    for (int i = 0; i < numRuns; i++) {\n      // Run WITHOUT minicore\n      Minicore.resetForTesting();\n      timesWithoutMinicore[i] = measureConnectionTime();\n      System.out.printf(\"  Run %d - without: %dms%n\", i + 1, timesWithoutMinicore[i]);\n\n      // Run WITH minicore (sync init)\n      Minicore.resetForTesting();\n      long startMinicore = System.currentTimeMillis();\n      Minicore.initialize();\n      minicoreTimes[i] = System.currentTimeMillis() - startMinicore;\n      timesWithMinicore[i] = measureConnectionTime();\n      for (String log : Minicore.getInstance().getLoadResult().getLogs()) {\n        System.out.printf(\"  %s%n\", log);\n      }\n      System.out.printf(\n          \"  Run %d - with (init=%dms): %dms%n\", i + 1, minicoreTimes[i], timesWithMinicore[i]);\n    }\n\n    // Calculate averages\n    long avgWith = average(timesWithMinicore);\n    long avgWithout = average(timesWithoutMinicore);\n    long avgMinicoreInit = average(minicoreTimes);\n    assertTrue(\n        (avgWith - avgWithout) < MAX_MINICORE_INIT_TIME_MS,\n        \"Minicore initialization time difference exceeded\");\n\n    System.out.println(\"\\n=== testExecuteSelect with/without Minicore Impact ===\");\n    System.out.println(\"Cold start (first connection with minicore):\");\n    System.out.printf(\"  Minicore init: %dms%n\", coldMinicoreInitTime);\n    System.out.printf(\"  Connection:    %dms%n\", coldConnectionTime);\n    System.out.printf(\"  Total:         %dms%n\", coldTotalTime);\n    System.out.println(\"\\nWarm JVM (interleaved runs):\");\n    System.out.printf(\"  Minicore sync init time: avg=%dms%n\", avgMinicoreInit);\n    System.out.printf(\"  With sync minicore (%d runs): avg=%dms%n\", numRuns, avgWith);\n    System.out.printf(\"  Without minicore (%d runs): avg=%dms%n\", numRuns, avgWithout);\n    System.out.printf(\"  Connection time difference: %dms%n\", avgWith - avgWithout);\n  }\n\n  private long measureConnectionTime() throws SQLException {\n    long start = System.currentTimeMillis();\n    try (Connection conn = getConnection()) {\n      try (Statement statement = conn.createStatement()) {\n        ResultSet rs = statement.executeQuery(\"select 1;\");\n        rs.next();\n        assertEquals(1, rs.getInt(1));\n      }\n    }\n    return System.currentTimeMillis() - start;\n  }\n\n  private long average(long[] values) {\n    long sum = 0;\n    for (long v : values) {\n      sum += v;\n    }\n    return sum / values.length;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/driver/DriverInitializerTest.java",
    "content": "package net.snowflake.client.internal.driver;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.minicore.Minicore;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class DriverInitializerTest {\n\n  @Test\n  public void testInitializeTriggersMinicore() {\n    DriverInitializer.resetForTesting();\n    Minicore.resetForTesting();\n\n    DriverInitializer.initialize();\n\n    assertTrue(DriverInitializer.isInitialized(), \"DriverInitializer should be initialized\");\n    assertTrue(\n        Minicore.hasInitializationStarted(),\n        \"Minicore async initialization should have been started by DriverInitializer.initialize()\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ArrowResultChunkTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport org.junit.jupiter.api.Test;\n\npublic class ArrowResultChunkTest {\n  @Test\n  public void testEmptyChunkIterator() throws SnowflakeSQLException {\n    ArrowResultChunk.ArrowChunkIterator iterator = ArrowResultChunk.getEmptyChunkIterator();\n\n    assertThat(iterator.next(), is(false));\n    assertThat(iterator.isAfterLast(), is(true));\n    assertThat(iterator.isLast(), is(false));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/AuthenticatedProxyLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AbstractDriverIT.connectAndVerifySimpleQuery;\nimport static net.snowflake.client.AbstractDriverIT.getFullPathFileInResource;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.annotations.RunOnGCP;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Integration tests verifying that Snowflake JDBC operations work through an authenticated proxy.\n *\n * <p>Uses WireMock in forward proxy mode with {@code --proxy-pass-through true} so that all proxied\n * requests (both Snowflake API and cloud storage) are forwarded to their original destinations. The\n * driver is configured with {@code proxyUser}/{@code proxyPassword} to exercise the authenticated\n * proxy code path.\n */\n@Tag(TestTags.OTHERS)\n@RunOnGCP\npublic class AuthenticatedProxyLatestIT extends BaseWiremockTest {\n\n  private static final String PROXY_USER = \"testUser\";\n  private static final String PROXY_PASSWORD = \"testPassword\";\n  private static final String TEST_DATA_FILE = \"orders_100.csv\";\n\n  @AfterEach\n  public void tearDown() {\n    super.tearDown();\n  }\n\n  private void addAuthenticatedProxyProperties(Properties props) {\n    String proxyProtocol = getProxyProtocol(props);\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyProtocol\", proxyProtocol);\n    props.put(\"proxyHost\", WIREMOCK_HOST);\n    props.put(\"proxyPort\", String.valueOf(getProxyPort(proxyProtocol)));\n    props.put(\"proxyUser\", PROXY_USER);\n    props.put(\"proxyPassword\", PROXY_PASSWORD);\n  }\n\n  /**\n   * Sets up WireMock to reject all requests with 407 Proxy Authentication Required. Used for\n   * negative tests where we expect the connection to fail.\n   */\n  private void rejectAllWith407() {\n    String mapping =\n        \"{\\n\"\n            + \"  \\\"request\\\": {\\n\"\n            + \"    \\\"method\\\": \\\"ANY\\\",\\n\"\n            + \"    \\\"urlPattern\\\": \\\".*\\\"\\n\"\n            + \"  },\\n\"\n            + \"  \\\"response\\\": {\\n\"\n            + \"    \\\"status\\\": 407,\\n\"\n            + \"    \\\"headers\\\": {\\n\"\n            + \"      \\\"Proxy-Authenticate\\\": \\\"Basic realm=\\\\\\\"WireMock Proxy\\\\\\\"\\\"\\n\"\n            + \"    }\\n\"\n            + \"  }\\n\"\n            + \"}\";\n    addMapping(mapping);\n  }\n\n  private void verifyRequestToProxy(String pathPattern, int minExpectedCount) {\n    String body = String.format(\"{ \\\"method\\\":\\\"POST\\\",\\\"urlPattern\\\": \\\".*%s.*\\\" }\", pathPattern);\n    HttpPost postRequest = createWiremockPostRequest(body, \"/__admin/requests/count\");\n    try (CloseableHttpClient client = HttpClients.createDefault();\n        CloseableHttpResponse response = client.execute(postRequest)) {\n      String responseString = EntityUtils.toString(response.getEntity());\n      ObjectMapper mapper = new ObjectMapper();\n      JsonNode json = mapper.readTree(responseString);\n      int actualCount = json.get(\"count\").asInt();\n      assertTrue(\n          actualCount >= minExpectedCount,\n          String.format(\n              \"expected at least %d requests for pattern '%s', but found %d\",\n              minExpectedCount, pathPattern, actualCount));\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private void verifyProxyWasUsed() {\n    verifyRequestToProxy(\".*login.*\", 1);\n    verifyRequestToProxy(\".*query.*\", 1);\n  }\n\n  @Override\n  protected Properties getProperties() {\n    Properties props = super.getProperties();\n    // Disable telemetry to avoid race conditions between tests\n    props.put(\"CLIENT_TELEMETRY_ENABLED\", \"false\");\n    return props;\n  }\n\n  @Test\n  public void testSimpleQueryThroughAuthenticatedProxy() throws SQLException {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    addAuthenticatedProxyProperties(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyWasUsed();\n  }\n\n  @Test\n  public void testCreateTemporaryStageAndPutThroughAuthenticatedProxy() throws SQLException {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    addAuthenticatedProxyProperties(props);\n\n    String jdbcUrl = String.format(\"jdbc:snowflake://%s:%s\", props.get(\"host\"), props.get(\"port\"));\n    try (Connection con = DriverManager.getConnection(jdbcUrl, props);\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"CREATE TEMPORARY STAGE testAuthProxy_stage\");\n\n      String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n      assertTrue(\n          stmt.execute(\"PUT file://\" + sourceFilePath + \" @testAuthProxy_stage\"),\n          \"Failed to put a file via authenticated proxy\");\n\n      try (ResultSet rs = stmt.executeQuery(\"LS @testAuthProxy_stage\")) {\n        assertTrue(rs.next(), \"Stage should contain the uploaded file\");\n      }\n    }\n\n    verifyProxyWasUsed();\n  }\n\n  @Test\n  public void testConnectionFailsWhenProxyReturns407() {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    String proxyProtocol = getProxyProtocol(props);\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyProtocol\", proxyProtocol);\n    props.put(\"proxyHost\", WIREMOCK_HOST);\n    props.put(\"proxyPort\", String.valueOf(getProxyPort(proxyProtocol)));\n    // No proxyUser/proxyPassword — all requests will be rejected with 407\n\n    rejectAllWith407();\n\n    assertThrows(SQLException.class, () -> connectAndVerifySimpleQuery(props));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/AutoConnectionConfigurationLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY;\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SNOWFLAKE_HOME_KEY;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.isWindows;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport com.fasterxml.jackson.dataformat.toml.TomlMapper;\nimport com.google.common.base.Strings;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.FileAttribute;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.driver.AutoConfigurationHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n@Tag(TestTags.CONNECTION)\npublic class AutoConnectionConfigurationLatestIT extends BaseJDBCTest {\n  private static final List<String> ENV_VARIABLES_KEYS =\n      new ArrayList<>(Arrays.asList(SNOWFLAKE_HOME_KEY, SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY));\n  private Path tempPath = null;\n  private TomlMapper tomlMapper = new TomlMapper();\n  private Map<String, String> envVariables = new HashMap<String, String>();\n  Map<String, String> params = new HashMap<String, String>();\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    params = BaseJDBCTest.getConnectionParameters();\n    tempPath = Files.createTempDirectory(\".snowflake\");\n    ENV_VARIABLES_KEYS.forEach(\n        key -> {\n          if (SnowflakeUtil.systemGetEnv(key) != null) {\n            envVariables.put(key, SnowflakeUtil.systemGetEnv(key));\n          }\n        });\n  }\n\n  @AfterEach\n  public void cleanUp() throws IOException {\n    SnowflakeUtil.systemUnsetEnv(SNOWFLAKE_HOME_KEY);\n    SnowflakeUtil.systemUnsetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY);\n    Files.walk(tempPath).map(Path::toFile).forEach(File::delete);\n    Files.delete(tempPath);\n    envVariables.forEach(SnowflakeUtil::systemSetEnv);\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"connectionScenarios\")\n  public void testConnectionScenarios(String connectionName, boolean shouldThrow)\n      throws IOException, SQLException {\n    prepareConnectionConfigurationTomlFile();\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_HOME_KEY, tempPath.toString());\n    final String connectionString;\n\n    if (null != connectionName && connectionName.equals(\"systemEnvConfig\")) {\n      SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, connectionName);\n      connectionString = AutoConfigurationHelper.AUTO_CONNECTION_PREFIX;\n    } else {\n      connectionString =\n          connectionName != null\n              ? AutoConfigurationHelper.AUTO_CONNECTION_PREFIX + \"?connectionName=\" + connectionName\n              : AutoConfigurationHelper.AUTO_CONNECTION_PREFIX;\n    }\n\n    if (shouldThrow) {\n      SnowflakeSQLException ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> DriverManager.getConnection(connectionString, null));\n      assertTrue(ex.getMessage().contains(\"not found in connections.toml file.\"));\n    } else {\n      try (Connection con = DriverManager.getConnection(connectionString, null)) {\n        assertNotNull(con);\n      } catch (Exception e) {\n        fail(\"Should not fail. \" + e.getMessage());\n      }\n    }\n  }\n\n  private void prepareConnectionConfigurationTomlFile() throws IOException {\n    Path path = Paths.get(tempPath.toString(), \"connections.toml\");\n    Path filePath = createFilePathWithPermission(path);\n    File file = filePath.toFile();\n\n    Map<String, Map<String, String>> configuration = new HashMap<String, Map<String, String>>();\n    Map<String, String> configurationParams = new HashMap<String, String>();\n    configurationParams.put(\"account\", params.get(\"account\"));\n    configurationParams.put(\"user\", params.get(\"user\"));\n    configurationParams.put(\"port\", params.get(\"port\"));\n    configurationParams.put(\"host\", params.get(\"host\"));\n    configurationParams.put(\"ssl\", params.get(\"ssl\"));\n\n    if (!Strings.isNullOrEmpty(params.get(\"private_key_file\"))) {\n      configurationParams.put(\"private_key_file\", params.get(\"private_key_file\"));\n      configurationParams.put(\"authenticator\", params.get(\"authenticator\"));\n      if (!Strings.isNullOrEmpty(params.get(\"private_key_pwd\"))) {\n        configurationParams.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n      }\n    } else if (!Strings.isNullOrEmpty(params.get(\"password\"))) {\n      configurationParams.put(\"password\", params.get(\"password\"));\n    }\n\n    configuration.put(\"default\", configurationParams);\n    configuration.put(\"readOnly\", configurationParams);\n    configuration.put(\"systemEnvConfig\", configurationParams);\n    tomlMapper.writeValue(file, configuration);\n  }\n\n  private Path createFilePathWithPermission(Path path) throws IOException {\n    if (!isWindows()) {\n      FileAttribute<Set<PosixFilePermission>> fileAttribute =\n          PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString(\"rw-------\"));\n      return Files.createFile(path, fileAttribute);\n    } else {\n      return Files.createFile(path);\n    }\n  }\n\n  private static List<Arguments> connectionScenarios() {\n    return Arrays.asList(\n        Arguments.of(\"notConfigured\", true),\n        Arguments.of(\"readOnly\", false),\n        Arguments.of(\"systemEnvConfig\", false),\n        Arguments.of(null, false));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BaseJDBCTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.Reader;\nimport java.io.Writer;\nimport java.sql.Array;\nimport java.sql.Blob;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.NClob;\nimport java.sql.Ref;\nimport java.sql.ResultSet;\nimport java.sql.RowId;\nimport java.sql.SQLClientInfoException;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.SQLXML;\nimport java.sql.Statement;\nimport java.time.LocalDateTime;\nimport java.time.ZonedDateTime;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.UUID;\nimport javax.xml.transform.Result;\nimport javax.xml.transform.Source;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.SFException;\n\npublic class BaseJDBCTest extends AbstractDriverIT {\n  // Test UUID unique per session\n  static final String TEST_UUID = UUID.randomUUID().toString();\n\n  protected static Connection getConnectionWithWildcardsDisabled() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"ENABLE_WILDCARDS_IN_SHOW_METADATA_COMMANDS\", \"false\");\n    return getConnection(props);\n  }\n\n  protected interface MethodRaisesSQLException {\n    void run() throws SQLException;\n  }\n\n  protected interface MethodRaisesSFException {\n    void run() throws SFException;\n  }\n\n  protected interface MethodRaisesSQLClientInfoException {\n    void run() throws SQLClientInfoException;\n  }\n\n  protected void expectConnectionAlreadyClosedException(MethodRaisesSQLException f) {\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              f.run();\n            });\n    assertEquals((int) ErrorCode.CONNECTION_CLOSED.getMessageCode(), ex.getErrorCode());\n  }\n\n  protected void expectStatementAlreadyClosedException(MethodRaisesSQLException f) {\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              f.run();\n            });\n    assertEquals((int) ErrorCode.STATEMENT_CLOSED.getMessageCode(), ex.getErrorCode());\n  }\n\n  protected void expectResultSetAlreadyClosedException(MethodRaisesSQLException f) {\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              f.run();\n            });\n    assertEquals((int) ErrorCode.RESULTSET_ALREADY_CLOSED.getMessageCode(), ex.getErrorCode());\n  }\n\n  protected void expectFeatureNotSupportedException(MethodRaisesSQLException f) {\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              f.run();\n            });\n    assertTrue(ex instanceof SQLFeatureNotSupportedException);\n  }\n\n  protected void expectSQLClientInfoException(MethodRaisesSQLClientInfoException f) {\n    SQLClientInfoException ex =\n        assertThrows(\n            SQLClientInfoException.class,\n            () -> {\n              f.run();\n            });\n  }\n\n  int getSizeOfResultSet(ResultSet rs) throws SQLException {\n    int count = 0;\n    while (rs.next()) {\n      count++;\n    }\n    return count;\n  }\n\n  List<String> getInfoBySQL(String sqlCmd) throws SQLException {\n    Connection con = getConnection();\n    Statement st = con.createStatement();\n    List<String> result = new ArrayList<>();\n    ResultSet rs = st.executeQuery(sqlCmd);\n    while (rs.next()) {\n      result.add(rs.getString(1));\n    }\n    return result;\n  }\n\n  /**\n   * Parses a datetime string in the format YYYY-MM-DD HH24:MI:SS.S... +TZH:TZM and creates\n   * ZonedDateTime object.\n   *\n   * @param dateTimeString a datetime string\n   * @return a ZonedDateTime object.\n   */\n  ZonedDateTime parseTimestampTZ(String dateTimeString) {\n    String[] dts = dateTimeString.split(\"\\\\s\");\n    return ZonedDateTime.parse(\n        (dts[0] + \"T\" + dts[1] + dts[2].substring(0, 3) + \":\" + dts[2].substring(3)));\n  }\n\n  /**\n   * Parses a datetime string in the format YYYY-MM-DD HH24:MI:SS.S... and creates LocalDateTime\n   * object.\n   *\n   * @param dateTimeString a datetime string\n   * @return a LocalDateTime object.\n   */\n  LocalDateTime parseTimestampNTZ(String dateTimeString) {\n    String[] dts = dateTimeString.split(\"\\\\s\");\n    return LocalDateTime.parse(dts[0] + \"T\" + dts[1]);\n  }\n\n  class FakeRef implements Ref {\n\n    @Override\n    public String getBaseTypeName() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Object getObject(Map<String, Class<?>> map) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Object getObject() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public void setObject(Object value) throws SQLException {}\n  }\n\n  class FakeBlob implements Blob {\n    @Override\n    public long length() throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public byte[] getBytes(long pos, int length) throws SQLException {\n      return new byte[0];\n    }\n\n    @Override\n    public InputStream getBinaryStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public long position(byte[] pattern, long start) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public long position(Blob pattern, long start) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public int setBytes(long pos, byte[] bytes) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public OutputStream setBinaryStream(long pos) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public void truncate(long len) throws SQLException {}\n\n    @Override\n    public void free() throws SQLException {}\n\n    @Override\n    public InputStream getBinaryStream(long pos, long length) throws SQLException {\n      return null;\n    }\n  }\n\n  class FakeArray implements Array {\n    @Override\n    public String getBaseTypeName() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public int getBaseType() throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public Object getArray() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Object getArray(Map<String, Class<?>> map) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Object getArray(long index, int count) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public ResultSet getResultSet() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public ResultSet getResultSet(long index, int count) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map)\n        throws SQLException {\n      return null;\n    }\n\n    @Override\n    public void free() throws SQLException {}\n  }\n\n  class FakeRowId implements RowId {\n    @Override\n    public byte[] getBytes() {\n      return new byte[0];\n    }\n  }\n\n  class FakeNClob implements NClob {\n    @Override\n    public long length() throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public String getSubString(long pos, int length) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Reader getCharacterStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public InputStream getAsciiStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public long position(String searchstr, long start) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public long position(Clob searchstr, long start) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public int setString(long pos, String str) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public int setString(long pos, String str, int offset, int len) throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public OutputStream setAsciiStream(long pos) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Writer setCharacterStream(long pos) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public void truncate(long len) throws SQLException {}\n\n    @Override\n    public void free() throws SQLException {}\n\n    @Override\n    public Reader getCharacterStream(long pos, long length) throws SQLException {\n      return null;\n    }\n  }\n\n  class FakeSQLXML implements SQLXML {\n    @Override\n    public void free() throws SQLException {}\n\n    @Override\n    public InputStream getBinaryStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public OutputStream setBinaryStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Reader getCharacterStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public Writer setCharacterStream() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public String getString() throws SQLException {\n      return null;\n    }\n\n    @Override\n    public void setString(String value) throws SQLException {}\n\n    @Override\n    public <T extends Source> T getSource(Class<T> sourceClass) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public <T extends Result> T setResult(Class<T> resultClass) throws SQLException {\n      return null;\n    }\n  }\n\n  class FakeReader extends Reader {\n    @Override\n    public int read(char[] cbuf, int off, int len) throws IOException {\n      return 0;\n    }\n\n    @Override\n    public void close() throws IOException {}\n  }\n\n  class FakeInputStream extends InputStream {\n    @Override\n    public int read() throws IOException {\n      return 0;\n    }\n  }\n\n  class FakeCalendar extends Calendar {\n\n    @Override\n    protected void computeTime() {}\n\n    @Override\n    protected void computeFields() {}\n\n    @Override\n    public void add(int field, int amount) {}\n\n    @Override\n    public void roll(int field, boolean up) {}\n\n    @Override\n    public int getMinimum(int field) {\n      return 0;\n    }\n\n    @Override\n    public int getMaximum(int field) {\n      return 0;\n    }\n\n    @Override\n    public int getGreatestMinimum(int field) {\n      return 0;\n    }\n\n    @Override\n    public int getLeastMaximum(int field) {\n      return 0;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BaseJDBCWithSharedConnectionIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic class BaseJDBCWithSharedConnectionIT extends BaseJDBCTest {\n\n  protected static Connection connection;\n\n  @BeforeAll\n  public static void setUpConnection() throws SQLException {\n    connection = getConnection();\n  }\n\n  @AfterAll\n  public static void closeConnection() throws SQLException {\n    if (connection != null && !connection.isClosed()) {\n      connection.close();\n    }\n  }\n\n  public Statement createStatement(String queryResultFormat) throws SQLException {\n    Statement stmt = connection.createStatement();\n    stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    return stmt;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BaseWiremockTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AbstractDriverIT.getConnectionParameters;\nimport static net.snowflake.client.AssumptionUtils.assumeNotRunningOnGithubActionsMac;\nimport static net.snowflake.client.AssumptionUtils.assumeNotRunningOnJava21;\nimport static net.snowflake.client.AssumptionUtils.assumeNotRunningOnJava8;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.UnsupportedEncodingException;\nimport java.net.ServerSocket;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic abstract class BaseWiremockTest {\n\n  protected static final SFLogger logger = SFLoggerFactory.getLogger(BaseWiremockTest.class);\n  protected static final String WIREMOCK_HOME_DIR = \".wiremock\";\n  protected static final String WIREMOCK_M2_PATH =\n      \"/.m2/repository/org/wiremock/wiremock-standalone/3.8.0/wiremock-standalone-3.8.0.jar\";\n  protected static final String WIREMOCK_HOST = \"localhost\";\n  protected static final String TRUST_STORE_PROPERTY = \"javax.net.ssl.trustStore\";\n  protected static final String MAPPINGS_BASE_DIR = \"/wiremock/mappings\";\n  protected static int wiremockHttpPort;\n  protected static int wiremockHttpsPort;\n  private static String originalTrustStorePath;\n  protected static Process wiremockStandalone;\n\n  @BeforeAll\n  public static void setUpClass() {\n    assumeNotRunningOnJava8();\n    assumeNotRunningOnJava21();\n    assumeNotRunningOnGithubActionsMac(); // disabled until issue with access to localhost\n    // (https://github.com/snowflakedb/snowflake-jdbc/pull/1807#discussion_r1686229430) is fixed on\n    // github actions mac image. Ticket to enable when fixed: SNOW-1555950\n    originalTrustStorePath = systemGetProperty(TRUST_STORE_PROPERTY);\n    startWiremockStandAlone();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    restoreTrustStorePathProperty();\n    resetWiremock();\n    HttpUtil.reset();\n  }\n\n  @AfterAll\n  public static void tearDownClass() {\n    stopWiremockStandAlone(wiremockStandalone);\n  }\n\n  protected static void startWiremockStandAlone() {\n    // retrying in case of fail in port bindings\n    await()\n        .alias(\"wait for wiremock responding\")\n        .atMost(Duration.ofSeconds(10))\n        .until(\n            () -> {\n              try {\n                wiremockHttpPort = findFreePort();\n                wiremockHttpsPort = findFreePort();\n                wiremockStandalone = startWiremockProcess(wiremockHttpPort, wiremockHttpsPort);\n                waitForWiremockOnPort(wiremockHttpPort);\n                return true;\n              } catch (Exception e) {\n                logger.warn(\"Failed to start wiremock, retrying: \", e);\n                return false;\n              }\n            });\n  }\n\n  protected static Process startWiremockProcess(int wiremockHttpPort, int wiremockHttpsPort)\n      throws IOException {\n    String javaExecutable =\n        System.getProperty(\"java.home\") + File.separator + \"bin\" + File.separator + \"java\";\n    return new ProcessBuilder(\n            javaExecutable,\n            \"-jar\",\n            getWiremockStandAlonePath(),\n            \"--root-dir\",\n            System.getProperty(\"user.dir\") + File.separator + WIREMOCK_HOME_DIR + File.separator,\n            \"--enable-browser-proxying\", // work as forward proxy\n            \"--proxy-pass-through\",\n            \"true\",\n            \"--preserve-host-header\",\n            \"--port\",\n            String.valueOf(wiremockHttpPort),\n            \"--https-port\",\n            String.valueOf(wiremockHttpsPort),\n            \"--https-keystore\",\n            getResourceURL(\"wiremock\" + File.separator + \"ca-cert.jks\"),\n            \"--ca-keystore\",\n            getResourceURL(\"wiremock\" + File.separator + \"ca-cert.jks\"))\n        .inheritIO()\n        .start();\n  }\n\n  protected void resetWiremock() {\n    HttpPost postRequest;\n    postRequest = new HttpPost(\"http://\" + WIREMOCK_HOST + \":\" + getAdminPort() + \"/__admin/reset\");\n    try (CloseableHttpClient client = HttpClients.createDefault();\n        CloseableHttpResponse response = client.execute(postRequest)) {\n      assertEquals(200, response.getStatusLine().getStatusCode());\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private static String getWiremockStandAlonePath() {\n    return System.getProperty(\"user.home\") + WIREMOCK_M2_PATH;\n  }\n\n  protected static void waitForWiremockOnPort(int port) {\n    await()\n        .pollDelay(Duration.ofSeconds(1))\n        .atMost(Duration.ofSeconds(3))\n        .until(() -> isWiremockResponding(port));\n  }\n\n  private static boolean isWiremockResponding(int port) {\n    try (CloseableHttpClient httpClient = HttpClients.createDefault()) {\n      HttpGet request =\n          new HttpGet(String.format(\"http://%s:%d/__admin/mappings\", WIREMOCK_HOST, port));\n      CloseableHttpResponse response = httpClient.execute(request);\n      return response.getStatusLine().getStatusCode() == 200;\n    } catch (Exception e) {\n      logger.warn(\"Waiting for wiremock to respond: \", e);\n    }\n    return false;\n  }\n\n  protected static void stopWiremockStandAlone(Process wiremockStandalone) {\n    if (wiremockStandalone != null) {\n      wiremockStandalone.destroyForcibly();\n      await()\n          .alias(\"stop wiremock\")\n          .atMost(Duration.ofSeconds(10))\n          .until(() -> !wiremockStandalone.isAlive());\n    }\n  }\n\n  protected static int findFreePort() {\n    try {\n      ServerSocket socket = new ServerSocket(0);\n      int port = socket.getLocalPort();\n      socket.close();\n      return port;\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  protected Properties getProperties() {\n    Map<String, String> params = getConnectionParameters();\n    Properties props = new Properties();\n    props.put(\"host\", params.get(\"host\"));\n    props.put(\"port\", params.get(\"port\"));\n    props.put(\"account\", params.get(\"account\"));\n    props.put(\"user\", params.get(\"user\"));\n    props.put(\"role\", params.get(\"role\"));\n\n    // Handle authentication - prioritize private key, fallback to password\n    if (params.get(\"private_key_file\") != null) {\n      props.put(\"private_key_file\", params.get(\"private_key_file\"));\n      props.put(\"authenticator\", params.get(\"authenticator\"));\n      if (params.get(\"private_key_pwd\") != null) {\n        props.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n      }\n    } else if (params.get(\"password\") != null) {\n      props.put(\"password\", params.get(\"password\"));\n    }\n    props.put(\"warehouse\", params.get(\"warehouse\"));\n    props.put(\"db\", params.get(\"database\"));\n    props.put(\"schema\", params.get(\"schema\"));\n    props.put(\"ssl\", params.get(\"ssl\"));\n    props.put(\"insecureMode\", true); // OCSP disabled for wiremock proxy tests\n    return props;\n  }\n\n  protected HttpPost createWiremockPostRequest(String body, String path) {\n    return createWiremockPostRequest(body, path, getAdminPort());\n  }\n\n  protected HttpPost createWiremockPostRequest(String body, String path, int adminPort) {\n    HttpPost postRequest = new HttpPost(\"http://\" + WIREMOCK_HOST + \":\" + adminPort + path);\n    final StringEntity entity;\n    try {\n      entity = new StringEntity(body);\n    } catch (UnsupportedEncodingException e) {\n      throw new RuntimeException(e);\n    }\n    postRequest.setEntity(entity);\n    postRequest.setHeader(\"Accept\", \"application/json\");\n    postRequest.setHeader(\"Content-type\", \"application/json\");\n    return postRequest;\n  }\n\n  protected static void restoreTrustStorePathProperty() {\n    if (originalTrustStorePath != null) {\n      System.setProperty(TRUST_STORE_PROPERTY, originalTrustStorePath);\n    } else {\n      System.clearProperty(TRUST_STORE_PROPERTY);\n    }\n  }\n\n  private int getAdminPort() {\n    return wiremockHttpPort;\n  }\n\n  private static String getResourceURL(String relativePath) {\n    return Paths.get(systemGetProperty(\"user.dir\"), \"src\", \"test\", \"resources\", relativePath)\n        .toAbsolutePath()\n        .toString();\n  }\n\n  protected void setCustomTrustStorePropertyPath() {\n    System.setProperty(\n        TRUST_STORE_PROPERTY, getResourceURL(\"wiremock\" + File.separator + \"ca-cert.jks\"));\n  }\n\n  protected void importMapping(String mappingImport) {\n    HttpPost request = createWiremockPostRequest(mappingImport, \"/__admin/mappings/import\");\n    try (CloseableHttpClient httpClient = HttpClients.createDefault();\n        CloseableHttpResponse response = httpClient.execute(request)) {\n      Assumptions.assumeTrue(response.getStatusLine().getStatusCode() == 200);\n    } catch (Exception e) {\n      logger.error(\"Importing mapping failed\", e);\n      Assumptions.abort(\"Importing mapping failed\");\n    }\n  }\n\n  protected void addMapping(String mapping) {\n    HttpPost postRequest = createWiremockPostRequest(mapping, \"/__admin/mappings\");\n    try (CloseableHttpClient client = HttpClients.createDefault();\n        CloseableHttpResponse response = client.execute(postRequest)) {\n      assertEquals(201, response.getStatusLine().getStatusCode());\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  /**\n   * Reads JSON content from a file, supporting both absolute paths and classpath resources.\n   *\n   * @param filePath The file path (absolute or from the resources directory).\n   * @return JSON content as a String.\n   * @throws IOException If an error occurs while reading the file.\n   */\n  private String readJSONFromFile(String filePath) throws IOException {\n    // Check if the file exists as an absolute file path\n    Path sourceFilePath = Paths.get(filePath);\n    if (Files.exists(sourceFilePath)) {\n      return new String(Files.readAllBytes(sourceFilePath), StandardCharsets.UTF_8);\n    }\n\n    // If not found, attempt to read from the classpath (resources directory).\n    // Has to start from '/' followed by a subdirectory of the resources directory.\n    try (InputStream inputStream = getClass().getResourceAsStream(filePath)) {\n      if (inputStream == null) {\n        throw new IllegalStateException(\n            \"Could not find file under the specified path: \" + filePath);\n      }\n      try (InputStreamReader isr = new InputStreamReader(inputStream);\n          BufferedReader br = new BufferedReader(isr)) {\n        return br.lines().collect(Collectors.joining(\"\\n\"));\n      }\n    }\n  }\n\n  /**\n   * Reads JSON content from a file and replaces placeholders dynamically.\n   *\n   * @param mappingPath The path to the JSON file in the classpath.\n   * @param placeholdersMappings A map of placeholders to be replaced in the JSON content.\n   * @return The JSON content as a String with placeholders replaced.\n   * @throws IOException If an error occurs while reading the file.\n   */\n  protected String getWireMockMappingFromFile(\n      String mappingPath, Map<String, Object> placeholdersMappings) throws IOException {\n    String jsonContent = readJSONFromFile(mappingPath);\n\n    // Replace placeholders with actual values\n    for (Map.Entry<String, Object> entry : placeholdersMappings.entrySet()) {\n      jsonContent = jsonContent.replace(entry.getKey(), String.valueOf(entry.getValue()));\n    }\n    return jsonContent;\n  }\n\n  /** A minimal POJO representing a serve event from WireMock. */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  public static class MinimalServeEvent {\n    private MinimalRequest request;\n\n    public MinimalRequest getRequest() {\n      return request;\n    }\n\n    public void setRequest(MinimalRequest request) {\n      this.request = request;\n    }\n\n    @JsonIgnoreProperties(ignoreUnknown = true)\n    public static class MinimalRequest {\n      private String url;\n      private Date loggedDate;\n      private Map<String, MinimalQueryParameter> queryParams;\n\n      public String getUrl() {\n        return url;\n      }\n\n      public void setUrl(String url) {\n        this.url = url;\n      }\n\n      public Date getLoggedDate() {\n        return loggedDate;\n      }\n\n      public void setLoggedDate(Date loggedDate) {\n        this.loggedDate = loggedDate;\n      }\n\n      public Map<String, MinimalQueryParameter> getQueryParams() {\n        return queryParams;\n      }\n\n      public void setQueryParams(Map<String, MinimalQueryParameter> queryParams) {\n        this.queryParams = queryParams;\n      }\n    }\n\n    @JsonIgnoreProperties(ignoreUnknown = true)\n    public static class MinimalQueryParameter {\n      private List<String> values;\n\n      public List<String> getValues() {\n        return values;\n      }\n\n      public void setValues(List<String> values) {\n        this.values = values;\n      }\n\n      /** Returns the first value in the query parameter list, or null if no values are present. */\n      public String firstValue() {\n        return (values != null && !values.isEmpty()) ? values.get(0) : null;\n      }\n    }\n  }\n\n  /**\n   * A wrapper for the serve events JSON structure returned by WireMock. The JSON has a top-level\n   * \"requests\" field which is an array of serve events.\n   */\n  @JsonIgnoreProperties(ignoreUnknown = true)\n  public static class ServeEventsWrapper {\n    private List<MinimalServeEvent> requests = new ArrayList<>();\n\n    public List<MinimalServeEvent> getRequests() {\n      return requests;\n    }\n\n    public void setRequests(List<MinimalServeEvent> requests) {\n      this.requests = requests;\n    }\n  }\n\n  /**\n   * Retrieves all serve events recorded by WireMock by querying the admin endpoint. This\n   * implementation uses our minimal POJOs to avoid deserialization issues.\n   *\n   * <p>We have to use wiremock api endpoints to retrieve those events, because we are unable to\n   * import wiremock.stubbing.ServeEvent - it would cause tests to fail on Java8 - since it would be\n   * still imported during compilation.\n   *\n   * @return A list of MinimalServeEvent objects representing the requests WireMock has recorded.\n   */\n  protected List<MinimalServeEvent> getAllServeEvents() {\n    return getAllServeEvents(getAdminPort());\n  }\n\n  protected List<MinimalServeEvent> getAllServeEvents(int port) {\n    String url = \"http://\" + WIREMOCK_HOST + \":\" + port + \"/__admin/requests\";\n    try (CloseableHttpClient client = HttpClients.createDefault()) {\n      HttpGet request = new HttpGet(url);\n      try (CloseableHttpResponse response = client.execute(request)) {\n        String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);\n        ObjectMapper mapper = new ObjectMapper();\n        ServeEventsWrapper wrapper = mapper.readValue(json, ServeEventsWrapper.class);\n        return wrapper.getRequests();\n      }\n    } catch (Exception e) {\n      throw new RuntimeException(\"Failed to get serve events from WireMock\", e);\n    }\n  }\n\n  protected void importMappingFromResources(String relativePath) {\n    try (InputStream is = BaseWiremockTest.class.getResourceAsStream(relativePath)) {\n      String scenario = IOUtils.toString(Objects.requireNonNull(is), StandardCharsets.UTF_8.name());\n      importMapping(scenario);\n    } catch (Exception e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  /**\n   * Verifies the count of requests matching a given URL pattern.\n   *\n   * @param expectedCount The expected number of requests\n   * @param urlPattern The URL pattern to match against (supports regex)\n   */\n  protected void verifyRequestCount(int expectedCount, String urlPattern) {\n    String requestBody =\n        String.format(\"{\\\"method\\\": \\\"POST\\\", \\\"urlPattern\\\": \\\"%s\\\"}\", urlPattern);\n\n    HttpPost postRequest = createWiremockPostRequest(requestBody, \"/__admin/requests/count\");\n    try (CloseableHttpClient client = HttpClients.createDefault();\n        CloseableHttpResponse response = client.execute(postRequest)) {\n      String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);\n      ObjectMapper mapper = new ObjectMapper();\n      Map<String, String> countResponse =\n          mapper.readValue(json, new TypeReference<Map<String, String>>() {});\n      int actualCount = Integer.parseInt(countResponse.get(\"count\"));\n      assertEquals(\n          expectedCount,\n          actualCount,\n          String.format(\n              \"Expected %d requests matching pattern '%s', but found %d\",\n              expectedCount, urlPattern, actualCount));\n    } catch (Exception e) {\n      throw new RuntimeException(\"Failed to verify request count from WireMock\", e);\n    }\n  }\n\n  protected String getProxyProtocol(Properties props) {\n    return props.get(\"ssl\").toString().equals(\"on\") ? \"https\" : \"http\";\n  }\n\n  protected int getProxyPort(String proxyProtocol) {\n    if (Objects.equals(proxyProtocol, \"http\")) {\n      return wiremockHttpPort;\n    } else {\n      return wiremockHttpsPort;\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BatchExecutionIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.TestUtil.randomIntList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.BatchUpdateException;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class BatchExecutionIT extends BaseJDBCTest {\n  @Test\n  public void testClearingBatchAfterStatementExecution() throws SQLException {\n    String tableName = TestUtil.randomTableName(\"SNOW-1853752\");\n    int itemsInBatch = 3;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          String.format(\"CREATE OR REPLACE TABLE %s(id int, s varchar(2))\", tableName));\n      List<ThrowingCallable<Integer, SQLException>> executeMethods =\n          Arrays.asList(\n              () -> statement.executeBatch().length, () -> statement.executeLargeBatch().length);\n      for (ThrowingCallable<Integer, SQLException> executeMethod : executeMethods) {\n        for (int i : randomIntList(itemsInBatch, 10)) {\n          statement.addBatch(\n              String.format(\"INSERT INTO %s(id, s) VALUES (%d, 's%d')\", tableName, i, i));\n        }\n        assertEquals(itemsInBatch, executeMethod.call());\n        // default behaviour - batch is not cleared\n        assertEquals(itemsInBatch, executeMethod.call());\n        statement.clearBatch();\n        for (int i : randomIntList(itemsInBatch, 10)) {\n          statement.addBatch(\n              String.format(\n                  \"INSERT INTO %s(id, s) VALUES (%d, 'longer string %d')\", tableName, i, i));\n        }\n        assertThrows(BatchUpdateException.class, executeMethod::call);\n        // second call should also fail since batch should not be cleared\n        assertThrows(BatchUpdateException.class, executeMethod::call);\n        // clear batch for next execution in loop\n        statement.clearBatch();\n      }\n    }\n  }\n\n  /**\n   * ThrowingCallable is defined here since is not available in OldDriverTests from main package.\n   * Can be removed when OldDriver version is set to >=3.15.1\n   */\n  @FunctionalInterface\n  interface ThrowingCallable<A, T extends Throwable> {\n    A call() throws T;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BatchExecutionLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.TestUtil.randomIntList;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.BatchUpdateException;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.util.ThrowingCallable;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class BatchExecutionLatestIT extends BaseJDBCTest {\n  /** Available after version 3.21.0 */\n  @Test\n  public void testClearingBatchAfterStatementExecution() throws SQLException {\n    String tableName = TestUtil.randomTableName(\"SNOW-1853752\");\n    int itemsInBatch = 3;\n    Properties properties = new Properties();\n    properties.put(\"CLEAR_BATCH_ONLY_AFTER_SUCCESSFUL_EXECUTION\", true);\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          String.format(\"CREATE OR REPLACE TABLE %s(id int, s varchar(2))\", tableName));\n      List<ThrowingCallable<Integer, SQLException>> executeMethods =\n          Arrays.asList(\n              () -> statement.executeBatch().length, () -> statement.executeLargeBatch().length);\n      for (ThrowingCallable<Integer, SQLException> executeMethod : executeMethods) {\n        for (int i : randomIntList(itemsInBatch, 10)) {\n          statement.addBatch(\n              String.format(\"INSERT INTO %s(id, s) VALUES (%d, 's%d')\", tableName, i, i));\n        }\n        assertEquals(itemsInBatch, executeMethod.call());\n        assertEquals(0, executeMethod.call());\n        for (int i : randomIntList(itemsInBatch, 10)) {\n          statement.addBatch(\n              String.format(\n                  \"INSERT INTO %s(id, s) VALUES (%d, 'longer string %d')\", tableName, i, i));\n        }\n        assertThrows(BatchUpdateException.class, executeMethod::call);\n        // second call should also fail since batch should not be cleared\n        assertThrows(BatchUpdateException.class, executeMethod::call);\n        // clear batch for next execution in loop\n        statement.clearBatch();\n      }\n    }\n  }\n\n  /** Available after version 3.21.0 */\n  @Test\n  public void testClearingBatchAfterPreparedStatementExecutionWithArrayBinding()\n      throws SQLException {\n    String tableName = TestUtil.randomTableName(\"SNOW-1853752\");\n    int itemsInBatch = 3;\n    Properties properties = new Properties();\n    properties.put(\"CLEAR_BATCH_ONLY_AFTER_SUCCESSFUL_EXECUTION\", true);\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          String.format(\"CREATE OR REPLACE TABLE %s(id int, s varchar(2))\", tableName));\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\n              String.format(\"INSERT INTO %s(id, s) VALUES (?, ?)\", tableName))) {\n        List<ThrowingCallable<Integer, SQLException>> executeMethods =\n            Arrays.asList(\n                () -> preparedStatement.executeBatch().length,\n                () -> preparedStatement.executeLargeBatch().length);\n        for (ThrowingCallable<Integer, SQLException> executeMethod : executeMethods) {\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i);\n            preparedStatement.setString(2, \"s\" + i);\n            preparedStatement.addBatch();\n          }\n          assertEquals(itemsInBatch, executeMethod.call());\n          assertEquals(0, executeMethod.call());\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i * 10);\n            preparedStatement.setString(2, \"longer string \" + i);\n            preparedStatement.addBatch();\n          }\n          // With array binding there is SnowflakeSQLException thrown but BatchUpdateException is\n          // more expected - should we change?\n          assertThrows(SnowflakeSQLException.class, executeMethod::call);\n          // second call should also fail since batch should not be cleared\n          assertThrows(SnowflakeSQLException.class, executeMethod::call);\n          // clear batch for next execution in loop\n          preparedStatement.clearBatch();\n        }\n      }\n    }\n  }\n\n  /** Available after version 3.21.0 */\n  @Test\n  @Disabled(\"TODO SNOW-1878297 NULLIF should cause turning off array binding on server side\")\n  public void testClearingBatchAfterPreparedStatementExecutionWithoutArrayBinding()\n      throws SQLException {\n    String tableName = TestUtil.randomTableName(\"SNOW-1853752\");\n    int itemsInBatch = 3;\n    Properties properties = new Properties();\n    properties.put(\"CLEAR_BATCH_ONLY_AFTER_SUCCESSFUL_EXECUTION\", true);\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          String.format(\"CREATE OR REPLACE TABLE %s(id int, s varchar(2))\", tableName));\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\n              // NULLIF prohibit array binding and forces calling sqls one by one\n              String.format(\"INSERT INTO %s(id, s) VALUES (?, NULLIF(?, ''))\", tableName))) {\n        List<ThrowingCallable<Integer, SQLException>> executeMethods =\n            Arrays.asList(\n                () -> preparedStatement.executeBatch().length,\n                () -> preparedStatement.executeLargeBatch().length);\n        for (ThrowingCallable<Integer, SQLException> executeMethod : executeMethods) {\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i);\n            preparedStatement.setString(2, \"s\" + i);\n            preparedStatement.addBatch();\n          }\n          assertEquals(itemsInBatch, executeMethod.call());\n          assertEquals(0, executeMethod.call());\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i * 10);\n            preparedStatement.setString(2, \"longer string \" + i);\n            preparedStatement.addBatch();\n          }\n          assertThrows(BatchUpdateException.class, executeMethod::call);\n          // second call should also fail since batch should not be cleared\n          assertThrows(BatchUpdateException.class, executeMethod::call);\n          // clear batch for next execution in loop\n          preparedStatement.clearBatch();\n        }\n      }\n    }\n  }\n\n  /** Available after version 3.13.31 when executeLargeBatch was fixed */\n  @Test\n  public void testClearingBatchAfterPreparedStatementExecutionWithArrayBindingDefaultBehaviour()\n      throws SQLException {\n    String tableName = TestUtil.randomTableName(\"SNOW-1853752\");\n    int itemsInBatch = 3;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          String.format(\"CREATE OR REPLACE TABLE %s(id int, s varchar(2))\", tableName));\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\n              String.format(\"INSERT INTO %s(id, s) VALUES (?, ?)\", tableName))) {\n        List<BatchExecutionIT.ThrowingCallable<Integer, SQLException>> executeMethods =\n            Arrays.asList(\n                () -> preparedStatement.executeBatch().length,\n                () -> preparedStatement.executeLargeBatch().length);\n        for (BatchExecutionIT.ThrowingCallable<Integer, SQLException> executeMethod :\n            executeMethods) {\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i);\n            preparedStatement.setString(2, \"s\" + i);\n            preparedStatement.addBatch();\n          }\n          assertEquals(itemsInBatch, executeMethod.call());\n          assertEquals(0, executeMethod.call());\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i * 10);\n            preparedStatement.setString(2, \"longer string \" + i);\n            preparedStatement.addBatch();\n          }\n          // With array binding there is SnowflakeSQLException thrown but BatchUpdateException is\n          // more expected - should we change?\n          assertThrows(SnowflakeSQLException.class, executeMethod::call);\n          // default behaviour - prepared statement batch is always cleared after execution\n          assertEquals(0, executeMethod.call());\n          // clear batch for next execution in loop\n          preparedStatement.clearBatch();\n        }\n      }\n    }\n  }\n\n  /** Available after version 3.13.31 when executeLargeBatch was fixed */\n  @Test\n  @Disabled(\"TODO SNOW-1878297 NULLIF should cause turning off array binding on server side\")\n  public void testClearingBatchAfterPreparedStatementExecutionWithoutArrayBindingDefaultBehaviour()\n      throws SQLException {\n    String tableName = TestUtil.randomTableName(\"SNOW-1853752\");\n    int itemsInBatch = 3;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          String.format(\"CREATE OR REPLACE TABLE %s(id int, s varchar(2))\", tableName));\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\n              // NULLIF prohibit array binding and forces calling sqls one by one\n              String.format(\"INSERT INTO %s(id, s) VALUES (?, NULLIF(?, ''))\", tableName))) {\n        List<BatchExecutionIT.ThrowingCallable<Integer, SQLException>> executeMethods =\n            Arrays.asList(\n                () -> preparedStatement.executeBatch().length,\n                () -> preparedStatement.executeLargeBatch().length);\n        for (BatchExecutionIT.ThrowingCallable<Integer, SQLException> executeMethod :\n            executeMethods) {\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i);\n            preparedStatement.setString(2, \"s\" + i);\n            preparedStatement.addBatch();\n          }\n          assertEquals(itemsInBatch, executeMethod.call());\n          assertEquals(0, executeMethod.call());\n          for (int i : randomIntList(itemsInBatch, 10)) {\n            preparedStatement.setInt(1, i * 10);\n            preparedStatement.setString(2, \"longer string \" + i);\n            preparedStatement.addBatch();\n          }\n          assertThrows(BatchUpdateException.class, executeMethod::call);\n          // default behaviour - prepared statement batch is always cleared after execution\n          assertEquals(0, executeMethod.call());\n          // clear batch for next execution in loop\n          preparedStatement.clearBatch();\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BindUploaderIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.bind.BindUploader;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class BindUploaderIT extends BaseJDBCTest {\n  BindUploader bindUploader;\n  Connection conn;\n  SFSession session;\n\n  TimeZone prevTimeZone; // store last time zone and restore after tests\n\n  // We don't insert to a table here, but the binds we test the upload with\n  // are constructed by SnowflakePreparedStatementImpl.\n  // The class first typechecks the query to determine whether array bind is\n  // supported, so we need this to force the class to construct array binds\n  private static final String createTableSQL =\n      \"create or replace table test_binduploader(col1 INTEGER, \"\n          + \"col2 INTEGER, col3 DOUBLE, col4 DOUBLE, col5 DOUBLE, col6 VARCHAR, col7 BINARY, \"\n          + \"col8 DATE, col9 TIME, col10 TIMESTAMP)\";\n  static final String dummyInsert = \"insert into test_binduploader VALUES(?,?,?,?,?,?,?,?,?,?)\";\n  private static final String deleteTableSQL = \"drop table if exists test_binduploader\";\n\n  static final Object[] row1 = {\n    42,\n    1234L,\n    12.34f,\n    12.34d,\n    new BigDecimal(42),\n    \"row1\",\n    new byte[] {-128, 127},\n    new Date(0),\n    new Time(0),\n    new Timestamp(0)\n  };\n  private static final Object[] row2 = {\n    420,\n    12340L,\n    120.34f,\n    120.34d,\n    new BigDecimal(420),\n    \"row2\",\n    new byte[] {127, -128},\n    new Date(1),\n    new Time(1),\n    new Timestamp(1)\n  };\n\n  static final String csv1 =\n      \"42,1234,12.34,12.34,42,row1,807F,1970-01-01,00:00:00.000000000,1970-01-01 00:00:00.000000000 Z\";\n  static final String csv2 =\n      \"420,12340,120.34,120.34,420,row2,7F80,1970-01-01,00:00:00.001000000,1970-01-01 00:00:00.001000000 Z\";\n\n  static final String STAGE_DIR = \"binduploaderit\";\n  static final String SELECT_FROM_STAGE =\n      \"select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from '@SYSTEM$BIND/\"\n          + STAGE_DIR\n          + \"' ORDER BY $1 ASC\";\n\n  @BeforeAll\n  public static void classSetUp() throws Exception {\n    Connection connection = getConnection();\n    connection.createStatement().execute(createTableSQL);\n    connection.close();\n  }\n\n  @AfterAll\n  public static void classTearDown() throws Exception {\n    Connection connection = getConnection();\n    connection.createStatement().execute(deleteTableSQL);\n    connection.close();\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    conn = getConnection();\n    session = conn.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    bindUploader = BindUploader.newInstance(session, STAGE_DIR);\n    prevTimeZone = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    conn.close();\n    bindUploader.close();\n    TimeZone.setDefault(prevTimeZone);\n  }\n\n  static Map<String, ParameterBindingDTO> getBindings(Connection conn) throws SQLException {\n    SnowflakePreparedStatementImpl stmt =\n        (SnowflakePreparedStatementImpl) conn.prepareStatement(dummyInsert);\n    bind(stmt, row1);\n    bind(stmt, row2);\n    return stmt.getBatchParameterBindings();\n  }\n\n  static void bind(SnowflakePreparedStatementImpl stmt, Object[] row) throws SQLException {\n    stmt.setInt(1, (int) row[0]);\n    stmt.setLong(2, (long) row[1]);\n    stmt.setFloat(3, (float) row[2]);\n    stmt.setDouble(4, (double) row[3]);\n    stmt.setBigDecimal(5, (BigDecimal) row[4]);\n    stmt.setString(6, (String) row[5]);\n    stmt.setBytes(7, (byte[]) row[6]);\n    stmt.setDate(8, (Date) row[7]);\n    stmt.setTime(9, (Time) row[8]);\n    stmt.setTimestamp(10, (Timestamp) row[9]);\n    stmt.addBatch();\n  }\n\n  static String parseRow(ResultSet rs) throws Exception {\n    StringBuilder sb = new StringBuilder();\n    for (int i = 1; i <= 10; i++) {\n      sb.append(rs.getString(i));\n      if (i != 10) {\n        sb.append(',');\n      }\n    }\n    return sb.toString();\n  }\n\n  @Test\n  public void testIsArrayBindEmpty() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    assertFalse(BindUploader.isArrayBind(map));\n  }\n\n  @Test\n  public void testIsArrayBindNull() {\n    assertFalse(BindUploader.isArrayBind(null));\n  }\n\n  @Test\n  public void testIsArrayBindNonArray() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    map.put(\"1\", new ParameterBindingDTO(\"\", \"string value\"));\n    assertFalse(BindUploader.isArrayBind(map));\n  }\n\n  @Test\n  public void testIsArrayBindEmptyArray() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    map.put(\"1\", new ParameterBindingDTO(\"\", new ArrayList<String>()));\n    assertTrue(BindUploader.isArrayBind(map));\n  }\n\n  @Test\n  public void testIsArrayBindNonEmptyArray() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    List<String> list = new ArrayList<>();\n    list.add(\"bind value\");\n    map.put(\"1\", new ParameterBindingDTO(\"\", list));\n    list.add(\"another bind value\");\n    assertTrue(BindUploader.isArrayBind(map));\n  }\n\n  @Test\n  public void tetArrayBindValueCountEmpty() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    assertEquals(0, BindUploader.arrayBindValueCount(map));\n  }\n\n  @Test\n  public void testArrayBindValueCountNull() {\n    assertEquals(0, BindUploader.arrayBindValueCount(null));\n  }\n\n  @Test\n  public void testArrayBindValueCountNonArray() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    map.put(\"1\", new ParameterBindingDTO(\"\", \"string value\"));\n    assertEquals(0, BindUploader.arrayBindValueCount(map));\n  }\n\n  @Test\n  public void testArrayBindValueCountEmptyArray() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    map.put(\"1\", new ParameterBindingDTO(\"\", new ArrayList<String>()));\n    assertEquals(0, BindUploader.arrayBindValueCount(map));\n  }\n\n  @Test\n  public void testArrayBindValueCountNonEmptyArray() {\n    Map<String, ParameterBindingDTO> map = new HashMap<>();\n    List<String> list = new ArrayList<>();\n    list.add(\"bind value\");\n    list.add(\"another bind value\");\n    map.put(\"1\", new ParameterBindingDTO(\"\", list));\n    assertEquals(2, BindUploader.arrayBindValueCount(map));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BindUploaderLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.SELECT_FROM_STAGE;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.STAGE_DIR;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.bind;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.csv1;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.csv2;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.dummyInsert;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.getBindings;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.parseRow;\nimport static net.snowflake.client.internal.jdbc.BindUploaderIT.row1;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.bind.BindUploader;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Bind Uploader tests for the latest JDBC driver. This doesn't work for the oldest supported\n * driver. Revisit this tests whenever bumping up the oldest supported driver to examine if the\n * tests still is not applicable. If it is applicable, move tests to BindUploaderIT so that both the\n * latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.OTHERS)\npublic class BindUploaderLatestIT extends BaseJDBCTest {\n  BindUploader bindUploader;\n  Connection conn;\n  SFSession session;\n  TimeZone prevTimeZone; // store last time zone and restore after tests\n\n  @BeforeAll\n  public static void classSetUp() throws Exception {\n    BindUploaderIT.classSetUp();\n  }\n\n  @AfterAll\n  public static void classTearDown() throws Exception {\n    BindUploaderIT.classTearDown();\n  }\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    conn = getConnection();\n    session = conn.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    bindUploader = BindUploader.newInstance(session, STAGE_DIR);\n    prevTimeZone = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    conn.close();\n    bindUploader.close();\n    TimeZone.setDefault(prevTimeZone);\n  }\n\n  // Test setting the input stream buffer size to be quite small and ensure that 2 files (1\n  // corresponding to each input stream) are created in internal stage.\n  @Test\n  public void testUploadedResultsMultiple() throws Exception {\n\n    // Get an estimate of how many bytes are in 1 of the rows being uploaded and set this value to\n    // be the input stream buffer size. For 2 similarly sized rows, we should now have 2 files.\n    int lengthOfOneRow = csv1.getBytes(\"UTF-8\").length;\n    bindUploader.setInputStreamBufferSize(lengthOfOneRow);\n    bindUploader.upload(getBindings(conn));\n    // assert 2 files were created on internal stage\n    assertEquals(2, bindUploader.getFileCount());\n    Statement stmt = conn.createStatement();\n    // assert that the results look proper\n    ResultSet rs = stmt.executeQuery(SELECT_FROM_STAGE);\n    rs.next();\n    assertEquals(csv1, parseRow(rs));\n    rs.next();\n    assertEquals(csv2, parseRow(rs));\n    assertFalse(rs.next());\n    rs.close();\n    stmt.close();\n  }\n\n  // Test single csv upload and successful parsing\n  @Test\n  public void testUploadedResultsSimple() throws Exception {\n    bindUploader.upload(getBindings(conn));\n\n    Statement stmt = conn.createStatement();\n    ResultSet rs = stmt.executeQuery(SELECT_FROM_STAGE);\n    rs.next();\n    assertEquals(csv1, parseRow(rs));\n    rs.next();\n    assertEquals(csv2, parseRow(rs));\n    assertFalse(rs.next());\n    rs.close();\n    stmt.close();\n  }\n\n  @Test\n  public void testUploadStreamLargeBatch() throws Exception {\n    // create large batch so total bytes transferred are about 10 times the size of input stream\n    // buffer\n    int batchSize = 1024 * 1024;\n    SnowflakePreparedStatementImpl stmt =\n        (SnowflakePreparedStatementImpl) conn.prepareStatement(dummyInsert);\n    for (int i = 0; i < batchSize; i++) {\n      bind(stmt, row1);\n    }\n    Map<String, ParameterBindingDTO> parameterBindings = stmt.getBatchParameterBindings();\n    bindUploader.upload(parameterBindings);\n    ResultSet rs = stmt.executeQuery(SELECT_FROM_STAGE);\n    for (int i = 0; i < batchSize; i++) {\n      rs.next();\n      assertEquals(csv1, parseRow(rs));\n    }\n    assertFalse(rs.next());\n    rs.close();\n    stmt.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BindingAndInsertingStructuredTypesLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Array;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.ZoneId;\nimport java.time.ZoneOffset;\nimport java.time.ZonedDateTime;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.internal.core.structs.SnowflakeObjectTypeFactories;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.AllTypesClass;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.SimpleClass;\nimport net.snowflake.client.providers.ResultFormatProvider;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.RESULT_SET)\npublic class BindingAndInsertingStructuredTypesLatestIT extends BaseJDBCTest {\n  public Connection init(ResultSetFormatType queryResultFormat) throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection(BaseJDBCTest.DONT_INJECT_SOCKET_TIMEOUT);\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_IN_CLIENT_RESPONSE = true\");\n      stmt.execute(\"alter session set IGNORE_CLIENT_VESRION_IN_STRUCTURED_TYPES_RESPONSE = true\");\n      stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_IN_BINDS = enable\");\n      stmt.execute(\"alter session set enable_structured_types_in_fdn_tables=true\");\n      stmt.execute(\"ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'\");\n      stmt.execute(\n          \"alter session set jdbc_query_result_format = '\"\n              + queryResultFormat.sessionParameterTypeValue\n              + \"'\");\n      if (queryResultFormat == ResultSetFormatType.NATIVE_ARROW) {\n        stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true\");\n        stmt.execute(\"alter session set FORCE_ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true\");\n      }\n    }\n    return conn;\n  }\n\n  @BeforeEach\n  public void setup() {\n    SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new);\n    SnowflakeObjectTypeFactories.register(AllTypesClass.class, AllTypesClass::new);\n  }\n\n  @AfterEach\n  public void clean() {\n    SnowflakeObjectTypeFactories.unregister(SimpleClass.class);\n    SnowflakeObjectTypeFactories.unregister(AllTypesClass.class);\n  }\n\n  // TODO Structured types feature exists only on QA environments\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteObject(ResultSetFormatType queryResultFormat) throws SQLException {\n    SimpleClass sc = new SimpleClass(\"text1\", 2);\n    SimpleClass sc2 = new SimpleClass(\"text2\", 3);\n    try (Connection connection = init(queryResultFormat)) {\n      Statement statement = connection.createStatement();\n      statement.execute(\n          \"CREATE OR REPLACE TABLE test_table (ob OBJECT(string varchar, intValue NUMBER))\");\n      try (SnowflakePreparedStatementImpl stmt =\n              (SnowflakePreparedStatementImpl)\n                  connection.prepareStatement(\"insert into test_table select ?\");\n          SnowflakePreparedStatementImpl stmt3 =\n              (SnowflakePreparedStatementImpl)\n                  connection.prepareStatement(\"SELECT ob FROM test_table where ob = ?\"); ) {\n\n        stmt.setObject(1, sc);\n        stmt.executeUpdate();\n\n        stmt.setObject(1, sc2);\n        stmt.executeUpdate();\n\n        stmt3.setObject(1, sc2);\n\n        try (ResultSet resultSet = stmt3.executeQuery()) {\n\n          resultSet.next();\n          SimpleClass object = resultSet.getObject(1, SimpleClass.class);\n          assertEquals(\"text2\", object.getString());\n          assertEquals(Integer.valueOf(\"3\"), object.getIntValue());\n          assertFalse(resultSet.next());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteNullObject(ResultSetFormatType queryResultFormat) throws SQLException {\n    Assumptions.assumeTrue(queryResultFormat != ResultSetFormatType.NATIVE_ARROW);\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmtement2 =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"insert into test_table select null\");\n        SnowflakePreparedStatementImpl statement3 =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"SELECT * FROM test_table\"); ) {\n\n      statement.execute(\n          \"CREATE OR REPLACE TABLE test_table (ob OBJECT(string varchar, intValue NUMBER))\");\n\n      stmtement2.executeUpdate();\n\n      try (ResultSet resultSet = statement3.executeQuery()) {\n        assertTrue(resultSet.next());\n        assertNull(resultSet.getObject(1));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteObjectBindingNull(ResultSetFormatType queryResultFormat)\n      throws SQLException {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmt =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"insert into test_table select ?\");\n        SnowflakePreparedStatementImpl stmt2 =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"SELECT * FROM test_table\"); ) {\n      statement.execute(\n          \"CREATE OR REPLACE TABLE test_table (ob OBJECT(string varchar, intValue NUMBER))\");\n      stmt.setObject(1, null);\n      stmt.executeUpdate();\n      try (ResultSet resultSet = stmt2.executeQuery()) {\n        resultSet.next();\n        SimpleClass object = resultSet.getObject(1, SimpleClass.class);\n        assertNull(object);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteObjectAllTypes(ResultSetFormatType queryResultFormat) throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC));\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmt =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"insert into test_all_types_object select ?\");\n        SnowflakePreparedStatementImpl stmt2 =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"select * from test_all_types_object where ob=?\"); ) {\n\n      statement.execute(\n          \" CREATE OR REPLACE TABLE test_all_types_object (\"\n              + \"                 ob OBJECT(string VARCHAR, \"\n              + \"                  b TINYINT, \"\n              + \"                  s SMALLINT, \"\n              + \"                  i INTEGER, \"\n              + \"                  l BIGINT, \"\n              + \"                  f FLOAT, \"\n              + \"                  d DOUBLE, \"\n              + \"                  bd NUMBER(38,2), \"\n              + \"                  bool BOOLEAN, \"\n              + \"                  timestampLtz TIMESTAMP_LTZ, \"\n              + \"                  timestampNtz TIMESTAMP_NTZ, \"\n              + \"                  timestampTz TIMESTAMP_TZ, \"\n              + \"                  date DATE,\"\n              + \"                  time TIME, \"\n              + \"                   binary BINARY, \"\n              + \"                  simpleClass OBJECT(string VARCHAR, intValue INTEGER)\"\n              + \"                  ) )\");\n\n      AllTypesClass allTypeInstance =\n          new AllTypesClass(\n              \"string\",\n              \"1\".getBytes(StandardCharsets.UTF_8)[0],\n              Short.valueOf(\"2\"),\n              Integer.valueOf(3),\n              Long.valueOf(4),\n              1.1f,\n              2.24,\n              new BigDecimal(\"999999999999999999999999999999999999.55\"),\n              Boolean.TRUE,\n              Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)),\n              toTimestamp(ZonedDateTime.of(2021, 12, 23, 9, 44, 44, 0, ZoneId.of(\"UTC\"))),\n              toTimestamp(ZonedDateTime.of(2021, 12, 23, 9, 44, 44, 0, ZoneId.of(\"Asia/Tokyo\"))),\n              Date.valueOf(\"2023-12-24\"),\n              Time.valueOf(\"12:34:56\"),\n              new byte[] {'a', 'b', 'c'},\n              new SimpleClass(\"testString\", 2));\n      stmt.setObject(1, allTypeInstance);\n      stmt.executeUpdate();\n      statement.execute(\"ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'\");\n\n      stmt2.setObject(1, allTypeInstance);\n      try (ResultSet resultSet = stmt2.executeQuery()) {\n        resultSet.next();\n        AllTypesClass object = resultSet.getObject(1, AllTypesClass.class);\n        assertEquals(\"string\", object.getString());\n        assertEquals(49, (long) object.getB());\n        assertEquals(2, (long) object.getS());\n        assertEquals(3, (long) object.getI());\n        assertEquals(4, (long) object.getL());\n        assertEquals(1.1, (double) object.getF(), 0.01);\n        assertEquals(2.24, (double) object.getD(), 0.01);\n        assertEquals(new BigDecimal(\"999999999999999999999999999999999999.55\"), object.getBd());\n        assertEquals(Boolean.TRUE, object.getBool());\n        assertEquals(\n            Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)), object.getTimestampLtz());\n        assertEquals(\n            Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 9, 44, 44)), object.getTimestampNtz());\n        assertEquals(\n            toTimestamp(ZonedDateTime.of(2021, 12, 23, 9, 44, 44, 0, ZoneId.of(\"Asia/Tokyo\"))),\n            object.getTimestampTz());\n        // TODO uncomment after merge SNOW-928973: Date field is returning one day less when getting\n        // through getString method\n        //        assertEquals(Date.valueOf(LocalDate.of(2023, 12, 24)), object.getDate());\n        assertEquals(Time.valueOf(LocalTime.of(12, 34, 56)), object.getTime());\n        assertArrayEquals(new byte[] {'a', 'b', 'c'}, object.getBinary());\n        assertEquals(\"testString\", object.getSimpleClass().getString());\n        assertEquals(Integer.valueOf(\"2\"), object.getSimpleClass().getIntValue());\n      }\n    }\n  }\n\n  public static Timestamp toTimestamp(ZonedDateTime dateTime) {\n    return new Timestamp(dateTime.toInstant().getEpochSecond() * 1000L);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteArray(ResultSetFormatType queryResultFormat) throws SQLException {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmt =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\n                    \"INSERT INTO array_of_integers (arrayInt) SELECT ?;\"); ) {\n\n      statement.execute(\" CREATE OR REPLACE TABLE array_of_integers(arrayInt ARRAY(INTEGER))\");\n\n      Array array = connection.createArrayOf(\"INTEGER\", new Integer[] {1, 2, 3});\n      Array arrayFromLowerCaseType = connection.createArrayOf(\"integer\", new Integer[] {1, 2, 3});\n      stmt.setArray(1, array);\n      stmt.executeUpdate();\n      stmt.setArray(1, arrayFromLowerCaseType);\n      stmt.executeUpdate();\n\n      try (ResultSet resultSet = statement.executeQuery(\"SELECT * from array_of_integers\"); ) {\n        resultSet.next();\n\n        Long[] resultArray = (Long[]) resultSet.getArray(1).getArray();\n        assertEquals(Long.valueOf(1), resultArray[0]);\n        assertEquals(Long.valueOf(2), resultArray[1]);\n        assertEquals(Long.valueOf(3), resultArray[2]);\n\n        resultSet.next();\n        Long[] resultArrayFromLowerCaseType = (Long[]) resultSet.getArray(1).getArray();\n        assertArrayEquals(resultArray, resultArrayFromLowerCaseType);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteArrayNoBinds(ResultSetFormatType queryResultFormat) throws SQLException {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmt =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\n                    \"insert into array_of_integers select ([1, 2, 3]::array(integer));\"); ) {\n\n      statement.execute(\" CREATE OR REPLACE TABLE array_of_integers(arrayInt ARRAY(INTEGER))\");\n\n      stmt.executeUpdate();\n\n      try (ResultSet resultSet = statement.executeQuery(\"SELECT * from array_of_integers\"); ) {\n        resultSet.next();\n        Long[] resultArray = (Long[]) resultSet.getArray(1).getArray();\n        assertEquals(Long.valueOf(1), resultArray[0]);\n        assertEquals(Long.valueOf(2), resultArray[1]);\n        assertEquals(Long.valueOf(3), resultArray[2]);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteMapOfSqlData(ResultSetFormatType queryResultFormat) throws SQLException {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmt =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"INSERT INTO map_of_objects (mapp) SELECT ?;\");\n        SnowflakePreparedStatementImpl stmt2 =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"select * from map_of_objects where mapp=?\"); ) {\n\n      statement.execute(\n          \" CREATE OR REPLACE TABLE map_of_objects(mapp MAP(VARCHAR, OBJECT(string VARCHAR, intValue INTEGER)))\");\n\n      Map<String, SimpleClass> mapStruct =\n          Stream.of(\n                  new Object[][] {\n                    {\"x\", new SimpleClass(\"string1\", 1)},\n                    {\"y\", new SimpleClass(\"string2\", 2)},\n                  })\n              .collect(Collectors.toMap(data -> (String) data[0], data -> (SimpleClass) data[1]));\n\n      stmt.setMap(1, mapStruct, Types.STRUCT);\n      stmt.executeUpdate();\n\n      stmt2.setMap(1, mapStruct, Types.STRUCT);\n\n      try (ResultSet resultSet = stmt2.executeQuery()) {\n        resultSet.next();\n        Map<String, SimpleClass> map =\n            resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, SimpleClass.class);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testWriteMapOfInteger(ResultSetFormatType queryResultFormat) throws SQLException {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement();\n        SnowflakePreparedStatementImpl stmt =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"INSERT INTO map_of_objects (mapp) SELECT ?;\");\n        SnowflakePreparedStatementImpl stmt2 =\n            (SnowflakePreparedStatementImpl)\n                connection.prepareStatement(\"select * from map_of_objects where mapp=?\"); ) {\n\n      statement.execute(\" CREATE OR REPLACE TABLE map_of_objects(mapp MAP(VARCHAR, INTEGER))\");\n\n      Map<String, Integer> mapStruct = new HashMap<>();\n      mapStruct.put(\"x\", 1);\n      mapStruct.put(\"y\", 2);\n\n      stmt.setMap(1, mapStruct, Types.INTEGER);\n      stmt.executeUpdate();\n\n      stmt2.setMap(1, mapStruct, Types.INTEGER);\n\n      try (ResultSet resultSet = stmt2.executeQuery()) {\n        resultSet.next();\n        Map<String, Integer> map =\n            resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Integer.class);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BindingDataIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Types;\nimport java.util.Calendar;\nimport java.util.TimeZone;\nimport java.util.stream.Stream;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\nimport org.junit.jupiter.params.provider.ValueSource;\n\n/** Integration tests for binding variable */\n@Tag(TestTags.OTHERS)\npublic class BindingDataIT extends BaseJDBCWithSharedConnectionIT {\n  static TimeZone timeZone;\n\n  @BeforeAll\n  public static void setTimeZone() {\n    timeZone = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  @AfterAll\n  public static void resetTimeZone() {\n    TimeZone.setDefault(timeZone);\n  }\n\n  @ParameterizedTest\n  @ValueSource(shorts = {0, 1, -1, Short.MIN_VALUE, Short.MAX_VALUE})\n  public void testBindShort(short shortValue) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_short(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_short values (?)\")) {\n          preparedStatement.setShort(1, shortValue);\n          assertEquals(1, preparedStatement.executeUpdate());\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_short where c1 = ?\")) {\n          preparedStatement.setShort(1, shortValue);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getShort(\"C1\"), is(shortValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_short\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(shorts = {0, 1, -1, Short.MIN_VALUE, Short.MAX_VALUE})\n  public void testBindShortViaSetObject(short shortValue) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_short(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_short values (?)\")) {\n          preparedStatement.setObject(1, new Short(shortValue));\n          preparedStatement.executeUpdate();\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_short where c1 = ?\")) {\n          preparedStatement.setObject(1, new Short(shortValue));\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getShort(\"C1\"), is(shortValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_short\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(ints = {0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE})\n  public void testBindInt(int intValue) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_int(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_int values (?)\")) {\n          preparedStatement.setInt(1, intValue);\n          preparedStatement.executeUpdate();\n        }\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_int where c1 = ?\")) {\n          preparedStatement.setInt(1, intValue);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getInt(\"C1\"), is(intValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_int\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(bytes = {0, 1, -1, Byte.MAX_VALUE, Byte.MIN_VALUE})\n  public void testBindByte(byte byteValue) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_byte(c1 integer)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_byte values (?)\")) {\n          preparedStatement.setByte(1, byteValue);\n          preparedStatement.executeUpdate();\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_byte where c1 = ?\")) {\n          preparedStatement.setInt(1, byteValue);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getByte(\"C1\"), is(byteValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_byte\");\n      }\n    }\n  }\n\n  @Test\n  public void testBindNull() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_null(id number, val \" + \"number)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_null values (?, ?)\")) {\n          preparedStatement.setInt(1, 0);\n          preparedStatement.setBigDecimal(2, null);\n          preparedStatement.addBatch();\n\n          preparedStatement.setInt(1, 1);\n          preparedStatement.setNull(1, Types.INTEGER);\n          preparedStatement.addBatch();\n\n          preparedStatement.setInt(1, 2);\n          preparedStatement.setObject(1, null, Types.BIGINT);\n          preparedStatement.addBatch();\n\n          preparedStatement.setInt(1, 3);\n          preparedStatement.setObject(1, null, Types.BIGINT, 0);\n          preparedStatement.addBatch();\n\n          preparedStatement.executeBatch();\n\n          try (ResultSet rs =\n              statement.executeQuery(\"select * from test_bind_null \" + \"order by id asc\")) {\n            int count = 0;\n            while (rs.next()) {\n              assertThat(rs.getBigDecimal(\"VAL\"), is(nullValue()));\n              count++;\n            }\n\n            assertThat(count, is(4));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_null\");\n      }\n    }\n  }\n\n  static class TimeProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(Time.valueOf(\"00:00:00\")),\n          Arguments.of(Time.valueOf(\"12:34:56\")),\n          Arguments.of(Time.valueOf(\"12:00:00\")),\n          Arguments.of(Time.valueOf(\"11:59:59\")),\n          Arguments.of(Time.valueOf(\"15:30:00\")),\n          Arguments.of(Time.valueOf(\"13:01:01\")));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(TimeProvider.class)\n  public void testBindTime(Time timeVal) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_time(c1 time)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_time values (?)\")) {\n          preparedStatement.setTime(1, timeVal);\n          preparedStatement.executeUpdate();\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_time where c1 = ?\")) {\n          preparedStatement.setTime(1, timeVal);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getTime(\"C1\"), is(timeVal));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_time\");\n      }\n    }\n  }\n\n  /**\n   * Bind time with calendar is not supported now. Everything is in UTC, need to revisit in the\n   * future\n   */\n  @ParameterizedTest\n  @ArgumentsSource(TimeProvider.class)\n  public void testBindTimeWithCalendar(Time timeVal) throws SQLException {\n    Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n    Calendar laCal = Calendar.getInstance(TimeZone.getTimeZone(\"PST\"));\n\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_time_calendar(c1 \" + \"time)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_time_calendar values (?)\")) {\n          preparedStatement.setTime(1, timeVal, laCal);\n          preparedStatement.executeUpdate();\n        }\n\n        // bind time with UTC\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_time_calendar where c1 = ?\")) {\n          preparedStatement.setTime(1, timeVal, laCal);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getTime(\"C1\", utcCal), is(timeVal));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_time_calendar\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(TimeProvider.class)\n  public void testBindTimeViaSetObject(Time timeVal) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_time(c1 time)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_time values (?)\")) {\n          preparedStatement.setObject(1, timeVal, Types.TIME);\n          preparedStatement.executeUpdate();\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_time where c1 = ?\")) {\n          preparedStatement.setObject(1, timeVal, Types.TIME);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getTime(\"C1\"), is(timeVal));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_time\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(TimeProvider.class)\n  public void testBindTimeViaSetObjectCast(Time timeVal) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_time(c1 time)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_time values (?)\")) {\n          preparedStatement.setObject(1, timeVal);\n          preparedStatement.executeUpdate();\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_time where c1 = ?\")) {\n          preparedStatement.setObject(1, timeVal);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getTime(\"C1\"), is(timeVal));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_time\");\n      }\n    }\n  }\n\n  static class DateProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(Date.valueOf(\"2000-01-01\")),\n          Arguments.of(Date.valueOf(\"3000-01-01\")),\n          Arguments.of(Date.valueOf(\"1970-01-01\")),\n          Arguments.of(Date.valueOf(\"1969-01-01\")),\n          Arguments.of(Date.valueOf(\"1500-01-01\")),\n          Arguments.of(Date.valueOf(\"1400-01-01\")),\n          Arguments.of(Date.valueOf(\"1000-01-01\")));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DateProvider.class)\n  public void testBindDate(Date dateValue) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_date(c1 date)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_date values (?)\")) {\n          preparedStatement.setDate(1, dateValue);\n          preparedStatement.executeUpdate();\n        }\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_date where c1 = ?\")) {\n          preparedStatement.setDate(1, dateValue);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getDate(\"C1\"), is(dateValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_date\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DateProvider.class)\n  public void testBindDateWithCalendar(Date dateValue) throws SQLException {\n    Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_date(c1 date)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_date values (?)\")) {\n          preparedStatement.setDate(1, dateValue, calendar);\n          preparedStatement.executeUpdate();\n        }\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_date where c1 = ?\")) {\n          preparedStatement.setDate(1, dateValue, calendar);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getDate(\"C1\"), is(dateValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_date\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(ints = {0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE})\n  public void testBindObjectWithScaleZero(int intValue) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_bind_object_0(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_bind_object_0 values (?)\")) {\n          preparedStatement.setObject(1, intValue, Types.NUMERIC, 0);\n          preparedStatement.executeUpdate();\n        }\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_bind_object_0 where c1 = ?\")) {\n          preparedStatement.setObject(1, intValue, Types.NUMERIC, 0);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            assertThat(resultSet.getInt(\"C1\"), is(intValue));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_bind_object_0\");\n      }\n    }\n  }\n\n  /** Binding null as all types. */\n  @Test\n  public void testBindNullForAllTypes() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace table TEST_BIND_ALL_TYPES(C0 string,\"\n              + \"C1 number(20, 3), C2 INTEGER, C3 double, C4 varchar(1000),\"\n              + \"C5 string, C6 date, C7 time, C8 timestamp_ntz, \"\n              + \"C9 timestamp_ltz, C10 timestamp_tz,\"\n              + \"C11 BINARY, C12 BOOLEAN)\");\n\n      for (SnowflakeTypeHelper.JavaSQLType t : SnowflakeTypeHelper.JavaSQLType.ALL_TYPES) {\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"insert into TEST_BIND_ALL_TYPES values(?, ?,?,?, ?,?,?, ?,?,?, ?,?,?)\")) {\n          preparedStatement.setString(1, t.toString());\n          for (int i = 2; i <= 13; ++i) {\n            preparedStatement.setNull(i, t.getType());\n          }\n          preparedStatement.executeUpdate();\n        }\n      }\n\n      try (ResultSet result = statement.executeQuery(\"select * from TEST_BIND_ALL_TYPES\")) {\n        while (result.next()) {\n          String testType = result.getString(1);\n          for (int i = 2; i <= 13; ++i) {\n            assertNull(result.getString(i), String.format(\"Java Type: %s is not null\", testType));\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/BindingDataLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Array;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.ZoneId;\nimport java.time.ZonedDateTime;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.util.SFPair;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\n/**\n * Binding Data integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the version of oldest supported driver\n * to examine if the tests still are not applicable. If it is applicable, move tests to\n * BindingDataIT so that both the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.OTHERS)\npublic class BindingDataLatestIT extends AbstractDriverIT {\n  TimeZone origTz = TimeZone.getDefault();\n  TimeZone tokyoTz = TimeZone.getTimeZone(\"Asia/Tokyo\");\n  TimeZone australiaTz = TimeZone.getTimeZone(\"Australia/Sydney\");\n  Calendar tokyo = Calendar.getInstance(tokyoTz);\n\n  List<Time> times =\n      Arrays.asList(\n          Time.valueOf(\"00:00:00\"),\n          Time.valueOf(\"11:59:59\"),\n          Time.valueOf(\"12:00:00\"),\n          Time.valueOf(\"12:34:56\"),\n          Time.valueOf(\"13:01:01\"),\n          Time.valueOf(\"15:30:00\"),\n          Time.valueOf(\"23:59:59\"));\n\n  List<SFPair<String, Date>> gregorianJulianDates =\n      Arrays.asList(\n          SFPair.of(\"0001-01-01\", Date.valueOf(\"0001-01-01\")),\n          SFPair.of(\"0100-03-01\", Date.valueOf(\"0100-03-01\")),\n          SFPair.of(\"0400-02-29\", Date.valueOf(\"0400-02-29\")),\n          SFPair.of(\"0400-03-01\", Date.valueOf(\"0400-03-01\")),\n          SFPair.of(\"1400-03-01\", Date.valueOf(\"1400-03-01\")),\n          SFPair.of(\"1582-10-15\", Date.valueOf(\"1582-10-15\")),\n          SFPair.of(\"1900-02-28\", Date.valueOf(\"1900-02-28\")),\n          SFPair.of(\"1900-03-01\", Date.valueOf(\"1900-03-01\")),\n          SFPair.of(\"1969-12-31\", Date.valueOf(\"1969-12-31\")),\n          SFPair.of(\"1970-01-01\", Date.valueOf(\"1970-01-01\")),\n          SFPair.of(\"2000-02-28\", Date.valueOf(\"2000-02-28\")),\n          SFPair.of(\"2000-02-29\", Date.valueOf(\"2000-02-29\")),\n          SFPair.of(\"2000-03-01\", Date.valueOf(\"2000-03-01\")),\n          SFPair.of(\"2023-10-26\", Date.valueOf(\"2023-10-26\")),\n          SFPair.of(\"2024-02-29\", Date.valueOf(\"2024-02-29\")));\n\n  @Test\n  public void testBindTimestampTZ() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table testBindTimestampTZ(cola int, colb timestamp_tz)\");\n      statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_TZ\");\n\n      long milliSeconds = System.currentTimeMillis();\n      Timestamp ts = new Timestamp(milliSeconds);\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into testBindTimestampTZ values (?, ?)\")) {\n        prepStatement.setInt(1, 123);\n        prepStatement.setTimestamp(2, ts, Calendar.getInstance(TimeZone.getTimeZone(\"EST\")));\n        prepStatement.execute();\n      }\n\n      try (ResultSet resultSet =\n          statement.executeQuery(\"select cola, colb from testBindTimestampTz\")) {\n        assertTrue(resultSet.next());\n        assertThat(\"integer\", resultSet.getInt(1), equalTo(123));\n        assertThat(\"timestamp_tz\", resultSet.getTimestamp(2), equalTo(ts));\n      }\n    }\n  }\n\n  /**\n   * Test that stage binding and regular binding insert and return the same value for timestamp_ntz\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testTimestampBindingWithNTZType() throws SQLException {\n    TimeZone.setDefault(tokyoTz);\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table stageinsert(ind int, ltz0 timestamp_ltz, tz0 timestamp_tz, ntz0 timestamp_ntz)\");\n        statement.execute(\n            \"create or replace table regularinsert(ind int, ltz0 timestamp_ltz, tz0 timestamp_tz, ntz0 timestamp_ntz)\");\n        statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_NTZ\");\n        statement.execute(\"alter session set TIMEZONE='Asia/Tokyo'\");\n        Timestamp currT = new Timestamp(System.currentTimeMillis());\n\n        // insert using regular binging\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"insert into regularinsert values (?,?,?,?)\")) {\n          prepStatement.setInt(1, 1);\n          prepStatement.setTimestamp(2, currT, tokyo);\n          prepStatement.setTimestamp(3, currT, tokyo);\n          prepStatement.setTimestamp(4, currT);\n          prepStatement.addBatch();\n          prepStatement.executeBatch();\n        }\n        // insert using stage binding\n        statement.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n        executePsStatementForTimestampTest(connection, \"stageinsert\", currT);\n\n        // Compare the results\n        try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsert\");\n            ResultSet rs2 = statement.executeQuery(\"select * from regularinsert\")) {\n          assertTrue(rs1.next());\n          assertTrue(rs2.next());\n\n          assertEquals(rs1.getInt(1), rs2.getInt(1));\n\n          // Check tz type and ltz type columns have the same value.\n          assertEquals(rs1.getTimestamp(2), rs1.getTimestamp(3));\n\n          assertEquals(rs1.getTimestamp(2), rs2.getTimestamp(2));\n          assertEquals(rs1.getTimestamp(3), rs2.getTimestamp(3));\n          assertEquals(rs1.getTimestamp(4), rs2.getTimestamp(4));\n        }\n      } finally {\n        statement.execute(\"drop table if exists stageinsert\");\n        statement.execute(\"drop table if exists regularinsert\");\n        TimeZone.setDefault(origTz);\n      }\n    }\n  }\n\n  /**\n   * Test that stage binding and regular binding insert and return the same value for timestamp_ltz\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testTimestampBindingWithLTZType() throws SQLException {\n    TimeZone.setDefault(tokyoTz);\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table stageinsert(ind int, ltz0 timestamp_ltz, tz0 timestamp_tz, ntz0 timestamp_ntz)\");\n        statement.execute(\n            \"create or replace table regularinsert(ind int, ltz0 timestamp_ltz, tz0 timestamp_tz, ntz0 timestamp_ntz)\");\n        statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_LTZ\");\n        statement.execute(\"alter session set TIMEZONE='Asia/Tokyo'\");\n        Timestamp currT = new Timestamp(System.currentTimeMillis());\n\n        // insert using regular binging\n        executePsStatementForTimestampTest(connection, \"regularinsert\", currT);\n\n        // insert using stage binding\n        statement.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n        executePsStatementForTimestampTest(connection, \"stageinsert\", currT);\n\n        // Compare the results\n        try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsert\");\n            ResultSet rs2 = statement.executeQuery(\"select * from regularinsert\")) {\n          assertTrue(rs1.next());\n          assertTrue(rs2.next());\n\n          assertEquals(rs1.getInt(1), rs2.getInt(1));\n\n          // Check that all the values are the same.\n          assertEquals(rs1.getTimestamp(2), rs1.getTimestamp(3));\n          assertEquals(rs1.getTimestamp(3), rs1.getTimestamp(4));\n\n          assertEquals(rs1.getTimestamp(2), rs2.getTimestamp(2));\n          assertEquals(rs1.getTimestamp(3), rs2.getTimestamp(3));\n          assertEquals(rs1.getTimestamp(4), rs2.getTimestamp(4));\n        }\n      } finally {\n        statement.execute(\"drop table if exists stageinsert\");\n        statement.execute(\"drop table if exists regularinsert\");\n        TimeZone.setDefault(origTz);\n      }\n    }\n  }\n\n  /**\n   * Test that stage binding and regular binding insert and return the same value for timestamp_ltz\n   * when the local timezone has the daylight saving. This test is added in version > 3.16.1\n   *\n   * <p>When CLIENT_TIMESTAMP_TYPE_MAPPING setting is mismatched with target data type (e.g\n   * MAPPING=LTZ and insert to NTZ or MAPPING=NTZ and insert to TZ/LTZ there could be different\n   * result as the timezone offset is applied on client side and removed on server side. This only\n   * occurs around the boundary of daylight-savings and the difference from the source data would be\n   * one hour. Both regular binding and stage binding have such issue but they also behave\n   * diffently, for some data only regular binding gets the extra hour while sometime only stage\n   * binding does. The workaround is to use CLIENT_TIMESTAMP_TYPE_MAPPING=LTZ to insert LTZ/TZ data\n   * and use CLIENT_TIMESTAMP_TYPE_MAPPING=NTZ to insert NTZ data.\n   *\n   * <p>This test cannot run on the GitHub testing because of the \"ALTER SESSION SET\n   * CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\" This command should be executed with the system admin.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testTimestampBindingWithLTZTypeForDayLightSavingTimeZone() throws SQLException {\n    Calendar australia = Calendar.getInstance(australiaTz);\n    TimeZone.setDefault(australiaTz);\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table stageinsert(ind int, ltz0 timestamp_ltz, ltz1 timestamp_ltz, ltz2 timestamp_ltz, tz0 timestamp_tz, tz1 timestamp_tz, tz2 timestamp_tz, ntz0 timestamp_ntz, ntz1 timestamp_ntz, ntz2 timestamp_ntz)\");\n        statement.execute(\n            \"create or replace table regularinsert(ind int, ltz0 timestamp_ltz, ltz1 timestamp_ltz, ltz2 timestamp_ltz, tz0 timestamp_tz, tz1 timestamp_tz, tz2 timestamp_tz, ntz0 timestamp_ntz, ntz1 timestamp_ntz, ntz2 timestamp_ntz)\");\n        statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_LTZ\");\n        statement.execute(\"alter session set TIMEZONE='UTC'\");\n\n        Timestamp ts1 = new Timestamp(1403049600000L);\n        Timestamp ts2 = new Timestamp(1388016000000L);\n        Timestamp ts3 = new Timestamp(System.currentTimeMillis());\n\n        // insert using regular binging\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"insert into regularinsert values (?,?,?,?,?,?,?,?,?,?)\")) {\n          prepStatement.setInt(1, 1);\n          prepStatement.setTimestamp(2, ts1);\n          prepStatement.setTimestamp(3, ts2);\n          prepStatement.setTimestamp(4, ts3);\n\n          prepStatement.setTimestamp(5, ts1);\n          prepStatement.setTimestamp(6, ts2);\n          prepStatement.setTimestamp(7, ts3);\n\n          prepStatement.setTimestamp(8, ts1, australia);\n          prepStatement.setTimestamp(9, ts2, australia);\n          prepStatement.setTimestamp(10, ts3, australia);\n\n          prepStatement.addBatch();\n          prepStatement.executeBatch();\n        }\n\n        // insert using stage binding\n        statement.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"insert into stageinsert values (?,?,?,?,?,?,?,?,?,?)\")) {\n          prepStatement.setInt(1, 1);\n          prepStatement.setTimestamp(2, ts1);\n          prepStatement.setTimestamp(3, ts2);\n          prepStatement.setTimestamp(4, ts3);\n\n          prepStatement.setTimestamp(5, ts1);\n          prepStatement.setTimestamp(6, ts2);\n          prepStatement.setTimestamp(7, ts3);\n\n          prepStatement.setTimestamp(8, ts1);\n          prepStatement.setTimestamp(9, ts2);\n          prepStatement.setTimestamp(10, ts3);\n\n          prepStatement.addBatch();\n          prepStatement.executeBatch();\n        }\n\n        // Compare the results\n        try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsert\");\n            ResultSet rs2 = statement.executeQuery(\"select * from regularinsert\")) {\n          assertTrue(rs1.next());\n          assertTrue(rs2.next());\n\n          assertEquals(rs1.getInt(1), rs2.getInt(1));\n          assertEquals(rs1.getTimestamp(2), rs2.getTimestamp(2));\n          assertEquals(rs1.getTimestamp(3), rs2.getTimestamp(3));\n          assertEquals(rs1.getTimestamp(4), rs2.getTimestamp(4));\n          assertEquals(rs1.getTimestamp(5), rs2.getTimestamp(5));\n          assertEquals(rs1.getTimestamp(6), rs2.getTimestamp(6));\n          assertEquals(rs1.getTimestamp(7), rs2.getTimestamp(7));\n          assertEquals(rs1.getTimestamp(8), rs2.getTimestamp(8));\n          assertEquals(rs1.getTimestamp(9), rs2.getTimestamp(9));\n          assertEquals(rs1.getTimestamp(10), rs2.getTimestamp(10));\n\n          assertEquals(ts1.getTime(), rs1.getTimestamp(2).getTime());\n          assertEquals(ts2.getTime(), rs1.getTimestamp(3).getTime());\n          assertEquals(ts3.getTime(), rs1.getTimestamp(4).getTime());\n          assertEquals(ts1.getTime(), rs1.getTimestamp(5).getTime());\n          assertEquals(ts2.getTime(), rs1.getTimestamp(6).getTime());\n          assertEquals(ts3.getTime(), rs1.getTimestamp(7).getTime());\n          assertEquals(ts1.getTime(), rs1.getTimestamp(8).getTime());\n          assertEquals(ts2.getTime(), rs1.getTimestamp(9).getTime());\n          assertEquals(ts3.getTime(), rs1.getTimestamp(10).getTime());\n\n          assertEquals(ts1.getTime(), rs2.getTimestamp(2).getTime());\n          assertEquals(ts2.getTime(), rs2.getTimestamp(3).getTime());\n          assertEquals(ts3.getTime(), rs2.getTimestamp(4).getTime());\n          assertEquals(ts1.getTime(), rs2.getTimestamp(5).getTime());\n          assertEquals(ts2.getTime(), rs2.getTimestamp(6).getTime());\n          assertEquals(ts3.getTime(), rs2.getTimestamp(7).getTime());\n          assertEquals(ts1.getTime(), rs2.getTimestamp(8).getTime());\n          assertEquals(ts2.getTime(), rs2.getTimestamp(9).getTime());\n          assertEquals(ts3.getTime(), rs2.getTimestamp(10).getTime());\n        }\n      } finally {\n        statement.execute(\"drop table if exists stageinsert\");\n        statement.execute(\"drop table if exists regularinsert\");\n        TimeZone.setDefault(origTz);\n      }\n    }\n  }\n\n  /**\n   * Test that binding TIMESTAMP_LTZ does not affect other date time bindings time zones as a side\n   * effect\n   *\n   * <p>This test cannot run on the GitHub testing because of the \"ALTER SESSION SET\n   * CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\" This command should be executed with the system admin.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testTimestampLtzBindingNoLongerBreaksOtherDatetimeBindings() throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(\"America/Chicago\"));\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table stageinsertdates(ind int, t1 timestamp_ltz, d1 date)\");\n        Date date1 =\n            new java.sql.Date(\n                ZonedDateTime.of(2016, 2, 19, 0, 0, 0, 0, ZoneId.systemDefault())\n                    .toInstant()\n                    .toEpochMilli());\n        Timestamp ts1 = new Timestamp(date1.getTime());\n        // insert using stage binding\n        statement.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"insert into stageinsertdates values (?,?, ?)\")) {\n          prepStatement.setInt(1, 1);\n          prepStatement.setTimestamp(2, ts1);\n          prepStatement.setDate(3, date1);\n          prepStatement.addBatch();\n          prepStatement.executeBatch();\n          prepStatement.getConnection().commit();\n        }\n\n        try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsertdates\")) {\n          assertTrue(rs1.next());\n\n          assertEquals(1, rs1.getInt(1));\n          assertEquals(ts1, rs1.getTimestamp(2));\n          assertEquals(date1, rs1.getDate(3));\n        }\n      } finally {\n        TimeZone.setDefault(origTz);\n      }\n    }\n  }\n\n  @Test\n  public void testGregorianJulianDateConversions() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table stageinsertdates(ind int, d1 date)\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into stageinsertdates values (?,?)\")) {\n        for (int i = 0; i < gregorianJulianDates.size(); i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.setDate(2, gregorianJulianDates.get(i).right);\n          prepStatement.addBatch();\n        }\n\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsertdates order by ind\")) {\n        for (int i = 0; i < gregorianJulianDates.size(); i++) {\n          assertTrue(rs1.next());\n          assertEquals(i, rs1.getInt(1));\n          assertEquals(gregorianJulianDates.get(i).left, rs1.getDate(2).toLocalDate().toString());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = {\"TIMESTAMP_LTZ\", \"TIMESTAMP_NTZ\"})\n  public void testGregorianJulianTimestampConversions(String timestampType) throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    List<Timestamp> gregorianJulianTimestamps =\n        Arrays.asList(\n            Timestamp.valueOf(\"0001-01-01 00:00:00\"),\n            Timestamp.valueOf(\"0100-03-01 00:00:00\"),\n            Timestamp.valueOf(\"0400-02-29 00:00:00\"),\n            Timestamp.valueOf(\"0400-03-01 00:00:00\"),\n            Timestamp.valueOf(\"1582-10-15 00:00:00\"),\n            Timestamp.valueOf(\"1900-02-28 00:00:00\"),\n            Timestamp.valueOf(\"1900-03-01 00:00:00\"),\n            Timestamp.valueOf(\"1969-12-31 00:00:00\"),\n            Timestamp.valueOf(\"1970-01-01 00:00:00\"),\n            Timestamp.valueOf(\"2000-02-28 00:00:00\"),\n            Timestamp.valueOf(\"2000-02-29 00:00:00\"),\n            Timestamp.valueOf(\"2000-03-01 00:00:00\"),\n            Timestamp.valueOf(\"2023-10-26 00:00:00\"),\n            Timestamp.valueOf(\"2024-02-29 00:00:00\"));\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace table stageinsertdates(ind int, d1 \" + timestampType + \")\");\n      statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=\" + timestampType);\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into stageinsertdates values (?,?)\")) {\n        for (int i = 0; i < gregorianJulianTimestamps.size(); i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.setTimestamp(2, gregorianJulianTimestamps.get(i));\n          prepStatement.addBatch();\n        }\n\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsertdates order by ind\")) {\n        for (int i = 0; i < gregorianJulianTimestamps.size(); i++) {\n          assertTrue(rs1.next());\n          assertEquals(i, rs1.getInt(1));\n          assertEquals(gregorianJulianTimestamps.get(i), rs1.getTimestamp(2));\n        }\n      }\n    } finally {\n      TimeZone.setDefault(origTz);\n    }\n  }\n\n  /**\n   * This test cannot run on the GitHub testing because of the \"ALTER SESSION SET\n   * CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\" This command should be executed with the system admin.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testGregorianJulianDateConversionsWithStageBindings() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table stageinsertdates(ind int, d1 date)\");\n      statement.execute(\"alter session set CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into stageinsertdates values (?,?)\")) {\n        for (int i = 0; i < gregorianJulianDates.size(); i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.setDate(2, gregorianJulianDates.get(i).right);\n          prepStatement.addBatch();\n        }\n\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsertdates order by ind\")) {\n        for (int i = 0; i < gregorianJulianDates.size(); i++) {\n          assertTrue(rs1.next());\n          assertEquals(i, rs1.getInt(1));\n          assertEquals(gregorianJulianDates.get(i).left, rs1.getDate(2).toLocalDate().toString());\n        }\n      }\n    }\n  }\n\n  /**\n   * This test cannot run on the GitHub testing because of the \"ALTER SESSION SET\n   * CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\" This command should be executed with the system admin.\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ValueSource(strings = {\"TIMESTAMP_LTZ\", \"TIMESTAMP_NTZ\"})\n  @DontRunOnGithubActions\n  public void testGregorianJulianTimestampConversionsWithStageBindings(String timestampType)\n      throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    List<Timestamp> gregorianJulianTimestamps =\n        Arrays.asList(\n            Timestamp.valueOf(\"0001-01-01 00:00:00\"),\n            Timestamp.valueOf(\"0100-03-01 00:00:00\"),\n            Timestamp.valueOf(\"0400-02-29 00:00:00\"),\n            Timestamp.valueOf(\"0400-03-01 00:00:00\"),\n            Timestamp.valueOf(\"1582-10-15 00:00:00\"),\n            Timestamp.valueOf(\"1900-02-28 00:00:00\"),\n            Timestamp.valueOf(\"1900-03-01 00:00:00\"),\n            Timestamp.valueOf(\"1969-12-31 00:00:00\"),\n            Timestamp.valueOf(\"1970-01-01 00:00:00\"),\n            Timestamp.valueOf(\"2000-02-28 00:00:00\"),\n            Timestamp.valueOf(\"2000-02-29 00:00:00\"),\n            Timestamp.valueOf(\"2000-03-01 00:00:00\"),\n            Timestamp.valueOf(\"2023-10-26 00:00:00\"),\n            Timestamp.valueOf(\"2024-02-29 00:00:00\"));\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace table stageinsertdates(ind int, d1 \" + timestampType + \")\");\n      statement.execute(\"alter session set CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n      statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=\" + timestampType);\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into stageinsertdates values (?,?)\")) {\n        for (int i = 0; i < gregorianJulianTimestamps.size(); i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.setTimestamp(2, gregorianJulianTimestamps.get(i));\n          prepStatement.addBatch();\n        }\n\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs1 = statement.executeQuery(\"select * from stageinsertdates order by ind\")) {\n        for (int i = 0; i < gregorianJulianTimestamps.size(); i++) {\n          assertTrue(rs1.next());\n          assertEquals(i, rs1.getInt(1));\n          assertEquals(gregorianJulianTimestamps.get(i), rs1.getTimestamp(2));\n        }\n      }\n    } finally {\n      TimeZone.setDefault(origTz);\n    }\n  }\n\n  @Test\n  public void testInsertTimeColumnAsWallClockTimeRegardlessOfTimezone() throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(\"Pacific/Honolulu\"));\n\n    Properties props = new Properties();\n    props.put(\"CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME\", true);\n    try (Connection connection = getConnection(DONT_INJECT_SOCKET_TIMEOUT, props, false, false);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table test_wall_clock_time(ind int, t1 time)\");\n      statement.execute(\"alter session set TIMEZONE='America/Los_Angeles';\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into test_wall_clock_time values (?,?)\")) {\n        for (int i = 0; i < times.size(); i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.setTime(2, times.get(i));\n          prepStatement.addBatch();\n        }\n\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs =\n          statement.executeQuery(\"select * from test_wall_clock_time order by ind\")) {\n        for (int i = 0; i < times.size(); i++) {\n          assertTrue(rs.next());\n          assertEquals(i, rs.getInt(1));\n          assertEquals(times.get(i).toLocalTime(), rs.getTime(2).toLocalTime());\n          // check if inserted time is wall clock time\n          assertEquals(times.get(i).toString(), rs.getString(2));\n        }\n      }\n    } finally {\n      TimeZone.setDefault(origTz);\n    }\n  }\n\n  /**\n   * This test cannot run on the GitHub testing because of the \"ALTER SESSION SET\n   * CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\" This command should be executed with the system admin.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testInsertTimeColumnAsWallClockTimeRegardlessOfTimezoneWithStageBinding()\n      throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(\"Pacific/Honolulu\"));\n\n    Properties props = new Properties();\n    props.put(\"CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME\", true);\n    try (Connection connection = getConnection(DONT_INJECT_SOCKET_TIMEOUT, props, false, false);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table test_wall_clock_time(ind int, t1 time)\");\n      statement.execute(\"alter session set TIMEZONE='America/Los_Angeles';\");\n      statement.execute(\"alter session set CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into test_wall_clock_time values (?,?)\")) {\n        for (int i = 0; i < times.size(); i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.setTime(2, times.get(i));\n          prepStatement.addBatch();\n        }\n\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs =\n          statement.executeQuery(\"select * from test_wall_clock_time order by ind\")) {\n        for (int i = 0; i < times.size(); i++) {\n          assertTrue(rs.next());\n          assertEquals(i, rs.getInt(1));\n          assertEquals(times.get(i).toLocalTime(), rs.getTime(2).toLocalTime());\n          // check if inserted time is wall clock time\n          assertEquals(times.get(i).toString(), rs.getString(2));\n        }\n      }\n    } finally {\n      TimeZone.setDefault(origTz);\n    }\n  }\n\n  /***\n   * Verifies that without enabling CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME time column gets shifted to UTC on insert\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testInsertTimeColumnNotAsWallClockTimeAsUtc() throws SQLException {\n    TimeZone.setDefault(TimeZone.getTimeZone(\"Pacific/Honolulu\"));\n\n    Properties props = new Properties();\n    props.put(\"CLIENT_TREAT_TIME_AS_WALL_CLOCK_TIME\", false);\n    try (Connection connection = getConnection(DONT_INJECT_SOCKET_TIMEOUT, props, false, false);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table test_wall_clock_time(ind int, t1 time)\");\n      statement.execute(\"alter session set TIMEZONE='America/Los_Angeles';\");\n\n      String localTimeValue = \"00:00:00\";\n      String utcTimeValue = \"10:00:00\"; // 00:00 in Pacific/Honolulu is 10:00 UTC\n      Time localTime = Time.valueOf(localTimeValue);\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into test_wall_clock_time values (?,?)\")) {\n        prepStatement.setInt(1, 0);\n        prepStatement.setTime(2, localTime);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n        prepStatement.getConnection().commit();\n      }\n\n      try (ResultSet rs =\n          statement.executeQuery(\"select * from test_wall_clock_time order by ind\")) {\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        // check if time gets shifted to UTC time on insert\n        assertEquals(utcTimeValue, rs.getString(2));\n      }\n    } finally {\n      TimeZone.setDefault(origTz);\n    }\n  }\n\n  @Nested\n  class BindingArrays {\n    @Test\n    @DontRunOnGithubActions\n    public void testBindArrayOfDates() throws SQLException {\n      try (Connection connection = getConnection()) {\n        Date date1 = Date.valueOf(\"2023-01-01\");\n        Date date2 = Date.valueOf(\"2023-01-02\");\n        Date date3 = Date.valueOf(\"2023-01-03\");\n\n        Date[] dates = new Date[] {date1, date2, date3};\n        PreparedStatement statement =\n            connection.prepareStatement(\"SELECT to_array(?)::ARRAY(DATE)\");\n\n        statement.setArray(1, connection.createArrayOf(\"DATE\", dates));\n        try (ResultSet rs = statement.executeQuery()) {\n          assertTrue(rs.next());\n          Array array = rs.getArray(1);\n          assertEquals(\"DATE\", array.getBaseTypeName());\n          assertEquals(Types.DATE, array.getBaseType());\n          Object[] result = (Object[]) array.getArray();\n          assertArrayEquals(dates, result);\n        }\n      }\n    }\n\n    private Connection getConnection() throws SQLException {\n      Connection conn = BaseJDBCTest.getConnection(BaseJDBCTest.DONT_INJECT_SOCKET_TIMEOUT);\n      try (Statement stmt = conn.createStatement()) {\n        stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_IN_CLIENT_RESPONSE = true\");\n        stmt.execute(\"alter session set IGNORE_CLIENT_VESRION_IN_STRUCTURED_TYPES_RESPONSE = true\");\n        stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_IN_BINDS = enable\");\n        stmt.execute(\"alter session set enable_structured_types_in_fdn_tables=true\");\n      }\n      return conn;\n    }\n  }\n\n  private void executePsStatementForTimestampTest(\n      Connection connection, String tableName, Timestamp timestamp) throws SQLException {\n    try (PreparedStatement prepStatement =\n        connection.prepareStatement(\"insert into \" + tableName + \" values (?,?,?,?)\")) {\n      prepStatement.setInt(1, 1);\n      prepStatement.setTimestamp(2, timestamp);\n      prepStatement.setTimestamp(3, timestamp);\n      prepStatement.setTimestamp(4, timestamp);\n      prepStatement.addBatch();\n      prepStatement.executeBatch();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/CallableStatementIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.net.URL;\nimport java.sql.CallableStatement;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.Calendar;\nimport java.util.HashMap;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class CallableStatementIT extends CallableStatementITBase {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareCall(String queryResultFormat) throws SQLException {\n    // test CallableStatement with no binding parameters\n    try (Connection connection = getConnection(queryResultFormat)) {\n      try (CallableStatement callableStatement = connection.prepareCall(\"call square_it(5)\")) {\n        assertThat(callableStatement.getParameterMetaData().getParameterCount(), is(0));\n      }\n      // test CallableStatement with 1 binding parameter\n      try (CallableStatement callableStatement = connection.prepareCall(\"call square_it(?)\")) {\n        // test that getParameterMetaData works with CallableStatement. At this point, it always\n        // returns\n        // the type as \"text.\"\n        assertThat(callableStatement.getParameterMetaData().getParameterType(1), is(Types.VARCHAR));\n        callableStatement.getParameterMetaData().getParameterTypeName(1);\n        assertThat(callableStatement.getParameterMetaData().getParameterTypeName(1), is(\"text\"));\n        callableStatement.setFloat(1, 7.0f);\n        try (ResultSet rs = callableStatement.executeQuery()) {\n          assertTrue(rs.next());\n          assertEquals(49.0f, rs.getFloat(1), 1.0f);\n        }\n      }\n      // test CallableStatement with 2 binding parameters\n      try (CallableStatement callableStatement = connection.prepareCall(\"call add_nums(?,?)\")) {\n        callableStatement.setDouble(1, 32);\n        callableStatement.setDouble(2, 15);\n        try (ResultSet rs = callableStatement.executeQuery()) {\n          assertTrue(rs.next());\n          assertEquals(47, rs.getDouble(1), .5);\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testFeatureNotSupportedException() throws Throwable {\n    try (Connection connection = getConnection()) {\n      CallableStatement callableStatement = connection.prepareCall(\"select ?\");\n      expectFeatureNotSupportedException(\n          () -> callableStatement.registerOutParameter(1, Types.INTEGER));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.registerOutParameter(1, Types.INTEGER, 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.registerOutParameter(1, Types.INTEGER, \"int\"));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.registerOutParameter(\"param_name\", Types.INTEGER));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.registerOutParameter(\"param_name\", Types.INTEGER, 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.registerOutParameter(\"param_name\", Types.INTEGER, \"int\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getArray(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getArray(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getBigDecimal(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getBigDecimal(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getBlob(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getBlob(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getBoolean(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getBoolean(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getByte(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getByte(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getBytes(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getBytes(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getCharacterStream(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getCharacterStream(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getClob(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getClob(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getDate(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getDate(1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getDate(\"param_name\", Calendar.getInstance()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getDate(1, Calendar.getInstance()));\n      expectFeatureNotSupportedException(() -> callableStatement.getDouble(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getDouble(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getFloat(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getFloat(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getInt(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getInt(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getLong(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getLong(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getNCharacterStream(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getNCharacterStream(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getNClob(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getNClob(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getNString(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getNString(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getObject(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getObject(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getObject(1, String.class));\n      expectFeatureNotSupportedException(() -> callableStatement.getObject(1, new HashMap<>()));\n      expectFeatureNotSupportedException(() -> callableStatement.getObject(\"param_name\"));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getObject(\"param_name\", String.class));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getObject(\"param_name\", new HashMap<>()));\n      expectFeatureNotSupportedException(() -> callableStatement.getRef(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getRef(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getRowId(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getRowId(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getShort(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getShort(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getSQLXML(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getSQLXML(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getString(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getString(1));\n      expectFeatureNotSupportedException(() -> callableStatement.getTime(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getTime(1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getTime(\"param_name\", Calendar.getInstance()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getTime(1, Calendar.getInstance()));\n      expectFeatureNotSupportedException(() -> callableStatement.getTimestamp(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getTimestamp(1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getTimestamp(\"param_name\", Calendar.getInstance()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.getTimestamp(1, Calendar.getInstance()));\n      expectFeatureNotSupportedException(() -> callableStatement.getURL(\"param_name\"));\n      expectFeatureNotSupportedException(() -> callableStatement.getURL(1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setAsciiStream(\"param_name\", new FakeInputStream()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setAsciiStream(\"param_name\", new FakeInputStream(), 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBigDecimal(\"param_name\", BigDecimal.ONE));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBinaryStream(\"param_name\", new FakeInputStream()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBinaryStream(\"param_name\", new FakeInputStream(), 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBinaryStream(\"param_name\", new FakeInputStream(), 5213L));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBlob(\"param_name\", new FakeInputStream()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBlob(\"param_name\", new FakeInputStream(), 1L));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBlob(\"param_name\", new FakeBlob()));\n      expectFeatureNotSupportedException(() -> callableStatement.setBoolean(\"param_name\", true));\n      expectFeatureNotSupportedException(() -> callableStatement.setByte(\"param_name\", (byte) 6));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setBytes(\"param_name\", \"bytes\".getBytes()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setCharacterStream(\"param_name\", new FakeReader()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setCharacterStream(\"param_name\", new FakeReader(), 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setCharacterStream(\"param_name\", new FakeReader(), 1L));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setClob(\"param_name\", new FakeReader()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setClob(\"param_name\", new FakeReader(), 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setClob(\"param_name\", new FakeNClob()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setDate(\"param_name\", Date.valueOf(\"2019-07-07\")));\n      expectFeatureNotSupportedException(\n          () ->\n              callableStatement.setDate(\n                  \"param_name\", Date.valueOf(\"2019-07-07\"), Calendar.getInstance()));\n      expectFeatureNotSupportedException(() -> callableStatement.setDouble(\"param_name\", 3.0));\n      expectFeatureNotSupportedException(() -> callableStatement.setFloat(\"param_name\", 3.0f));\n      expectFeatureNotSupportedException(() -> callableStatement.setInt(\"param_name\", 3));\n      expectFeatureNotSupportedException(() -> callableStatement.setLong(\"param_name\", 3L));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setNCharacterStream(\"param_name\", new FakeReader()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setNCharacterStream(\"param_name\", new FakeReader(), 1L));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setNClob(\"param_name\", new FakeNClob()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setNClob(\"param_name\", new FakeReader(), 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setNClob(\"param_name\", new FakeReader()));\n      expectFeatureNotSupportedException(() -> callableStatement.setNString(\"param_name\", \"test\"));\n      expectFeatureNotSupportedException(() -> callableStatement.setNull(\"param_name\", Types.NULL));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setNull(\"param_name\", Types.NULL, \"null\"));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setObject(\"param_name\", new Object()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setObject(\"param_name\", new Object(), Types.JAVA_OBJECT));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setObject(\"param_name\", new Object(), Types.JAVA_OBJECT, 2));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setRowId(\"param_name\", new FakeRowId()));\n      expectFeatureNotSupportedException(() -> callableStatement.setShort(\"param_name\", (short) 1));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setSQLXML(\"param_name\", new FakeSQLXML()));\n      expectFeatureNotSupportedException(() -> callableStatement.setString(\"param_name\", \"test\"));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setTime(\"param_name\", new Time(50)));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setTime(\"param_name\", new Time(50), Calendar.getInstance()));\n      expectFeatureNotSupportedException(\n          () -> callableStatement.setTimestamp(\"param_name\", new Timestamp(50)));\n      expectFeatureNotSupportedException(\n          () ->\n              callableStatement.setTimestamp(\n                  \"param_name\", new Timestamp(50), Calendar.getInstance()));\n      URL fakeURL = new URL(\"http://localhost:8888/\");\n      expectFeatureNotSupportedException(() -> callableStatement.setURL(1, fakeURL));\n      expectFeatureNotSupportedException(() -> callableStatement.wasNull());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/CallableStatementITBase.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\n\npublic class CallableStatementITBase extends BaseJDBCTest {\n  public static Connection getConnection() throws SQLException {\n    return BaseJDBCTest.getConnection();\n  }\n\n  public static Connection getConnection(String queryResultFormat) throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection();\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  private final String createStoredProcedure =\n      \"create or replace procedure square_it(num FLOAT) returns float not \"\n          + \"null language javascript as $$ return NUM * NUM; $$\";\n  private final String createSecondStoredProcedure =\n      \"create or replace procedure add_nums(x DOUBLE, y DOUBLE) \"\n          + \"returns double not null language javascript as $$ return X + Y; $$\";\n  private final String deleteStoredProcedure = \"drop procedure if exists square_it(FLOAT)\";\n  private final String deleteSecondStoredProcedure = \"drop procedure if exists add_nums(INT, INT)\";\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      statement.execute(createStoredProcedure);\n      statement.execute(createSecondStoredProcedure);\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      statement.execute(deleteStoredProcedure);\n      statement.execute(deleteSecondStoredProcedure);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/CallableStatementLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.CallableStatement;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Types;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeCallableStatementImpl;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class CallableStatementLatestIT extends CallableStatementITBase {\n\n  /**\n   * Test that function that removes curly brackets from outside of call statements works properly\n   */\n  @Test\n  public void testParseSqlEscapeSyntaxFunction() {\n    String[] callStatements = {\n      \"{call square_it(5)}\", \"call no_bracket_function(44)\", \"call {bracket_function(a=?)}\"\n    };\n    String[] expectedStatements = {\n      \"call square_it(5)\", \"call no_bracket_function(44)\", \"call {bracket_function(a=?)}\"\n    };\n    for (int i = 0; i < callStatements.length; i++) {\n      assertEquals(\n          expectedStatements[i],\n          SnowflakeCallableStatementImpl.parseSqlEscapeSyntax(callStatements[i]));\n    }\n  }\n\n  /**\n   * Test that prepareCall works the same as before with curly bracket syntax.\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareCallWithCurlyBracketSyntax(String queryResultFormat) throws SQLException {\n    // test CallableStatement with no binding parameters\n    try (Connection connection = getConnection(queryResultFormat)) {\n      try (CallableStatement callableStatement = connection.prepareCall(\"{call square_it(5)}\")) {\n        assertThat(callableStatement.getParameterMetaData().getParameterCount(), is(0));\n      }\n      // test CallableStatement with 1 binding parameter\n      try (CallableStatement callableStatement = connection.prepareCall(\"{call square_it(?)}\")) {\n        // test that getParameterMetaData works with CallableStatement. At this point, it always\n        // returns\n        // the type as \"text.\"\n        assertThat(callableStatement.getParameterMetaData().getParameterType(1), is(Types.VARCHAR));\n        callableStatement.getParameterMetaData().getParameterTypeName(1);\n        assertThat(callableStatement.getParameterMetaData().getParameterTypeName(1), is(\"text\"));\n        callableStatement.setFloat(1, 7.0f);\n        try (ResultSet rs = callableStatement.executeQuery()) {\n          assertTrue(rs.next());\n          assertEquals(49.0f, rs.getFloat(1), 1.0f);\n        }\n      }\n      // test CallableStatement with 2 binding parameters\n      try (CallableStatement callableStatement = connection.prepareCall(\"{call add_nums(?,?)}\")) {\n        callableStatement.setDouble(1, 32);\n        callableStatement.setDouble(2, 15);\n        try (ResultSet rs = callableStatement.executeQuery()) {\n          assertTrue(rs.next());\n          assertEquals(47, rs.getDouble(1), .5);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ChunkDownloaderS3RetryUrlLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class ChunkDownloaderS3RetryUrlLatestIT extends AbstractDriverIT {\n\n  private SFStatement sfStatement;\n  private SFBaseSession sfBaseSession;\n  private ChunkDownloadContext sfContext;\n\n  @BeforeEach\n  public void setup() throws SQLException, InterruptedException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      sfBaseSession = connection.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession();\n      sfStatement = statement.unwrap(SnowflakeStatementImpl.class).getSfStatement();\n      int rowCount = 170000;\n      try (ResultSet rs =\n          statement.executeQuery(\n              \"select randstr(100, random()) from table(generator(rowcount => \"\n                  + rowCount\n                  + \"))\")) {\n        List<SnowflakeResultSetSerializable> resultSetSerializables =\n            ((SnowflakeResultSet) rs).getResultSetSerializables(100 * 1024 * 1024);\n        SnowflakeResultSetSerializable resultSetSerializable = resultSetSerializables.get(0);\n        SnowflakeChunkDownloader downloader =\n            new SnowflakeChunkDownloader((SnowflakeResultSetSerializableV1) resultSetSerializable);\n        SnowflakeResultChunk chunk = downloader.getNextChunkToConsume();\n        String qrmk = ((SnowflakeResultSetSerializableV1) resultSetSerializable).getQrmk();\n        Map<String, String> chunkHeadersMap =\n            ((SnowflakeResultSetSerializableV1) resultSetSerializable).getChunkHeadersMap();\n        sfContext =\n            new ChunkDownloadContext(\n                downloader, chunk, qrmk, 0, chunkHeadersMap, 0, 0, 0, 7, sfBaseSession);\n      }\n    }\n  }\n\n  /**\n   * Tests that the driver does not modify the presigned-URL used to download result chunks from AWS\n   * S3 bucket\n   */\n  @Test\n  public void testParamsInRetryS3Url() throws Exception {\n    HttpGet getRequest = new HttpGet(new URIBuilder(sfContext.getResultChunk().getUrl()).build());\n    CloseableHttpClient httpClient =\n        HttpUtil.getHttpClient(sfContext.getChunkDownloader().getHttpClientSettingsKey(), null);\n    for (Map.Entry<String, String> entry : sfContext.getChunkHeadersMap().entrySet()) {\n      getRequest.addHeader(entry.getKey(), entry.getValue());\n    }\n    RestRequest.executeWithRetries(\n        httpClient,\n        getRequest,\n        sfContext.getNetworkTimeoutInMilli() / 1000,\n        sfContext.getAuthTimeout(),\n        sfContext.getSocketTimeout(),\n        1, // retry count\n        0, // inject socket timeout\n        null, // cancelling\n        false, // without cookies\n        false, // include retry parameters\n        false, // include request GUID\n        true,\n        false,\n        new ExecTimeTelemetryData(),\n        null, // retry HTTP 403\n        sfContext.getChunkDownloader().getHttpClientSettingsKey(),\n        null,\n        false);\n\n    assertFalse(getRequest.containsHeader(\"retryCount\"));\n    assertFalse(getRequest.containsHeader(\"retryReason\"));\n    assertFalse(getRequest.containsHeader(\"clientStartTime\"));\n    assertFalse(getRequest.containsHeader(\"request_guid\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ClientMemoryLimitParallelIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AbstractDriverIT.getConnection;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/** attempts to test the CLIENT_MEMORY_LIMIT working in multi-threading */\n@Tag(TestTags.OTHERS)\npublic class ClientMemoryLimitParallelIT extends BaseJDBCWithSharedConnectionIT {\n  private static Logger LOGGER =\n      LoggerFactory.getLogger(ClientMemoryLimitParallelIT.class.getName());\n\n  private static final int rowCount = 10000;\n  private static final String createTestTableSQL =\n      \"create or replace table testtable_cml \\n\"\n          + \"(c1 number, c2 number, c3 number, c4 number,\\n\"\n          + \"c5 number, c6 number, c7 number, c8 number,\\n\"\n          + \"c9 number, c10 number, c11 number, c12 number,\\n\"\n          + \"c13 text, c14 text, c15 text, c16 text, c17 text, c18 text, c19 text, \"\n          + \"c20 text, c21 text, c22 text,\\n\"\n          + \"c23 text, c24 text, c25 text, c26 text, c27 text, c28 text, c29 text, \"\n          + \"c30 text, c31 text, c32 text,\\n\"\n          + \"c33 text, c34 text, c35 text, c36 text, c37 text, c38 text, c39 text, \"\n          + \"c40 text, c41 text, c42 text,\\n\"\n          + \"c43 text, c44 text, c45 text, c46 text, c47 text, c48 text, c49 text, \"\n          + \"c50 text, c51 text, c52 text,\\n\"\n          + \"c53 text, c54 text, c55 text, c56 text, c57 text, c58 text, c59 text, \"\n          + \"c60 text)\\n\"\n          + \"as\\n\"\n          + \"select \\n\"\n          + \"seq1(), seq2()+10, seq4()+100, seq8()+1000, \\n\"\n          + \"seq1(), seq2()+10, seq4()+100, seq8()+1000, \\n\"\n          + \"seq1(), seq2()+10, seq4()+100, seq8()+1000, \\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100),\\n\"\n          + \"random(),random(1),random(10),random(100)\\n\"\n          + \"from table(generator(rowcount => \"\n          + rowCount\n          + \"));\";\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(createTestTableSQL);\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"drop table if exists testtable_cml\");\n    }\n  }\n\n  /**\n   * This test attempts to set small CLIENT_MEMORY_LIMIT and client chunk size to make sure no OOM\n   * in multi-threading\n   */\n  @Test\n  @Disabled(\"Long term high memory usage test\")\n  void testParallelQueries() throws Exception {\n    Runnable testQuery =\n        new Runnable() {\n          public void run() {\n            try {\n              Properties paramProperties = new Properties();\n              try (Connection connection = getConnection(paramProperties);\n                  Statement statement = connection.createStatement()) {\n                queryRows(statement, 100, 48);\n              }\n            } catch (SQLException e) {\n              // do not expect exception in test\n              assertEquals(null, e);\n            }\n          }\n        };\n    Thread t1 = new Thread(testQuery);\n    Thread t2 = new Thread(testQuery);\n    Thread t3 = new Thread(testQuery);\n    Thread t4 = new Thread(testQuery);\n    Thread t5 = new Thread(testQuery);\n\n    t1.start();\n    t2.start();\n    t3.start();\n    t4.start();\n    t5.start();\n\n    t1.join();\n    t2.join();\n    t3.join();\n    t4.join();\n    t5.join();\n  }\n\n  /**\n   * This test attempts to set CLIENT_MEMORY_LIMIT smaller than the maximum client chunk size and\n   * make sure there is no hanging\n   */\n  @Test\n  void testQueryNotHanging() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      queryRows(statement, 100, 160);\n    }\n  }\n\n  private static void queryRows(Statement stmt, int limit, int chunkSize) throws SQLException {\n    stmt.execute(\"alter session set CLIENT_MEMORY_LIMIT=\" + limit);\n    stmt.execute(\"alter session set CLIENT_RESULT_CHUNK_SIZE=\" + chunkSize);\n    String query;\n    query = \"select * from testtable_cml\";\n\n    try (ResultSet resultSet = stmt.executeQuery(query)) {\n\n      // fetch data\n      int rowIdx = 0;\n      while (resultSet.next()) {\n        rowIdx++;\n        if (rowIdx % 1000 == 0) {\n          LOGGER.info(Thread.currentThread().getName() + \": processedRows: \" + rowIdx);\n        }\n      }\n      assertEquals(rowIdx, rowCount);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/CompressedStreamFactoryTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.github.luben.zstd.ZstdInputStream;\nimport com.github.luben.zstd.ZstdOutputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.GZIPOutputStream;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.Header;\nimport org.apache.http.message.BasicHeader;\nimport org.junit.jupiter.api.Test;\n\npublic class CompressedStreamFactoryTest {\n\n  private final CompressedStreamFactory factory = new CompressedStreamFactory();\n\n  @Test\n  public void testDetectContentEncodingAndGetInputStream_Gzip() throws Exception {\n    // Original data to compress and validate\n    String originalData = \"Some data in GZIP\";\n\n    // Creating encoding header\n    Header encodingHeader = new BasicHeader(\"Content-Encoding\", \"gzip\");\n\n    // Creating a gzip byte array using GZIPOutputStream\n    byte[] gzipData;\n    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n        GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {\n      gzipOutputStream.write(originalData.getBytes(StandardCharsets.UTF_8));\n      gzipOutputStream.close(); // close to flush and finish the compression\n      gzipData = byteArrayOutputStream.toByteArray();\n    }\n\n    // Mocking input stream with the gzip data\n    InputStream gzipStream = new ByteArrayInputStream(gzipData);\n\n    // Call the private method using reflection\n    InputStream resultStream = factory.createBasedOnEncodingHeader(gzipStream, encodingHeader);\n\n    // Decompress and validate the data matches original\n    assertTrue(resultStream instanceof GZIPInputStream);\n    String decompressedData = IOUtils.toString(resultStream, StandardCharsets.UTF_8);\n    assertEquals(originalData, decompressedData);\n  }\n\n  @Test\n  public void testDetectContentEncodingAndGetInputStream_Zstd() throws Exception {\n    // Original data to compress and validate\n    String originalData = \"Some data in ZSTD\";\n\n    // Creating encoding header\n    Header encodingHeader = new BasicHeader(\"Content-Encoding\", \"zstd\");\n\n    // Creating a zstd byte array using ZstdOutputStream\n    byte[] zstdData;\n    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n        ZstdOutputStream zstdOutputStream = new ZstdOutputStream(byteArrayOutputStream)) {\n      zstdOutputStream.write(originalData.getBytes(StandardCharsets.UTF_8));\n      zstdOutputStream.close(); // close to flush and finish the compression\n      zstdData = byteArrayOutputStream.toByteArray();\n    }\n\n    // Mocking input stream with the zstd data\n    InputStream zstdStream = new ByteArrayInputStream(zstdData);\n\n    // Call the private method using reflection\n    InputStream resultStream = factory.createBasedOnEncodingHeader(zstdStream, encodingHeader);\n\n    // Decompress and validate the data matches original\n    assertTrue(resultStream instanceof ZstdInputStream);\n    String decompressedData = IOUtils.toString(resultStream, StandardCharsets.UTF_8);\n    assertEquals(originalData, decompressedData);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectStringParseTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport org.junit.jupiter.api.Test;\n\npublic class ConnectStringParseTest {\n  @Test\n  public void testParseAccountName() throws SnowflakeSQLException {\n    Properties info = new Properties();\n    info.setProperty(\"username\", \"test\");\n    String jdbcConnectString = \"jdbc:snowflake://abc.us-east-1.snowflakecomputing.com\";\n    SnowflakeConnectString cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertThat(\n        cstring.getParameters().get(SFSessionProperty.ACCOUNT.getPropertyKey().toUpperCase()),\n        is(\"abc\"));\n\n    // Hostname should be updated by default.\n    jdbcConnectString = \"jdbc:snowflake://abc_test.us-east-1.snowflakecomputing.com\";\n    cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertThat(\n        cstring.getParameters().get(SFSessionProperty.ACCOUNT.getPropertyKey().toUpperCase()),\n        is(\"abc_test\"));\n    assertThat(cstring.getHost(), is(\"abc-test.us-east-1.snowflakecomputing.com\"));\n\n    jdbcConnectString = \"jdbc:snowflake://abc-test.us-east-1.snowflakecomputing.com\";\n    cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertThat(\n        cstring.getParameters().get(SFSessionProperty.ACCOUNT.getPropertyKey().toUpperCase()),\n        is(\"abc-test\"));\n    assertThat(cstring.getHost(), is(\"abc-test.us-east-1.snowflakecomputing.com\"));\n\n    //  Host name should be updated if the parameter is set and it has underscores in it.\n    jdbcConnectString = \"jdbc:snowflake://abc_test.us-east-1.snowflakecomputing.com\";\n    info.setProperty(SFSessionProperty.ALLOW_UNDERSCORES_IN_HOST.getPropertyKey(), \"false\");\n    cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertThat(\n        cstring.getParameters().get(SFSessionProperty.ACCOUNT.getPropertyKey().toUpperCase()),\n        is(\"abc_test\"));\n    assertThat(cstring.getHost(), is(\"abc-test.us-east-1.snowflakecomputing.com\"));\n\n    // No change if hostname does not have underscores in it.\n    jdbcConnectString = \"jdbc:snowflake://abc-test.us-east-1.snowflakecomputing.com\";\n    cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertThat(\n        cstring.getParameters().get(SFSessionProperty.ACCOUNT.getPropertyKey().toUpperCase()),\n        is(\"abc-test\"));\n    assertThat(cstring.getHost(), is(\"abc-test.us-east-1.snowflakecomputing.com\"));\n\n    // The host URL should be updated whether the ACCOUNT property is set or not\n    info.setProperty(\"ACCOUNT\", \"abc_test\");\n    cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertThat(\n        cstring.getParameters().get(SFSessionProperty.ACCOUNT.getPropertyKey().toUpperCase()),\n        is(\"abc_test\"));\n    assertThat(cstring.getHost(), is(\"abc-test.us-east-1.snowflakecomputing.com\"));\n  }\n\n  @Test\n  public void testParseWithIllegalUriCharacters() {\n    Properties info = new Properties();\n    String jdbcConnectString =\n        \"jdbc:snowflake://abc-test.us-east-1.snowflakecomputing.com/?private_key_file=C:\\\\temp\\\\rsa_key.p8&private_key_file_pwd=test_password&user=test_user\";\n    SnowflakeConnectString cstring = SnowflakeConnectString.parse(jdbcConnectString, info);\n    assertEquals(\"://:-1\", cstring.toString());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionAlreadyClosedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.util.Properties;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class ConnectionAlreadyClosedIT extends BaseJDBCTest {\n\n  @Test\n  public void testClosedConnection() throws Throwable {\n    Connection connection = getConnection();\n    connection.close();\n\n    expectConnectionAlreadyClosedException(connection::getMetaData);\n    expectConnectionAlreadyClosedException(connection::getAutoCommit);\n    expectConnectionAlreadyClosedException(connection::commit);\n    expectConnectionAlreadyClosedException(connection::rollback);\n    expectConnectionAlreadyClosedException(connection::isReadOnly);\n    expectConnectionAlreadyClosedException(connection::getCatalog);\n    expectConnectionAlreadyClosedException(connection::getSchema);\n    expectConnectionAlreadyClosedException(connection::getTransactionIsolation);\n    expectConnectionAlreadyClosedException(connection::getWarnings);\n    expectConnectionAlreadyClosedException(connection::clearWarnings);\n    expectConnectionAlreadyClosedException(() -> connection.nativeSQL(\"select 1\"));\n    expectConnectionAlreadyClosedException(() -> connection.setAutoCommit(false));\n    expectConnectionAlreadyClosedException(() -> connection.setReadOnly(false));\n    expectConnectionAlreadyClosedException(() -> connection.setCatalog(\"fakedb\"));\n    expectConnectionAlreadyClosedException(() -> connection.setSchema(\"fakedb\"));\n    expectConnectionAlreadyClosedException(\n        () -> connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED));\n    expectSQLClientInfoException(() -> connection.setClientInfo(new Properties()));\n    expectSQLClientInfoException(() -> connection.setClientInfo(\"name\", \"value\"));\n\n    SnowflakeConnection sfConnection = connection.unwrap(SnowflakeConnection.class);\n    expectConnectionAlreadyClosedException(sfConnection::getRole);\n    expectConnectionAlreadyClosedException(sfConnection::getWarehouse);\n    expectConnectionAlreadyClosedException(sfConnection::getDatabase);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionFeatureNotSupportedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Savepoint;\nimport java.util.HashMap;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CONNECTION)\npublic class ConnectionFeatureNotSupportedIT extends BaseJDBCTest {\n  @Test\n  public void testFeatureNotSupportedException() throws Throwable {\n    try (Connection connection = getConnection()) {\n      expectFeatureNotSupportedException(() -> connection.rollback(new FakeSavepoint()));\n      expectFeatureNotSupportedException(\n          () -> connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE));\n      expectFeatureNotSupportedException(\n          () -> connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ));\n      expectFeatureNotSupportedException(\n          () -> connection.prepareStatement(\"select 1\", new int[] {1, 2}));\n      expectFeatureNotSupportedException(\n          () -> connection.prepareStatement(\"select 1\", new String[] {\"c1\", \"c2\"}));\n      expectFeatureNotSupportedException(\n          () ->\n              connection.prepareStatement(\n                  \"select 1\", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY));\n      expectFeatureNotSupportedException(\n          () ->\n              connection.createStatement(\n                  ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY));\n      expectFeatureNotSupportedException(() -> connection.setTypeMap(new HashMap<>()));\n      expectFeatureNotSupportedException(connection::setSavepoint);\n      expectFeatureNotSupportedException(() -> connection.setSavepoint(\"fake\"));\n      expectFeatureNotSupportedException(() -> connection.releaseSavepoint(new FakeSavepoint()));\n      expectFeatureNotSupportedException(connection::createBlob);\n      expectFeatureNotSupportedException(connection::createNClob);\n      expectFeatureNotSupportedException(connection::createSQLXML);\n      expectFeatureNotSupportedException(\n          () -> connection.createStruct(\"fakeType\", new Object[] {}));\n      expectFeatureNotSupportedException(\n          () -> connection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));\n    }\n  }\n\n  class FakeSavepoint implements Savepoint {\n    @Override\n    public int getSavepointId() throws SQLException {\n      return 0;\n    }\n\n    @Override\n    public String getSavepointName() throws SQLException {\n      return \"\";\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AssumptionUtils.assumeRunningOnGithubActions;\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.SecureRandom;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLClientInfoException;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\nimport net.snowflake.client.api.datasource.SnowflakeDataSourceFactory;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.datasource.SnowflakeBasicDataSource;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.codec.binary.Base64;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\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.CsvSource;\n\n/** Connection integration tests */\n@Tag(TestTags.CONNECTION)\npublic class ConnectionIT extends BaseJDBCWithSharedConnectionIT {\n  // create a local constant for this code for testing purposes (already defined in GS)\n  private static final int SESSION_CREATION_OBJECT_DOES_NOT_EXIST_NOT_AUTHORIZED = 390201;\n  private static final int ROLE_IN_CONNECT_STRING_DOES_NOT_EXIST = 390189;\n\n  String errorMessage = null;\n\n  @TempDir private File tmpFolder;\n\n  @Test\n  public void testSimpleConnection() throws SQLException {\n    Connection con = getConnection();\n    try (Statement statement = con.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"show parameters\")) {\n      assertTrue(resultSet.next());\n      assertFalse(con.isClosed());\n    }\n    con.close();\n    assertTrue(con.isClosed());\n    con.close(); // ensure no exception\n  }\n\n  @Test\n  @Disabled\n  public void test300ConnectionsWithSingleClientInstance() throws SQLException {\n    // concurrent testing\n    int size = 300;\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      statement.execute(\n          \"create or replace table bigTable(rowNum number,rando \"\n              + \"number) as (select seq4(),\"\n              + \"uniform(1, 10, random()) from table(generator(rowcount=>10000000)) v)\");\n      statement.execute(\"create or replace table conTable(colA number)\");\n\n      ExecutorService taskRunner = Executors.newFixedThreadPool(size);\n      for (int i = 0; i < size; i++) {\n        ConcurrentConnections newTask = new ConcurrentConnections();\n        taskRunner.submit(newTask);\n      }\n      assertEquals(null, errorMessage);\n      taskRunner.shutdownNow();\n    }\n  }\n\n  /**\n   * Test that login timeout kick in when connect through datasource\n   *\n   * @throws SQLException if any SQL error occurs\n   */\n  @Test\n  public void testLoginTimeoutViaDataSource() throws SQLException {\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setUrl(\"jdbc:snowflake://fakeaccount.snowflakecomputing.com\");\n    ds.setUser(\"fakeUser\");\n    ds.setPassword(\"fakePassword\");\n    ds.setAccount(\"fakeAccount\");\n    ds.setLoginTimeout(10);\n\n    long startLoginTime = System.currentTimeMillis();\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              ds.getConnection();\n            });\n    assertThat(e.getErrorCode(), is(ErrorCode.NETWORK_ERROR.getMessageCode()));\n\n    long endLoginTime = System.currentTimeMillis();\n\n    assertTrue(endLoginTime - startLoginTime < 30000);\n  }\n\n  @Test\n  public void testSetCatalogSchema() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      String db = connection.getCatalog();\n      String schema = connection.getSchema();\n      connection.setCatalog(db);\n      connection.setSchema(\"PUBLIC\");\n\n      // get the current schema\n      try (ResultSet rst = statement.executeQuery(\"select current_schema()\")) {\n        assertTrue(rst.next());\n        assertEquals(\"PUBLIC\", rst.getString(1));\n        assertEquals(db, connection.getCatalog());\n        assertEquals(\"PUBLIC\", connection.getSchema());\n      }\n      // get the current schema\n      connection.setSchema(schema);\n      try (ResultSet rst = statement.executeQuery(\"select current_schema()\")) {\n        assertTrue(rst.next());\n        assertEquals(schema, rst.getString(1));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testConnectionGetAndSetDBAndSchema() throws SQLException {\n    final String SECOND_DATABASE = \"SECOND_DATABASE\";\n    final String SECOND_SCHEMA = \"SECOND_SCHEMA\";\n    try (Statement statement = connection.createStatement()) {\n      try {\n        final String database = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_DATABASE\").toUpperCase();\n        final String schema = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SCHEMA\").toUpperCase();\n\n        assertEquals(database, connection.getCatalog());\n        assertEquals(schema, connection.getSchema());\n\n        statement.execute(String.format(\"create or replace database %s\", SECOND_DATABASE));\n        statement.execute(String.format(\"create or replace schema %s\", SECOND_SCHEMA));\n        statement.execute(String.format(\"use database %s\", database));\n\n        connection.setCatalog(SECOND_DATABASE);\n        assertEquals(SECOND_DATABASE, connection.getCatalog());\n        assertEquals(\"PUBLIC\", connection.getSchema());\n\n        connection.setSchema(SECOND_SCHEMA);\n        assertEquals(SECOND_SCHEMA, connection.getSchema());\n\n        statement.execute(String.format(\"use database %s\", database));\n        statement.execute(String.format(\"use schema %s\", schema));\n\n        assertEquals(database, connection.getCatalog());\n        assertEquals(schema, connection.getSchema());\n      } finally {\n        statement.execute(String.format(\"drop database if exists %s\", SECOND_DATABASE));\n      }\n    }\n  }\n\n  @Test\n  public void testConnectionClientInfo() throws SQLException {\n    Properties property = connection.getClientInfo();\n    assertEquals(0, property.size());\n    Properties clientInfo = new Properties();\n    clientInfo.setProperty(\"name\", \"Peter\");\n    clientInfo.setProperty(\"description\", \"SNOWFLAKE JDBC\");\n    SQLClientInfoException e =\n        assertThrows(\n            SQLClientInfoException.class,\n            () -> {\n              connection.setClientInfo(clientInfo);\n            });\n    assertEquals(SqlState.INVALID_PARAMETER_VALUE, e.getSQLState());\n    assertEquals(ErrorCode.INVALID_PARAMETER_VALUE.getMessageCode(), e.getErrorCode());\n    assertEquals(2, e.getFailedProperties().size());\n\n    e =\n        assertThrows(\n            SQLClientInfoException.class,\n            () -> {\n              connection.setClientInfo(\"ApplicationName\", \"valueA\");\n            });\n    assertEquals(SqlState.INVALID_PARAMETER_VALUE, e.getSQLState());\n    assertEquals(ErrorCode.INVALID_PARAMETER_VALUE.getMessageCode(), e.getErrorCode());\n    assertEquals(1, e.getFailedProperties().size());\n  }\n\n  // only support get and set\n  @Test\n  public void testNetworkTimeout() throws SQLException {\n    int millis = connection.getNetworkTimeout();\n    assertEquals(0, millis);\n    connection.setNetworkTimeout(null, 200);\n    assertEquals(200, connection.getNetworkTimeout());\n    // Reset timeout to 0 since we are reusing connection in tests\n    connection.setNetworkTimeout(null, 0);\n    assertEquals(0, millis);\n  }\n\n  @Test\n  public void testAbort() throws SQLException {\n    Connection con = getConnection();\n    assertTrue(!con.isClosed());\n    con.abort(null);\n    assertTrue(con.isClosed());\n  }\n\n  @Test\n  public void testSetQueryTimeoutInConnectionStr() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"queryTimeout\", \"5\");\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      SQLException e =\n          assertThrows(\n              SQLException.class,\n              () -> {\n                statement.executeQuery(\n                    \"select count(*) from table(generator(timeLimit => 1000000))\");\n              });\n      assertEquals(SqlState.QUERY_CANCELED, e.getSQLState());\n      assertEquals(\"SQL execution canceled\", e.getMessage());\n    }\n  }\n\n  @Test\n  public void testConnectViaDataSource() throws SQLException {\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n\n    Map<String, String> params = getConnectionParameters();\n    String account = params.get(\"account\");\n    String host = params.get(\"host\");\n    String port = params.get(\"port\");\n    String user = params.get(\"user\");\n    String password = params.get(\"password\");\n    String ssl = params.get(\"ssl\");\n\n    String connectStr = String.format(\"jdbc:snowflake://%s:%s\", host, port);\n\n    ds.setUrl(connectStr);\n    ds.setAccount(account);\n    ds.setSsl(\"on\".equals(ssl));\n    ds.setUser(user);\n\n    // Handle authentication - prioritize private key, fallback to password\n    if (params.get(\"private_key_file\") != null) {\n      ds.setPrivateKeyFile(params.get(\"private_key_file\"), params.get(\"private_key_pwd\"));\n    } else if (password != null) {\n      ds.setPassword(password);\n    } else {\n      // Skip test if no authentication method is available\n      org.junit.jupiter.api.Assumptions.assumeTrue(\n          false, \"No authentication method available - missing both private key and password\");\n    }\n\n    try (Connection connection = ds.getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n      resultSet.next();\n      assertThat(\"select 1\", resultSet.getInt(1), equalTo(1));\n    }\n\n    // get connection by server name\n    // this is used by ibm cast iron studio\n    ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setSsl(\"on\".equals(ssl));\n    ds.setAccount(account);\n    ds.setPortNumber(Integer.parseInt(port));\n    ds.setUser(user);\n\n    // Handle authentication for second DataSource instance\n    if (params.get(\"private_key_file\") != null) {\n      ds.setPrivateKeyFile(params.get(\"private_key_file\"), params.get(\"private_key_pwd\"));\n    } else if (password != null) {\n      ds.setPassword(password);\n    } else {\n      // Skip test if no authentication method is available\n      org.junit.jupiter.api.Assumptions.assumeTrue(\n          false, \"No authentication method available - missing both private key and password\");\n    }\n\n    try (Connection connection = ds.getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n      resultSet.next();\n      assertThat(\"select 1\", resultSet.getInt(1), equalTo(1));\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testDataSourceOktaSerialization() throws Exception {\n    // test with username/password authentication\n    // set up DataSource object and ensure connection works\n    Map<String, String> params = getConnectionParameters();\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setSsl(\"on\".equals(params.get(\"ssl\")));\n    ds.setAccount(params.get(\"account\"));\n    ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n    ds.setUser(params.get(\"ssoUser\"));\n    ds.setPassword(params.get(\"ssoPassword\"));\n    ds.setAuthenticator(\"https://snowflakecomputing.okta.com/\");\n    try (Connection con = ds.getConnection();\n        Statement statement = con.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n      resultSet.next();\n      assertThat(\"select 1\", resultSet.getInt(1), equalTo(1));\n      File serializedFile = new File(tmpFolder, \"serializedStuff.ser\");\n      serializedFile.createNewFile();\n      // serialize datasource object into a file\n      try (FileOutputStream outputFile = new FileOutputStream(serializedFile);\n          ObjectOutputStream out = new ObjectOutputStream(outputFile)) {\n        out.writeObject(ds);\n      }\n      // deserialize into datasource object again\n      try (FileInputStream inputFile = new FileInputStream(serializedFile);\n          ObjectInputStream in = new ObjectInputStream(inputFile)) {\n        SnowflakeBasicDataSource ds2 = (SnowflakeBasicDataSource) in.readObject();\n\n        // test connection a second time\n        try (Connection connection = ds2.getConnection();\n            Statement statement2 = connection.createStatement();\n            ResultSet resultSet2 = statement2.executeQuery(\"select 1\")) {\n          resultSet2.next();\n          assertThat(\"select 1\", resultSet2.getInt(1), equalTo(1));\n        }\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testConnectUsingKeyPair() throws Exception {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n\n    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\");\n    SecureRandom random = SecureRandom.getInstance(\"SHA1PRNG\");\n    keyPairGenerator.initialize(2048, random);\n\n    KeyPair keyPair = keyPairGenerator.generateKeyPair();\n    PublicKey publicKey = keyPair.getPublic();\n    PrivateKey privateKey = keyPair.getPrivate();\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n\n      String encodePublicKey = Base64.encodeBase64String(publicKey.getEncoded());\n\n      statement.execute(\n          String.format(\"alter user %s set rsa_public_key='%s'\", testUser, encodePublicKey));\n    }\n\n    String uri = parameters.get(\"uri\");\n\n    Properties properties = new Properties();\n    properties.put(\"account\", parameters.get(\"account\"));\n    properties.put(\"user\", testUser);\n    properties.put(\"ssl\", parameters.get(\"ssl\"));\n    properties.put(\"port\", parameters.get(\"port\"));\n\n    // test correct private key one\n    properties.put(\"privateKey\", privateKey);\n    try (Connection connection = DriverManager.getConnection(uri, properties)) {\n      assertFalse(connection.isClosed());\n    }\n\n    // test datasource connection using private key\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setUrl(uri);\n    ds.setAccount(parameters.get(\"account\"));\n    ds.setUser(parameters.get(\"user\"));\n    ds.setSsl(\"on\".equals(parameters.get(\"ssl\")));\n    ds.setPortNumber(Integer.valueOf(parameters.get(\"port\")));\n    ds.setPrivateKey(privateKey);\n\n    try (Connection con = ds.getConnection()) {\n      assertFalse(con.isClosed());\n    }\n    // test wrong private key\n    keyPair = keyPairGenerator.generateKeyPair();\n    PublicKey publicKey2 = keyPair.getPublic();\n    PrivateKey privateKey2 = keyPair.getPrivate();\n    properties.put(\"privateKey\", privateKey2);\n    connectExpectingInvalidJWTError(uri, properties);\n\n    // test multiple key pair\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n      String encodePublicKey2 = Base64.encodeBase64String(publicKey2.getEncoded());\n      statement.execute(\n          String.format(\"alter user %s set rsa_public_key_2='%s'\", testUser, encodePublicKey2));\n    }\n\n    try (Connection connection = DriverManager.getConnection(uri, properties)) {\n      assertFalse(connection.isClosed());\n    }\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n      statement.execute(String.format(\"alter user %s unset rsa_public_key\", testUser));\n      statement.execute(String.format(\"alter user %s unset rsa_public_key_2\", testUser));\n    }\n  }\n\n  @Test\n  public void testBadPrivateKey() throws Exception {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n\n    String uri = parameters.get(\"uri\");\n\n    Properties properties = new Properties();\n    properties.put(\"account\", parameters.get(\"account\"));\n    properties.put(\"user\", testUser);\n    properties.put(\"ssl\", parameters.get(\"ssl\"));\n\n    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"DSA\");\n    PrivateKey dsaPrivateKey = keyPairGenerator.generateKeyPair().getPrivate();\n\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              properties.put(\"privateKey\", \"bad string\");\n              DriverManager.getConnection(uri, properties);\n            });\n    assertThat(e.getErrorCode(), is(ErrorCode.INVALID_PARAMETER_TYPE.getMessageCode()));\n\n    e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              properties.put(\"privateKey\", dsaPrivateKey);\n              DriverManager.getConnection(uri, properties);\n            });\n    assertThat(e.getErrorCode(), is(ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY.getMessageCode()));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDifferentKeyLength() throws Exception {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n\n    Integer[] testCases = {2048, 4096, 8192};\n\n    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(\"RSA\");\n    SecureRandom random = SecureRandom.getInstance(\"SHA1PRNG\");\n\n    for (Integer keyLength : testCases) {\n      keyPairGenerator.initialize(keyLength, random);\n\n      KeyPair keyPair = keyPairGenerator.generateKeyPair();\n      PublicKey publicKey = keyPair.getPublic();\n      PrivateKey privateKey = keyPair.getPrivate();\n\n      try (Connection connection = getConnection();\n          Statement statement = connection.createStatement()) {\n        statement.execute(\"use role accountadmin\");\n\n        String encodePublicKey = Base64.encodeBase64String(publicKey.getEncoded());\n\n        statement.execute(\n            String.format(\"alter user %s set rsa_public_key='%s'\", testUser, encodePublicKey));\n      }\n\n      String uri = parameters.get(\"uri\");\n\n      Properties properties = new Properties();\n      properties.put(\"account\", parameters.get(\"account\"));\n      properties.put(\"user\", testUser);\n      properties.put(\"ssl\", parameters.get(\"ssl\"));\n      properties.put(\"port\", parameters.get(\"port\"));\n      properties.put(\"role\", \"accountadmin\");\n\n      // test correct private key one\n      properties.put(\"privateKey\", privateKey);\n      try (Connection connection = DriverManager.getConnection(uri, properties)) {\n        assertFalse(connection.isClosed());\n      }\n\n      try (Connection connection = getConnection();\n          Statement statement = connection.createStatement()) {\n        statement.execute(\"use role accountadmin\");\n        statement.execute(String.format(\"alter user %s unset rsa_public_key\", testUser));\n      }\n    }\n  }\n\n  /** Verify the passed memory parameters are set in the session */\n  @Test\n  public void testClientMemoryParameters() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"CLIENT_PREFETCH_THREADS\", \"6\");\n    paramProperties.put(\"CLIENT_RESULT_CHUNK_SIZE\", 48);\n    paramProperties.put(\"CLIENT_MEMORY_LIMIT\", 1000);\n    try (Connection connection = getConnection(paramProperties);\n        Statement statement = connection.createStatement()) {\n      for (Enumeration<?> enums = paramProperties.propertyNames(); enums.hasMoreElements(); ) {\n        String key = (String) enums.nextElement();\n        try (ResultSet rs =\n            statement.executeQuery(String.format(\"show parameters like '%s'\", key))) {\n          assertTrue(rs.next());\n          String value = rs.getString(\"value\");\n          assertThat(key, value, equalTo(paramProperties.get(key).toString()));\n        }\n      }\n    }\n  }\n\n  /** Verify the JVM memory parameters are set in the session */\n  @Test\n  public void testClientMemoryJvmParameters() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"CLIENT_PREFETCH_THREADS\", \"6\");\n    paramProperties.put(\"CLIENT_RESULT_CHUNK_SIZE\", 48);\n    paramProperties.put(\"CLIENT_MEMORY_LIMIT\", 1000L);\n\n    // set JVM parameters\n    System.setProperty(\n        \"net.snowflake.jdbc.clientPrefetchThreads\",\n        paramProperties.get(\"CLIENT_PREFETCH_THREADS\").toString());\n    System.setProperty(\n        \"net.snowflake.jdbc.clientResultChunkSize\",\n        paramProperties.get(\"CLIENT_RESULT_CHUNK_SIZE\").toString());\n    System.setProperty(\n        \"net.snowflake.jdbc.clientMemoryLimit\",\n        paramProperties.get(\"CLIENT_MEMORY_LIMIT\").toString());\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      for (Enumeration<?> enums = paramProperties.propertyNames(); enums.hasMoreElements(); ) {\n        String key = (String) enums.nextElement();\n        try (ResultSet rs =\n            statement.executeQuery(String.format(\"show parameters like '%s'\", key))) {\n          assertTrue(rs.next());\n          String value = rs.getString(\"value\");\n          assertThat(key, value, equalTo(paramProperties.get(key).toString()));\n        }\n      }\n    } finally {\n      System.clearProperty(\"net.snowflake.jdbc.clientPrefetchThreads\");\n      System.clearProperty(\"net.snowflake.jdbc.clientResultChunkSize\");\n      System.clearProperty(\"net.snowflake.jdbc.clientMemoryLimit\");\n    }\n  }\n\n  /**\n   * Verify the connection and JVM memory parameters are set in the session. The connection\n   * parameters take precedence over JVM.\n   */\n  @Test\n  public void testClientMixedMemoryJvmParameters() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"CLIENT_PREFETCH_THREADS\", \"6\");\n    paramProperties.put(\"CLIENT_RESULT_CHUNK_SIZE\", 48);\n    paramProperties.put(\"CLIENT_MEMORY_LIMIT\", 1000L);\n\n    // set JVM parameters\n    System.setProperty(\n        \"net.snowflake.jdbc.clientPrefetchThreads\",\n        paramProperties.get(\"CLIENT_PREFETCH_THREADS\").toString());\n    System.setProperty(\n        \"net.snowflake.jdbc.clientResultChunkSize\",\n        paramProperties.get(\"CLIENT_RESULT_CHUNK_SIZE\").toString());\n    System.setProperty(\n        \"net.snowflake.jdbc.clientMemoryLimit\",\n        paramProperties.get(\"CLIENT_MEMORY_LIMIT\").toString());\n\n    paramProperties.put(\"CLIENT_PREFETCH_THREADS\", \"8\");\n    paramProperties.put(\"CLIENT_RESULT_CHUNK_SIZE\", 64);\n    paramProperties.put(\"CLIENT_MEMORY_LIMIT\", 2000L);\n\n    try (Connection connection = getConnection(paramProperties);\n        Statement statement = connection.createStatement()) {\n      for (Enumeration<?> enums = paramProperties.propertyNames(); enums.hasMoreElements(); ) {\n        String key = (String) enums.nextElement();\n        try (ResultSet rs =\n            statement.executeQuery(String.format(\"show parameters like '%s'\", key))) {\n          assertTrue(rs.next());\n          String value = rs.getString(\"value\");\n          assertThat(key, value, equalTo(paramProperties.get(key).toString()));\n        }\n      }\n    } finally {\n      System.clearProperty(\"net.snowflake.jdbc.clientPrefetchThreads\");\n      System.clearProperty(\"net.snowflake.jdbc.clientResultChunkSize\");\n      System.clearProperty(\"net.snowflake.jdbc.clientMemoryLimit\");\n    }\n  }\n  /**\n   * Verify the passed heartbeat frequency, which is too large, is changed to the maximum valid\n   * value.\n   */\n  @Test\n  public void testHeartbeatFrequencyTooLarge() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY, 4000);\n    try (Connection connection = getConnection(paramProperties);\n        Statement statement = connection.createStatement()) {\n      connection.getClientInfo(CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY);\n\n      for (Enumeration<?> enums = paramProperties.propertyNames(); enums.hasMoreElements(); ) {\n        String key = (String) enums.nextElement();\n        try (ResultSet rs =\n            statement.executeQuery(String.format(\"show parameters like '%s'\", key))) {\n          assertTrue(rs.next());\n          String value = rs.getString(\"value\");\n\n          assertThat(key, value, equalTo(\"3600\"));\n        }\n      }\n      SFSession session = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      assertEquals(3600, session.getHeartbeatFrequency());\n    }\n  }\n\n  @Test\n  public void testNativeSQL() throws Throwable {\n    // today returning the source SQL.\n    assertEquals(\"select 1\", connection.nativeSQL(\"select 1\"));\n  }\n\n  @Test\n  public void testGetTypeMap() throws Throwable {\n    // return an empty type map. setTypeMap is not supported.\n    assertEquals(Collections.emptyMap(), connection.getTypeMap());\n  }\n\n  @Test\n  public void testHolderbility() throws Throwable {\n    try (Connection connection = getConnection()) {\n      try {\n        connection.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);\n      } catch (SQLFeatureNotSupportedException ex) {\n        // nop\n      }\n      // return an empty type map. setTypeMap is not supported.\n      assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, connection.getHoldability());\n    }\n  }\n\n  @Test\n  public void testIsValid() throws Throwable {\n    try (Connection connection = getConnection()) {\n      assertTrue(connection.isValid(10));\n      assertThrows(\n          SQLException.class,\n          () -> {\n            assertTrue(connection.isValid(-10));\n          });\n    }\n  }\n\n  @Test\n  public void testUnwrapper() throws Throwable {\n    try (Connection connection = getConnection()) {\n      boolean canUnwrap = connection.isWrapperFor(SnowflakeConnectionImpl.class);\n      assertTrue(canUnwrap);\n      SnowflakeConnectionImpl sfconnection = connection.unwrap(SnowflakeConnectionImpl.class);\n      sfconnection.createStatement();\n      assertThrows(\n          SQLException.class,\n          () -> {\n            connection.unwrap(SnowflakeDriver.class);\n          });\n    }\n  }\n\n  @Test\n  public void testStatementsAndResultSetsClosedByConnection() throws SQLException {\n    Connection connection = getConnection();\n    Statement statement1 = connection.createStatement();\n    Statement statement2 = connection.createStatement();\n    ResultSet rs1 = statement2.executeQuery(\"select 2;\");\n    ResultSet rs2 = statement2.executeQuery(\"select 2;\");\n    ResultSet rs3 = statement2.executeQuery(\"select 2;\");\n    PreparedStatement statement3 = connection.prepareStatement(\"select 2;\");\n    connection.close();\n    assertTrue(statement1.isClosed());\n    assertTrue(statement2.isClosed());\n    assertTrue(statement3.isClosed());\n    assertTrue(rs1.isClosed());\n    assertTrue(rs2.isClosed());\n    assertTrue(rs3.isClosed());\n  }\n\n  @Test\n  public void testReadDateAfterSplittingResultSet() throws Exception {\n    Connection conn = getConnection();\n    try (Statement statement = conn.createStatement()) {\n      statement.execute(\"create or replace table table_with_date (int_c int, date_c date)\");\n      statement.execute(\"insert into table_with_date values (1, '2015-10-25')\");\n\n      try (ResultSet rs = statement.executeQuery(\"select * from table_with_date\")) {\n        final SnowflakeResultSet resultSet = rs.unwrap(SnowflakeResultSet.class);\n        final long arbitrarySizeInBytes = 1024;\n        final List<SnowflakeResultSetSerializable> serializables =\n            resultSet.getResultSetSerializables(arbitrarySizeInBytes);\n        final ArrayList<Date> dates = new ArrayList<>();\n\n        // Get connection URL for ResultSetRetrieveConfig\n        String url = conn.getMetaData().getURL();\n        String sfFullURL = url.replace(\"jdbc:snowflake://\", \"https://\");\n\n        for (SnowflakeResultSetSerializable s : serializables) {\n          ResultSet srs =\n              s.getResultSet(\n                  SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                      .setProxyProperties(new Properties())\n                      .setSfFullURL(sfFullURL)\n                      .build());\n          srs.next();\n          dates.add(srs.getDate(2));\n        }\n        assertEquals(1, dates.size());\n        assertEquals(\"2015-10-25\", dates.get(0).toString());\n      }\n    }\n  }\n\n  @Test\n  public void testResultSetsClosedByStatement() throws SQLException {\n    Statement statement2 = connection.createStatement();\n    ResultSet rs1 = statement2.executeQuery(\"select 2;\");\n    ResultSet rs2 = statement2.executeQuery(\"select 2;\");\n    ResultSet rs3 = statement2.executeQuery(\"select 2;\");\n    PreparedStatement statement3 = connection.prepareStatement(\"select 2;\");\n    ResultSet rs4 = statement3.executeQuery();\n    assertFalse(rs1.isClosed());\n    assertFalse(rs2.isClosed());\n    assertFalse(rs3.isClosed());\n    assertFalse(rs4.isClosed());\n    statement2.close();\n    statement3.close();\n    assertTrue(rs1.isClosed());\n    assertTrue(rs2.isClosed());\n    assertTrue(rs3.isClosed());\n    assertTrue(rs4.isClosed());\n  }\n\n  @ParameterizedTest\n  @CsvSource({\n    \"db,\" + SESSION_CREATION_OBJECT_DOES_NOT_EXIST_NOT_AUTHORIZED,\n    \"schema,\" + SESSION_CREATION_OBJECT_DOES_NOT_EXIST_NOT_AUTHORIZED,\n    \"warehouse,\" + SESSION_CREATION_OBJECT_DOES_NOT_EXIST_NOT_AUTHORIZED,\n    \"role,\" + ROLE_IN_CONNECT_STRING_DOES_NOT_EXIST\n  })\n  public void testValidateDefaultParameters(String propertyName, int expectedErrorCode)\n      throws Throwable {\n    Map<String, String> params = getConnectionParameters();\n    Properties props;\n\n    props = setCommonConnectionParameters(true);\n    props.put(propertyName, \"NOT_EXISTS\");\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              DriverManager.getConnection(params.get(\"uri\"), props);\n            });\n    assertEquals(ex.getErrorCode(), expectedErrorCode, \"error code\");\n  }\n\n  @Test\n  public void testNoValidateDefaultParameters() throws Throwable {\n    Map<String, String> params = getConnectionParameters();\n    Properties props;\n\n    props = setCommonConnectionParameters(false);\n    props.put(\"db\", \"NOT_EXISTS\");\n    DriverManager.getConnection(params.get(\"uri\"), props);\n\n    // schema is invalid\n    props = setCommonConnectionParameters(false);\n    props.put(\"schema\", \"NOT_EXISTS\");\n    DriverManager.getConnection(params.get(\"uri\"), props);\n\n    // warehouse is invalid\n    props = setCommonConnectionParameters(false);\n    props.put(\"warehouse\", \"NOT_EXISTS\");\n    DriverManager.getConnection(params.get(\"uri\"), props);\n\n    // role is invalid\n    props = setCommonConnectionParameters(false);\n    props.put(\"role\", \"NOT_EXISTS\");\n    Properties finalProps = props;\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              DriverManager.getConnection(params.get(\"uri\"), finalProps);\n            });\n    assertEquals(ex.getErrorCode(), ROLE_IN_CONNECT_STRING_DOES_NOT_EXIST, \"error code\");\n  }\n\n  private Properties kvMap2Properties(\n      Map<String, String> params, boolean validateDefaultParameters) {\n    Properties props = new Properties();\n    props.put(\"validateDefaultParameters\", validateDefaultParameters);\n    props.put(\"account\", params.get(\"account\"));\n    props.put(\"ssl\", params.get(\"ssl\"));\n    props.put(\"role\", params.get(\"role\"));\n    props.put(\"user\", params.get(\"user\"));\n\n    // Handle authentication - prioritize private key, fallback to password\n    if (params.get(\"private_key_file\") != null) {\n      props.put(\"private_key_file\", params.get(\"private_key_file\"));\n      props.put(\"authenticator\", params.get(\"authenticator\"));\n      if (params.get(\"private_key_pwd\") != null) {\n        props.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n      }\n    } else if (params.get(\"password\") != null) {\n      props.put(\"password\", params.get(\"password\"));\n    }\n\n    props.put(\"db\", params.get(\"database\"));\n    props.put(\"schema\", params.get(\"schema\"));\n    props.put(\"warehouse\", params.get(\"warehouse\"));\n    return props;\n  }\n\n  private Properties setCommonConnectionParameters(boolean validateDefaultParameters) {\n    Map<String, String> params = getConnectionParameters();\n    return kvMap2Properties(params, validateDefaultParameters);\n  }\n\n  @Test\n  @Disabled(\"Requires ORG connection parameters\")\n  public void testFailOverOrgAccount() throws SQLException {\n    // only when set_git_info.sh picks up a SOURCE_PARAMETER_FILE\n    assumeRunningOnGithubActions();\n\n    Map<String, String> kvParams = getConnectionParameters(null, \"ORG\");\n    Properties connProps = kvMap2Properties(kvParams, false);\n    String uri = kvParams.get(\"uri\");\n\n    try (Connection con = DriverManager.getConnection(uri, connProps);\n        Statement statement = con.createStatement()) {\n      statement.execute(\"select 1\");\n    }\n  }\n\n  private void connectExpectingInvalidJWTError(String fullUri, Properties properties) {\n    SQLException e =\n        assertThrows(\n            SQLException.class, () -> DriverManager.getConnection(fullUri, properties).close());\n    assertEquals(390144, e.getErrorCode());\n  }\n\n  private class ConcurrentConnections implements Runnable {\n\n    ConcurrentConnections() {}\n\n    @Override\n    public void run() {\n      try (Connection con = getConnection();\n          Statement statement = con.createStatement()) {\n        statement.executeQuery(\"select * from bigTable\");\n\n      } catch (SQLException ex) {\n        ex.printStackTrace();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.core.SessionUtil.CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY;\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.not;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.instanceOf;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport javax.net.ssl.SSLHandshakeException;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.annotations.RunOnAWS;\nimport net.snowflake.client.api.auth.AuthenticatorType;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\nimport net.snowflake.client.api.datasource.SnowflakeDataSourceFactory;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.api.resultset.SnowflakeAsyncResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.statement.SnowflakePreparedStatement;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.datasource.SnowflakeBasicDataSource;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.core.SecurityUtil;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.core.auth.ClientAuthnDTO;\nimport net.snowflake.client.internal.core.auth.ClientAuthnParameter;\nimport net.snowflake.client.internal.log.SFLogger;\nimport net.snowflake.client.internal.log.SFLoggerFactory;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.client.utils.URIBuilder;\nimport org.apache.http.entity.StringEntity;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n/**\n * Connection integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the oldest supported driver to examine\n * if the tests still is not applicable. If it is applicable, move tests to ConnectionIT so that\n * both the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.CONNECTION)\npublic class ConnectionLatestIT extends BaseJDBCTest {\n  @TempDir private File tmpFolder;\n  private static final SFLogger logger = SFLoggerFactory.getLogger(ConnectionLatestIT.class);\n\n  @Test\n  public void testDisableQueryContextCache() throws SQLException {\n    // Disable QCC via prop\n    Properties props = new Properties();\n    props.put(\"disableQueryContextCache\", \"true\");\n    try (Connection con = getConnection(props);\n        Statement statement = con.createStatement()) {\n      statement.execute(\"select 1\");\n      SFSession session = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      // if QCC disable, this should be null\n      assertNull(session.getQueryContextDTO());\n    }\n  }\n\n  /**\n   * Verify the passed heartbeat frequency matches the output value if the input is valid (between\n   * 900 and 3600).\n   */\n  @Test\n  public void testHeartbeatFrequencyValidValue() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY, 1800);\n    try (Connection connection = getConnection(paramProperties)) {\n      for (Enumeration<?> enums = paramProperties.propertyNames(); enums.hasMoreElements(); ) {\n        String key = (String) enums.nextElement();\n        try (ResultSet rs =\n            connection\n                .createStatement()\n                .executeQuery(String.format(\"show parameters like '%s'\", key))) {\n          assertTrue(rs.next());\n          String value = rs.getString(\"value\");\n\n          assertThat(key, value, equalTo(paramProperties.get(key).toString()));\n        }\n      }\n      SFSession session = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      assertEquals(1800, session.getHeartbeatFrequency());\n    }\n  }\n\n  /**\n   * Verify the passed heartbeat frequency, which is too small, is changed to the smallest valid\n   * value.\n   */\n  @Test\n  public void testHeartbeatFrequencyTooSmall() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY, 2);\n    try (Connection connection = getConnection(paramProperties)) {\n      for (Enumeration<?> enums = paramProperties.propertyNames(); enums.hasMoreElements(); ) {\n        String key = (String) enums.nextElement();\n        try (ResultSet rs =\n            connection\n                .createStatement()\n                .executeQuery(String.format(\"show parameters like '%s'\", key))) {\n          assertTrue(rs.next());\n          String value = rs.getString(\"value\");\n\n          assertThat(key, value, equalTo(\"900\"));\n        }\n      }\n\n      SFSession session = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      assertEquals(900, session.getHeartbeatFrequency());\n    }\n  }\n\n  /**\n   * Test that PUT/GET statements can return query IDs. If there is a group of files\n   * uploaded/downloaded, the getResultSet() function will return the query ID of the last PUT or\n   * GET statement in the batch.\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void putGetStatementsHaveQueryID() throws Throwable {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n      File destFolder = new File(tmpFolder, \"dest\");\n      destFolder.mkdirs();\n      String destFolderCanonicalPath = destFolder.getCanonicalPath();\n      statement.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n      SnowflakeStatement snowflakeStatement = statement.unwrap(SnowflakeStatement.class);\n      String createStageQueryId = snowflakeStatement.getQueryID();\n      TestUtil.assertValidQueryId(createStageQueryId);\n      String putStatement = \"PUT file://\" + sourceFilePath + \" @testPutGet_stage\";\n      String resultSetPutQueryId = \"\";\n      try (ResultSet resultSet = snowflakeStatement.executeAsyncQuery(putStatement)) {\n        String statementPutQueryId = snowflakeStatement.getQueryID();\n        TestUtil.assertValidQueryId(statementPutQueryId);\n        assertNotEquals(\n            createStageQueryId, statementPutQueryId, \"create query id is override by put query id\");\n        resultSetPutQueryId = resultSet.unwrap(SnowflakeResultSet.class).getQueryID();\n        TestUtil.assertValidQueryId(resultSetPutQueryId);\n        assertEquals(resultSetPutQueryId, statementPutQueryId);\n      }\n      try (ResultSet resultSet =\n          snowflakeStatement.executeAsyncQuery(\n              \"GET @testPutGet_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\")) {\n        String statementGetQueryId = snowflakeStatement.getQueryID();\n        String resultSetGetQueryId = resultSet.unwrap(SnowflakeResultSet.class).getQueryID();\n        TestUtil.assertValidQueryId(resultSetGetQueryId);\n        assertNotEquals(\n            resultSetGetQueryId, resultSetPutQueryId, \"put and get query id should be different\");\n        assertEquals(resultSetGetQueryId, statementGetQueryId);\n      }\n    }\n  }\n\n  /** Added in > 3.14.4 */\n  @Test\n  @DontRunOnGithubActions\n  public void putGetStatementsHaveQueryIDEvenWhenFail() throws Throwable {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n      File destFolder = new File(tmpFolder, \"dest\");\n      destFolder.mkdirs();\n      String destFolderCanonicalPath = destFolder.getCanonicalPath();\n      SnowflakeStatement snowflakeStatement = statement.unwrap(SnowflakeStatement.class);\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> {\n                statement\n                    .executeQuery(\"PUT file://\" + sourceFilePath + \" @not_existing_state\")\n                    .close();\n              });\n      TestUtil.assertValidQueryId(snowflakeStatement.getQueryID());\n      assertEquals(snowflakeStatement.getQueryID(), e.getQueryId());\n\n      String putQueryId = snowflakeStatement.getQueryID();\n      e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> {\n                statement\n                    .executeQuery(\n                        \"GET @not_existing_state 'file://\"\n                            + destFolderCanonicalPath\n                            + \"' parallel=8\")\n                    .close();\n              });\n      TestUtil.assertValidQueryId(snowflakeStatement.getQueryID());\n      assertEquals(snowflakeStatement.getQueryID(), e.getQueryId());\n\n      String getQueryId = snowflakeStatement.getQueryID();\n      assertNotEquals(putQueryId, getQueryId, \"put and get query id should be different\");\n      String stageName = \"stage_\" + SnowflakeUtil.randomAlphaNumeric(10);\n      statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n      TestUtil.assertValidQueryId(snowflakeStatement.getQueryID());\n      e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> {\n                statement.executeQuery(\"PUT file://not_existing_file @\" + stageName);\n              });\n      TestUtil.assertValidQueryId(snowflakeStatement.getQueryID());\n      assertEquals(snowflakeStatement.getQueryID(), e.getQueryId());\n    }\n  }\n\n  @Test\n  public void testAsyncQueryOpenAndCloseConnection()\n      throws SQLException, IOException, InterruptedException {\n    // open connection and run asynchronous query\n    String queryID = null;\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement();\n        ResultSet rs1 =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"CALL SYSTEM$WAIT(5, 'SECONDS')\")) {\n      // Retrieve query ID for part 2 of test, check status of query\n      queryID = rs1.unwrap(SnowflakeResultSet.class).getQueryID();\n\n      SnowflakeAsyncResultSet asyncResultSet = rs1.unwrap(SnowflakeAsyncResultSet.class);\n      await()\n          .atMost(Duration.ofSeconds(5))\n          .until(\n              () -> asyncResultSet.getStatus().getStatus(),\n              not(equalTo(QueryStatus.Status.NO_DATA)));\n      QueryStatus queryStatus = asyncResultSet.getStatus();\n      // Query should take 5 seconds so should be running\n      assertEquals(QueryStatus.Status.RUNNING, queryStatus.getStatus());\n      assertEquals(QueryStatus.Status.RUNNING.name(), queryStatus.getName());\n      // wait while query finishes running\n      await()\n          .atMost(Duration.ofSeconds(10))\n          .until(() -> asyncResultSet.getStatus().getStatus(), equalTo(QueryStatus.Status.SUCCESS));\n    }\n\n    // Create a new connection and new instance of a resultSet using query ID\n    try (Connection con = getConnection()) {\n      SQLException e =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  con.unwrap(SnowflakeConnection.class)\n                      .createResultSet(\"Totally invalid query ID\")\n                      .close());\n      assertEquals(SqlState.INVALID_PARAMETER_VALUE, e.getSQLState());\n      try (ResultSet rs = con.unwrap(SnowflakeConnection.class).createResultSet(queryID)) {\n        QueryStatus status = rs.unwrap(SnowflakeAsyncResultSet.class).getStatus();\n        // Assert status of query is a success\n        assertTrue(status.isSuccess());\n        assertEquals(\"No error reported\", status.getErrorMessage());\n        assertEquals(0, status.getErrorCode());\n        assertEquals(1, getSizeOfResultSet(rs));\n        try (Statement statement = con.createStatement();\n            // Create another query that will not be successful (querying table that does not exist)\n            ResultSet rs1 =\n                statement\n                    .unwrap(SnowflakeStatement.class)\n                    .executeAsyncQuery(\"select * from nonexistentTable\")) {\n          SnowflakeAsyncResultSet asyncResultSet = rs1.unwrap(SnowflakeAsyncResultSet.class);\n          await()\n              .atMost(Duration.ofSeconds(10))\n              .until(\n                  () ->\n                      asyncResultSet.getStatus().getStatus()\n                          == QueryStatus.Status.FAILED_WITH_ERROR);\n          status = asyncResultSet.getStatus();\n          assertEquals(2003, status.getErrorCode());\n          assertEquals(\n              \"SQL compilation error:\\n\"\n                  + \"Object 'NONEXISTENTTABLE' does not exist or not authorized.\",\n              status.getErrorMessage());\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testGetErrorMessageFromAsyncQuery() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      // Create another query that will not be successful (querying table that does not exist)\n      try (ResultSet rs1 =\n          statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(\"bad query!\")) {\n        assertThrows(\n            SQLException.class,\n            () -> {\n              rs1.next();\n            });\n        assertEquals(\n            \"SQL compilation error:\\n\" + \"syntax error line 1 at position 0 unexpected 'bad'.\",\n            rs1.unwrap(SnowflakeAsyncResultSet.class).getStatus().getErrorMessage());\n      }\n      try (ResultSet rs2 =\n          statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(\"select 1\")) {\n        rs2.next();\n        // Assert there is no error message when query is successful\n        assertEquals(\n            \"No error reported\",\n            rs2.unwrap(SnowflakeAsyncResultSet.class).getStatus().getErrorMessage());\n      }\n    }\n  }\n\n  @Test\n  public void testAsyncAndSynchronousQueries() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      // execute some statements that you want to be synchronous\n      try {\n        statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_TZ\");\n        statement.execute(\"create or replace table smallTable (colA string, colB int)\");\n        statement.execute(\"create or replace table uselessTable (colA string, colB int)\");\n        statement.execute(\"insert into smallTable values ('row1', 1), ('row2', 2), ('row3', 3)\");\n        statement.execute(\"insert into uselessTable values ('row1', 1), ('row2', 2), ('row3', 3)\");\n        // Select from uselessTable asynchronously; drop it synchronously afterwards\n        try (ResultSet rs =\n                statement\n                    .unwrap(SnowflakeStatement.class)\n                    .executeAsyncQuery(\"select * from smallTable\");\n            // execute a query that you don't want to wait for\n            ResultSet rs1 =\n                statement\n                    .unwrap(SnowflakeStatement.class)\n                    .executeAsyncQuery(\"select * from uselessTable\");\n            // Drop the table that was queried asynchronously. Should not drop until after async\n            // query\n            // finishes, because this\n            // query IS synchronous\n            ResultSet rs2 = statement.executeQuery(\"drop table uselessTable\")) {\n          while (rs2.next()) {\n            assertEquals(\"USELESSTABLE successfully dropped.\", rs2.getString(1));\n          }\n          // able to successfully fetch results in spite of table being dropped\n          assertEquals(3, getSizeOfResultSet(rs1));\n          statement.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_LTZ\");\n\n          // come back to the asynchronously executed result set after finishing other things\n          assertTrue(rs.next());\n          assertEquals(rs.getString(1), \"row1\");\n          assertEquals(rs.getInt(2), 1);\n          assertTrue(rs.next());\n          assertEquals(rs.getString(1), \"row2\");\n          assertEquals(rs.getInt(2), 2);\n          assertTrue(rs.next());\n          assertEquals(rs.getString(1), \"row3\");\n          assertEquals(rs.getInt(2), 3);\n        }\n      } finally {\n        statement.execute(\"drop table smallTable\");\n      }\n    }\n  }\n\n  /**\n   * Can be used in > 3.14.4 (when {@link QueryStatus} was added, called QueryStatusV2 at that time)\n   */\n  @Test\n  public void testQueryStatusErrorMessageAndErrorCodeChangeOnAsyncQuery() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement();\n        ResultSet rs1 =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 2))\")) {\n      SnowflakeAsyncResultSet sfResultSet = rs1.unwrap(SnowflakeAsyncResultSet.class);\n      // status should change state to RUNNING and then to SUCCESS\n      await()\n          .atMost(Duration.ofSeconds(5))\n          .until(() -> sfResultSet.getStatus().getStatus(), equalTo(QueryStatus.Status.RUNNING));\n\n      // it may take more time to finish the test when running in parallel in CI builds\n      await()\n          .atMost(Duration.ofSeconds(10))\n          .until(() -> sfResultSet.getStatus().getStatus(), equalTo(QueryStatus.Status.SUCCESS));\n    }\n  }\n\n  @Test\n  public void testPreparedStatementAsyncQuery() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table testTable(colA string, colB boolean)\");\n        try (PreparedStatement prepStatement =\n            con.prepareStatement(\"insert into testTable values (?,?)\")) {\n          prepStatement.setInt(1, 33);\n          prepStatement.setBoolean(2, true);\n          // call executeAsyncQuery\n          try (ResultSet rs =\n              prepStatement.unwrap(SnowflakePreparedStatement.class).executeAsyncQuery()) {\n            // Get access to results by calling next() function\n            // next () will block until results are ready\n            assertTrue(rs.next());\n            // the resultSet consists of a single row, single column containing the number of rows\n            // that have\n            // been updated by the insert\n            // the number of updated rows in testTable is 1 so 1 is returned\n            assertEquals(rs.getString(1), \"1\");\n          }\n        }\n      } finally {\n        statement.execute(\"drop table testTable\");\n      }\n    }\n  }\n\n  /**\n   * MANUAL TESTING OF ASYNCHRONOUS QUERYING\n   *\n   * <p>This test does not provide reliable results because the status of the queries often depends\n   * on the GS server's behavior. We can often replicate QUEUED and RESUMING_WAREHOUSE statuses,\n   * however.\n   */\n  // @Test\n  public void testQueryStatuses() throws SQLException, IOException, InterruptedException {\n    // Before running test, close warehouse and re-open it!\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement();\n        ResultSet rs =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 5))\")) {\n      Thread.sleep(100);\n      QueryStatus status = rs.unwrap(SnowflakeAsyncResultSet.class).getStatus();\n      // Since warehouse has just been restarted, warehouse should still be booting\n      assertEquals(QueryStatus.Status.RESUMING_WAREHOUSE, status.getStatus());\n\n      // now try to get QUEUED status\n      try (ResultSet rs1 =\n              statement\n                  .unwrap(SnowflakeStatement.class)\n                  .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 60))\");\n          ResultSet rs2 =\n              statement\n                  .unwrap(SnowflakeStatement.class)\n                  .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 60))\");\n          ResultSet rs3 =\n              statement\n                  .unwrap(SnowflakeStatement.class)\n                  .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 60))\");\n          ResultSet rs4 =\n              statement\n                  .unwrap(SnowflakeStatement.class)\n                  .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 60))\")) {\n        // Retrieve query ID for part 2 of test, check status of query\n        Thread.sleep(100);\n        status = rs4.unwrap(SnowflakeAsyncResultSet.class).getStatus();\n        // Since 4 queries were started at once, status is most likely QUEUED\n        assertEquals(QueryStatus.Status.QUEUED, status);\n      }\n    }\n  }\n\n  @Test\n  public void testAsyncQueryStatus() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement();\n        ResultSet rs =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"SELECT SYSTEM$WAIT(2, 'SECONDS')\")) {\n\n      String queryId = rs.unwrap(SnowflakeAsyncResultSet.class).getQueryID();\n      SnowflakeConnection snowflakeConnection = con.unwrap(SnowflakeConnection.class);\n\n      await()\n          .atMost(Duration.ofSeconds(3))\n          .until(\n              () -> snowflakeConnection.getQueryStatus(queryId).getStatus(),\n              equalTo(QueryStatus.Status.RUNNING));\n\n      await()\n          .atMost(Duration.ofSeconds(5))\n          .until(\n              () -> snowflakeConnection.getQueryStatus(queryId).getStatus(),\n              equalTo(QueryStatus.Status.SUCCESS));\n    }\n  }\n\n  @Test\n  public void testHttpsLoginTimeoutWithOutSSL() throws InterruptedException {\n    Properties properties = new Properties();\n    properties.put(\"account\", \"wrongaccount\");\n    properties.put(\"loginTimeout\", \"20\");\n    properties.put(\"user\", \"fakeuser\");\n    properties.put(\"password\", \"fakepassword\");\n    // Adding authenticator type for code coverage purposes\n    properties.put(\"authenticator\", AuthenticatorType.SNOWFLAKE.toString());\n    properties.put(\"ssl\", \"off\");\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              Map<String, String> params = getConnectionParameters();\n              // use wrongaccount in url\n              String host = params.get(\"host\");\n              String[] hostItems = host.split(\"\\\\.\");\n              String wrongUri = params.get(\"uri\").replace(\"://\" + hostItems[0], \"://wrongaccount\");\n\n              DriverManager.getConnection(wrongUri, properties);\n            });\n    assertThat(\n        \"Communication error\", e.getErrorCode(), equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n  }\n\n  @Test\n  public void testWrongHostNameTimeout() throws InterruptedException {\n    Properties properties = new Properties();\n    properties.put(\"account\", \"testaccount\");\n    properties.put(\"loginTimeout\", \"20\");\n    properties.put(\"user\", \"fakeuser\");\n    properties.put(\"password\", \"fakepassword\");\n    // Adding authenticator type for code coverage purposes\n    properties.put(\"authenticator\", AuthenticatorType.SNOWFLAKE.toString());\n    long connStart = System.currentTimeMillis(), conEnd;\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              Map<String, String> params = getConnectionParameters();\n              // use wrongaccount in url\n              String host = params.get(\"host\");\n              String[] hostItems = host.split(\"\\\\.\");\n              String wrongUri =\n                  params\n                      .get(\"uri\")\n                      .replace(\".\" + hostItems[hostItems.length - 2] + \".\", \".wronghostname.\");\n\n              DriverManager.getConnection(wrongUri, properties);\n            });\n    assertThat(\n        \"Communication error\", e.getErrorCode(), equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n\n    conEnd = System.currentTimeMillis();\n    assertThat(\"Login time out not taking effective\", conEnd - connStart < 300000);\n  }\n\n  @Test\n  public void testHttpsLoginTimeoutWithSSL() throws InterruptedException {\n    long connStart = 0, conEnd;\n    Properties properties = new Properties();\n    properties.put(\"account\", \"wrongaccount\");\n    properties.put(\"loginTimeout\", \"5\");\n    properties.put(\"user\", \"fakeuser\");\n    properties.put(\"password\", \"fakepassword\");\n    // only when ssl is on can trigger the login timeout\n    // ssl is off will trigger 404\n    properties.put(\"ssl\", \"on\");\n    connStart = System.currentTimeMillis();\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              Map<String, String> params = getConnectionParameters();\n              // use wrongaccount in url\n              String host = params.get(\"host\");\n              String[] hostItems = host.split(\"\\\\.\");\n              String wrongUri = params.get(\"uri\").replace(\"://\" + hostItems[0], \"://wrongaccount\");\n              DriverManager.getConnection(wrongUri, properties);\n            });\n    assertThat(\n        \"Communication error\", e.getErrorCode(), equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n\n    conEnd = System.currentTimeMillis();\n    assertThat(\"Login time out not taking effective\", conEnd - connStart < 300000);\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testKeyPairFileDataSourceSerialization() throws Exception {\n    // test with key/pair authentication where key is in file\n    // set up DataSource object and ensure connection works\n    Map<String, String> params = getConnectionParameters();\n    try {\n      SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n      ds.setServerName(params.get(\"host\"));\n      ds.setSsl(\"on\".equals(params.get(\"ssl\")));\n      ds.setAccount(params.get(\"account\"));\n      ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n      ds.setUser(params.get(\"user\"));\n      String privateKeyLocation = getFullPathFileInResource(\"encrypted_rsa_key.p8\");\n      ds.setPrivateKeyFile(privateKeyLocation, \"test\");\n\n      // set up public key\n      setUpPublicKey(\"encrypted_rsa_key.pub\", params.get(\"user\"));\n\n      connectAndExecuteSelect1(ds);\n\n      File serializedFile = new File(tmpFolder, \"serializedStuff.ser\");\n      serializedFile.createNewFile();\n      // serialize datasource object into a file\n      try (FileOutputStream outputFile = new FileOutputStream(serializedFile);\n          ObjectOutputStream out = new ObjectOutputStream(outputFile)) {\n        out.writeObject(ds);\n      }\n      // deserialize into datasource object again\n      try (FileInputStream inputFile = new FileInputStream(serializedFile);\n          ObjectInputStream in = new ObjectInputStream(inputFile)) {\n        SnowflakeBasicDataSource ds2 = (SnowflakeBasicDataSource) in.readObject();\n        // test connection a second time\n        connectAndExecuteSelect1(ds2);\n      }\n    } finally {\n      unsetPublicKey(params.get(\"user\"));\n    }\n  }\n\n  private static String readPrivateKeyFileToBase64Content(String fileName) throws IOException {\n    return Base64.getEncoder()\n        .encodeToString(Files.readAllBytes(Paths.get(getFullPathFileInResource(fileName))));\n  }\n\n  /** Works in > 3.18.0 */\n  @Test\n  @DontRunOnGithubActions\n  public void testKeyPairBase64DataSourceSerialization() throws Exception {\n    // test with key/pair authentication where key is passed as a Base64 string value\n    // set up DataSource object and ensure connection works\n    Map<String, String> params = getConnectionParameters();\n    try {\n      SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n      ds.setServerName(params.get(\"host\"));\n      ds.setSsl(\"on\".equals(params.get(\"ssl\")));\n      ds.setAccount(params.get(\"account\"));\n      ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n      ds.setUser(params.get(\"user\"));\n      String privateKeyBase64 = readPrivateKeyFileToBase64Content(\"encrypted_rsa_key.p8\");\n      ds.setPrivateKeyBase64(privateKeyBase64, \"test\");\n\n      // set up public key\n      setUpPublicKey(\"encrypted_rsa_key.pub\", params.get(\"user\"));\n\n      connectAndExecuteSelect1(ds);\n\n      File serializedFile = new File(tmpFolder, \"serializedStuff.ser\");\n      serializedFile.createNewFile();\n      // serialize datasource object into a file\n      try (FileOutputStream outputFile = new FileOutputStream(serializedFile);\n          ObjectOutputStream out = new ObjectOutputStream(outputFile)) {\n        out.writeObject(ds);\n      }\n      // deserialize into datasource object again\n      try (FileInputStream inputFile = new FileInputStream(serializedFile);\n          ObjectInputStream in = new ObjectInputStream(inputFile)) {\n        SnowflakeBasicDataSource ds2 = (SnowflakeBasicDataSource) in.readObject();\n        // test connection a second time\n        connectAndExecuteSelect1(ds2);\n      }\n    } finally {\n      unsetPublicKey(params.get(\"user\"));\n    }\n  }\n\n  /**\n   * This test may be split but let's keep it with multiple keys checks to not slow down test *\n   * executions\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPrivateKeyInConnectionString() throws SQLException, IOException {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n    String baseUri = parameters.get(\"uri\");\n    try {\n      // Test with non-password-protected private key file (.pem)\n      setUpPublicKey(\"rsa_key.pub\", testUser);\n\n      Properties properties = preparePropertiesForPrivateKeyTests(parameters);\n\n      // PKCS #8\n      String privateKeyLocation = getFullPathFileInResource(\"rsa_key.p8\");\n      String uri = uriWithPrivateKeyFile(baseUri, privateKeyLocation);\n      connectSuccessfully(uri, properties);\n\n      // PKCS #1\n      privateKeyLocation = getFullPathFileInResource(\"rsa_key.pem\");\n      uri = uriWithPrivateKeyFile(baseUri, privateKeyLocation);\n      connectSuccessfully(uri, properties);\n\n      // test with password-protected private key file (.p8)\n      setUpPublicKey(\"encrypted_rsa_key.pub\", testUser);\n\n      privateKeyLocation = getFullPathFileInResource(\"encrypted_rsa_key.p8\");\n      uri = uriWithPrivateKeyFileAndPassword(baseUri, privateKeyLocation, \"test\");\n\n      connectSuccessfully(uri, properties);\n      // test with incorrect password for private key\n      uri = uriWithPrivateKeyFileAndPassword(baseUri, privateKeyLocation, \"wrong_password\");\n      connectExpectingInvalidOrUnsupportedPrivateKey(uri, properties);\n\n      // test with invalid public/private key combo (using 1st public key with 2nd private key)\n      setUpPublicKey(\"rsa_key.pub\", testUser);\n\n      privateKeyLocation = getFullPathFileInResource(\"encrypted_rsa_key.p8\");\n      uri = uriWithPrivateKeyFileAndPassword(baseUri, privateKeyLocation, \"test\");\n      connectExpectingInvalidJWTError(uri, properties);\n\n      // test with invalid private key\n      privateKeyLocation = getFullPathFileInResource(\"invalid_private_key.pem\");\n      uri = uriWithPrivateKeyFile(baseUri, privateKeyLocation);\n      connectExpectingInvalidOrUnsupportedPrivateKey(uri, properties);\n\n    } finally {\n      unsetPublicKey(testUser);\n    }\n  }\n\n  private static String uriWithPrivateKeyFile(String baseUri, String privateKeyLocation) {\n    return baseUri\n        + \"/?\"\n        + SFSessionProperty.PRIVATE_KEY_FILE.getPropertyKey()\n        + \"=\"\n        + privateKeyLocation;\n  }\n\n  private static String uriWithPrivateKeyFileAndPassword(\n      String baseUri, String privateKeyLocation, String password) {\n    return uriWithPrivateKeyFile(baseUri, privateKeyLocation)\n        + \"&\"\n        + SFSessionProperty.PRIVATE_KEY_FILE_PWD.getPropertyKey()\n        + \"=\"\n        + password;\n  }\n\n  private static void connectExpectingInvalidJWTError(String fullUri, Properties properties) {\n    SQLException e =\n        assertThrows(\n            SQLException.class, () -> DriverManager.getConnection(fullUri, properties).close());\n    assertEquals(390144, e.getErrorCode());\n  }\n\n  private static void connectSuccessfully(String uri, Properties properties) throws SQLException {\n    try (Connection connection = DriverManager.getConnection(uri, properties)) {}\n  }\n\n  private static void setUpPublicKey(String fileName, String testUser)\n      throws SQLException, IOException {\n    Properties properties = new Properties();\n    properties.put(\"role\", \"accountadmin\");\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      String pathfile = getFullPathFileInResource(fileName);\n      String pubKey = new String(Files.readAllBytes(Paths.get(pathfile)));\n      pubKey = pubKey.replace(\"-----BEGIN PUBLIC KEY-----\", \"\");\n      pubKey = pubKey.replace(\"-----END PUBLIC KEY-----\", \"\");\n      statement.execute(String.format(\"alter user %s set rsa_public_key='%s'\", testUser, pubKey));\n    }\n  }\n\n  private static void unsetPublicKey(String testUser) throws SQLException {\n    Properties props = new Properties();\n    props.put(\"role\", \"accountadmin\");\n    try (Connection connection = getConnection(props);\n        Statement statement = connection.createStatement()) {\n      statement.execute(String.format(\"alter user %s unset rsa_public_key\", testUser));\n    }\n  }\n\n  // This will only work with JDBC driver versions higher than 3.15.1\n  @Test\n  @DontRunOnGithubActions\n  public void testPrivateKeyInConnectionStringWithBouncyCastle() throws SQLException, IOException {\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"true\");\n    testPrivateKeyInConnectionString();\n  }\n\n  /**\n   * Works in > 3.18.0\n   *\n   * <p>This test may be split but let's keep it with multiple keys checks to not slow down test\n   * executions\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPrivateKeyBase64InConnectionString() throws SQLException, IOException {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n    String baseUri = parameters.get(\"uri\");\n    try {\n      // Test with non-password-protected private key file (.pem)\n      setUpPublicKey(\"rsa_key.pub\", testUser);\n\n      Properties properties = preparePropertiesForPrivateKeyTests(parameters);\n\n      // PKCS #8\n      String privateKeyBase64 = readPrivateKeyFileToBase64Content(\"rsa_key.p8\");\n      String uri = uriWithPrivateKeyBase64(baseUri, privateKeyBase64);\n      connectSuccessfully(uri, properties);\n\n      // PKCS #1\n      privateKeyBase64 = readPrivateKeyFileToBase64Content(\"rsa_key.pem\");\n      uri = uriWithPrivateKeyBase64(baseUri, privateKeyBase64);\n      connectSuccessfully(uri, properties);\n\n      // test with password-protected private key file (.p8)\n      setUpPublicKey(\"encrypted_rsa_key.pub\", testUser);\n\n      privateKeyBase64 = readPrivateKeyFileToBase64Content(\"encrypted_rsa_key.p8\");\n      uri = uriWithPrivateKeyBase64AndPassword(baseUri, privateKeyBase64, \"test\");\n      connectSuccessfully(uri, properties);\n\n      // test with incorrect password for private key\n      uri = uriWithPrivateKeyBase64AndPassword(baseUri, privateKeyBase64, \"wrong_password\");\n      connectExpectingInvalidOrUnsupportedPrivateKey(uri, properties);\n\n      // test with invalid public/private key combo (using 1st public key with 2nd private key)\n      setUpPublicKey(\"rsa_key.pub\", testUser);\n\n      privateKeyBase64 = readPrivateKeyFileToBase64Content(\"encrypted_rsa_key.p8\");\n      uri = uriWithPrivateKeyBase64AndPassword(baseUri, privateKeyBase64, \"test\");\n      connectExpectingInvalidJWTError(uri, properties);\n\n      // test with invalid private key\n      privateKeyBase64 = readPrivateKeyFileToBase64Content(\"invalid_private_key.pem\");\n      uri = uriWithPrivateKeyBase64(baseUri, privateKeyBase64);\n\n      connectExpectingInvalidOrUnsupportedPrivateKey(uri, properties);\n    } finally {\n      // clean up\n      unsetPublicKey(testUser);\n    }\n  }\n\n  private static String uriWithPrivateKeyBase64(String baseUri, String privateKeyBase64) {\n    return baseUri\n        + \"/?\"\n        + SFSessionProperty.PRIVATE_KEY_BASE64.getPropertyKey()\n        + \"=\"\n        + privateKeyBase64;\n  }\n\n  private static String uriWithPrivateKeyBase64AndPassword(\n      String baseUri, String privateKeyBase64, String password) {\n    return uriWithPrivateKeyBase64(baseUri, privateKeyBase64)\n        + \"&\"\n        + SFSessionProperty.PRIVATE_KEY_FILE_PWD.getPropertyKey()\n        + \"=\"\n        + password;\n  }\n\n  private static Properties preparePropertiesForPrivateKeyTests(Map<String, String> parameters) {\n    Properties properties = new Properties();\n    properties.put(\"account\", parameters.get(\"account\"));\n    properties.put(\"user\", parameters.get(\"user\"));\n    properties.put(\"ssl\", parameters.get(\"ssl\"));\n    properties.put(\"port\", parameters.get(\"port\"));\n    return properties;\n  }\n\n  private static void connectExpectingInvalidOrUnsupportedPrivateKey(\n      String uri, Properties properties) {\n    SQLException e =\n        assertThrows(\n            SQLException.class, () -> DriverManager.getConnection(uri, properties).close());\n    assertEquals(\n        (int) ErrorCode.INVALID_OR_UNSUPPORTED_PRIVATE_KEY.getMessageCode(), e.getErrorCode());\n  }\n\n  /** Works in > 3.18.0 */\n  @Test\n  @DontRunOnGithubActions\n  public void testPrivateKeyBase64InConnectionStringWithBouncyCastle()\n      throws SQLException, IOException {\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"true\");\n    testPrivateKeyBase64InConnectionString();\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testBasicDataSourceSerialization() throws Exception {\n    // test with username/password authentication\n    // set up DataSource object and ensure connection works\n    Map<String, String> params = getConnectionParameters();\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setSsl(\"on\".equals(params.get(\"ssl\")));\n    ds.setAccount(params.get(\"account\"));\n    ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n    ds.setUser(params.get(\"user\"));\n\n    // Use private key authentication if available, otherwise password\n    if (params.get(\"private_key_file\") != null && !params.get(\"private_key_file\").isEmpty()) {\n      ds.setPrivateKeyFile(params.get(\"private_key_file\"), params.get(\"private_key_pwd\"));\n    } else {\n      ds.setPassword(params.get(\"password\"));\n    }\n\n    connectAndExecuteSelect1(ds);\n\n    File serializedFile = new File(tmpFolder, \"serializedStuff.ser\");\n    serializedFile.createNewFile();\n    // serialize datasource object into a file\n    try (FileOutputStream outputFile = new FileOutputStream(serializedFile);\n        ObjectOutputStream out = new ObjectOutputStream(outputFile)) {\n      out.writeObject(ds);\n    }\n    // deserialize into datasource object again\n    try (FileInputStream inputFile = new FileInputStream(serializedFile);\n        ObjectInputStream in = new ObjectInputStream(inputFile)) {\n      SnowflakeBasicDataSource ds2 = (SnowflakeBasicDataSource) in.readObject();\n      connectAndExecuteSelect1(ds2);\n    }\n  }\n\n  private static void connectAndExecuteSelect1(SnowflakeDataSource ds) throws SQLException {\n    try (Connection con = ds.getConnection();\n        Statement statement = con.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n      assertTrue(resultSet.next());\n      assertEquals(1, resultSet.getInt(1));\n    }\n  }\n\n  // Wait for the async query finish\n  private void waitForAsyncQueryDone(Connection connection, String queryID) throws Exception {\n    SFSession session = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    await()\n        .atMost(Duration.ofSeconds(30))\n        .until(() -> !(session.getQueryStatus(queryID)).isStillRunning());\n  }\n\n  @Test\n  public void testGetChildQueryIdsForAsyncSingleStatement() throws Exception {\n    String queryID = null;\n    String[] queryIDs = null;\n    try (Connection connection = getConnection();\n        Connection connection2 = getConnection();\n        Statement statement = connection.createStatement()) {\n      String query0 = \"create or replace temporary table test_multi (cola int);\";\n      String query1 = \"insert into test_multi VALUES (111), (222);\";\n      String query2 = \"select cola from test_multi order by cola asc\";\n\n      // Get ResultSet for first statement\n      try (ResultSet rs = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(query0)) {\n        queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        waitForAsyncQueryDone(connection, queryID);\n        queryIDs = connection.unwrap(SnowflakeConnectionImpl.class).getChildQueryIds(queryID);\n        assertEquals(queryIDs.length, 1);\n      }\n\n      try (ResultSet rs =\n          connection.unwrap(SnowflakeConnection.class).createResultSet(queryIDs[0])) {\n        assertTrue(rs.next());\n        assertEquals(rs.getString(1), \"Table TEST_MULTI successfully created.\");\n        assertFalse(rs.next());\n      }\n\n      // Get ResultSet for second statement in another connection\n      try (ResultSet rs = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(query1)) {\n        queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        waitForAsyncQueryDone(connection2, queryID);\n        queryIDs = connection2.unwrap(SnowflakeConnectionImpl.class).getChildQueryIds(queryID);\n        assertEquals(queryIDs.length, 1);\n      }\n\n      try (ResultSet rs =\n          connection2.unwrap(SnowflakeConnection.class).createResultSet(queryIDs[0])) {\n        assertTrue(rs.next());\n        assertEquals(rs.getInt(1), 2); // insert 2 rows\n        assertFalse(rs.next());\n      }\n\n      // Get ResultSet for third statement in another connection\n      try (ResultSet rs = statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(query2)) {\n        queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        waitForAsyncQueryDone(connection2, queryID);\n        queryIDs = connection2.unwrap(SnowflakeConnectionImpl.class).getChildQueryIds(queryID);\n        assertEquals(queryIDs.length, 1);\n      }\n\n      try (ResultSet rs =\n          connection2.unwrap(SnowflakeConnection.class).createResultSet(queryIDs[0])) {\n        assertTrue(rs.next());\n        assertEquals(rs.getInt(1), 111);\n        assertTrue(rs.next());\n        assertEquals(rs.getInt(1), 222);\n        assertFalse(rs.next());\n      }\n    }\n  }\n\n  @Test\n  public void testGetChildQueryIdsForAsyncMultiStatement() throws Exception {\n    String queryID = null;\n    String[] queryIDs = null;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      String multiStmtQuery =\n          \"create or replace temporary table test_multi (cola int);\"\n              + \"insert into test_multi VALUES (111), (222);\"\n              + \"select cola from test_multi order by cola asc\";\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      try (ResultSet rs =\n          statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(multiStmtQuery)) {\n        queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n      }\n    }\n    // Get the child query IDs in a new connection\n    try (Connection connection = getConnection()) {\n      waitForAsyncQueryDone(connection, queryID);\n      queryIDs = connection.unwrap(SnowflakeConnectionImpl.class).getChildQueryIds(queryID);\n      assertEquals(queryIDs.length, 3);\n\n      // First statement ResultSet\n      try (ResultSet rs =\n          connection.unwrap(SnowflakeConnection.class).createResultSet(queryIDs[0])) {\n        assertTrue(rs.next());\n        assertEquals(rs.getString(1), \"Table TEST_MULTI successfully created.\");\n        assertFalse(rs.next());\n      }\n\n      // Second statement ResultSet\n      try (ResultSet rs =\n          connection.unwrap(SnowflakeConnection.class).createResultSet(queryIDs[1])) {\n        assertTrue(rs.next());\n        assertEquals(rs.getInt(1), 2);\n        assertFalse(rs.next());\n      }\n\n      // Third statement ResultSet\n      try (ResultSet rs =\n          connection.unwrap(SnowflakeConnection.class).createResultSet(queryIDs[2])) {\n        assertTrue(rs.next());\n        assertEquals(rs.getInt(1), 111);\n        assertTrue(rs.next());\n        assertEquals(rs.getInt(1), 222);\n        assertFalse(rs.next());\n      }\n    }\n  }\n\n  @Test\n  public void testGetChildQueryIdsNegativeTestQueryIsRunning() throws Exception {\n    String queryID = null;\n    try (Connection connection = getConnection()) {\n      try (Statement statement = connection.createStatement()) {\n        String multiStmtQuery = \"select 1; call system$wait(10); select 2\";\n\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n        try (ResultSet rs =\n            statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(multiStmtQuery)) {\n          queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        }\n        String finalQueryID = queryID;\n        SQLException ex =\n            assertThrows(\n                SQLException.class,\n                () -> {\n                  connection.unwrap(SnowflakeConnectionImpl.class).getChildQueryIds(finalQueryID);\n                });\n        String msg = ex.getMessage();\n        if (!msg.contains(\"Status of query associated with resultSet is\")\n            || !msg.contains(\"Results not generated.\")) {\n          ex.printStackTrace();\n          QueryStatus qs =\n              connection\n                  .unwrap(SnowflakeConnectionImpl.class)\n                  .getSfSession()\n                  .getQueryStatus(queryID);\n          fail(\"Don't get expected message, query Status: \" + qs + \" actual message is: \" + msg);\n        }\n\n      } finally {\n        try (Statement statement = connection.createStatement()) {\n          statement.execute(\"select system$cancel_query('\" + queryID + \"')\");\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testGetChildQueryIdsNegativeTestQueryFailed() throws Exception {\n    String queryID;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      String multiStmtQuery = \"select 1; select to_date('not_date'); select 2\";\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      try (ResultSet rs =\n          statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(multiStmtQuery)) {\n        queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n      }\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () -> {\n                waitForAsyncQueryDone(connection, queryID);\n                connection.unwrap(SnowflakeConnectionImpl.class).getChildQueryIds(queryID);\n              });\n      assertTrue(\n          ex.getMessage()\n              .contains(\n                  \"Uncaught Execution of multiple statements failed on statement \\\"select\"\n                      + \" to_date('not_date')\\\"\"));\n    }\n  }\n\n  /**\n   * See SNOW-496117 for more details. Don't run on github because the github testing deployment is\n   * likely not having the test account we used here.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testAuthenticatorEndpointWithDashInAccountName() throws Exception {\n    Map<String, String> params = getConnectionParameters();\n    String serverUrl =\n        String.format(\n            \"%s://%s:%s\",\n            params.get(\"ssl\").equals(\"on\") ? \"https\" : \"http\",\n            params.get(\"host\"),\n            params.get(\"port\"));\n\n    HttpPost postRequest =\n        new HttpPost(\n            new URIBuilder(serverUrl).setPath(SessionUtil.SF_PATH_AUTHENTICATOR_REQUEST).build());\n\n    Map<String, Object> data =\n        Collections.singletonMap(ClientAuthnParameter.ACCOUNT_NAME.name(), \"snowhouse-local\");\n    ClientAuthnDTO authnData = new ClientAuthnDTO(data, null);\n\n    ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    String json = mapper.writeValueAsString(authnData);\n\n    StringEntity input = new StringEntity(json, StandardCharsets.UTF_8);\n    input.setContentType(\"application/json\");\n    postRequest.setEntity(input);\n    postRequest.addHeader(\"accept\", \"application/json\");\n\n    String theString =\n        HttpUtil.executeGeneralRequest(\n            postRequest, 60, 0, 0, 0, new HttpClientSettingsKey(null), null);\n\n    JsonNode jsonNode = mapper.readTree(theString);\n    assertTrue(jsonNode.get(\"success\").asBoolean());\n  }\n\n  @Test\n  public void testReadOnly() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n\n      connection.setReadOnly(true);\n      assertEquals(connection.isReadOnly(), false);\n\n      connection.setReadOnly(false);\n      try {\n        statement.execute(\"create or replace table readonly_test(c1 int)\");\n        assertFalse(connection.isReadOnly());\n      } finally {\n        statement.execute(\"drop table if exists readonly_test\");\n      }\n    }\n  }\n\n  /**\n   * Test case for the method testDownloadStreamWithFileNotFoundException. This test verifies that a\n   * SQLException is thrown when attempting to download a file that does not exist. It verifies that\n   * the error code is ErrorCode.S3_OPERATION_ERROR so only runs on AWS.\n   */\n  @Test\n  @RunOnAWS\n  public void testDownloadStreamWithFileNotFoundException() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"CREATE OR REPLACE TEMP STAGE testDownloadStream_stage\");\n      long startDownloadTime = System.currentTimeMillis();\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () -> {\n                connection\n                    .unwrap(SnowflakeConnection.class)\n                    .downloadStream(\n                        \"@testDownloadStream_stage\",\n                        \"/fileNotExist.gz\",\n                        DownloadStreamConfig.builder().setDecompress(true).build());\n              });\n      assertThat(ex.getErrorCode(), is(ErrorCode.FILE_NOT_FOUND.getMessageCode()));\n\n      long endDownloadTime = System.currentTimeMillis();\n      // S3Client retries some exception for a default timeout of 5 minutes\n      // Check that 404 was not retried\n      assertTrue(endDownloadTime - startDownloadTime < 400000);\n    }\n  }\n\n  @Test\n  public void testIsAsyncSession() throws SQLException, InterruptedException {\n    // Run a query that takes > 4 seconds to complete\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement();\n        ResultSet rs =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"select count(*) from table(generator(timeLimit => 4))\")) {\n      // Assert that activeAsyncQueries is non-empty with running query. Session is async and not\n      // safe to close\n      long start = System.currentTimeMillis();\n      SnowflakeConnectionImpl snowflakeConnection = con.unwrap(SnowflakeConnectionImpl.class);\n      assertTrue(snowflakeConnection.getSfSession().isAsyncSession());\n      assertFalse(snowflakeConnection.getSfSession().isSafeToClose());\n      // ensure that query is finished\n      assertTrue(rs.next());\n      // ensure query took > 4 seconds\n      assertTrue(\n          Duration.ofMillis(System.currentTimeMillis() - start).compareTo(Duration.ofSeconds(4))\n              > 0);\n      // Assert that there are no longer any queries running.\n      // First, assert session is safe to close. This iterates through active queries, fetches their\n      // status, and removes them from the activeQueriesMap if they are no longer active.\n      assertTrue(snowflakeConnection.getSfSession().isSafeToClose());\n      // Next, assert session is no longer async (just fetches size of activeQueriesMap with no\n      // other action)\n      assertFalse(snowflakeConnection.getSfSession().isAsyncSession());\n    }\n  }\n\n  private Boolean isPbes2KeySupported() throws SQLException, IOException, SecurityException {\n\n    final String privateKeyFileNameEnv = \"SNOWFLAKE_PRIVATE_KEY_FILENAME\";\n    final String publicKeyFileNameEnv = \"SNOWFLAKE_PUBLIC_KEY_FILENAME\";\n    final String passphraseEnv = \"SNOWFLAKE_TEST_PASSPHRASE\";\n\n    String privateKeyFile = System.getenv(privateKeyFileNameEnv);\n    String publicKeyFile = System.getenv(publicKeyFileNameEnv);\n    String passphrase = System.getenv(passphraseEnv);\n\n    assertNotNull(\n        passphrase,\n        privateKeyFileNameEnv\n            + \" environment variable can't be empty. \"\n            + \"Please provide the filename for your private key located in the resource folder\");\n\n    assertNotNull(\n        passphrase,\n        publicKeyFileNameEnv\n            + \" environment variable can't be empty. \"\n            + \"Please provide the filename for your public key located in the resource folder\");\n\n    assertNotNull(\n        passphrase, passphraseEnv + \" environment variable is required to decrypt private key.\");\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n    Properties properties = new Properties();\n    properties.put(\"account\", parameters.get(\"account\"));\n    properties.put(\"user\", testUser);\n    properties.put(\"ssl\", parameters.get(\"ssl\"));\n    properties.put(\"port\", parameters.get(\"port\"));\n    Connection connection = getConnection();\n    Statement statement = connection.createStatement();\n    statement.execute(\"use role accountadmin\");\n    String pathFile = getFullPathFileInResource(publicKeyFile);\n    String pubKey = new String(Files.readAllBytes(Paths.get(pathFile)));\n    pubKey = pubKey.replace(\"-----BEGIN PUBLIC KEY-----\", \"\");\n    pubKey = pubKey.replace(\"-----END PUBLIC KEY-----\", \"\");\n    pubKey = pubKey.replace(\"\\n\", \"\");\n    statement.execute(String.format(\"alter user %s set rsa_public_key='%s'\", testUser, pubKey));\n    connection.close();\n    String privateKeyLocation = getFullPathFileInResource(privateKeyFile);\n    String uri =\n        parameters.get(\"uri\")\n            + \"/?private_key_pwd=\"\n            + passphrase\n            + \"&private_key_file=\"\n            + privateKeyLocation;\n    try (Connection conn = DriverManager.getConnection(uri, properties)) {\n    } catch (SQLException e) {\n      return false;\n    }\n\n    unsetPublicKey(testUser);\n    return true;\n  }\n\n  /**\n   * Added in > 3.15.1. This test is ignored and won't run until you explicitly uncomment the Ignore\n   * annotation. You can only run this test locally, i.e.: mvn -DjenkinsIT\n   * -DtestCategory=net.snowflake.client.category.TestCategoryConnection verify\n   *\n   * <p>In order to run the test successfully, you need to first do the following: 1.) Generate a\n   * private and public key using OpenSSL v3: openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 aes256\n   * -inform PEM -out rsa-key-aes256.p8 openssl rsa -in rsa-key-aes256.p8 -pubout -out\n   * rsa-key-aes256.pub 2.) Export the following environment variables: export\n   * SNOWFLAKE_TEST_PASSPHRASE=your_key's_passphrase export\n   * SNOWFLAKE_PRIVATE_KEY_FILENAME=rsa-key-aes256.p8 export\n   * SNOWFLAKE_PUBLIC_KEY_FILENAME=rsa-key-aes256.pub 3.) Move the private and public keys to the\n   * src/test/resources folder 4.) After the test is complete, remove the files from the resources\n   * folder\n   *\n   * <p>We're testing if we can decrypt a private key generated using OpenSSL v3. This was failing\n   * with an invalid private key error before SNOW-896618 added the ability to decrypt private keys\n   * with the JVM argument {@value\n   * net.snowflake.client.internal.core.SecurityUtil#USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM}\n   *\n   * @throws SQLException\n   * @throws IOException\n   */\n  @Test\n  @Disabled\n  @DontRunOnGithubActions\n  public void testPbes2Support() throws SQLException, IOException {\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"false\");\n    boolean pbes2Supported = isPbes2KeySupported();\n\n    // The expectation is that this is going to fail (meaning the test passes when pbes2Supported is\n    // false) when we use the default JDK security providers without Bouncy Castle added.\n    // If the time comes when the JDK's default providers finally support PBES2 then this test will\n    // fail letting us know that we don't need the Bouncy Castle dependency anymore for private key\n    // decryption.\n    String failureMessage =\n        \"The failure means that the JDK version can decrypt a private key generated by OpenSSL v3 and \"\n            + \"BouncyCastle shouldn't be needed anymore\";\n    assertFalse(pbes2Supported, failureMessage);\n\n    // The expectation is that this is going to pass once we add Bouncy Castle in the list of\n    // providers\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"true\");\n    pbes2Supported = isPbes2KeySupported();\n    failureMessage =\n        \"Bouncy Castle Provider should have been loaded with the -D\"\n            + SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM\n            + \"JVM argument and this should have decrypted the private key generated by OpenSSL v3\";\n    assertTrue(pbes2Supported, failureMessage);\n  }\n\n  // Test for regenerating okta one-time token for versions > 3.15.1\n  @Test\n  @Disabled\n  public void testDataSourceOktaGenerates429StatusCode() throws Exception {\n    // test with username/password authentication\n    // set up DataSource object and ensure connection works\n    Map<String, String> params = getConnectionParameters();\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n    ds.setServerName(params.get(\"host\"));\n    ds.setSsl(\"on\".equals(params.get(\"ssl\")));\n    ds.setAccount(params.get(\"account\"));\n    ds.setPortNumber(Integer.parseInt(params.get(\"port\")));\n    ds.setUser(params.get(\"ssoUser\"));\n    ds.setPassword(params.get(\"ssoPassword\"));\n    ds.setAuthenticator(\"<okta address>\");\n    Runnable r =\n        () -> {\n          try {\n            ds.getConnection();\n          } catch (SQLException e) {\n            throw new RuntimeException(e);\n          }\n        };\n    List<Thread> threadList = new ArrayList<>();\n    for (int i = 0;\n        i < 30;\n        ++i) { // https://docs.snowflake.com/en/user-guide/admin-security-fed-auth-use#http-429-errors\n      threadList.add(new Thread(r));\n    }\n    threadList.forEach(Thread::start);\n    for (Thread thread : threadList) {\n      thread.join();\n    }\n  }\n\n  /**\n   * SNOW-1465374: For TIMESTAMP_LTZ we were returning timestamps without timezone when scale was\n   * set e.g. to 6 in Arrow format The problem wasn't visible when calling getString, but was\n   * visible when we called toString on passed getTimestamp since we returned {@link\n   * java.sql.Timestamp}, not {@link SnowflakeTimestampWithTimezone}\n   *\n   * <p>Timestamps before 1582-10-05 are always returned as {@link java.sql.Timestamp}, not {@link\n   * SnowflakeTimestampWithTimezone} {SnowflakeTimestampWithTimezone}\n   *\n   * <p>Added in > 3.16.1\n   */\n  @Test\n  public void shouldGetDifferentTimestampLtzConsistentBetweenFormats() throws Exception {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.executeUpdate(\n          \"create or replace table DATETIMETZ_TYPE(timestamp_tzcol timestamp_ltz, timestamp_tzpcol timestamp_ltz(6), timestamptzcol timestampltz, timestampwtzcol timestamp with local time zone);\");\n      Arrays.asList(\n              \"insert into DATETIMETZ_TYPE values('9999-12-31 23:59:59.999999999','9999-12-31 23:59:59.999999','9999-12-31 23:59:59.999999999','9999-12-31 23:59:59.999999999');\",\n              \"insert into DATETIMETZ_TYPE values('1582-01-01 00:00:00.000000001','1582-01-01 00:00:00.000001','1582-01-01 00:00:00.000000001','1582-01-01 00:00:00.000000001');\",\n              \"insert into DATETIMETZ_TYPE values('2000-06-18 18:29:30.123456789 +0100','2000-06-18 18:29:30.123456 +0100','2000-06-18 18:29:30.123456789 +0100','2000-06-18 18:29:30.123456789 +0100');\",\n              \"insert into DATETIMETZ_TYPE values(current_timestamp(),current_timestamp(),current_timestamp(),current_timestamp());\",\n              \"insert into DATETIMETZ_TYPE values('2000-06-18 18:29:30.12345 -0530','2000-06-18 18:29:30.123 -0530','2000-06-18 18:29:30.123456 -0530','2000-06-18 18:29:30.123 -0530');\",\n              \"insert into DATETIMETZ_TYPE values('2000-06-18 18:29:30','2000-06-18 18:29:30','2000-06-18 18:29:30','2000-06-18 18:29:30');\",\n              \"insert into DATETIMETZ_TYPE values('1582-10-04 00:00:00.000000001','1582-10-04 00:00:00.000001','1582-10-04 00:00:00.000000001','1582-10-04 00:00:00.000000001');\",\n              \"insert into DATETIMETZ_TYPE values('1582-10-05 00:00:00.000000001','1582-10-05 00:00:00.000001','1582-10-05 00:00:00.000000001','1582-10-05 00:00:00.000000001');\",\n              \"insert into DATETIMETZ_TYPE values('1583-10-05 00:00:00.000000001','1583-10-05 00:00:00.000001','1583-10-05 00:00:00.000000001','1583-10-05 00:00:00.000000001');\")\n          .forEach(\n              insert -> {\n                try {\n                  statement.executeUpdate(insert);\n                } catch (SQLException e) {\n                  throw new RuntimeException(e);\n                }\n              });\n      try (ResultSet arrowResultSet = statement.executeQuery(\"select * from DATETIMETZ_TYPE\")) {\n        try (Connection jsonConnection = getConnection();\n            Statement jsonStatement = jsonConnection.createStatement()) {\n          jsonStatement.execute(\"alter session set JDBC_QUERY_RESULT_FORMAT=JSON\");\n          try (ResultSet jsonResultSet =\n              jsonStatement.executeQuery(\"select * from DATETIMETZ_TYPE\")) {\n            int rowIdx = 0;\n            while (arrowResultSet.next()) {\n              logger.debug(\"Checking row \" + rowIdx);\n              assertTrue(jsonResultSet.next());\n              for (int column = 1; column <= 4; ++column) {\n                logger.trace(\n                    \"JSON row[{}],column[{}] as string '{}', timestamp string '{}', as timestamp numeric '{}', tz offset={}, timestamp class {}\",\n                    rowIdx,\n                    column,\n                    jsonResultSet.getString(column),\n                    jsonResultSet.getTimestamp(column),\n                    jsonResultSet.getTimestamp(column).getTime(),\n                    jsonResultSet.getTimestamp(column).getTimezoneOffset(),\n                    jsonResultSet.getTimestamp(column).getClass());\n                logger.trace(\n                    \"ARROW row[{}],column[{}] as string '{}', timestamp string '{}', as timestamp numeric '{}', tz offset={}, timestamp class {}\",\n                    rowIdx,\n                    column,\n                    arrowResultSet.getString(column),\n                    arrowResultSet.getTimestamp(column),\n                    arrowResultSet.getTimestamp(column).getTime(),\n                    arrowResultSet.getTimestamp(column).getTimezoneOffset(),\n                    arrowResultSet.getTimestamp(column).getClass());\n                assertEquals(\n                    jsonResultSet.getString(column),\n                    arrowResultSet.getString(column),\n                    \"Expecting that string representation are the same for row \"\n                        + rowIdx\n                        + \" and column \"\n                        + column);\n                assertEquals(\n                    jsonResultSet.getTimestamp(column).toString(),\n                    arrowResultSet.getTimestamp(column).toString(),\n                    \"Expecting that string representation (via toString) are the same for row \"\n                        + rowIdx\n                        + \" and column \"\n                        + column);\n                assertEquals(\n                    jsonResultSet.getTimestamp(column),\n                    arrowResultSet.getTimestamp(column),\n                    \"Expecting that timestamps are the same for row \"\n                        + rowIdx\n                        + \" and column \"\n                        + column);\n              }\n              rowIdx++;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testSetHoldability() throws Throwable {\n    try (Connection connection = getConnection()) {\n      connection.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT);\n      // return an empty type map. setTypeMap is not supported.\n      assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, connection.getHoldability());\n      connection.close();\n      expectConnectionAlreadyClosedException(\n          () -> connection.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT));\n    }\n  }\n\n  /** Added in > 3.14.5 and modified in > 3.18.0 */\n  @Test\n  public void shouldGetOverridenConnectionAndSocketTimeouts() throws Exception {\n    Duration origConnectionTimeout = HttpUtil.getConnectionTimeout();\n    Duration origSocketTimeout = HttpUtil.getSocketTimeout();\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"HTTP_CLIENT_CONNECTION_TIMEOUT\", 100);\n    paramProperties.put(\"HTTP_CLIENT_SOCKET_TIMEOUT\", 200);\n\n    try (Connection connection = getConnection(paramProperties)) {\n      assertEquals(Duration.ofMillis(100), HttpUtil.getConnectionTimeout());\n      assertEquals(Duration.ofMillis(200), HttpUtil.getSocketTimeout());\n    } finally {\n      // reset to original values\n      HttpUtil.setConnectionTimeout((int) origConnectionTimeout.toMillis());\n      HttpUtil.setSocketTimeout((int) origSocketTimeout.toMillis());\n    }\n  }\n\n  /** Added in > 3.19.0 */\n  @Test\n  public void shouldFailOnSslExceptionWithLinkToTroubleShootingGuide() throws InterruptedException {\n    Properties properties = new Properties();\n    properties.put(\"user\", \"fakeuser\");\n    properties.put(\"password\", \"testpassword\");\n    properties.put(\"ocspFailOpen\", Boolean.FALSE.toString());\n\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              DriverManager.getConnection(\"jdbc:snowflake://expired.badssl.com/\", properties);\n            });\n    // *.badssl.com may fail with timeout\n    if (!(e.getCause() instanceof SSLHandshakeException)\n        && e.getCause().getMessage().toLowerCase().contains(\"timed out\")) {\n      return;\n    }\n    assertThat(e.getCause(), instanceOf(SSLHandshakeException.class));\n    assertTrue(\n        e.getMessage()\n            .contains(\n                \"https://docs.snowflake.com/en/user-guide/client-connectivity-troubleshooting/overview\"));\n  }\n\n  @Test\n  public void testGetRoleWarehouseAndDatabase() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      SnowflakeConnection sfCon = con.unwrap(SnowflakeConnection.class);\n\n      try (ResultSet rs = statement.executeQuery(\"select current_role()\")) {\n        assertTrue(rs.next());\n        assertEquals(rs.getString(1), sfCon.getRole());\n      }\n\n      try (ResultSet rs = statement.executeQuery(\"select current_warehouse()\")) {\n        assertTrue(rs.next());\n        assertEquals(rs.getString(1), sfCon.getWarehouse());\n      }\n\n      try (ResultSet rs = statement.executeQuery(\"select current_database()\")) {\n        assertTrue(rs.next());\n        String currentDatabase = rs.getString(1);\n        assertEquals(currentDatabase, sfCon.getDatabase());\n        assertEquals(con.getCatalog(), sfCon.getDatabase());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionManual.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.internal.core.SessionUtil;\n\n/**\n * Connection Manual Tests\n *\n * <p>This test can run from the command line. Intentionally Junit/Hamcrest matcher is not used to\n * simplify the commandline option.\n *\n * <p>- Prerequisite: 1. Create a shell script.\n *\n * <p>#!/bin/bash -e # # Run ConnectionManualTest using IntelliJ remote debug # export\n * SNOWFLAKE_TEST_ACCOUNT=ACCOUNT export SNOWFLAKE_TEST_SSO_USER=USER export\n * SNOWFLAKE_TEST_PORT=PORT export SNOWFLAKE_TEST_SSL=SSL export SNOWFLAKE_TEST_DATABASE=DATABASE\n * export SNOWFLAKE_TEST_SCHEMA=SCHEMA export SNOWFLAKE_TEST_SCHEMA2=SCHEMA2 export\n * SNOWFLAKE_TEST_ADMIN_ACCOUNT=ADMIN_ACCOUNT export SNOWFLAKE_TEST_ADMIN_USER=ADMIN_USER export\n * SNOWFLAKE_TEST_ADMIN_PASSWORD=ADMIN_PASSWORD\n *\n * <p># This delete step doesn't need to be done in the future. # Test code will take care of it. #\n * echo \"[INFO] Deleting id token cache\" # rm -f ~/.cache/snowflake/temporary_credential.json\n *\n * <p>java -ea \\ -agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y \\ -cp\n * lib/snowflake-jdbc-3.6.6.jar:target/test-classes \\ net.snowflake.client.jdbc.ConnectionManual\n *\n * <p>2. Open Run/Debug Configuration and create a Remote configuration as Listen mode using 5005\n * and named it \"ConnectionManual\".\n *\n * <p>- Debugging\n *\n * <p>1. Set breakpoints on IntelliJ. 2. Run mvn install to build a new JDBC jar, which is required\n * to locate Snowflake JDBC driver from the driver manager. 3. Start the \"ConnectionManual\" remote\n * 4. Run the script. ./run.sh 5. Enjoy debugging!\n */\npublic class ConnectionManual {\n  public static void main(String args[]) throws Throwable {\n    ConnectionManual test = new ConnectionManual();\n    test.setTokenValidityForTest();\n    try {\n      test.testSSO();\n    } finally {\n      test.resetTokenValidity();\n    }\n  }\n\n  private Properties getProperties() {\n    String account = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\");\n    String ssoUser = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SSO_USER\");\n    String ssl = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SSL\");\n\n    Properties properties = new Properties();\n    properties.put(\"user\", ssoUser);\n    properties.put(\"account\", account);\n    properties.put(\"ssl\", ssl);\n    properties.put(\"tracing\", \"FINEST\");\n    properties.put(\"authenticator\", \"externalbrowser\");\n    properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n    return properties;\n  }\n\n  private String getUrl() {\n    String account = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\");\n    String port = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PORT\");\n    return String.format(\"jdbc:snowflake://%s.reg.snowflakecomputing.com:%s\", account, port);\n  }\n\n  private Connection getAdminConnection() throws Throwable {\n    String adminAccount = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ADMIN_ACCOUNT\");\n    if (adminAccount == null) {\n      adminAccount = \"snowflake\";\n    }\n    String adminUser = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ADMIN_USER\");\n    String adminPassword = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ADMIN_PASSWORD\");\n    String port = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_PORT\");\n    String ssl = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SSL\");\n    if (ssl == null) {\n      ssl = \"on\";\n    }\n\n    Properties properties = new Properties();\n    properties.put(\"user\", adminUser);\n    properties.put(\"password\", adminPassword);\n    properties.put(\"account\", adminAccount);\n    properties.put(\"ssl\", ssl);\n\n    // connect url\n    String url =\n        String.format(\"jdbc:snowflake://%s.reg.snowflakecomputing.com:%s\", adminAccount, port);\n    return DriverManager.getConnection(url, properties);\n  }\n\n  private void setTokenValidityForTest() throws Throwable {\n    try (Connection con = getAdminConnection();\n        Statement statement = con.createStatement()) {\n      statement.execute(\n          \"alter system set \"\n              + \"MASTER_TOKEN_VALIDITY=60, \"\n              + \"SESSION_TOKEN_VALIDITY=30, \"\n              + \"ID_TOKEN_VALIDITY=60\");\n\n      // ALLOW_UNPROTECTED_ID_TOKEN will be deprecated in the future\n      // con.createStatement().execute(\"alter account testaccount set \" +\n      //                                \"ALLOW_UNPROTECTED_ID_TOKEN=true;\");\n      statement.execute(\"alter account testaccount set ID_TOKEN_FEATURE_ENABLED=true;\");\n      statement.execute(\"alter account testaccount set ALLOW_ID_TOKEN=true;\");\n    }\n  }\n\n  private void resetTokenValidity() throws Throwable {\n    try (Connection con = getAdminConnection();\n        Statement statement = con.createStatement()) {\n      statement.execute(\n          \"alter system set \"\n              + \"MASTER_TOKEN_VALIDITY=default, \"\n              + \"SESSION_TOKEN_VALIDITY=default, \"\n              + \"ID_TOKEN_VALIDITY=default\");\n    }\n  }\n\n  private void testSSO() throws Throwable {\n    String database = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_DATABASE\");\n    String schema1 = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SCHEMA\");\n    String schema2 = TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SCHEMA2\");\n\n    Properties properties = getProperties();\n    String url = getUrl();\n\n    SessionUtil.deleteIdTokenCache(\n        String.format(\"%s.reg.snowflakecomputing.com\", properties.getProperty(\"account\")),\n        properties.getProperty(\"user\"));\n\n    System.out.println(\n        \"[INFO] 1st connection gets id token and stores in the cache file. \"\n            + \"This popup a browser to SSO login\");\n    try (Connection con1 = DriverManager.getConnection(url, properties)) {\n      assertTrue(database.equalsIgnoreCase(con1.getCatalog()));\n      assertTrue(schema1.equalsIgnoreCase(con1.getSchema()));\n    }\n\n    System.out.println(\n        \"[INFO] 2nd connection reads the cache file and uses the id token. \"\n            + \"This should not popups a browser.\");\n    properties.setProperty(\"database\", database);\n    properties.setProperty(\"schema\", schema1);\n    try (Connection con2 = DriverManager.getConnection(url, properties);\n        Statement statement = con2.createStatement()) {\n      assertTrue(database.equalsIgnoreCase(con2.getCatalog()));\n      assertTrue(schema1.equalsIgnoreCase(con2.getSchema()));\n\n      System.out.println(\"[INFO] Running a statement... 10 seconds\");\n      statement.execute(\"select seq8() from table(generator(timelimit=>10))\");\n      assertTrue(database.equalsIgnoreCase(con2.getCatalog()));\n      assertTrue(schema1.equalsIgnoreCase(con2.getSchema()));\n\n      System.out.println(\"[INFO] Running a statement... 1 second\");\n      statement.execute(\"select seq8() from table(generator(timelimit=>1))\");\n      assertTrue(database.equalsIgnoreCase(con2.getCatalog()));\n      assertTrue(schema1.equalsIgnoreCase(con2.getSchema()));\n\n      System.out.println(\"[INFO] Running a statement... 90 seconds\");\n      statement.execute(\"select seq8() from table(generator(timelimit=>90))\");\n      assertTrue(database.equalsIgnoreCase(con2.getCatalog()));\n      assertTrue(schema1.equalsIgnoreCase(con2.getSchema()));\n    }\n\n    System.out.println(\n        \"[INFO] 3rd connection reads the cache file and uses the id token. \"\n            + \"This should work even after closing the previous connections. \"\n            + \"A specified schema should be set in the connection object.\");\n    properties.setProperty(\"schema\", schema2);\n    try (Connection con3 = DriverManager.getConnection(url, properties)) {\n      assertTrue(database.equalsIgnoreCase(con3.getCatalog()));\n      assertTrue(schema1.equalsIgnoreCase(con3.getSchema()));\n    }\n\n    System.out.println(\n        \"[INFO] 4th connection reads the cache file and tries to use the id token. \"\n            + \"However we manually banned IdToken by set some parameters. \"\n            + \"So a web browser pop up is expected to show up here.\");\n    try (Connection conAdmin = getAdminConnection();\n        Statement statement = conAdmin.createStatement()) {\n      // ALLOW_UNPROTECTED_ID_TOKEN will be deprecated in the future\n      // conAdmin.createStatement().execute(\"alter account testaccount set \" +\n      //                                \"ALLOW_UNPROTECTED_ID_TOKEN=false;\");\n      statement.execute(\"alter account testaccount set ID_TOKEN_FEATURE_ENABLED=false;\");\n      statement.execute(\"alter account testaccount set ALLOW_ID_TOKEN=false;\");\n      try (Connection con4 = DriverManager.getConnection(url, properties)) {\n        System.out.println(\n            \"Finished. You might need to close login page in web browser to exit this test.\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionPoolingIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.mchange.v2.c3p0.ComboPooledDataSource;\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport java.beans.PropertyVetoException;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.commons.dbcp.BasicDataSource;\nimport org.apache.commons.dbcp.PoolingDataSource;\nimport org.apache.commons.pool.impl.GenericObjectPool;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Connection pool interface test */\n@Tag(TestTags.CONNECTION)\npublic class ConnectionPoolingIT {\n  private BasicDataSource bds = null;\n  private ComboPooledDataSource cpds = null;\n  private final String connectStr;\n  private final String account;\n  private final String user;\n  private final String password;\n  private final String database;\n  private final String schema;\n  private final String ssl;\n\n  public ConnectionPoolingIT() {\n    Map<String, String> params = BaseJDBCTest.getConnectionParameters();\n    connectStr = params.get(\"uri\");\n    account = params.get(\"account\");\n    user = params.get(\"user\");\n    password = params.get(\"password\");\n    database = params.get(\"database\");\n    schema = params.get(\"schema\");\n    ssl = params.get(\"ssl\");\n  }\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Connection connection = BaseJDBCTest.getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table test_pooling(colA string)\");\n      statement.execute(\"insert into test_pooling values('test_str')\");\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    try (Connection connection = BaseJDBCTest.getConnection();\n        Statement statement = connection.createStatement(); ) {\n      statement.execute(\"drop table if exists TEST_POOLING\");\n    }\n  }\n\n  /*  Testing DBCP Library */\n  private void setUpDBCPConnection() {\n    bds = new BasicDataSource();\n    bds.setDriverClassName(BaseJDBCTest.DRIVER_CLASS);\n    bds.setUsername(this.user);\n    bds.setPassword(this.password);\n    bds.setUrl(connectStr);\n\n    String propertyString =\n        String.format(\n            \"ssl=%s;db=%s;schema=%s;account=%s;\",\n            this.ssl, this.database, this.schema, this.account);\n    bds.setConnectionProperties(propertyString);\n  }\n\n  @Test\n  public void testDBCPConnection() throws SQLException, Exception {\n    setUpDBCPConnection();\n    for (int i = 0; i < 10; i++) {\n      Thread t = new Thread(new queryUsingPool(bds));\n      t.join();\n    }\n  }\n\n  private GenericObjectPool connectionPool = null;\n\n  private PoolingDataSource setUpPoolingDataSource() throws Exception {\n    Class.forName(BaseJDBCTest.DRIVER_CLASS);\n    connectionPool = new GenericObjectPool();\n    connectionPool.setMaxActive(20);\n    return new PoolingDataSource(connectionPool);\n  }\n\n  public GenericObjectPool getConnectionPool() {\n    return connectionPool;\n  }\n\n  @Test\n  public void testPoolingDataSource() throws Exception, SQLException {\n    PoolingDataSource pds = setUpPoolingDataSource();\n    for (int i = 0; i < 10; i++) {\n      Thread t = new Thread(new queryUsingPool(pds));\n      t.join();\n    }\n  }\n\n  /* Test C3P0 Library */\n  private void setUpC3P0Connection() throws PropertyVetoException, SQLException {\n    Properties connectionProperties = setUpConnectionProperty();\n    cpds = new ComboPooledDataSource();\n    cpds.setDriverClass(BaseJDBCTest.DRIVER_CLASS);\n    cpds.setJdbcUrl(connectStr);\n    cpds.setProperties(connectionProperties);\n  }\n\n  @Test\n  public void testC3P0ConnectionPool() throws Exception {\n    setUpC3P0Connection();\n\n    for (int i = 0; i < 10; i++) {\n      Thread t = new Thread(new queryUsingPool(cpds));\n      t.join();\n    }\n  }\n\n  @Test\n  public void testHikariConnectionPool() throws Exception {\n    HikariDataSource ds = initHikariDataSource();\n    for (int i = 0; i < 10; i++) {\n      Thread t = new Thread(new queryUsingPool(ds));\n      t.join();\n    }\n  }\n\n  private HikariDataSource initHikariDataSource() throws SQLException {\n    HikariConfig config = new HikariConfig();\n    config.setDriverClassName(BaseJDBCTest.DRIVER_CLASS);\n    config.setDataSourceProperties(setUpConnectionProperty());\n    config.setJdbcUrl(connectStr);\n    return new HikariDataSource(config);\n  }\n\n  private static class queryUsingPool implements Runnable {\n    ComboPooledDataSource cpds = null;\n    BasicDataSource bds = null;\n    PoolingDataSource pds = null;\n    HikariDataSource hds = null;\n\n    queryUsingPool(ComboPooledDataSource cpds) {\n      this.cpds = cpds;\n    }\n\n    queryUsingPool(BasicDataSource bds) {\n      this.bds = bds;\n    }\n\n    queryUsingPool(PoolingDataSource pds) {\n      this.pds = pds;\n    }\n\n    queryUsingPool(HikariDataSource hds) {\n      this.hds = hds;\n    }\n\n    @Override\n    public void run() {\n      Connection con = null;\n      try {\n        if (cpds != null) {\n          con = cpds.getConnection();\n        } else if (bds != null) {\n          con = bds.getConnection();\n        } else if (pds != null) {\n          con = pds.getConnection();\n        } else if (hds != null) {\n          con = hds.getConnection();\n        }\n\n        try (Statement st = con.createStatement();\n            ResultSet resultSet = st.executeQuery(\"SELECT * FROM test_pooling\")) {\n          while (resultSet.next()) {\n            assertEquals(\"test_str\", resultSet.getString(1));\n          }\n        }\n      } catch (Exception e) {\n        System.out.println(e);\n      }\n    }\n  }\n\n  private Properties setUpConnectionProperty() {\n    Properties properties = new Properties();\n    // use the default connection string if it is not set in environment\n    properties.put(\"user\", this.user);\n\n    // Handle authentication - prioritize private key, fallback to password\n    Map<String, String> params = BaseJDBCTest.getConnectionParameters();\n    if (params.get(\"private_key_file\") != null) {\n      properties.put(\"private_key_file\", params.get(\"private_key_file\"));\n      properties.put(\"authenticator\", params.get(\"authenticator\"));\n      if (params.get(\"private_key_pwd\") != null) {\n        properties.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n      }\n    } else if (this.password != null) {\n      properties.put(\"password\", this.password);\n    }\n\n    properties.put(\"account\", this.account);\n    properties.put(\"db\", this.database);\n    properties.put(\"schema\", this.schema);\n    properties.put(\"ssl\", this.ssl);\n\n    return properties;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionWithDisableOCSPModeLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.stream.Stream;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.SFTrustManager;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n/** Tests for connection with DisableOCSPchecks and insecuremode settings. */\n@Tag(TestTags.CONNECTION)\npublic class ConnectionWithDisableOCSPModeLatestIT extends BaseJDBCTest {\n  private static final int DISABLE_OCSP_INSECURE_MODE_MISMATCH = 200064;\n\n  @BeforeEach\n  public void setUp() {\n    SFTrustManager.deleteCache();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    SFTrustManager.cleanTestSystemParameters();\n  }\n\n  private static Stream<Arguments> testParameters() {\n    return Stream.of(Arguments.of(true, true), Arguments.of(true, null), Arguments.of(null, true));\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"testParameters\")\n  public void shouldConnectIfDisableOCSPChecksAndInsecureModeAreSet(\n      Boolean disableOCSPChecks, Boolean insecureMode) throws SQLException {\n    Properties properties = getProperties(disableOCSPChecks, insecureMode);\n    connectAndVerifySimpleQuery(properties);\n  }\n\n  private static Stream<Arguments> testParametersMismatch() {\n    return Stream.of(Arguments.of(true, false), Arguments.of(false, true));\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"testParametersMismatch\")\n  public void shouldThrowWhenThereIsMismatchInDisableOCSPChecksAndInsecureMode(\n      Boolean disableOCSPChecks, Boolean insecureMode) {\n    Properties properties = getProperties(disableOCSPChecks, insecureMode);\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(\n                    String.format(\n                        \"jdbc:snowflake://%s:%s\", properties.get(\"host\"), properties.get(\"port\")),\n                    properties));\n    assertEquals(DISABLE_OCSP_INSECURE_MODE_MISMATCH, e.getErrorCode());\n  }\n\n  private Properties getProperties(Boolean disableOCSPChecks, Boolean insecureMode) {\n    Map<String, String> params = getConnectionParameters();\n    Properties props = new Properties();\n    props.put(\"host\", params.get(\"host\"));\n    props.put(\"port\", params.get(\"port\"));\n    props.put(\"account\", params.get(\"account\"));\n    props.put(\"user\", params.get(\"user\"));\n    props.put(\"role\", params.get(\"role\"));\n\n    // Handle authentication - prioritize private key, fallback to password\n    if (params.get(\"private_key_file\") != null) {\n      props.put(\"private_key_file\", params.get(\"private_key_file\"));\n      props.put(\"authenticator\", params.get(\"authenticator\"));\n      if (params.get(\"private_key_pwd\") != null) {\n        props.put(\"private_key_pwd\", params.get(\"private_key_pwd\"));\n      }\n    } else if (params.get(\"password\") != null) {\n      props.put(\"password\", params.get(\"password\"));\n    }\n\n    props.put(\"warehouse\", params.get(\"warehouse\"));\n    props.put(\"db\", params.get(\"database\"));\n    props.put(\"schema\", params.get(\"schema\"));\n    props.put(\"ssl\", params.get(\"ssl\"));\n    if (disableOCSPChecks != null) {\n      props.put(\"disableOCSPChecks\", disableOCSPChecks);\n    }\n    if (insecureMode != null) {\n      props.put(\"insecureMode\", insecureMode);\n    }\n    return props;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ConnectionWithOCSPModeIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.api.exception.ErrorCode.NETWORK_ERROR;\nimport static org.hamcrest.CoreMatchers.anyOf;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.IsInstanceOf.instanceOf;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.security.cert.CertificateExpiredException;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport javax.net.ssl.SSLHandshakeException;\nimport javax.net.ssl.SSLPeerUnverifiedException;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.SFOCSPException;\nimport net.snowflake.client.internal.core.SFTrustManager;\nimport org.hamcrest.Matcher;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests for connection with OCSP mode mainly negative cases by injecting errors.\n *\n * <p>Ensure running hang_webserver.py in the backend to simulate connection hang.\n *\n * <p>hang_webserver.py 12345\n */\n@Tag(TestTags.CONNECTION)\npublic class ConnectionWithOCSPModeIT extends BaseJDBCTest {\n  private final String testUser = \"fakeuser\";\n  private final String testPassword = \"testpassword\";\n  private final String testRevokedCertConnectString = \"jdbc:snowflake://revoked.badssl.com/\";\n\n  private static int nameCounter = 0;\n\n  @BeforeEach\n  public void setUp() {\n    SFTrustManager.deleteCache();\n  }\n\n  @AfterEach\n  public void tearDown() {\n    SFTrustManager.cleanTestSystemParameters();\n  }\n\n  private static synchronized String genTestConnectString() {\n    String ret = \"jdbc:snowflake://fakeaccount\" + nameCounter + \".snowflakecomputing.com\";\n    ++nameCounter;\n    return ret;\n  }\n\n  private static Throwable getCause(Throwable ex) {\n    Throwable ex0 = ex;\n    while (ex0.getCause() != null) {\n      ex0 = ex0.getCause();\n    }\n    return ex0;\n  }\n\n  private Properties OCSPFailOpenProperties() {\n    Properties properties = new Properties();\n    properties.put(\"user\", testUser);\n    properties.put(\"password\", testPassword);\n    properties.put(\"ocspFailOpen\", Boolean.TRUE.toString());\n    properties.put(\"loginTimeout\", \"10\");\n    properties.put(\"tracing\", \"ALL\");\n    return properties;\n  }\n\n  private Properties OCSPFailClosedProperties() {\n    Properties properties = new Properties();\n    properties.put(\"user\", testUser);\n    properties.put(\"password\", testPassword);\n    properties.put(\"ocspFailOpen\", Boolean.FALSE.toString());\n    properties.put(\"loginTimeout\", \"10\");\n    properties.put(\"tracing\", \"ALL\");\n    return properties;\n  }\n\n  private Properties OCSPInsecureProperties() {\n    Properties properties = new Properties();\n    properties.put(\"user\", testUser);\n    properties.put(\"password\", testPassword);\n    properties.put(\"insecureMode\", Boolean.TRUE.toString());\n    properties.put(\"loginTimeout\", \"10\");\n    return properties;\n  }\n\n  /** Test OCSP response validity expired case. It should be ignored in FAIL_OPEN mode. */\n  @Test\n  public void testValidityExpiredOCSPResponseFailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INJECT_VALIDITY_ERROR, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /**\n   * Test OCSP response validity expired case. INVALID_OCSP_RESPONSE_VALIDITY should be raised in\n   * FAIL_CLOSED mode.\n   */\n  @Test\n  public void testValidityExpiredOCSPResponseFailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INJECT_VALIDITY_ERROR, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    Throwable cause = getCause(ex);\n    assertThat(cause, instanceOf(SFOCSPException.class));\n    assertThat(\n        ((SFOCSPException) cause).getErrorCode(),\n        equalTo(OCSPErrorCode.INVALID_OCSP_RESPONSE_VALIDITY));\n  }\n\n  /** Test no OCSP responder URL is attached. It should be ignored in FAIL_OPEN mode. */\n  @Test\n  public void testNoOCSPResponderURLFailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_NO_OCSP_RESPONDER_URL, Boolean.TRUE.toString());\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /**\n   * Test no OCSP responder URL is attached. NO_OCSP_URL_ATTACHED should be raised in FAIL_CLOSED\n   * mode.\n   */\n  @Test\n  public void testNoOCSPResponderURLFailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_NO_OCSP_RESPONDER_URL, Boolean.TRUE.toString());\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    Throwable cause = getCause(ex);\n    assertThat(cause, instanceOf(SFOCSPException.class));\n    assertThat(\n        ((SFOCSPException) cause).getErrorCode(), equalTo(OCSPErrorCode.NO_OCSP_URL_ATTACHED));\n  }\n\n  /** Test OCSP response validity expired case. It should be ignored in INSECURE mode. */\n  @Test\n  public void testValidityExpiredOCSPResponseInsecure() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INJECT_VALIDITY_ERROR, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPInsecureProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /** Test invalid attached signing certificate is invalid. Should be ignored in FAIL_OPEN mode. */\n  @Test\n  public void testCertAttachedInvalidFailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INVALID_SIGNING_CERT, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /**\n   * Test invalid attached signing certificate is invalid. EXPIRED_OCSP_SIGNING_CERTIFICATE should\n   * be raised.\n   */\n  @Test\n  public void testCertAttachedInvalidFailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INVALID_SIGNING_CERT, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    Throwable cause = getCause(ex);\n    assertThat(cause, instanceOf(SFOCSPException.class));\n    assertThat(\n        ((SFOCSPException) cause).getErrorCode(),\n        equalTo(OCSPErrorCode.EXPIRED_OCSP_SIGNING_CERTIFICATE));\n  }\n\n  /** Test UNKNOWN certificate. Should be ignored in FAIL_OPEN mode. */\n  @Test\n  public void testUnknownOCSPCertFailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INJECT_UNKNOWN_STATUS, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /** Test UNKNOWN certificate. CERTIFICATE_STATUS_UNKNOWN should be raised. */\n  @Test\n  public void testUnknownOCSPCertFailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INJECT_UNKNOWN_STATUS, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    Throwable cause = getCause(ex);\n    assertThat(cause, instanceOf(SFOCSPException.class));\n    assertThat(\n        ((SFOCSPException) cause).getErrorCode(),\n        equalTo(OCSPErrorCode.CERTIFICATE_STATUS_UNKNOWN));\n  }\n\n  /**\n   * Test REVOKED certificate. CERTIFICATE_STATUS_REVOKED should be raised even in FAIL_OPEN\n   * mode. @Test public void testRevokedCertFailOpen() { try {\n   * DriverManager.getConnection(testRevokedCertConnectString, OCSPFailOpenProperties());\n   * fail(\"should fail\"); } catch (SQLException ex) { assertThat(ex,\n   * instanceOf(SnowflakeSQLException.class)); assertThat(ex.getErrorCode(),\n   * equalTo(NETWORK_ERROR.getMessageCode())); Throwable cause = getCause(ex); assertThat((cause,\n   * instanceOf(SFOCSPException.class)) || (cause, instanceOf(CertificateExpiredException.class)));\n   * assertThat(((SFOCSPException) cause).getErrorCode(),\n   * equalTo(OCSPErrorCode.CERTIFICATE_STATUS_REVOKED)); } }\n   */\n\n  /**\n   * Test REVOKED certificate. CERTIFICATE_STATUS_REVOKED should be raised. @Test public void\n   * testRevokedCertFailClosed() { try { DriverManager.getConnection(testRevokedCertConnectString,\n   * OCSPFailClosedProperties()); fail(\"should fail\"); } catch (SQLException ex) { assertThat(ex,\n   * instanceOf(SnowflakeSQLException.class)); assertThat(ex.getErrorCode(),\n   * equalTo(NETWORK_ERROR.getMessageCode())); Throwable cause = getCause(ex); assertThat(cause,\n   * instanceOf(SFOCSPException.class)); assertThat(((SFOCSPException) cause).getErrorCode(),\n   * equalTo(OCSPErrorCode.CERTIFICATE_STATUS_REVOKED)); } }\n   */\n\n  /** Test OCSP Cache server hang and timeout. Should fall back to OCSP responder. */\n  @Test\n  public void testOCSPCacheServerTimeoutFailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT, \"1000\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL, \"http://localhost:12345/hang\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /**\n   * Test OCSP Cache server hang and timeout. Should fall back to OCSP responder even in FAIL_CLOSED\n   * mode.\n   */\n  @Test\n  public void testOCSPCacheServerTimeoutFailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_OCSP_RESPONSE_CACHE_SERVER_TIMEOUT, \"1000\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_URL, \"http://localhost:12345/hang\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.TRUE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n  }\n\n  /** Test OCSP Responder hang and timeout. Should be ignored in FAIL_OPEN mode. */\n  @Test\n  public void testOCSPResponderTimeoutFailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT, \"1000\");\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_RESPONDER_URL, \"http://localhost:12345/hang\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /** Test OCSP Responder hang and timeout. SocketTimeoutException exception should be raised. */\n  @Test\n  @DontRunOnGithubActions\n  public void testOCSPResponderTimeoutFailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT, \"1000\");\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_RESPONDER_URL, \"http://localhost:12345/hang\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n  }\n\n  /** Test OCSP Responder returning HTTP 403. Should be ignored in FAIL_OPEN mode. */\n  @Test\n  public void testOCSPResponder403FailOpen() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_RESPONDER_URL, \"http://localhost:12345/403\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailOpenProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n    assertThat(ex.getMessage(), httpStatus403Or404Or513());\n  }\n\n  /**\n   * Test OCSP Responder returning HTTP 403. HTTP 403 error should be raised. NOTE: don't confuse\n   * with the FAIL_OPEN test case that also returns HTTP 403. It is raised because the test endpoint\n   * is invalid.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testOCSPResponder403FailClosed() {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_RESPONDER_URL, \"http://localhost:12345/403\");\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(genTestConnectString(), OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(ex.getErrorCode(), equalTo(NETWORK_ERROR.getMessageCode()));\n  }\n\n  /** Test Certificate Expired. Will fail in both FAIL_OPEN and FAIL_CLOSED. */\n  @Test\n  @Disabled(\"Issuer of root CA expired\")\n  // https://support.sectigo.com/articles/Knowledge/Sectigo-AddTrust-External-CA-Root-Expiring-May-30-2020\n  public void testExpiredCert() {\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(\n                        \"jdbc:snowflake://expired.badssl.com/\", OCSPFailClosedProperties())\n                    .close());\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n    assertThat(getCause(ex), instanceOf(CertificateExpiredException.class));\n  }\n\n  /** Test Wrong host. Will fail in both FAIL_OPEN and FAIL_CLOSED. */\n  @Test\n  public void testWrongHost() throws InterruptedException {\n    SQLException ex =\n        assertThrows(\n            SQLException.class,\n            () ->\n                DriverManager.getConnection(\n                        \"jdbc:snowflake://wrong.host.badssl.com/\", OCSPFailClosedProperties())\n                    .close());\n    // *.badssl.com may fail with timeout\n    if (!(ex.getCause() instanceof SSLPeerUnverifiedException)\n        && !(ex.getCause() instanceof SSLHandshakeException)\n        && ex.getCause().getMessage().toLowerCase().contains(\"timed out\")) {\n      return;\n    }\n    assertThat(ex, instanceOf(SnowflakeSQLException.class));\n\n    // The certificates used by badssl.com expired around 05/17/2022,\n    // https://github.com/chromium/badssl.com/issues/504. After the certificates had been\n    // updated,\n    // the exception seems to be changed from SSLPeerUnverifiedException to\n    // SSLHandshakeException.\n    assertThat(\n        ex.getCause(),\n        anyOf(\n            instanceOf(SSLPeerUnverifiedException.class), instanceOf(SSLHandshakeException.class)));\n  }\n\n  private static Matcher<String> httpStatus403Or404Or513() {\n    return anyOf(\n        containsString(\"HTTP status=403\"),\n        containsString(\"HTTP status=404\"),\n        containsString(\"HTTP status=513\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/CustomProxyLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AbstractDriverIT.getFullPathFileInResource;\nimport static net.snowflake.client.internal.jdbc.SnowflakeDriverIT.findFile;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.net.Authenticator;\nimport java.net.PasswordAuthentication;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpProtocol;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.common.core.SqlState;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\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;\n\n// To run these tests, you must:\n// 1.) Start up a proxy connection. The simplest ways are via Squid or BurpSuite. Confluence doc on\n// setup here:\n// https://snowflakecomputing.atlassian.net/wiki/spaces/EN/pages/65438343/How+to+setup+Proxy+Server+for+Client+tests\n// 2.) Enter your own username and password for the account you're connecting to\n// 3.) Adjust parameters like role, database, schema, etc to match with account accordingly\n\n@Tag(TestTags.OTHERS)\npublic class CustomProxyLatestIT {\n  @TempDir private File tmpFolder;\n\n  /**\n   * Before running this test, change the user and password to appropriate values. Set up 2\n   * different proxy ports that can run simultaneously. This is easy with Burpsuite.\n   *\n   * <p>This tests that separate, successful connections can be made with 2 separate proxies at the\n   * same time.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @Disabled\n  public void test2ProxiesWithSameJVM() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n    props.put(\"useProxy\", true);\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8080\");\n    // Set up the first connection and proxy\n    try (Connection con1 =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con1.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n\n    // Change the proxy settings for the 2nd connection, but all other properties can be re-used.\n    // Set up the second connection.\n    props.put(\"proxyPort\", \"8081\");\n    try (Connection con2 =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com\", props);\n        Statement statement = con2.createStatement();\n        ResultSet rs = statement.executeQuery(\"select 2\")) {\n      assertTrue(rs.next());\n      assertEquals(2, rs.getInt(1));\n      // To ensure that the http client map is functioning properly, make a third connection with\n      // the\n      // same properties and proxy as the first connection.\n    }\n\n    props.put(\"proxyPort\", \"8080\");\n    try (Connection con3 =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con3.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n      // Assert that although there are 3 connections, 2 of them (1st and 3rd) use the same\n      // httpclient\n      // object in the map. The total map size should be 2 for the 3 connections.\n      assertEquals(2, HttpUtil.httpClient.size());\n    }\n  }\n\n  /**\n   * This requires a TLS proxy connection. This can be done by configuring the squid.conf file (with\n   * squid proxy) and adding certs to the keystore. For info on setup, see\n   * https://snowflakecomputing.atlassian.net/wiki/spaces/EN/pages/65438343/How+to+setup+Proxy+Server+for+Client+tests.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @Disabled\n  public void testTLSIssue() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n    props.put(\"tracing\", \"ALL\");\n    props.put(\"useProxy\", true);\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"3128\");\n    // protocol must be specified for https (default is http)\n    props.put(\"proxyProtocol\", \"https\");\n    try (Connection con =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n\n    // Test with jvm properties instead\n    props.put(\"useProxy\", false);\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"http.proxyHost\", \"localhost\");\n    System.setProperty(\"http.proxyPort\", \"3128\");\n    try (Connection con =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n  }\n\n  /**\n   * Test nonProxyHosts is honored with JVM proxy parameters. Recommended test: 1. Test that proxy\n   * is not used with current settings since nonProxyHosts = * 2. Change nonProxyHosts value to\n   * nonsense value to ensure it can be set and proxy still works 3. Run same 2 above tests but with\n   * http instead of https proxy parameters for non-TLS proxy\n   */\n  @Test\n  @Disabled\n  public void testJVMParamsWithNonProxyHostsHonored() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n    props.put(\"tracing\", \"ALL\");\n    // Set JVM properties. Test once with TLS proxy, and edit to test with non-TLS proxy\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"https.proxyHost\", \"localhost\");\n    System.setProperty(\"https.proxyPort\", \"3128\");\n    System.setProperty(\"http.nonProxyHosts\", \"*\");\n    try (Connection con =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n  }\n\n  /** Test TLS issue against S3 client to ensure proxy works with PUT/GET statements */\n  @Test\n  @Disabled\n  public void testTLSIssueWithConnectionStringAgainstS3()\n      throws ClassNotFoundException, SQLException {\n\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=3128&useProxy=true&proxyProtocol=https\";\n    // should finish correctly\n    runProxyConnection(connectionUrl);\n  }\n\n  /**\n   * Before running this test, change the user and password to appropriate values. Set up a proxy\n   * with Burpsuite so you can see what POST and GET requests are going through the proxy.\n   *\n   * <p>This tests that the NonProxyHosts field is successfully updated for the same HttpClient\n   * object.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @Disabled\n  public void testNonProxyHostAltering() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n    props.put(\"useProxy\", true);\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8080\");\n    props.put(\"nonProxyHosts\", \"*.snowflakecomputing.com\");\n    // Set up the first connection and proxy\n    try (Connection con =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n      // Assert that nonProxyHosts string is correct for initial value\n      HttpUtil.httpClient\n          .entrySet()\n          .forEach((entry) -> assertEquals(\".foo.com|.baz.com\", entry.getKey().getNonProxyHosts()));\n    }\n    // Now make 2nd connection with all the same settings except different nonProxyHosts field\n    props.put(\"nonProxyHosts\", \"*.snowflakecomputing.com\");\n    // Manually check here that nonProxyHost setting works by checking that nothing else goes\n    // through proxy from this point onward. *.snowflakecomputing.com should ensure that proxy is\n    // skipped.\n    try (Connection con =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select 2\")) {\n      assertTrue(rs.next());\n      assertEquals(2, rs.getInt(1));\n      assertEquals(1, HttpUtil.httpClient.size());\n      // Assert that the entry contains the correct updated value for nonProxyHosts string\n      HttpUtil.httpClient\n          .entrySet()\n          .forEach(\n              (entry) ->\n                  assertEquals(\"*.snowflakecomputing.com\", entry.getKey().getNonProxyHosts()));\n    }\n  }\n\n  /**\n   * This tests that the HttpClient object is re-used when no proxies are present.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @Disabled\n  public void testSizeOfHttpClientNoProxies() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n    // Set up the first connection and proxy\n    try (Connection con =\n            DriverManager.getConnection(\n                \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      assertEquals(1, rs.getInt(1));\n    }\n\n    // put in some fake properties that won't get picked up because useProxy=false\n    props.put(\"useProxy\", false);\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"8080\");\n    try (Connection con =\n        DriverManager.getConnection(\n            \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props)) {\n      // Assert that the HttpClient table has only 1 entry for both non-proxy entries\n      assertEquals(1, HttpUtil.httpClient.size());\n    }\n\n    props.put(\"ocspFailOpen\", \"false\");\n    try (Connection con =\n        DriverManager.getConnection(\n            \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props)) {\n      // Table should grow in size by 1 when OCSP mode changes\n      assertEquals(2, HttpUtil.httpClient.size());\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testCorrectProxySettingFromConnectionString()\n      throws ClassNotFoundException, SQLException {\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=8080\"\n            + \"&useProxy=true\";\n    // should finish correctly\n    runProxyConnection(connectionUrl);\n\n    connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=8080\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&useProxy=true\";\n    // should finish correctly\n    runProxyConnection(connectionUrl);\n  }\n\n  @Test\n  @Disabled\n  public void testWrongProxyPortSettingFromConnectionString()\n      throws ClassNotFoundException, SQLException {\n\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=31281\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&nonProxyHosts=*.foo.com|localhost&useProxy=true\";\n    // should show warning for null response for the requests\n    runProxyConnection(connectionUrl);\n  }\n\n  @Test\n  @Disabled\n  public void testWrongProxyPasswordSettingFromConnectionString()\n      throws ClassNotFoundException, SQLException {\n\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=3128\"\n            + \"&proxyUser=testuser2&proxyPassword=test111\"\n            + \"&nonProxyHosts=*.foo.com|localhost&useProxy=true\";\n    // should show warning for null response for the requests\n    try {\n      runProxyConnection(connectionUrl);\n    } catch (SQLException e) {\n      assertThat(\n          \"JDBC driver encountered communication error\",\n          e.getErrorCode(),\n          equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testInvalidProxyPortFromConnectionString()\n      throws ClassNotFoundException, SQLException {\n\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&nonProxyHosts=*.foo.com|localhost&useProxy=true\";\n    // should throw SnowflakeSQLException: 200051\n    try {\n      runProxyConnection(connectionUrl);\n    } catch (SQLException e) {\n      assertThat(\n          \"invalid proxy error\",\n          e.getErrorCode(),\n          equalTo(ErrorCode.INVALID_PROXY_PROPERTIES.getMessageCode()));\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testNonProxyHostsFromConnectionString() throws ClassNotFoundException, SQLException {\n\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=31281\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&nonProxyHosts=*.snowflakecomputing.com|localhost&useProxy=true\";\n    // should finish correctly\n    runProxyConnection(connectionUrl);\n  }\n\n  @Test\n  @Disabled\n  public void testWrongNonProxyHostsFromConnectionString()\n      throws ClassNotFoundException, SQLException {\n\n    String connectionUrl =\n        \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=31281\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&nonProxyHosts=*.foo.com|localhost&useProxy=true\";\n    // should fail to connect\n\n    runProxyConnection(connectionUrl);\n  }\n\n  @Test\n  @Disabled\n  public void testUnsetJvmPropertiesForInvalidSettings() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n    props.put(\"tracing\", \"ALL\");\n    // Set JVM properties.\n    System.setProperty(\"proxyHost\", \"localhost\");\n    System.setProperty(\"proxyPort\", \"3128\");\n    try (Connection con =\n        DriverManager.getConnection(\n            \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props)) {\n      assertEquals(System.getProperty(\"proxyHost\"), null);\n      assertEquals(System.getProperty(\"proxyPort\"), null);\n    }\n  }\n\n  public void runProxyConnection(String connectionUrl) throws ClassNotFoundException, SQLException {\n    Authenticator.setDefault(\n        new Authenticator() {\n          @Override\n          public PasswordAuthentication getPasswordAuthentication() {\n            System.out.println(\"RequestorType: \" + getRequestorType());\n            System.out.println(\"Protocol: \" + getRequestingProtocol().toLowerCase());\n            return new PasswordAuthentication(\n                systemGetProperty(\"http.proxyUser\"),\n                systemGetProperty(\"http.proxyPassword\").toCharArray());\n          }\n        });\n\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"https.proxyHost\", \"localhost\");\n    System.setProperty(\"https.proxyPort\", \"3128\");\n\n    // SET USER AND PASSWORD FIRST\n    String user = \"USER\";\n    String passwd = \"PASSWORD\";\n    Properties _connectionProperties = new Properties();\n    _connectionProperties.put(\"user\", user);\n    _connectionProperties.put(\"password\", passwd);\n    _connectionProperties.put(\"role\", \"accountadmin\");\n    _connectionProperties.put(\"database\", \"SNOWHOUSE_IMPORT\");\n    _connectionProperties.put(\"schema\", \"DEV\");\n\n    Class.forName(\"net.snowflake.client.api.driver.SnowflakeDriver\");\n    long counter = 0;\n    while (true) {\n      try (Connection con = DriverManager.getConnection(connectionUrl, _connectionProperties);\n          Statement stmt = con.createStatement()) {\n        stmt.execute(\"use warehouse TINY_WAREHOUSE\");\n        stmt.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n        assertTrue(\n            stmt.execute(\n                \"PUT file://\" + getFullPathFileInResource(\"orders_100.csv\") + \" @testPutGet_stage\"),\n            \"Failed to put a file\");\n        String sql = \"select $1 from values(1),(3),(5),(7)\";\n        try (ResultSet res = stmt.executeQuery(sql)) {\n          while (res.next()) {\n            System.out.println(\"value: \" + res.getInt(1));\n          }\n          System.out.println(\"OK - \" + counter);\n        }\n      }\n      counter++;\n      break;\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testProxyConnectionWithAzure() throws ClassNotFoundException, SQLException {\n    String connectionUrl =\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\";\n    runAzureProxyConnection(\n        connectionUrl, /* usesConnectionProperties */ true, /* usesIncorrectJVMParameters */ true);\n  }\n\n  @Test\n  @Disabled\n  public void testProxyConnectionWithAzureWithConnectionString()\n      throws ClassNotFoundException, SQLException {\n    String connectionUrl =\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=8080\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&useProxy=true\";\n    runAzureProxyConnection(\n        connectionUrl, /* usesConnectionProperties */ false, /* usesIncorrectJVMParameters */ true);\n  }\n\n  @ParameterizedTest\n  @ValueSource(\n      strings = {\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&useProxy=true\",\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=cheese\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&useProxy=true\",\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=&proxyPort=3128\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&useProxy=true\",\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&useProxy=true\"\n      })\n  @Disabled\n  public void testProxyConnectionWithoutProxyPortOrHost(String connectionUrl) {\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () ->\n                runAzureProxyConnection(\n                    connectionUrl, /* usesConnectionProperties */\n                    false, /* usesIncorrectJVMParameters */\n                    true));\n    assertEquals(SqlState.CONNECTION_EXCEPTION, e.getSQLState());\n  }\n\n  /**\n   * Before running this test, change the user and password in runAzureProxyConnection() to\n   * appropriate values. Set up a proxy with Burpsuite so you can see what POST and GET requests are\n   * going through the proxy.\n   *\n   * <p>This tests that the NonProxyHosts field is successfully updated for the same HttpClient\n   * object.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @Disabled\n  public void testProxyConnectionWithJVMParameters() throws SQLException, ClassNotFoundException {\n    String connectionUrl =\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\";\n    // Set valid JVM system properties\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"http.proxyHost\", \"localhost\");\n    System.setProperty(\"http.proxyPort\", \"8080\");\n    System.setProperty(\"http.nonProxyHosts\", \"*.snowflakecomputing.com\");\n    SnowflakeUtil.systemSetEnv(\"NO_PROXY\", \"*.google.com\");\n    runAzureProxyConnection(\n        connectionUrl, /* usesConnectionProperties */\n        false, /* usesIncorrectJVMParameters */\n        false);\n    SnowflakeUtil.systemUnsetEnv(\"NO_PROXY\");\n  }\n\n  @Test\n  @Disabled\n  public void testProxyConnectionWithAzureWithWrongConnectionString()\n      throws ClassNotFoundException {\n    String connectionUrl =\n        \"jdbc:snowflake://aztestaccount.east-us-2.azure.snowflakecomputing.com/?tracing=ALL\"\n            + \"&proxyHost=localhost&proxyPort=31281\"\n            + \"&proxyUser=testuser1&proxyPassword=test\"\n            + \"&nonProxyHosts=*.foo.com%7Clocalhost&useProxy=true\";\n\n    try {\n      runAzureProxyConnection(\n          connectionUrl, /* usesConnectionProperties */\n          false, /* usesIncorrectJVMParameters */\n          true);\n    } catch (SQLException e) {\n      assertThat(\n          \"JDBC driver encountered communication error\",\n          e.getErrorCode(),\n          equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n    }\n  }\n\n  /**\n   * Test that http proxy is used when http.proxyProtocol is http and http.proxyHost/http.proxyPort\n   * is specified. Set up a http proxy and change the settings below.\n   */\n  @Test\n  @Disabled\n  public void testSetJVMProxyHttp() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"http.proxyHost\", \"localhost\");\n    System.setProperty(\"http.proxyPort\", \"3128\");\n    System.setProperty(\"http.nonProxyHosts\", \"*\");\n    System.setProperty(\"http.proxyProtocol\", \"http\");\n    try (Connection con =\n        DriverManager.getConnection(\n            \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props)) {\n      SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      HttpClientSettingsKey clientSettingsKey = sfSession.getHttpClientKey();\n      assertEquals(HttpProtocol.HTTP, clientSettingsKey.getProxyHttpProtocol());\n    }\n  }\n\n  /**\n   * Test that https proxy is used when http.proxyProtocol is https and\n   * https.proxyHost/https.proxyPort is specified. Set up a https proxy and change the settings\n   * below.\n   */\n  @Test\n  @Disabled\n  public void testSetJVMProxyHttps() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"https.proxyHost\", \"localhost\");\n    System.setProperty(\"https.proxyPort\", \"3128\");\n    System.setProperty(\"http.nonProxyHosts\", \"*\");\n    System.setProperty(\"http.proxyProtocol\", \"https\");\n    try (Connection con =\n        DriverManager.getConnection(\n            \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props)) {\n      SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      HttpClientSettingsKey clientSettingsKey = sfSession.getHttpClientKey();\n      assertEquals(HttpProtocol.HTTPS, clientSettingsKey.getProxyHttpProtocol());\n    }\n  }\n\n  /**\n   * Test that https proxy is used when https.proxyHost and https.proxyPort are specified. Set up a\n   * https proxy and change the settings below.\n   */\n  @Test\n  @Disabled\n  public void testSetJVMProxyDefaultHttps() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"USER\");\n    props.put(\"password\", \"PASSWORD\");\n\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"https.proxyHost\", \"localhost\");\n    System.setProperty(\"https.proxyPort\", \"3128\");\n    System.setProperty(\"http.nonProxyHosts\", \"*\");\n\n    try (Connection con =\n        DriverManager.getConnection(\n            \"jdbc:snowflake://s3testaccount.us-east-1.snowflakecomputing.com\", props)) {\n      SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      HttpClientSettingsKey clientSettingsKey = sfSession.getHttpClientKey();\n      assertEquals(HttpProtocol.HTTPS, clientSettingsKey.getProxyHttpProtocol());\n    }\n  }\n\n  public void runAzureProxyConnection(\n      String connectionUrl, boolean usesProperties, boolean usesIncorrectJVMProperties)\n      throws ClassNotFoundException, SQLException {\n    Authenticator.setDefault(\n        new Authenticator() {\n          @Override\n          public PasswordAuthentication getPasswordAuthentication() {\n            System.out.println(\"RequestorType: \" + getRequestorType());\n            System.out.println(\"Protocol: \" + getRequestingProtocol().toLowerCase());\n            return new PasswordAuthentication(\n                systemGetProperty(\"http.proxyUser\"),\n                systemGetProperty(\"http.proxyPassword\").toCharArray());\n          }\n        });\n\n    // Enable these parameters to use JVM proxy parameters instead of connection string proxy\n    // parameters.\n    // Connection parameters override JVM  proxy params, so these incorrect params won't cause\n    // failures IF connection proxy params are enabled and working.\n    if (usesIncorrectJVMProperties) {\n      System.setProperty(\"http.useProxy\", \"true\");\n      System.setProperty(\"http.proxyHost\", \"fakehost\");\n      System.setProperty(\"http.proxyPort\", \"8081\");\n      System.setProperty(\"https.proxyHost\", \"fakehost\");\n      System.setProperty(\"https.proxyPort\", \"8081\");\n    }\n\n    // SET USER AND PASSWORD FIRST\n    String user = \"USER\";\n    String passwd = \"PASSWORD\";\n    Properties _connectionProperties = new Properties();\n    _connectionProperties.put(\"user\", user);\n    _connectionProperties.put(\"password\", passwd);\n    _connectionProperties.put(\"role\", \"SYSADMIN\");\n    _connectionProperties.put(\"tracing\", \"ALL\");\n    if (usesProperties) {\n      _connectionProperties.put(\"useProxy\", true);\n      _connectionProperties.put(\"proxyHost\", \"localhost\");\n      _connectionProperties.put(\"proxyPort\", \"8080\");\n      _connectionProperties.put(\"proxyUser\", \"testuser1\");\n      _connectionProperties.put(\"proxyPassword\", \"test\");\n    }\n\n    Class.forName(\"net.snowflake.client.api.driver.SnowflakeDriver\");\n\n    String fileName = \"test_copy.csv\";\n    try (Connection con = DriverManager.getConnection(connectionUrl, _connectionProperties);\n        Statement stmt = con.createStatement()) {\n      try {\n        stmt.execute(\"create or replace warehouse MEGTEST\");\n        stmt.execute(\"use database MEGDB\");\n        stmt.execute(\"use schema MEGSCHEMA\");\n        stmt.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n\n        String TEST_DATA_FILE = \"orders_100.csv\";\n        String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n        File destFolder = new File(tmpFolder, \"dest\");\n        destFolder.mkdirs();\n        String destFolderCanonicalPath = destFolder.getCanonicalPath();\n        String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n        assertTrue(\n            stmt.execute(\"PUT file://\" + sourceFilePath + \" @testPutGet_stage\"),\n            \"Failed to put a file\");\n        findFile(stmt, \"ls @testPutGet_stage/\");\n\n        // download the file we just uploaded to stage\n        assertTrue(\n            stmt.execute(\n                \"GET @testPutGet_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n            \"Failed to get a file\");\n\n        // Make sure that the downloaded file exists, it should be gzip compressed\n        File downloaded = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE + \".gz\");\n        assertTrue(downloaded.exists());\n\n        Process p =\n            Runtime.getRuntime()\n                .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + TEST_DATA_FILE + \".gz\");\n        p.waitFor();\n\n        File original = new File(sourceFilePath);\n        File unzipped = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE);\n        assertEquals(original.length(), unzipped.length());\n      } catch (Throwable t) {\n        t.printStackTrace();\n      } finally {\n        stmt.execute(\"DROP STAGE IF EXISTS testGetPut_stage\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DatabaseMetaDataIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static java.sql.DatabaseMetaData.procedureReturnsResult;\nimport static java.sql.ResultSetMetaData.columnNullableUnknown;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.equalTo;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.hasItem;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.google.common.base.Strings;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.Statement;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.connection.SnowflakeDatabaseMetaData;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Database Metadata IT */\n@Tag(TestTags.DATABASE_META_DATA)\npublic class DatabaseMetaDataIT extends BaseJDBCWithSharedConnectionIT {\n  private static final Pattern VERSION_PATTERN =\n      Pattern.compile(\"^(\\\\d+)\\\\.(\\\\d+)(?:\\\\.\\\\d+)+\\\\s*.*\");\n  private static final String PI_PROCEDURE =\n      \"create or replace procedure GETPI()\\n\"\n          + \"    returns float not null\\n\"\n          + \"    language javascript\\n\"\n          + \"    as\\n\"\n          + \"    $$\\n\"\n          + \"    return 3.1415926;\\n\"\n          + \"    $$\\n\"\n          + \"    ;\";\n  private static final String STPROC1_PROCEDURE =\n      \"create or replace procedure stproc1(param1 float, param2 string)\\n\"\n          + \"    returns table(retval varchar)\\n\"\n          + \"    language javascript\\n\"\n          + \"    as\\n\"\n          + \"    $$\\n\"\n          + \"    var sql_command = \\\"Hello, world!\\\"\\n\"\n          + \"    $$\\n\"\n          + \"    ;\";\n\n  public static final int EXPECTED_MAX_CHAR_LENGTH = 16777216;\n\n  public static final int EXPECTED_MAX_BINARY_LENGTH = 8388608;\n\n  @Test\n  public void testGetConnection() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n    assertEquals(connection, metaData.getConnection());\n  }\n\n  @Test\n  public void testDatabaseAndDriverInfo() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n\n    // identifiers\n    assertEquals(\"Snowflake\", metaData.getDatabaseProductName());\n    assertEquals(\"Snowflake\", metaData.getDriverName());\n\n    // Snowflake JDBC driver version\n    String driverVersion = metaData.getDriverVersion();\n    Matcher m = VERSION_PATTERN.matcher(driverVersion);\n    assertTrue(m.matches());\n    int majorVersion = metaData.getDriverMajorVersion();\n    int minorVersion = metaData.getDriverMinorVersion();\n    assertEquals(m.group(1), String.valueOf(majorVersion));\n    assertEquals(m.group(2), String.valueOf(minorVersion));\n  }\n\n  @Test\n  public void testGetCatalogs() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n    assertEquals(\".\", metaData.getCatalogSeparator());\n    assertEquals(\"database\", metaData.getCatalogTerm());\n\n    ResultSet resultSet = metaData.getCatalogs();\n    verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_CATALOGS);\n    assertTrue(resultSet.isBeforeFirst());\n\n    int cnt = 0;\n    Set<String> allVisibleDatabases = new HashSet<>();\n    while (resultSet.next()) {\n      allVisibleDatabases.add(resultSet.getString(1));\n      if (cnt == 0) {\n        assertTrue(resultSet.isFirst());\n      }\n      ++cnt;\n      assertThrows(SQLFeatureNotSupportedException.class, resultSet::isLast);\n      assertThrows(SQLFeatureNotSupportedException.class, resultSet::isAfterLast);\n    }\n    assertThat(cnt, greaterThanOrEqualTo(1));\n    SQLException ex = assertThrows(SQLException.class, resultSet::isAfterLast);\n    assertEquals((int) ErrorCode.RESULTSET_ALREADY_CLOSED.getMessageCode(), ex.getErrorCode());\n    resultSet.close(); // double closing does nothing.\n    resultSet.next(); // no exception\n\n    List<String> allAccessibleDatabases =\n        getInfoBySQL(\"select database_name from information_schema.databases\");\n\n    assertTrue(allVisibleDatabases.containsAll(allAccessibleDatabases));\n  }\n\n  @Test\n  public void testGetSchemas() throws Throwable {\n    try (Connection conn = getConnectionWithWildcardsDisabled()) {\n      // CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX = false\n      DatabaseMetaData metaData = conn.getMetaData();\n      String currentSchema = conn.getSchema();\n      assertEquals(\"schema\", metaData.getSchemaTerm());\n      Set<String> schemas = new HashSet<>();\n      try (ResultSet resultSet = metaData.getSchemas()) {\n        verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_SCHEMAS);\n        while (resultSet.next()) {\n          String schema = resultSet.getString(1);\n          if (currentSchema.equals(schema) || !TestUtil.isSchemaGeneratedInTests(schema)) {\n            schemas.add(schema);\n          }\n        }\n      }\n      assertThat(schemas.size(), greaterThanOrEqualTo(1));\n\n      Set<String> schemasInDb = new HashSet<>();\n      try (ResultSet resultSet = metaData.getSchemas(conn.getCatalog(), \"%\")) {\n        while (resultSet.next()) {\n          String schema = resultSet.getString(1);\n          if (currentSchema.equals(schema) || !TestUtil.isSchemaGeneratedInTests(schema)) {\n            schemasInDb.add(schema);\n          }\n        }\n      }\n      Assumptions.assumeFalse(\n          schemasInDb.isEmpty(), \"Database \" + conn.getCatalog() + \" returned no schemas\");\n      assertThat(schemas.size(), greaterThanOrEqualTo(schemasInDb.size()));\n      schemasInDb.forEach(schemaInDb -> assertThat(schemas, hasItem(schemaInDb)));\n      assertTrue(schemas.contains(currentSchema));\n      assertTrue(schemasInDb.contains(currentSchema));\n    }\n\n    // CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX = true\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n\n      DatabaseMetaData metaData2 = connection.getMetaData();\n      assertEquals(\"schema\", metaData2.getSchemaTerm());\n      try (ResultSet resultSet = metaData2.getSchemas()) {\n        Set<String> schemas2 = new HashSet<>();\n        while (resultSet.next()) {\n          schemas2.add(resultSet.getString(1));\n        }\n        assertThat(schemas2.size(), equalTo(1));\n      }\n    }\n  }\n\n  @Test\n  public void testGetTableTypes() throws Throwable {\n    DatabaseMetaData metaData = connection.getMetaData();\n    try (ResultSet resultSet = metaData.getTableTypes()) {\n      Set<String> types = new HashSet<>();\n      while (resultSet.next()) {\n        types.add(resultSet.getString(1));\n      }\n      assertEquals(2, types.size());\n      assertTrue(types.contains(\"TABLE\"));\n      assertTrue(types.contains(\"VIEW\"));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetTables() throws Throwable {\n    Set<String> tables = null;\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      final String targetTable = \"T0\";\n      final String targetView = \"V0\";\n      try {\n        statement.execute(\"create or replace table \" + targetTable + \"(C1 int)\");\n        statement.execute(\"create or replace view \" + targetView + \" as select 1 as C\");\n\n        DatabaseMetaData metaData = connection.getMetaData();\n\n        // match table\n        try (ResultSet resultSet =\n            metaData.getTables(database, schema, \"%\", new String[] {\"TABLE\"})) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_TABLES);\n          tables = new HashSet<>();\n          while (resultSet.next()) {\n            tables.add(resultSet.getString(3));\n          }\n          assertTrue(tables.contains(\"T0\"));\n        }\n        // exact match table\n        try (ResultSet resultSet =\n            metaData.getTables(database, schema, targetTable, new String[] {\"TABLE\"})) {\n          tables = new HashSet<>();\n          while (resultSet.next()) {\n            tables.add(resultSet.getString(3));\n          }\n          assertEquals(targetTable, tables.iterator().next());\n        }\n        // match view\n        try (ResultSet resultSet =\n            metaData.getTables(database, schema, \"%\", new String[] {\"VIEW\"})) {\n          Set<String> views = new HashSet<>();\n          while (resultSet.next()) {\n            views.add(resultSet.getString(3));\n          }\n          assertTrue(views.contains(targetView));\n        }\n\n        try (ResultSet resultSet = metaData.getTablePrivileges(database, schema, targetTable)) {\n          assertEquals(1, getSizeOfResultSet(resultSet));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable);\n        statement.execute(\"drop view if exists \" + targetView);\n      }\n    }\n  }\n\n  @Test\n  public void testGetPrimarykeys() throws Throwable {\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      String database = conn.getCatalog();\n      String schema = conn.getSchema();\n      final String targetTable = \"T0\";\n      try {\n        statement.execute(\n            \"create or replace table \" + targetTable + \"(C1 int primary key, C2 string)\");\n\n        DatabaseMetaData metaData = conn.getMetaData();\n\n        try (ResultSet resultSet = metaData.getPrimaryKeys(database, schema, targetTable)) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_PRIMARY_KEYS);\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(\"TABLE_NAME\"));\n          assertEquals(\"C1\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(1, resultSet.getInt(\"KEY_SEQ\"));\n          assertNotEquals(\"\", resultSet.getString(\"PK_NAME\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable);\n      }\n    }\n  }\n\n  static void verifyResultSetMetaDataColumns(\n      ResultSet resultSet, DBMetadataResultSetMetadata metadata) throws SQLException {\n    final int numCol = metadata.getColumnNames().size();\n    ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n    assertEquals(numCol, resultSetMetaData.getColumnCount());\n\n    for (int col = 1; col <= numCol; ++col) {\n      List<String> colNames = metadata.getColumnNames();\n      List<String> colTypeNames = metadata.getColumnTypeNames();\n      List<Integer> colTypes = metadata.getColumnTypes();\n\n      assertEquals(\"\", resultSetMetaData.getCatalogName(col));\n      assertEquals(\"\", resultSetMetaData.getSchemaName(col));\n      assertEquals(\"T\", resultSetMetaData.getTableName(col));\n      assertEquals(colNames.get(col - 1), resultSetMetaData.getColumnName(col));\n\n      assertEquals(colNames.get(col - 1), resultSetMetaData.getColumnLabel(col));\n      assertEquals(\n          SnowflakeTypeUtil.javaTypeToClassName(resultSetMetaData.getColumnType(col)),\n          resultSetMetaData.getColumnClassName(col));\n      assertEquals(25, resultSetMetaData.getColumnDisplaySize(col));\n      assertEquals((int) colTypes.get(col - 1), resultSetMetaData.getColumnType(col));\n      assertEquals(colTypeNames.get(col - 1), resultSetMetaData.getColumnTypeName(col));\n      assertEquals(9, resultSetMetaData.getPrecision(col));\n      assertEquals(9, resultSetMetaData.getScale(col));\n\n      assertEquals(\n          SnowflakeTypeHelper.isJavaTypeSigned(resultSetMetaData.getColumnType(col)),\n          resultSetMetaData.isSigned(col));\n      assertFalse(resultSetMetaData.isAutoIncrement(col));\n      assertFalse(resultSetMetaData.isCurrency(col));\n      assertTrue(resultSetMetaData.isReadOnly(col));\n      assertTrue(resultSetMetaData.isSearchable(col));\n      assertFalse(resultSetMetaData.isWritable(col));\n      assertFalse(resultSetMetaData.isDefinitelyWritable(col));\n\n      assertEquals(columnNullableUnknown, resultSetMetaData.isNullable(col));\n    }\n  }\n\n  @Test\n  public void testGetImportedKeys() throws Throwable {\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      String database = conn.getCatalog();\n      String schema = conn.getSchema();\n      final String targetTable1 = \"T0\";\n      final String targetTable2 = \"T1\";\n      try {\n        statement.execute(\n            \"create or replace table \" + targetTable1 + \"(C1 int primary key, C2 string)\");\n        statement.execute(\n            \"create or replace table \"\n                + targetTable2\n                + \"(C1 int primary key, C2 string, C3 int references \"\n                + targetTable1\n                + \")\");\n\n        DatabaseMetaData metaData = conn.getMetaData();\n\n        try (ResultSet resultSet = metaData.getImportedKeys(database, schema, targetTable2)) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_FOREIGN_KEYS);\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"PKTABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"PKTABLE_SCHEM\"));\n          assertEquals(targetTable1, resultSet.getString(\"PKTABLE_NAME\"));\n          assertEquals(\"C1\", resultSet.getString(\"PKCOLUMN_NAME\"));\n          assertEquals(database, resultSet.getString(\"FKTABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"FKTABLE_SCHEM\"));\n          assertEquals(targetTable2, resultSet.getString(\"FKTABLE_NAME\"));\n          assertEquals(\"C3\", resultSet.getString(\"FKCOLUMN_NAME\"));\n          assertEquals(1, resultSet.getInt(\"KEY_SEQ\"));\n          assertNotEquals(\"\", resultSet.getString(\"PK_NAME\"));\n          assertNotEquals(\"\", resultSet.getString(\"FK_NAME\"));\n          assertEquals(DatabaseMetaData.importedKeyNoAction, resultSet.getShort(\"UPDATE_RULE\"));\n          assertEquals(DatabaseMetaData.importedKeyNoAction, resultSet.getShort(\"DELETE_RULE\"));\n          assertEquals(\n              DatabaseMetaData.importedKeyNotDeferrable, resultSet.getShort(\"DEFERRABILITY\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable1);\n        statement.execute(\"drop table if exists \" + targetTable2);\n      }\n    }\n  }\n\n  @Test\n  public void testGetExportedKeys() throws Throwable {\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      String database = conn.getCatalog();\n      String schema = conn.getSchema();\n      final String targetTable1 = \"T0\";\n      final String targetTable2 = \"T1\";\n      try {\n        statement.execute(\n            \"create or replace table \" + targetTable1 + \"(C1 int primary key, C2 string)\");\n        statement.execute(\n            \"create or replace table \"\n                + targetTable2\n                + \"(C1 int primary key, C2 string, C3 int references \"\n                + targetTable1\n                + \")\");\n\n        DatabaseMetaData metaData = conn.getMetaData();\n\n        try (ResultSet resultSet = metaData.getExportedKeys(database, schema, targetTable1)) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_FOREIGN_KEYS);\n\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"PKTABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"PKTABLE_SCHEM\"));\n          assertEquals(targetTable1, resultSet.getString(\"PKTABLE_NAME\"));\n          assertEquals(\"C1\", resultSet.getString(\"PKCOLUMN_NAME\"));\n          assertEquals(database, resultSet.getString(\"FKTABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"FKTABLE_SCHEM\"));\n          assertEquals(targetTable2, resultSet.getString(\"FKTABLE_NAME\"));\n          assertEquals(\"C3\", resultSet.getString(\"FKCOLUMN_NAME\"));\n          assertEquals(1, resultSet.getInt(\"KEY_SEQ\"));\n          assertNotEquals(\"\", resultSet.getString(\"PK_NAME\"));\n          assertNotEquals(\"\", resultSet.getString(\"FK_NAME\"));\n          assertEquals(DatabaseMetaData.importedKeyNoAction, resultSet.getShort(\"UPDATE_RULE\"));\n          assertEquals(DatabaseMetaData.importedKeyNoAction, resultSet.getShort(\"DELETE_RULE\"));\n          assertEquals(\n              DatabaseMetaData.importedKeyNotDeferrable, resultSet.getShort(\"DEFERRABILITY\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable1);\n        statement.execute(\"drop table if exists \" + targetTable2);\n      }\n    }\n  }\n\n  @Test\n  public void testGetCrossReferences() throws Throwable {\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      String database = conn.getCatalog();\n      String schema = conn.getSchema();\n      final String targetTable1 = \"T0\";\n      final String targetTable2 = \"T1\";\n      try {\n        statement.execute(\n            \"create or replace table \" + targetTable1 + \"(C1 int primary key, C2 string)\");\n        statement.execute(\n            \"create or replace table \"\n                + targetTable2\n                + \"(C1 int primary key, C2 string, C3 int references \"\n                + targetTable1\n                + \")\");\n\n        DatabaseMetaData metaData = conn.getMetaData();\n\n        try (ResultSet resultSet =\n            metaData.getCrossReference(\n                database, schema, targetTable1, database, schema, targetTable2)) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_FOREIGN_KEYS);\n\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"PKTABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"PKTABLE_SCHEM\"));\n          assertEquals(targetTable1, resultSet.getString(\"PKTABLE_NAME\"));\n          assertEquals(\"C1\", resultSet.getString(\"PKCOLUMN_NAME\"));\n          assertEquals(database, resultSet.getString(\"FKTABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"FKTABLE_SCHEM\"));\n          assertEquals(targetTable2, resultSet.getString(\"FKTABLE_NAME\"));\n          assertEquals(\"C3\", resultSet.getString(\"FKCOLUMN_NAME\"));\n          assertEquals(1, resultSet.getInt(\"KEY_SEQ\"));\n          assertNotEquals(\"\", resultSet.getString(\"PK_NAME\"));\n          assertNotEquals(\"\", resultSet.getString(\"FK_NAME\"));\n          assertEquals(DatabaseMetaData.importedKeyNoAction, resultSet.getShort(\"UPDATE_RULE\"));\n          assertEquals(DatabaseMetaData.importedKeyNoAction, resultSet.getShort(\"DELETE_RULE\"));\n          assertEquals(\n              DatabaseMetaData.importedKeyNotDeferrable, resultSet.getShort(\"DEFERRABILITY\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable1);\n        statement.execute(\"drop table if exists \" + targetTable2);\n      }\n    }\n  }\n\n  @Test\n  public void testGetObjectsDoesNotExists() throws Throwable {\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      String database = conn.getCatalog();\n      String schema = conn.getSchema();\n      final String targetTable = \"T0\";\n      final String targetView = \"V0\";\n      try {\n        statement.execute(\"create or replace table \" + targetTable + \"(C1 int)\");\n        statement.execute(\"create or replace view \" + targetView + \" as select 1 as C\");\n\n        DatabaseMetaData metaData = conn.getMetaData();\n\n        // sanity check if getTables really works.\n        try (ResultSet resultSet = metaData.getTables(database, schema, \"%\", null)) {\n          assertTrue(getSizeOfResultSet(resultSet) > 0);\n        }\n\n        // invalid object type. empty result is expected.\n        try (ResultSet resultSet =\n            metaData.getTables(database, schema, \"%\", new String[] {\"INVALID_TYPE\"})) {\n          assertEquals(0, getSizeOfResultSet(resultSet));\n        }\n\n        // rest of the cases should return empty results.\n        try (ResultSet resultSet = metaData.getSchemas(\"DB_NOT_EXIST\", \"SCHEMA_NOT_EXIST\")) {\n          assertFalse(resultSet.next());\n          assertTrue(resultSet.isClosed());\n        }\n\n        try (ResultSet resultSet =\n            metaData.getTables(\"DB_NOT_EXIST\", \"SCHEMA\\\\_NOT\\\\_EXIST\", \"%\", null)) {\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet =\n            metaData.getTables(database, \"SCHEMA\\\\_NOT\\\\_EXIST\", \"%\", null)) {\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet =\n            metaData.getColumns(\"DB_NOT_EXIST\", \"SCHEMA\\\\_NOT\\\\_EXIST\", \"%\", \"%\")) {\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet =\n            metaData.getColumns(database, \"SCHEMA\\\\_NOT\\\\_EXIST\", \"%\", \"%\")) {\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet =\n            metaData.getColumns(database, schema, \"TBL\\\\_NOT\\\\_EXIST\", \"%\")) {\n          assertFalse(resultSet.next());\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable);\n        statement.execute(\"drop view if exists \" + targetView);\n      }\n    }\n  }\n\n  @Test\n  public void testTypeInfo() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n    ResultSet resultSet = metaData.getTypeInfo();\n    resultSet.next();\n    assertEquals(\"NUMBER\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"INTEGER\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"DOUBLE\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"VARCHAR\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"DATE\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"TIME\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"TIMESTAMP\", resultSet.getString(1));\n    resultSet.next();\n    assertEquals(\"BOOLEAN\", resultSet.getString(1));\n    assertFalse(resultSet.next());\n  }\n\n  @Test\n  public void testProcedure() throws Throwable {\n    DatabaseMetaData metaData = connection.getMetaData();\n    assertEquals(\"procedure\", metaData.getProcedureTerm());\n    // no stored procedure support\n    assertTrue(metaData.supportsStoredProcedures());\n    try (ResultSet resultSet = metaData.getProcedureColumns(\"%\", \"%\", \"%\", \"%\")) {\n      assertEquals(0, getSizeOfResultSet(resultSet));\n    }\n    try (ResultSet resultSet = metaData.getProcedures(\"%\", \"%\", \"%\")) {\n      assertEquals(0, getSizeOfResultSet(resultSet));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetTablePrivileges() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      try {\n        statement.execute(\n            \"create or replace table PRIVTEST(colA string, colB number, colC \" + \"timestamp)\");\n        DatabaseMetaData metaData = connection.getMetaData();\n        try (ResultSet resultSet = metaData.getTablePrivileges(database, schema, \"PRIVTEST\")) {\n          verifyResultSetMetaDataColumns(\n              resultSet, DBMetadataResultSetMetadata.GET_TABLE_PRIVILEGES);\n          resultSet.next();\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(\"PRIVTEST\", resultSet.getString(\"TABLE_NAME\"));\n          assertFalse(isNullOrEmpty(resultSet.getString(\"GRANTOR\")));\n          assertFalse(isNullOrEmpty(resultSet.getString(\"GRANTEE\")));\n          assertEquals(\"OWNERSHIP\", resultSet.getString(\"PRIVILEGE\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_GRANTABLE\"));\n        }\n        // grant select privileges to table for role security admin and test that a new row of table\n        // privileges is added\n        statement.execute(\"grant select on table PRIVTEST to role securityadmin\");\n        try (ResultSet resultSet = metaData.getTablePrivileges(database, schema, \"PRIVTEST\")) {\n          resultSet.next();\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(\"PRIVTEST\", resultSet.getString(\"TABLE_NAME\"));\n          assertFalse(isNullOrEmpty(resultSet.getString(\"GRANTOR\")));\n          assertFalse(isNullOrEmpty(resultSet.getString(\"GRANTEE\")));\n          assertEquals(\"OWNERSHIP\", resultSet.getString(\"PRIVILEGE\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_GRANTABLE\"));\n          resultSet.next();\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(\"PRIVTEST\", resultSet.getString(\"TABLE_NAME\"));\n          assertFalse(isNullOrEmpty(resultSet.getString(\"GRANTOR\")));\n          assertFalse(isNullOrEmpty(resultSet.getString(\"GRANTEE\")));\n          assertEquals(\"SELECT\", resultSet.getString(\"PRIVILEGE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GRANTABLE\"));\n        }\n\n        // if tableNamePattern is null, empty resultSet is returned.\n        try (ResultSet resultSet = metaData.getTablePrivileges(null, null, null)) {\n          assertEquals(7, resultSet.getMetaData().getColumnCount());\n          assertEquals(0, getSizeOfResultSet(resultSet));\n        }\n      } finally {\n        statement.execute(\"drop table if exists PRIVTEST\");\n      }\n    }\n  }\n\n  @Test\n  public void testGetProcedures() throws SQLException {\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      try {\n        String database = conn.getCatalog();\n        String schema = conn.getSchema();\n\n        /* Create a procedure and put values into it */\n        statement.execute(PI_PROCEDURE);\n        DatabaseMetaData metaData = conn.getMetaData();\n        /* Call getFunctionColumns on FUNC111 and since there's no parameter name, get all rows back */\n        try (ResultSet resultSet = metaData.getProcedures(database, schema, \"GETPI\")) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_PROCEDURES);\n          resultSet.next();\n          assertEquals(\"GETPI\", resultSet.getString(\"PROCEDURE_NAME\"));\n          assertEquals(database, resultSet.getString(\"PROCEDURE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"PROCEDURE_SCHEM\"));\n          assertEquals(\"GETPI\", resultSet.getString(\"PROCEDURE_NAME\"));\n          assertEquals(\"user-defined procedure\", resultSet.getString(\"REMARKS\"));\n          assertEquals(procedureReturnsResult, resultSet.getShort(\"PROCEDURE_TYPE\"));\n          assertEquals(\"GETPI() RETURN FLOAT\", resultSet.getString(\"SPECIFIC_NAME\"));\n        }\n      } finally {\n        statement.execute(\"drop procedure if exists GETPI()\");\n      }\n    }\n  }\n\n  @Test\n  public void testDatabaseMetadata() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n\n    String dbVersion = metaData.getDatabaseProductVersion();\n    Matcher m = VERSION_PATTERN.matcher(dbVersion);\n    assertTrue(m.matches());\n    int majorVersion = metaData.getDatabaseMajorVersion();\n    int minorVersion = metaData.getDatabaseMinorVersion();\n    assertEquals(m.group(1), String.valueOf(majorVersion));\n    assertEquals(m.group(2), String.valueOf(minorVersion));\n\n    assertFalse(Strings.isNullOrEmpty(metaData.getSQLKeywords()));\n    assertFalse(Strings.isNullOrEmpty(metaData.getNumericFunctions()));\n    assertFalse(Strings.isNullOrEmpty(metaData.getStringFunctions()));\n    assertFalse(Strings.isNullOrEmpty(metaData.getSystemFunctions()));\n    assertFalse(Strings.isNullOrEmpty(metaData.getTimeDateFunctions()));\n\n    assertEquals(\"\\\\\", metaData.getSearchStringEscape());\n\n    assertTrue(metaData.getURL().startsWith(\"jdbc:snowflake://\"));\n    assertFalse(metaData.allProceduresAreCallable());\n    assertTrue(metaData.allTablesAreSelectable());\n    assertTrue(metaData.dataDefinitionCausesTransactionCommit());\n    assertFalse(metaData.dataDefinitionIgnoredInTransactions());\n    assertFalse(metaData.deletesAreDetected(1));\n    assertTrue(metaData.doesMaxRowSizeIncludeBlobs());\n    assertTrue(metaData.supportsTransactions());\n    assertEquals(Connection.TRANSACTION_READ_COMMITTED, metaData.getDefaultTransactionIsolation());\n    assertEquals(\"$\", metaData.getExtraNameCharacters());\n    assertEquals(\"\\\"\", metaData.getIdentifierQuoteString());\n    assertEquals(0, getSizeOfResultSet(metaData.getIndexInfo(null, null, null, true, true)));\n    assertThat(\n        metaData.getMaxBinaryLiteralLength(), greaterThanOrEqualTo(EXPECTED_MAX_BINARY_LENGTH));\n    assertEquals(255, metaData.getMaxCatalogNameLength());\n    assertThat(metaData.getMaxCharLiteralLength(), greaterThanOrEqualTo(EXPECTED_MAX_CHAR_LENGTH));\n    assertEquals(255, metaData.getMaxColumnNameLength());\n    assertEquals(0, metaData.getMaxColumnsInGroupBy());\n    assertEquals(0, metaData.getMaxColumnsInIndex());\n    assertEquals(0, metaData.getMaxColumnsInOrderBy());\n    assertEquals(0, metaData.getMaxColumnsInSelect());\n    assertEquals(0, metaData.getMaxColumnsInTable());\n    assertEquals(0, metaData.getMaxConnections());\n    assertEquals(0, metaData.getMaxCursorNameLength());\n    assertEquals(0, metaData.getMaxIndexLength());\n    assertEquals(0, metaData.getMaxProcedureNameLength());\n    assertEquals(0, metaData.getMaxRowSize());\n    assertEquals(255, metaData.getMaxSchemaNameLength());\n    assertEquals(0, metaData.getMaxStatementLength());\n    assertEquals(0, metaData.getMaxStatements());\n    assertEquals(255, metaData.getMaxTableNameLength());\n    assertEquals(0, metaData.getMaxTablesInSelect());\n    assertEquals(255, metaData.getMaxUserNameLength());\n    assertEquals(0, getSizeOfResultSet(metaData.getTablePrivileges(null, null, null)));\n    // assertEquals(\"\", metaData.getTimeDateFunctions());\n    assertEquals(TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_USER\"), metaData.getUserName());\n    assertFalse(metaData.insertsAreDetected(1));\n    assertTrue(metaData.isCatalogAtStart());\n    assertFalse(metaData.isReadOnly());\n    assertTrue(metaData.nullPlusNonNullIsNull());\n    assertFalse(metaData.nullsAreSortedAtEnd());\n    assertFalse(metaData.nullsAreSortedAtStart());\n    assertTrue(metaData.nullsAreSortedHigh());\n    assertFalse(metaData.nullsAreSortedLow());\n    assertFalse(metaData.othersDeletesAreVisible(1));\n    assertFalse(metaData.othersInsertsAreVisible(1));\n    assertFalse(metaData.othersUpdatesAreVisible(1));\n    assertFalse(metaData.ownDeletesAreVisible(1));\n    assertFalse(metaData.ownInsertsAreVisible(1));\n    assertFalse(metaData.ownUpdatesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE));\n    assertFalse(metaData.storesLowerCaseIdentifiers());\n    assertFalse(metaData.storesLowerCaseQuotedIdentifiers());\n    assertFalse(metaData.storesMixedCaseIdentifiers());\n    assertTrue(metaData.storesMixedCaseQuotedIdentifiers());\n    assertTrue(metaData.storesUpperCaseIdentifiers());\n    assertFalse(metaData.storesUpperCaseQuotedIdentifiers());\n    assertTrue(metaData.supportsAlterTableWithAddColumn());\n    assertTrue(metaData.supportsAlterTableWithDropColumn());\n    assertTrue(metaData.supportsANSI92EntryLevelSQL());\n    assertFalse(metaData.supportsANSI92FullSQL());\n    assertFalse(metaData.supportsANSI92IntermediateSQL());\n    assertTrue(metaData.supportsBatchUpdates());\n    assertTrue(metaData.supportsCatalogsInDataManipulation());\n    assertFalse(metaData.supportsCatalogsInIndexDefinitions());\n    assertFalse(metaData.supportsCatalogsInPrivilegeDefinitions());\n    assertFalse(metaData.supportsCatalogsInProcedureCalls());\n    assertTrue(metaData.supportsCatalogsInTableDefinitions());\n    assertTrue(metaData.supportsColumnAliasing());\n    assertFalse(metaData.supportsConvert());\n    assertFalse(metaData.supportsConvert(1, 2));\n    assertFalse(metaData.supportsCoreSQLGrammar());\n    assertTrue(metaData.supportsCorrelatedSubqueries());\n    assertTrue(metaData.supportsDataDefinitionAndDataManipulationTransactions());\n    assertFalse(metaData.supportsDataManipulationTransactionsOnly());\n    assertFalse(metaData.supportsDifferentTableCorrelationNames());\n    assertTrue(metaData.supportsExpressionsInOrderBy());\n    assertFalse(metaData.supportsExtendedSQLGrammar());\n    assertTrue(metaData.supportsFullOuterJoins());\n    assertFalse(metaData.supportsGetGeneratedKeys());\n    assertTrue(metaData.supportsGroupBy());\n    assertTrue(metaData.supportsGroupByBeyondSelect());\n    assertFalse(metaData.supportsGroupByUnrelated());\n    assertFalse(metaData.supportsIntegrityEnhancementFacility());\n    assertFalse(metaData.supportsLikeEscapeClause());\n    assertTrue(metaData.supportsLimitedOuterJoins());\n    assertFalse(metaData.supportsMinimumSQLGrammar());\n    assertFalse(metaData.supportsMixedCaseIdentifiers());\n    assertTrue(metaData.supportsMixedCaseQuotedIdentifiers());\n    assertFalse(metaData.supportsMultipleOpenResults());\n    assertFalse(metaData.supportsMultipleResultSets());\n    assertTrue(metaData.supportsMultipleTransactions());\n    assertFalse(metaData.supportsNamedParameters());\n    assertTrue(metaData.supportsNonNullableColumns());\n    assertFalse(metaData.supportsOpenCursorsAcrossCommit());\n    assertFalse(metaData.supportsOpenCursorsAcrossRollback());\n    assertFalse(metaData.supportsOpenStatementsAcrossCommit());\n    assertFalse(metaData.supportsOpenStatementsAcrossRollback());\n    assertTrue(metaData.supportsOrderByUnrelated());\n    assertTrue(metaData.supportsOuterJoins());\n    assertFalse(metaData.supportsPositionedDelete());\n    assertFalse(metaData.supportsPositionedUpdate());\n    assertTrue(\n        metaData.supportsResultSetConcurrency(\n            ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));\n    assertFalse(\n        metaData.supportsResultSetConcurrency(\n            ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY));\n    assertTrue(metaData.supportsResultSetType(ResultSet.TYPE_FORWARD_ONLY));\n    assertTrue(metaData.supportsResultSetHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT));\n    assertFalse(metaData.supportsResultSetHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT));\n    assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, metaData.getResultSetHoldability());\n    assertFalse(metaData.supportsSavepoints());\n    assertTrue(metaData.supportsSchemasInDataManipulation());\n    assertFalse(metaData.supportsSchemasInIndexDefinitions());\n    assertFalse(metaData.supportsSchemasInPrivilegeDefinitions());\n    assertFalse(metaData.supportsSchemasInProcedureCalls());\n    assertTrue(metaData.supportsSchemasInTableDefinitions());\n    assertFalse(metaData.supportsSelectForUpdate());\n    assertFalse(metaData.supportsStatementPooling());\n    assertTrue(metaData.supportsStoredFunctionsUsingCallSyntax());\n    assertTrue(metaData.supportsSubqueriesInComparisons());\n    assertTrue(metaData.supportsSubqueriesInExists());\n    assertTrue(metaData.supportsSubqueriesInIns());\n    assertFalse(metaData.supportsSubqueriesInQuantifieds());\n    assertTrue(metaData.supportsTableCorrelationNames());\n    assertTrue(metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED));\n    assertFalse(metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ));\n    assertFalse(metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE));\n    assertFalse(\n        metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED));\n    assertTrue(metaData.supportsUnion());\n    assertTrue(metaData.supportsUnionAll());\n    assertFalse(metaData.updatesAreDetected(1));\n    assertFalse(metaData.usesLocalFilePerTable());\n    assertFalse(metaData.usesLocalFiles());\n  }\n\n  @Test\n  public void testOtherEmptyTables() throws Throwable {\n    DatabaseMetaData metaData = connection.getMetaData();\n\n    // index is not supported.\n    try (ResultSet resultSet = metaData.getIndexInfo(null, null, null, true, true)) {\n      assertEquals(0, getSizeOfResultSet(resultSet));\n    }\n    // UDT is not supported.\n    try (ResultSet resultSet = metaData.getUDTs(null, null, null, new int[] {})) {\n      assertEquals(0, getSizeOfResultSet(resultSet));\n    }\n  }\n\n  @Test\n  public void testFeatureNotSupportedException() throws Throwable {\n    DatabaseMetaData metaData = connection.getMetaData();\n    expectFeatureNotSupportedException(\n        () -> metaData.getBestRowIdentifier(null, null, null, 0, true));\n    expectFeatureNotSupportedException(() -> metaData.getVersionColumns(null, null, null));\n    expectFeatureNotSupportedException(() -> metaData.getSuperTypes(null, null, null));\n    expectFeatureNotSupportedException(() -> metaData.getSuperTables(null, null, null));\n    expectFeatureNotSupportedException(() -> metaData.getAttributes(null, null, null, null));\n    expectFeatureNotSupportedException(metaData::getRowIdLifetime);\n    expectFeatureNotSupportedException(metaData::autoCommitFailureClosesAllResultSets);\n    expectFeatureNotSupportedException(metaData::getClientInfoProperties);\n    expectFeatureNotSupportedException(() -> metaData.getPseudoColumns(null, null, null, null));\n    expectFeatureNotSupportedException(metaData::generatedKeyAlwaysReturned);\n    expectFeatureNotSupportedException(\n        () -> metaData.isWrapperFor(SnowflakeDatabaseMetaData.class));\n  }\n\n  public static boolean isNullOrEmpty(String str) {\n    return str == null || str.isEmpty();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DatabaseMetaDataInternalIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataIT.EXPECTED_MAX_BINARY_LENGTH;\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataIT.verifyResultSetMetaDataColumns;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Database Metadata IT */\n@Tag(TestTags.DATABASE_META_DATA)\npublic class DatabaseMetaDataInternalIT extends BaseJDBCTest {\n  private Connection connection;\n  private Statement statement;\n  private DatabaseMetaData databaseMetaData;\n  private ResultSet resultSet;\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Connection con = getConnection()) {\n      initMetaData(con);\n    }\n  }\n\n  static void initMetaData(Connection con) throws SQLException {\n    try (Statement st = con.createStatement()) {\n\n      st.execute(\"create or replace database JDBC_DB1\");\n      st.execute(\"create or replace schema JDBC_SCHEMA11\");\n      st.execute(\"create or replace table JDBC_TBL111(colA string, colB decimal, colC timestamp)\");\n      st.execute(\"create or replace schema TEST_CTX\");\n      st.execute(\n          \"create or replace table JDBC_A (colA string, colB decimal, \"\n              + \"colC number PRIMARY KEY);\");\n      st.execute(\n          \"create or replace table JDBC_B (colA string, colB decimal, \"\n              + \"colC number FOREIGN KEY REFERENCES JDBC_A(colC));\");\n      st.execute(\"create or replace schema JDBC_SCHEMA12\");\n      st.execute(\"create or replace table JDBC_TBL121(colA varchar)\");\n      st.execute(\n          \"create or replace table JDBC_TBL122(colA NUMBER(20, 2) AUTOINCREMENT comment 'cmt\"\n              + \" colA', colB NUMBER(20, 2) DEFAULT(3) NOT NULL, colC NUMBER(20,2) IDENTITY(20,\"\n              + \" 2))\");\n      st.execute(\"create or replace database JDBC_DB2\");\n      st.execute(\"create or replace schema JDBC_SCHEMA21\");\n      st.execute(\"create or replace table JDBC_TBL211(colA string)\");\n      st.execute(\"create or replace table JDBC_BIN(bin1 binary(8388608), bin2 binary(100))\");\n\n      //    st.execute(\"create or replace table JDBC_TBL211(colA string(25) NOT NULL DEFAULT\n      // 'defstring')\");\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    try (Connection con = getConnection()) {\n      endMetaData(con);\n    }\n  }\n\n  static void endMetaData(Connection con) throws SQLException {\n    try (Statement st = con.createStatement()) {\n      st.execute(\"drop database if exists JDBC_DB1\");\n      st.execute(\"drop database if exists JDBC_DB2\");\n    }\n  }\n\n  @Test\n  @Disabled // TODO: SNOW-1805299\n  @DontRunOnGithubActions\n  public void testGetColumn() throws SQLException {\n    String getAllColumnsCount = \"select count(*) from db.information_schema.columns\";\n    connection = getConnection();\n    statement = connection.createStatement();\n    databaseMetaData = connection.getMetaData();\n\n    // Exclude SNOWFLAKE system database from DatabaseMetadata\n    ResultSet snowflakeResultSet = databaseMetaData.getColumns(\"SNOWFLAKE\", null, null, null);\n    int numSnowflakeColumns = getSizeOfResultSet(snowflakeResultSet);\n\n    resultSet = databaseMetaData.getColumns(null, null, null, null);\n    assertEquals(\n        getAllObjectCountInDBViaInforSchema(getAllColumnsCount),\n        getSizeOfResultSet(resultSet) - numSnowflakeColumns);\n\n    resultSet = databaseMetaData.getColumns(null, \"JDBC_SCHEMA11\", null, null);\n    assertEquals(3, getSizeOfResultSet(resultSet));\n\n    resultSet = databaseMetaData.getColumns(null, \"JDBC_SCH_MA11\", null, null);\n    assertEquals(3, getSizeOfResultSet(resultSet));\n\n    resultSet = databaseMetaData.getColumns(null, \"JDBC%\", null, null);\n    assertEquals(10, getSizeOfResultSet(resultSet));\n\n    resultSet = databaseMetaData.getColumns(null, \"JDBC_SCHEMA1_\", null, null);\n    assertEquals(7, getSizeOfResultSet(resultSet));\n\n    resultSet = databaseMetaData.getColumns(null, \"JDBC_SCHEMA21\", \"JDBC_BIN\", \"BIN1\");\n    resultSet.next();\n    assertEquals(EXPECTED_MAX_BINARY_LENGTH, resultSet.getInt(\"COLUMN_SIZE\"));\n    assertEquals(1, getSizeOfResultSet(resultSet) + 1);\n\n    resultSet = databaseMetaData.getColumns(null, \"JDBC_SCHEMA21\", \"JDBC_BIN\", \"BIN2\");\n    resultSet.next();\n    assertEquals(100, resultSet.getInt(\"COLUMN_SIZE\"));\n    assertEquals(1, getSizeOfResultSet(resultSet) + 1);\n\n    // test if return the correct info\n    resultSet = databaseMetaData.getColumns(\"JDBC_DB1\", \"JDBC_SCHEMA12\", \"JDBC_TBL122\", \"COLA\");\n    resultSet.next();\n    // fetchResultSet(resultSet, true);\n    assertEquals(\"JDBC_DB1\", resultSet.getString(\"TABLE_CAT\"));\n    assertEquals(\"JDBC_SCHEMA12\", resultSet.getString(\"TABLE_SCHEM\"));\n    assertEquals(\"JDBC_TBL122\", resultSet.getString(\"TABLE_NAME\"));\n    assertEquals(\"COLA\", resultSet.getString(\"COLUMN_NAME\"));\n    assertEquals(Types.DECIMAL, resultSet.getInt(\"DATA_TYPE\"));\n    assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n    assertEquals(\"20\", resultSet.getString(\"COLUMN_SIZE\"));\n    assertEquals(\"2\", resultSet.getString(\"DECIMAL_DIGITS\"));\n    assertEquals(DatabaseMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n    assertEquals(\"cmt colA\", resultSet.getString(\"REMARKS\"));\n    assertEquals(null, resultSet.getString(\"COLUMN_DEF\"));\n    assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n    assertEquals(\"YES\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n    resultSet.close();\n\n    // more on default and autoincrement\n    resultSet = databaseMetaData.getColumns(\"JDBC_DB1\", \"JDBC_SCHEMA12\", \"JDBC_TBL122\", \"COLB\");\n    resultSet.next();\n    assertEquals(DatabaseMetaData.columnNoNulls, resultSet.getInt(11));\n    assertEquals(\"3\", resultSet.getString(13));\n    assertEquals(\"NO\", resultSet.getString(23));\n    resultSet.close();\n\n    resultSet = databaseMetaData.getColumns(\"JDBC_DB1\", \"JDBC_SCHEMA1_\", null, \"COL_\");\n\n    resultSet = databaseMetaData.getColumns(\"JDBC_DB1\", \"JDBC_SCHEMA12\", \"JDBC_TBL122\", \"COLC\");\n    resultSet.next();\n    assertEquals(null, resultSet.getString(13));\n    assertEquals(\"YES\", resultSet.getString(23));\n\n    // SNOW-24558 Metadata request with special characters in table name\n    statement.execute(\"create or replace table \\\"@@specialchartable$1234\\\"(colA int)\");\n    resultSet = databaseMetaData.getColumns(null, null, \"@@specialchartable$%\", null);\n    assertEquals(1, getSizeOfResultSet(resultSet));\n\n    resultSet.close();\n    resultSet.next();\n    connection.close();\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetFunctions() throws SQLException {\n    connection = getConnection();\n    statement = connection.createStatement();\n    statement.execute(\n        \"create or replace function JDBC_DB1.JDBC_SCHEMA11.JDBCFUNCTEST111 \"\n            + \"(a number, b number) RETURNS NUMBER COMMENT='multiply numbers' as 'a*b'\");\n    statement.execute(\n        \"create or replace function JDBC_DB1.JDBC_SCHEMA12.JDBCFUNCTEST121 \"\n            + \"(a number, b number) RETURNS NUMBER COMMENT='multiply numbers' as 'a*b'\");\n    statement.execute(\n        \"create or replace function JDBC_DB1.JDBC_SCHEMA12.JDBCFUNCTEST122 \"\n            + \"(a number, b number) RETURNS NUMBER COMMENT='multiply numbers' as 'a*b'\");\n    statement.execute(\n        \"create or replace function JDBC_DB2.JDBC_SCHEMA21.JDBCFUNCTEST211 \"\n            + \"(a number, b number) RETURNS NUMBER COMMENT='multiply numbers' as 'a*b'\");\n    statement.execute(\n        \"create or replace function JDBC_DB2.JDBC_SCHEMA21.JDBCFUNCTEST212 () RETURNS TABLE(colA\"\n            + \" varchar) as 'select COLA from JDBC_DB2.JDBC_SCHEMA21.JDBC_TBL211'\");\n    databaseMetaData = connection.getMetaData();\n\n    // test each column return the right value\n    resultSet = databaseMetaData.getFunctions(\"JDBC_DB1\", \"JDBC_SCHEMA11\", \"JDBCFUNCTEST111\");\n    verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_FUNCTIONS);\n    resultSet.next();\n    assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n    assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n    assertEquals(\"JDBCFUNCTEST111\", resultSet.getString(\"FUNCTION_NAME\"));\n    assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n    assertEquals(DatabaseMetaData.functionNoTable, resultSet.getInt(\"FUNCTION_TYPE\"));\n    assertEquals(\"JDBCFUNCTEST111\", resultSet.getString(\"SPECIFIC_NAME\"));\n    assertFalse(resultSet.next());\n\n    // test a table function\n    resultSet = databaseMetaData.getFunctions(\"JDBC_DB2\", \"JDBC_SCHEMA21\", \"JDBCFUNCTEST212\");\n    resultSet.next();\n    assertEquals(DatabaseMetaData.functionReturnsTable, resultSet.getInt(\"FUNCTION_TYPE\"));\n    assertFalse(resultSet.next());\n\n    // test a builtin function\n    resultSet = databaseMetaData.getFunctions(null, null, \"AND\");\n    resultSet.next();\n    assertEquals(\"\", resultSet.getString(\"FUNCTION_CAT\"));\n    assertEquals(\"\", resultSet.getString(\"FUNCTION_SCHEM\"));\n    assertEquals(\"AND\", resultSet.getString(\"FUNCTION_NAME\"));\n    assertEquals(DatabaseMetaData.functionNoTable, resultSet.getInt(\"FUNCTION_TYPE\"));\n    assertEquals(\"AND\", resultSet.getString(\"SPECIFIC_NAME\"));\n    assertFalse(resultSet.next());\n\n    // test pattern\n    resultSet = databaseMetaData.getFunctions(null, null, \"JDBCFUNCTEST%\");\n    assertEquals(5, getSizeOfResultSet(resultSet));\n    resultSet = databaseMetaData.getFunctions(null, \"JDBC_SCHEMA1_\", \"_DBCFUNCTEST%\");\n    assertEquals(3, getSizeOfResultSet(resultSet));\n    // resultSet = databaseMetaData.getFunctions(\"JDBC_DB1\", \"AAAAAAAAAAA\", \"AAAAAAA\");\n\n    resultSet = databaseMetaData.getFunctions(\"JDBC_DB3\", \"JDBC_SCHEMA1_\", \"_DBCFUNCTEST%\");\n    assertEquals(0, getSizeOfResultSet(resultSet));\n\n    resultSet = databaseMetaData.getFunctions(\"JDBC_DB1\", \"JDBC_SCHEMA__\", \"_DBCFUNCTEST%\");\n    assertEquals(3, getSizeOfResultSet(resultSet));\n    resultSet = databaseMetaData.getFunctions(\"JDBC_DB1\", \"JDBC_SCHEMA1_\", \"_DBCFUNCTEST11_\");\n    assertEquals(1, getSizeOfResultSet(resultSet));\n    resultSet = databaseMetaData.getFunctions(\"JDBC_DB1\", null, \"_DBCFUNCTEST11_\");\n    assertEquals(1, getSizeOfResultSet(resultSet));\n\n    resultSet.close();\n    resultSet.next();\n\n    statement.close();\n    connection.close();\n  }\n\n  @Test\n  @Disabled // TODO: SNOW-1805299\n  @DontRunOnGithubActions\n  public void testGetSchema() throws SQLException {\n    String getSchemaCount = \"select count(*) from db.information_schema.schemata\";\n    connection = getConnection();\n    databaseMetaData = connection.getMetaData();\n    assertEquals(\"schema\", databaseMetaData.getSchemaTerm());\n\n    // Exclude SNOWFLAKE system database from DatabaseMetadata\n    ResultSet snowflakeResultSet = databaseMetaData.getSchemas(\"SNOWFLAKE\", null);\n    int numSnowflakeSchemas = getSizeOfResultSet(snowflakeResultSet);\n\n    resultSet = databaseMetaData.getSchemas();\n    assertEquals(\n        getAllObjectCountInDBViaInforSchema(getSchemaCount),\n        getSizeOfResultSet(resultSet) - numSnowflakeSchemas);\n\n    resultSet = databaseMetaData.getSchemas(null, null);\n    assertEquals(\n        getAllObjectCountInDBViaInforSchema(getSchemaCount),\n        getSizeOfResultSet(resultSet) - numSnowflakeSchemas);\n\n    resultSet = databaseMetaData.getSchemas(\"JDBC_DB1\", \"%\");\n    resultSet.next();\n    assertEquals(\"INFORMATION_SCHEMA\", resultSet.getString(1));\n    assertEquals(\"JDBC_DB1\", resultSet.getString(2));\n    resultSet.next();\n    assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(1));\n    assertEquals(\"JDBC_DB1\", resultSet.getString(2));\n    resultSet.next();\n    assertEquals(\"JDBC_SCHEMA12\", resultSet.getString(1));\n    assertEquals(\"JDBC_DB1\", resultSet.getString(2));\n    resultSet.next();\n    assertEquals(\"PUBLIC\", resultSet.getString(1));\n    assertEquals(\"JDBC_DB1\", resultSet.getString(2));\n\n    resultSet = databaseMetaData.getSchemas(\"JDBC_DB1\", \"JDBC%\");\n    assertEquals(2, getSizeOfResultSet(resultSet));\n    resultSet.close();\n    resultSet.next();\n\n    connection.close();\n  }\n\n  /**\n   * SNOW-51427 SNOW-54196 Enable persisted cache results for show objects This test ensures that\n   * cached show results are correctly created, used, and invalidated when it is called from JDBC's\n   * getTables() function Author: Andong Zhan Created on 09/28/2018\n   */\n  @Test\n  @Disabled // SNOW-85084 detected this is a flaky test, so ignore it here.\n  // We have other regression tests to cover it\n  @DontRunOnGithubActions\n  public void testGetTablesReusingCachedResults() throws SQLException {\n    Connection snowflakeConnection = getSnowflakeAdminConnection();\n    Statement snowflake = snowflakeConnection.createStatement();\n    snowflake.execute(\"alter system set OVERRIDE_USE_CACHED_RESULT = true;\");\n\n    connection = getConnection();\n    databaseMetaData = connection.getMetaData();\n    Statement stmt = connection.createStatement();\n\n    // Clean existing cache\n    stmt.execute(\"select system$drop_result_reuse_cache();\");\n\n    // Setup parameters\n    stmt.execute(\"alter session set USE_CACHED_RESULT = true;\");\n    stmt.execute(\"alter session set USE_CACHED_SHOW_RESULT = true;\");\n    String accountName = getAccountName(stmt);\n    // Setup test database\n    long accountId = getAccountId(stmt, accountName);\n    String dbname = \"JDBC_DSHOW\";\n    String schemaname = \"SSHOW_\" + randomAlphaNumeric(6);\n    stmt.execute(\"create or replace database \" + dbname);\n    stmt.execute(\"create or replace schema \" + schemaname);\n    stmt.execute(\"use schema \" + dbname + \".\" + schemaname);\n    stmt.execute(\"create table show1(c1 number);\");\n\n    // run show objects\n    long oldNumCacheRes = getNumCachedResults(stmt, accountId);\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    long newNumCacheRes = getNumCachedResults(stmt, accountId);\n    // the number of cached results should increase by one\n    assertEquals(1, newNumCacheRes - oldNumCacheRes);\n    // run show objects in database again and the #cache should be the same\n    oldNumCacheRes = newNumCacheRes;\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    // the number of cached results should not increase\n    assertEquals(0, newNumCacheRes - oldNumCacheRes);\n\n    // create new table, then the first getTables should create a new cache\n    // and the 2nd getTables should reuse the cache\n    stmt.execute(\"create table show2(c2 number);\");\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(1, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(0, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n\n    // rename table, then the first getTables should create a new cache\n    // and the 2nd getTables should reuse the cache\n    stmt.execute(\"alter table show2 rename to show3\");\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(1, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(0, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n\n    // comment table, then the first getTables should create a new cache\n    // and the 2nd getTables should reuse the cache\n    stmt.execute(\"alter table show3 set comment = 'show3'\");\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(1, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(0, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n\n    // insert table, then getTables should reuse last cache\n    stmt.execute(\"insert into show3 values (3),(4)\");\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(0, newNumCacheRes - oldNumCacheRes);\n\n    // drop table, then the first getTables should create a new cache\n    // and the 2nd getTables should reuse the cache\n    stmt.execute(\"drop table show1\");\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(1, newNumCacheRes - oldNumCacheRes);\n    oldNumCacheRes = newNumCacheRes;\n    resultSet = databaseMetaData.getTables(dbname, null, null, null);\n    newNumCacheRes = getNumCachedResults(stmt, accountId);\n    assertEquals(0, newNumCacheRes - oldNumCacheRes);\n\n    // clean up\n    stmt.execute(\"drop database if exists \" + dbname);\n    // Setup parameters\n    stmt.execute(\"alter session set USE_CACHED_RESULT = default;\");\n    snowflake.execute(\"alter system set OVERRIDE_USE_CACHED_RESULT = true;\");\n    stmt.execute(\"alter session set USE_CACHED_SHOW_RESULT = default;\");\n\n    stmt.close();\n    snowflake.close();\n    snowflakeConnection.close();\n    connection.close();\n  }\n\n  private long getNumCachedResults(Statement stmt, long accountId) throws SQLException {\n    String query =\n        \"select count($1:\\\"JobResultDPO:share\\\")\\n\"\n            + \"from table(dposcan('\\n\"\n            + \"  {\\n\"\n            + \"    \\\"slices\\\" : [{\\\"name\\\" : \\\"JobResultDPO:share\\\"}],\\n\"\n            + \"    \\\"ranges\\\" : [\\n\"\n            + \"            {\\\"name\\\" : \\\"accountId\\\", \\\"value\\\" : %d}\\n\"\n            + \"          ]\\n\"\n            + \"   }'));\";\n    stmt.execute(String.format(query, accountId));\n    resultSet = stmt.getResultSet();\n    assertTrue(resultSet.next());\n    return resultSet.getLong(1);\n  }\n\n  private static final String ALPHA_NUMERIC_STRING = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\n  public static String randomAlphaNumeric(int count) {\n    StringBuilder builder = new StringBuilder();\n    while (count-- != 0) {\n      int character = (int) (Math.random() * ALPHA_NUMERIC_STRING.length());\n      builder.append(ALPHA_NUMERIC_STRING.charAt(character));\n    }\n    return builder.toString();\n  }\n\n  private String getAccountName(Statement stmt) throws SQLException {\n    stmt.execute(\"select current_account_locator()\");\n    resultSet = stmt.getResultSet();\n    assertTrue(resultSet.next());\n    return resultSet.getString(1);\n  }\n\n  private long getAccountId(Statement stmt, String accountName) throws SQLException {\n    stmt.execute(\n        \"select to_number($1:\\\"AccountDPO:active_by_name\\\":id) as id\\n\"\n            + \"from table(dposcan('\\n\"\n            + \"  {\\n\"\n            + \"    \\\"slices\\\" : [{\\\"name\\\" : \\\"AccountDPO:active_by_name\\\"}],\\n\"\n            + \"    \\\"ranges\\\" : [\\n\"\n            + \"            {\\\"name\\\": \\\"name\\\", \\\"value\\\": \\\"\"\n            + accountName\n            + \"\\\"}\\n\"\n            + \"          ]\\n\"\n            + \"   }'));\");\n    resultSet = stmt.getResultSet();\n    assertTrue(resultSet.next());\n    return resultSet.getLong(1);\n  }\n\n  @Test\n  @Disabled // TODO: SNOW-1805299\n  @DontRunOnGithubActions\n  public void testGetTables() throws SQLException {\n    String getAllTable = \"select count(*) from db.information_schema.tables\";\n    String getAllBaseTable =\n        \"select count(*) from db.information_schema.\" + \"tables where table_type = 'BASE TABLE'\";\n    String getAllView =\n        \"select count(*) from db.information_schema.\" + \"tables where table_type = 'VIEW'\";\n    try (Connection connection = getConnection();\n        Statement stmt = connection.createStatement()) {\n\n      // set parameter\n      stmt.execute(\"alter session set ENABLE_DRIVER_TERSE_SHOW = true;\");\n      stmt.execute(\"alter session set qa_mode = false;\");\n\n      databaseMetaData = connection.getMetaData();\n\n      SQLException e =\n          assertThrows(\n              SQLException.class,\n              () -> databaseMetaData.getTables(null, null, null, new String[] {\"ALIAS\"}));\n      assertEquals(ErrorCode.FEATURE_UNSUPPORTED.getSqlState(), e.getSQLState());\n      assertEquals(ErrorCode.FEATURE_UNSUPPORTED.getMessageCode().intValue(), e.getErrorCode());\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(null, null, null, new String[] {\"SYSTEM_TABLE\"})) {\n        assertEquals(0, getSizeOfResultSet(resultSet));\n      }\n\n      // Get the count of tables in the SNOWFLAKE system database, so we can exclude them from\n      // subsequent assertions\n      int numSnowflakeTables = 0;\n      try (ResultSet snowflakeResultSet =\n          databaseMetaData.getTables(\"SNOWFLAKE\", null, null, null)) {\n        numSnowflakeTables = getSizeOfResultSet(snowflakeResultSet);\n      }\n\n      try (ResultSet resultSet = databaseMetaData.getTables(null, null, null, null)) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllTable),\n            getSizeOfResultSet(resultSet) - numSnowflakeTables);\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(null, null, null, new String[] {\"VIEW\", \"SYSTEM_TABLE\"})) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllView),\n            getSizeOfResultSet(resultSet) - numSnowflakeTables);\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(null, null, null, new String[] {\"TABLE\", \"SYSTEM_TABLE\"})) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllBaseTable), getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(\n              null, null, null, new String[] {\"TABLE\", \"VIEW\", \"SYSTEM_TABLE\"})) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllTable),\n            getSizeOfResultSet(resultSet) - numSnowflakeTables);\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(null, null, null, new String[] {\"TABLE\", \"VIEW\"})) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllTable),\n            getSizeOfResultSet(resultSet) - numSnowflakeTables);\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(null, null, null, new String[] {\"TABLE\"})) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllBaseTable), getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(null, null, null, new String[] {\"VIEW\"})) {\n        assertEquals(\n            getAllObjectCountInDBViaInforSchema(getAllView),\n            getSizeOfResultSet(resultSet) - numSnowflakeTables);\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(\"JDBC_DB1\", \"JDBC_SCHEMA11\", null, new String[] {\"TABLE\"})) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      // snow-26032. JDBC should strip backslash before sending the show functions to server\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(\"JDBC_DB1\", \"JDBC\\\\_SCHEMA11\", \"%\", new String[] {\"TABLE\"})) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(\"JDBC_DB1\", \"JDBC%\", null, new String[] {\"TABLE\"})) {\n        assertEquals(3, getSizeOfResultSet(resultSet));\n      }\n\n      // SNOW-487548: disable the key-value feature to hide is_hybrid column\n      // in show tables command. The column is controlled by two parameters:\n      // enable_key_value_table and qa_mode.\n      stmt.execute(\"alter session set ENABLE_KEY_VALUE_TABLE = false;\");\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(\n              \"JDBC_DB1\", \"JDBC_SCH%\", \"J_BC_TBL122\", new String[] {\"TABLE\"})) {\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(1));\n        assertEquals(\"JDBC_SCHEMA12\", resultSet.getString(2));\n        assertEquals(\"JDBC_TBL122\", resultSet.getString(3));\n        assertEquals(\"TABLE\", resultSet.getString(4));\n        assertEquals(\"\", resultSet.getString(5));\n        stmt.execute(\"alter session unset ENABLE_KEY_VALUE_TABLE;\");\n      }\n      try (ResultSet resultSet =\n          databaseMetaData.getTables(\"JDBC_DB1\", null, \"JDBC_TBL211\", new String[] {\"TABLE\"})) {\n        assertEquals(0, getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet = databaseMetaData.getTableTypes()) {\n        resultSet.next();\n        assertEquals(\"TABLE\", resultSet.getString(1));\n        resultSet.next();\n        assertEquals(\"VIEW\", resultSet.getString(1));\n      }\n      stmt.execute(\"alter session set ENABLE_DRIVER_TERSE_SHOW = default;\");\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetMetaDataUseConnectionCtx() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n\n      // setup: reset session db and schema, enable the parameter\n      statement.execute(\"use database JDBC_DB1\");\n      statement.execute(\"use schema JDBC_SCHEMA11\");\n      statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n\n      DatabaseMetaData databaseMetaData = connection.getMetaData();\n\n      // this should only return JDBC_SCHEMA11\n      try (ResultSet resultSet = databaseMetaData.getSchemas(null, null)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n      // only returns tables in JDBC_DB1.JDBC_SCHEMA11\n      try (ResultSet resultSet = databaseMetaData.getTables(null, null, null, null)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      statement.execute(\"use schema JDBC_SCHEMA12\");\n      try (ResultSet resultSet = databaseMetaData.getTables(null, null, null, null)) {\n        assertEquals(2, getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet = databaseMetaData.getColumns(null, null, null, null)) {\n        assertEquals(4, getSizeOfResultSet(resultSet));\n      }\n\n      statement.execute(\"use schema TEST_CTX\");\n      try (ResultSet resultSet = databaseMetaData.getPrimaryKeys(null, null, null)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet = databaseMetaData.getImportedKeys(null, null, null)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet = databaseMetaData.getExportedKeys(null, null, null)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      try (ResultSet resultSet =\n          databaseMetaData.getCrossReference(null, null, null, null, null, null)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n    }\n  }\n\n  private int getAllObjectCountInDBViaInforSchema(String SQLCmdTemplate) throws SQLException {\n    int objectCount = 0;\n    try (Connection con = getConnection();\n        Statement st = con.createStatement()) {\n      st.execute(\"alter session set ENABLE_BUILTIN_SCHEMAS = true\");\n      try (ResultSet dbNameRS =\n          st.executeQuery(\"select database_name from information_schema.databases\")) {\n        while (dbNameRS.next()) {\n          String databaseName = dbNameRS.getString(1);\n          String execSQLCmd = SQLCmdTemplate.replaceAll(\"db\", databaseName);\n          ResultSet object = st.executeQuery(execSQLCmd);\n          object.next();\n          objectCount += object.getInt(1);\n        }\n      }\n    }\n    return objectCount;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DatabaseMetaDataInternalLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataInternalIT.endMetaData;\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataInternalIT.initMetaData;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Database Metadata tests for the latest JDBC driver. This doesn't work for the oldest supported\n * driver. Revisit this tests whenever bumping up the oldest supported driver to examine if the\n * tests still is not applicable. If it is applicable, move tests to DatabaseMetaDataIT so that both\n * the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.DATABASE_META_DATA)\npublic class DatabaseMetaDataInternalLatestIT extends BaseJDBCTest {\n\n  @BeforeEach\n  public void setUp() throws Exception {\n    try (Connection con = getConnection()) {\n      initMetaData(con);\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws Exception {\n    try (Connection con = getConnection()) {\n      endMetaData(con);\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetMetaDataUseConnectionCtx() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n\n      // setup: reset session db and schema, enable the parameter\n      statement.execute(\"use database JDBC_DB1\");\n      statement.execute(\"use schema JDBC_SCHEMA11\");\n      statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n\n      DatabaseMetaData databaseMetaData = connection.getMetaData();\n\n      // Searches for tables only in database JDBC_DB1 and schema JDBC_SCHEMA11\n      try (ResultSet resultSet = databaseMetaData.getTables(null, null, null, null)) {\n        // Assert the tables are retrieved at schema level\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(1));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(2));\n      }\n      // Searches for tables only in database JDBC_DB1 and schema JDBC_SCHEMA11\n      try (ResultSet resultSet = databaseMetaData.getColumns(null, null, null, null); ) {\n        // Assert the columns are retrieved at schema level\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(1));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(2));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetFunctionColumns() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace function JDBC_DB1.JDBC_SCHEMA11.FUNC111 \"\n              + \"(a number, b number) RETURNS NUMBER COMMENT='multiply numbers' as 'a*b'\");\n      statement.execute(\n          \"create or replace table JDBC_DB1.JDBC_SCHEMA11.BIN_TABLE(bin1 binary, bin2 binary(100), \"\n              + \"sharedCol decimal)\");\n      statement.execute(\n          \"create or replace function JDBC_DB1.JDBC_SCHEMA11.FUNC112 \"\n              + \"() RETURNS TABLE(colA string(16777216), colB decimal, bin2 binary(8388608), sharedCol decimal) COMMENT= 'returns \"\n              + \"table of 4 columns'\"\n              + \" as 'select JDBC_DB1.JDBC_SCHEMA11.JDBC_TBL111.colA, JDBC_DB1.JDBC_SCHEMA11.JDBC_TBL111.colB, \"\n              + \"JDBC_DB1.JDBC_SCHEMA11.BIN_TABLE.bin2, JDBC_DB1.JDBC_SCHEMA11.BIN_TABLE.sharedCol from JDBC_DB1\"\n              + \".JDBC_SCHEMA11.JDBC_TBL111 inner join JDBC_DB1.JDBC_SCHEMA11.BIN_TABLE on JDBC_DB1.JDBC_SCHEMA11\"\n              + \".JDBC_TBL111.colB = JDBC_DB1.JDBC_SCHEMA11.BIN_TABLE.sharedCol'\");\n      DatabaseMetaData databaseMetaData = connection.getMetaData();\n      // test each column return the right value\n      try (ResultSet resultSet =\n          databaseMetaData.getFunctionColumns(\"JDBC_DB1\", \"JDBC_SCHEMA11\", \"FUNC111\", \"%\")) {\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC111\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionReturn, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER(38,0)\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"FUNC111(NUMBER, NUMBER) RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC111\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"A\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(1, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(1, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"FUNC111(NUMBER, NUMBER) RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC111\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"B\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(1, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(2, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"FUNC111(NUMBER, NUMBER) RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        assertFalse(resultSet.next());\n      }\n      try (ResultSet resultSet =\n          databaseMetaData.getFunctionColumns(\"JDBC_DB1\", \"JDBC_SCHEMA11\", \"FUNC112\", \"%\")) {\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"COLA\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"VARCHAR\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(0, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        assertEquals(\n            databaseMetaData.getMaxCharLiteralLength(), resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(1, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"COLB\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(2, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"BIN2\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.BINARY, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"BINARY\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        assertEquals(\n            databaseMetaData.getMaxBinaryLiteralLength(), resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(3, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        resultSet.next();\n        assertEquals(\"JDBC_DB1\", resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(\"JDBC_SCHEMA11\", resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"SHAREDCOL\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(4, resultSet.getInt(\"ORDINAL_POSITION\"));\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        // setting catalog to % will result in 0 columns. % does not apply for catalog, only for\n        // other\n        // params\n      }\n      try (ResultSet resultSet = databaseMetaData.getFunctionColumns(\"%\", \"%\", \"%\", \"%\")) {\n        assertEquals(0, getSizeOfResultSet(resultSet));\n      }\n    }\n  }\n\n  /** Tests that calling getTables() concurrently doesn't cause data race condition. */\n  @Test\n  @DontRunOnGithubActions\n  public void testGetTablesRaceCondition()\n      throws SQLException, ExecutionException, InterruptedException {\n    try (Connection connection = getConnection()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      DatabaseMetaData databaseMetaData = connection.getMetaData();\n\n      // Create 10 threads, each calls getTables() concurrently\n      ExecutorService executorService = Executors.newFixedThreadPool(10);\n      List<Future<?>> futures = new ArrayList<>();\n      for (int i = 0; i < 10; i++) {\n        futures.add(\n            executorService.submit(\n                () -> {\n                  try {\n                    databaseMetaData.getTables(database, schema, null, null);\n                  } catch (SQLException e) {\n                    throw new RuntimeException(e);\n                  }\n                }));\n      }\n      executorService.shutdown();\n      for (int i = 0; i < 10; i++) {\n        futures.get(i).get();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DatabaseMetaDataLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.TestUtil.GENERATED_SCHEMA_PREFIX;\nimport static net.snowflake.client.TestUtil.asyncAssert;\nimport static net.snowflake.client.TestUtil.escapeUnderscore;\nimport static net.snowflake.client.internal.api.implementation.metadata.SnowflakeDatabaseMetaDataImpl.NumericFunctionsSupported;\nimport static net.snowflake.client.internal.api.implementation.metadata.SnowflakeDatabaseMetaDataImpl.StringFunctionsSupported;\nimport static net.snowflake.client.internal.api.implementation.metadata.SnowflakeDatabaseMetaDataImpl.SystemFunctionsSupported;\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataIT.EXPECTED_MAX_BINARY_LENGTH;\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataIT.EXPECTED_MAX_CHAR_LENGTH;\nimport static net.snowflake.client.internal.jdbc.DatabaseMetaDataIT.verifyResultSetMetaDataColumns;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.lang.reflect.Field;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.function.Function;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.connection.SnowflakeDatabaseMetaData;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.util.ThrowingFunction;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInstance;\n\n/**\n * DatabaseMetaData test for the latest JDBC driver. This doesn't work for the oldest supported\n * driver. Revisit this tests whenever bumping up the oldest supported driver to examine if the\n * tests still is not applicable. If it is applicable, move tests to DatabaseMetaDataIT so that both\n * the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.DATABASE_META_DATA)\npublic class DatabaseMetaDataLatestIT extends BaseJDBCWithSharedConnectionIT {\n  private static final String TEST_PROC =\n      \"create or replace procedure testproc(param1 float, param2 string)\\n\"\n          + \"    returns varchar\\n\"\n          + \"    language javascript\\n\"\n          + \"    as\\n\"\n          + \"    $$\\n\"\n          + \"    var sql_command = \\\"Hello, world!\\\"\\n\"\n          + \"    $$\\n\"\n          + \"    ;\";\n\n  private static final String PI_PROCEDURE =\n      \"create or replace procedure GETPI()\\n\"\n          + \"    returns float not null\\n\"\n          + \"    language javascript\\n\"\n          + \"    as\\n\"\n          + \"    $$\\n\"\n          + \"    return 3.1415926;\\n\"\n          + \"    $$\\n\"\n          + \"    ;\";\n\n  private static final String MESSAGE_PROCEDURE =\n      \"create or replace procedure MESSAGE_PROC(message varchar)\\n\"\n          + \"    returns varchar not null\\n\"\n          + \"    language javascript\\n\"\n          + \"    as\\n\"\n          + \"    $$\\n\"\n          + \"    return message;\\n\"\n          + \"    $$\\n\"\n          + \"    ;\";\n  private static final String ENABLE_PATTERN_SEARCH =\n      SFSessionProperty.ENABLE_PATTERN_SEARCH.getPropertyKey();\n\n  private static String startingSchema;\n  private static String startingDatabase;\n\n  @BeforeAll\n  public static void prepare() {\n    try {\n      startingSchema = connection.getSchema();\n      startingDatabase = connection.getCatalog();\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  /** Create catalog and schema for tests with double quotes */\n  public void createDoubleQuotedSchemaAndCatalog(Statement statement) throws SQLException {\n    statement.execute(\"create or replace database \\\"dbwith\\\"\\\"quotes\\\"\");\n    statement.execute(\"create or replace schema \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\"\");\n  }\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"USE DATABASE \" + startingDatabase);\n      stmt.execute(\"USE SCHEMA \" + startingSchema);\n    }\n  }\n\n  /**\n   * Tests for getFunctions\n   *\n   * @throws Exception arises if any error occurs\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testUseConnectionCtx() throws Exception {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n      String schema = connection.getSchema();\n\n      TestUtil.withRandomSchema(\n          statement,\n          customSchema -> {\n            DatabaseMetaData databaseMetaData = connection.getMetaData();\n            // create tables within current schema.\n            statement.execute(\n                \"create or replace table CTX_TBL_A (colA string, colB decimal, \"\n                    + \"colC number PRIMARY KEY);\");\n            statement.execute(\n                \"create or replace table CTX_TBL_B (colA string, colB decimal, \"\n                    + \"colC number FOREIGN KEY REFERENCES CTX_TBL_A (colC));\");\n            statement.execute(\n                \"create or replace table CTX_TBL_C (colA string, colB decimal, \"\n                    + \"colC number, colD int, colE timestamp, colF string, colG number);\");\n            // now create more tables under current schema\n            statement.execute(\"use schema \" + schema);\n            statement.execute(\n                \"create or replace table CTX_TBL_D (colA string, colB decimal, \"\n                    + \"colC number PRIMARY KEY);\");\n            statement.execute(\n                \"create or replace table CTX_TBL_E (colA string, colB decimal, \"\n                    + \"colC number FOREIGN KEY REFERENCES CTX_TBL_D (colC));\");\n            statement.execute(\n                \"create or replace table CTX_TBL_F (colA string, colB decimal, \"\n                    + \"colC number, colD int, colE timestamp, colF string, colG number);\");\n\n            // this should only return `customSchema` schema and tables\n            statement.execute(\"use schema \" + customSchema);\n\n            try (ResultSet resultSet = databaseMetaData.getSchemas(null, null)) {\n              assertEquals(1, getSizeOfResultSet(resultSet));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getTables(null, null, null, null)) {\n              assertEquals(3, getSizeOfResultSet(resultSet));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getColumns(null, null, null, null)) {\n              assertEquals(13, getSizeOfResultSet(resultSet));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getPrimaryKeys(null, null, null)) {\n              assertEquals(1, getSizeOfResultSet(resultSet));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getImportedKeys(null, null, null)) {\n              assertEquals(1, getSizeOfResultSet(resultSet));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getExportedKeys(null, null, null)) {\n              assertEquals(1, getSizeOfResultSet(resultSet));\n            }\n\n            try (ResultSet resultSet =\n                databaseMetaData.getCrossReference(null, null, null, null, null, null)) {\n              assertEquals(1, getSizeOfResultSet(resultSet));\n            }\n            // Now compare results to setting client metadata to false.\n            statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=false\");\n            databaseMetaData = connection.getMetaData();\n\n            try (ResultSet resultSet = databaseMetaData.getSchemas(null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(2));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getTables(null, null, null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(6));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getColumns(null, null, null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(26));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getPrimaryKeys(null, null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(2));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getImportedKeys(null, null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(2));\n            }\n\n            try (ResultSet resultSet = databaseMetaData.getExportedKeys(null, null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(2));\n            }\n\n            try (ResultSet resultSet =\n                databaseMetaData.getCrossReference(null, null, null, null, null, null)) {\n              assertThat(getSizeOfResultSet(resultSet), greaterThanOrEqualTo(2));\n            }\n          });\n    }\n  }\n\n  /**\n   * This tests the ability to have quotes inside a schema or database. This fixes a bug where\n   * double-quoted function arguments like schemas, databases, etc were returning empty resultsets.\n   *\n   * @throws Exception\n   */\n  @Test\n  public void testDoubleQuotedDatabaseAndSchema() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = startingDatabase;\n      // To query the schema and table, we can use a normal java escaped quote. Wildcards are also\n      // escaped here\n      String schemaRandomPart = SnowflakeUtil.randomAlphaNumeric(5);\n      String querySchema =\n          TestUtil.ESCAPED_GENERATED_SCHEMA_PREFIX\n              + \"TEST\\\\_SCHEMA\\\\_\\\"WITH\\\\_QUOTES\"\n              + schemaRandomPart\n              + \"\\\"\";\n      String queryTable = \"TESTTABLE\\\\_\\\"WITH\\\\_QUOTES\\\"\";\n      // Create the schema and table. With SQL commands, double quotes must be escaped with another\n      // quote\n      String schemaName =\n          \"\\\"\"\n              + GENERATED_SCHEMA_PREFIX\n              + \"TEST_SCHEMA_\\\"\\\"WITH_QUOTES\"\n              + schemaRandomPart\n              + \"\\\"\\\"\\\"\";\n      TestUtil.withSchema(\n          statement,\n          schemaName,\n          () -> {\n            statement.execute(\n                \"create or replace table \\\"TESTTABLE_\\\"\\\"WITH_QUOTES\\\"\\\"\\\" (AMOUNT number,\"\n                    + \" \\\"COL_\\\"\\\"QUOTED\\\"\\\"\\\" string)\");\n            DatabaseMetaData metaData = connection.getMetaData();\n            try (ResultSet rs = metaData.getTables(database, querySchema, queryTable, null)) {\n              // Assert 1 row returned for the testtable_\"with_quotes\"\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n            try (ResultSet rs = metaData.getColumns(database, querySchema, queryTable, null)) {\n              // Assert 2 rows returned for the 2 rows in testtable_\"with_quotes\"\n              assertEquals(2, getSizeOfResultSet(rs));\n            }\n            try (ResultSet rs =\n                metaData.getColumns(database, querySchema, queryTable, \"COL\\\\_\\\"QUOTED\\\"\")) {\n              // Assert 1 row returned for the column col_\"quoted\"\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n            try (ResultSet rs = metaData.getSchemas(database, querySchema)) {\n              // Assert 1 row returned for the schema test_schema_\"with_quotes\"\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n          });\n    }\n  }\n\n  /**\n   * This tests the ability to have quotes inside a database or schema within getSchemas() function.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseInGetSchemas() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // Create a database with double quotes inside the database name\n      statement.execute(\"create or replace database \\\"\\\"\\\"quoteddb\\\"\\\"\\\"\");\n      // Create a database, lowercase, with no double quotes inside the database name\n      statement.execute(\"create or replace database \\\"unquoteddb\\\"\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      // Assert 2 rows returned for the PUBLIC and INFORMATION_SCHEMA schemas inside database\n      try (ResultSet rs = metaData.getSchemas(\"\\\"quoteddb\\\"\", null)) {\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n      // Assert no results are returned when failing to put quotes around quoted database\n      try (ResultSet rs = metaData.getSchemas(\"quoteddb\", null)) {\n        assertEquals(0, getSizeOfResultSet(rs));\n      }\n      // Assert 2 rows returned for the PUBLIC and INFORMATION_SCHEMA schemas inside database\n      try (ResultSet rs = metaData.getSchemas(\"unquoteddb\", null)) {\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n      // Assert no rows are returned when erroneously quoting unquoted database\n      try (ResultSet rs = metaData.getSchemas(\"\\\"unquoteddb\\\"\", null)) {\n        assertEquals(0, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseInGetTables() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // Create a database with double quotes inside the database name\n      createDoubleQuotedSchemaAndCatalog(statement);\n      // Create a table with two columns\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"testtable\\\" (col1 string, col2 string)\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet rs = metaData.getTables(\"dbwith\\\"quotes\", \"schemawith\\\"quotes\", null, null)) {\n        assertEquals(1, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseInGetColumns() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // Create a database and schema with double quotes inside the database name\n      createDoubleQuotedSchemaAndCatalog(statement);\n      // Create a table with two columns\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"testtable\\\"  (col1 string, col2 string)\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet rs = metaData.getColumns(\"dbwith\\\"quotes\", \"schemawith\\\"quotes\", null, null)) {\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseforGetPrimaryKeysAndForeignKeys() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // Create a database and schema with double quotes inside the database name\n      createDoubleQuotedSchemaAndCatalog(statement);\n      // Create a table with a primary key constraint\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"test1\\\"  (col1 integer not null, col2 integer not null, constraint pkey_1 primary key (col1, col2) not enforced)\");\n      // Create a table with a foreign key constraint that points to same columns as test1's primary\n      // key constraint\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"test2\\\" (col_a integer not null, col_b integer not null, constraint fkey_1 foreign key (col_a, col_b) references \\\"test1\\\" (col1, col2) not enforced)\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet rs = metaData.getPrimaryKeys(\"dbwith\\\"quotes\", \"schemawith\\\"quotes\", null)) {\n        // Assert 2 rows are returned for primary key constraint for table and schema with quotes\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n      try (ResultSet rs = metaData.getImportedKeys(\"dbwith\\\"quotes\", \"schemawith\\\"quotes\", null)) {\n        // Assert 2 rows are returned for foreign key constraint\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  /**\n   * For driver versions higher than 3.14.5 we can disable the abilty to use pattern searches for\n   * getPrimaryKeys and getImportedKeys functions by setting enablePatternSearch = false.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseforGetPrimaryKeysAndForeignKeysWithPatternSearchDisabled()\n      throws SQLException {\n    Properties properties = new Properties();\n    properties.put(ENABLE_PATTERN_SEARCH, false);\n    try (Connection con = getConnection(properties);\n        Statement statement = con.createStatement()) {\n      // Create a database and schema with double quotes inside the database name\n      createDoubleQuotedSchemaAndCatalog(statement);\n      // Create a table with a primary key constraint\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"test1\\\"  (col1 integer not null, col2 integer not null, constraint pkey_1 primary key (col1, col2) not enforced)\");\n      // Create a table with a foreign key constraint that points to same columns as test1's primary\n      // key constraint\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"test2\\\" (col_a integer not null, col_b integer not null, constraint fkey_1 foreign key (col_a, col_b) references \\\"test1\\\" (col1, col2) not enforced)\");\n      DatabaseMetaData metaData = con.getMetaData();\n      try (ResultSet rs = metaData.getPrimaryKeys(\"dbwith\\\"quotes\", \"schemawith\\\"quotes\", null)) {\n        // Assert 2 rows are returned for primary key constraint for table and schema with quotes\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n      try (ResultSet rs = metaData.getImportedKeys(\"dbwith\\\"quotes\", \"schemawith\\\"quotes\", null)) {\n        // Assert 2 rows are returned for foreign key constraint\n        assertEquals(2, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseInGetProcedures() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // Create a database and schema with double quotes inside the database name\n      createDoubleQuotedSchemaAndCatalog(statement);\n      // Create a procedure\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      statement.execute(\n          \"USE DATABASE \\\"dbwith\\\"\\\"quotes\\\"; USE SCHEMA \\\"schemawith\\\"\\\"quotes\\\"; \" + TEST_PROC);\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet rs = metaData.getProcedures(\"dbwith\\\"quotes\", null, \"TESTPROC\")) {\n        assertEquals(1, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDoubleQuotedDatabaseInGetTablePrivileges() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // Create a database and schema with double quotes inside the database name\n      createDoubleQuotedSchemaAndCatalog(statement);\n      // Create a table under the current user and role\n      statement.execute(\n          \"create or replace table \\\"dbwith\\\"\\\"quotes\\\".\\\"schemawith\\\"\\\"quotes\\\".\\\"testtable\\\" (col1 string, col2 string)\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet rs = metaData.getTablePrivileges(\"dbwith\\\"quotes\", null, \"%\")) {\n        assertEquals(1, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  /**\n   * Test that SQL injection with multistatements into DatabaseMetaData get functions are no longer\n   * possible.\n   *\n   * @throws Throwable\n   */\n  @Test\n  public void testGetFunctionSqlInjectionProtection() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // Enable multistatements since this is the only way a sql injection could occur\n        statement.execute(\"alter session set MULTI_STATEMENT_COUNT=0\");\n        DatabaseMetaData metaData = connection.getMetaData();\n        String schemaSqlInection =\n            \"%' in database testwh; select 11 as bar; show databases like '%\";\n        try (ResultSet resultSet = metaData.getSchemas(startingDatabase, schemaSqlInection)) {\n          // assert result set is empty\n          assertFalse(resultSet.next());\n        }\n\n        String columnSqlInjection = \"%' in schema testschema; show columns like '%\";\n        try (ResultSet resultSet =\n            metaData.getColumns(startingDatabase, startingSchema, null, columnSqlInjection)) {\n          // assert result set is empty\n          assertFalse(resultSet.next());\n        }\n\n        String functionSqlInjection = \"%' in account snowflake; show functions like '%\";\n        try (ResultSet resultSet =\n            metaData.getFunctions(startingDatabase, startingSchema, functionSqlInjection)) {\n          // assert result set is empty\n          assertFalse(resultSet.next());\n        }\n      } finally {\n        // Clean up by unsetting multistatement\n        statement.execute(\"alter session unset MULTI_STATEMENT_COUNT\");\n      }\n    }\n  }\n\n  /**\n   * This tests that wildcards can be used for the schema name for getProcedureColumns().\n   * Previously, only empty resultsets were returned.\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testGetProcedureColumnsWildcards() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = startingDatabase;\n      String schemaPrefix =\n          GENERATED_SCHEMA_PREFIX + SnowflakeUtil.randomAlphaNumeric(5).toUpperCase();\n      String schema1 = schemaPrefix + \"SCH1\";\n      String schema2 = schemaPrefix + \"SCH2\";\n      TestUtil.withSchema(\n          statement,\n          schema1,\n          () -> {\n            statement.execute(TEST_PROC);\n            TestUtil.withSchema(\n                statement,\n                schema2,\n                () -> {\n                  statement.execute(TEST_PROC);\n                  // Create 2 schemas, each with the same stored procedure declared in them\n                  DatabaseMetaData metaData = connection.getMetaData();\n                  try (ResultSet rs =\n                      metaData.getProcedureColumns(\n                          database, schemaPrefix + \"SCH_\", \"TESTPROC\", \"PARAM1\")) {\n                    // Assert 4 rows returned for the param PARAM1 that's present in each of the 2\n                    // identical stored procs in different schemas. A result row is returned for\n                    // each procedure, making the total rowcount 4\n                    assertEquals(4, getSizeOfResultSet(rs));\n                  }\n                });\n          });\n    }\n  }\n\n  @Test\n  public void testGetFunctions() throws SQLException {\n    DatabaseMetaData metadata = connection.getMetaData();\n    String supportedStringFuncs = metadata.getStringFunctions();\n    assertEquals(StringFunctionsSupported, supportedStringFuncs);\n\n    String supportedNumberFuncs = metadata.getNumericFunctions();\n    assertEquals(NumericFunctionsSupported, supportedNumberFuncs);\n\n    String supportedSystemFuncs = metadata.getSystemFunctions();\n    assertEquals(SystemFunctionsSupported, supportedSystemFuncs);\n  }\n\n  @Test\n  public void testGetStringValueFromColumnDef() throws SQLException {\n    Map<String, String> params = getConnectionParameters();\n    Properties properties = new Properties();\n    for (Map.Entry<?, ?> entry : params.entrySet()) {\n      if (entry.getValue() != null) {\n        properties.put(entry.getKey(), entry.getValue());\n      }\n    }\n    // test out connection parameter stringsQuoted to remove strings from quotes\n    properties.put(\"stringsQuotedForColumnDef\", \"true\");\n    try (Connection connection = DriverManager.getConnection(params.get(\"uri\"), properties);\n        Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = escapeUnderscore(connection.getSchema());\n      final String targetTable = \"T0\";\n\n      statement.execute(\n          \"create or replace table \"\n              + targetTable\n              + \"(C1 string, C2 string default '', C3 string default 'apples', C4 string default\"\n              + \" '\\\"apples\\\"', C5 int, C6 int default 5, C7 string default '''', C8 string\"\n              + \" default '''apples''''', C9  string default '%')\");\n\n      DatabaseMetaData metaData = connection.getMetaData();\n\n      try (ResultSet resultSet = metaData.getColumns(database, schema, targetTable, \"%\")) {\n        assertTrue(resultSet.next());\n        assertNull(resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"''\", resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"'apples'\", resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"'\\\"apples\\\"'\", resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertNull(resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"5\", resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"''''\", resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"'''apples'''''\", resultSet.getString(\"COLUMN_DEF\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"'%'\", resultSet.getString(\"COLUMN_DEF\"));\n      }\n    }\n  }\n\n  @Test\n  public void testGetColumnsNullable() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      String database = startingDatabase;\n      String schema = escapeUnderscore(startingSchema);\n      final String targetTable = \"T0\";\n\n      statement.execute(\n          \"create or replace table \"\n              + targetTable\n              + \"(C1 int, C2 varchar(100), C3 string default '', C4 number(18,4), C5 double,\"\n              + \" C6 boolean, C7 date not null, C8 time, C9 timestamp_ntz(7), C10 binary,C11\"\n              + \" variant, C12 timestamp_ltz(8), C13 timestamp_tz(3))\");\n\n      DatabaseMetaData metaData = connection.getMetaData();\n\n      try (ResultSet resultSet = metaData.getColumns(database, schema, targetTable, \"%\")) {\n        verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_COLUMNS);\n\n        // C1 metadata\n        assertTrue(resultSet.next());\n        assertTrue(resultSet.getBoolean(\"NULLABLE\"));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testSessionDatabaseParameter() throws Throwable {\n    String altdb = \"ALTERNATEDB\";\n    String altschema1 = \"ALTERNATESCHEMA1\";\n    String altschema2 = \"ALTERNATESCHEMA2\";\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      String catalog = connection.getCatalog();\n      String schema = connection.getSchema();\n      try {\n        statement.execute(\"create or replace database \" + altdb);\n        statement.execute(\"create or replace schema \" + altschema1);\n        statement.execute(\"create or replace schema \" + altschema2);\n        statement.execute(\n            \"create or replace table \"\n                + altdb\n                + \".\"\n                + altschema1\n                + \".testtable1 (colA string, colB number)\");\n        statement.execute(\n            \"create or replace table \"\n                + altdb\n                + \".\"\n                + altschema2\n                + \".testtable2 (colA string, colB number)\");\n        statement.execute(\n            \"create or replace table \"\n                + catalog\n                + \".\"\n                + schema\n                + \".testtable3 (colA string, colB number)\");\n        statement.execute(\"use database \" + altdb);\n        statement.execute(\"use schema \" + altschema1);\n\n        statement.execute(\"ALTER SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n        statement.execute(\"ALTER SESSION set CLIENT_METADATA_USE_SESSION_DATABASE=true\");\n\n        DatabaseMetaData metadata = connection.getMetaData();\n        try (ResultSet resultSet = metadata.getColumns(null, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(null, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        statement.execute(\"ALTER SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=false\");\n\n        metadata = connection.getMetaData();\n        try (ResultSet resultSet = metadata.getColumns(null, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(null, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n        statement.execute(\"ALTER SESSION set CLIENT_METADATA_USE_SESSION_DATABASE=false\");\n\n        metadata = connection.getMetaData();\n        try (ResultSet resultSet = metadata.getColumns(null, null, \"TESTTABLE_\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertTrue(resultSet.next());\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(null, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        statement.execute(\"ALTER SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n\n        metadata = connection.getMetaData();\n        try (ResultSet resultSet = metadata.getColumns(null, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(null, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, null, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema1, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n\n        try (ResultSet resultSet = metadata.getColumns(altdb, altschema2, \"%\", \"COLA\")) {\n          assertTrue(resultSet.next());\n          assertEquals(altschema2, resultSet.getString(\"TABLE_SCHEM\"));\n          assertFalse(resultSet.next());\n        }\n      } finally {\n        // clean up after creating extra database and schema\n        statement.execute(\"use database \" + catalog);\n        statement.execute(\"drop schema \" + altdb + \".\" + altschema1);\n        statement.execute(\"drop schema \" + altdb + \".\" + altschema2);\n        statement.execute(\"drop database \" + altdb);\n      }\n    }\n  }\n\n  /**\n   * Test function getFunctionColumns. This function has input parameters of database, schema, UDF\n   * name, and optional UDF parameter names. It returns rows of information about the UDF, and\n   * returns 1 row per return value and 1 row per input parameter.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testGetFunctionColumns() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = startingDatabase;\n      String schema = startingSchema;\n\n      /* Create a table and put values into it */\n      statement.execute(\n          \"create or replace table FuncColTest (colA int, colB string, colC \" + \"number);\");\n      statement.execute(\"INSERT INTO FuncColTest VALUES (4, 'Hello', 6);\");\n      statement.execute(\"INSERT INTO FuncColTest VALUES (8, 'World', 10);\");\n      /* Create a UDF that counts up the total rows in the table just created */\n      statement.execute(\n          \"create or replace function total_rows_in_table() returns number as \"\n              + \"'select count(*) from FuncColTest';\");\n      /* Create another UDF that takes in 2 numbers and multiplies them together */\n      statement.execute(\n          \"create or replace function FUNC111 \"\n              + \"(a number, b number) RETURNS NUMBER COMMENT='multiply numbers' as 'a*b'\");\n      /* Create another 2 tables to be used for another UDF */\n      statement.execute(\n          \"create or replace table BIN_TABLE(bin1 binary, bin2 binary(100), \"\n              + \"sharedCol decimal)\");\n      statement.execute(\n          \"create or replace table JDBC_TBL111(colA string, colB decimal, colC \" + \"timestamp)\");\n      /* Create a UDF that returns a table made up of 4 columns from 2 different tables, joined together */\n      statement.execute(\n          \"create or replace function FUNC112 () RETURNS TABLE(colA string(16777216), colB decimal, bin2 \"\n              + \"binary(8388608) , sharedCol decimal) COMMENT= 'returns table of 4 columns' as 'select\"\n              + \" JDBC_TBL111.colA, JDBC_TBL111.colB, BIN_TABLE.bin2, BIN_TABLE.sharedCol from\"\n              + \" JDBC_TBL111 inner join BIN_TABLE on JDBC_TBL111.colB =BIN_TABLE.sharedCol'\");\n      DatabaseMetaData metaData = connection.getMetaData();\n\n      /* Call getFunctionColumns on FUNC111 and since there's no parameter name, get all rows back */\n      try (ResultSet resultSet = metaData.getFunctionColumns(database, schema, \"FUNC111\", \"%\")) {\n        verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_FUNCTION_COLUMNS);\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC111\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionReturn, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER(38,0)\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"FUNC111(NUMBER, NUMBER) RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        /* Call next function to get next row in resultSet, which contains row with info about first parameter of FUNC111\n        function */\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC111\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"A\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(1, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(1, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"FUNC111(NUMBER, NUMBER) RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        /* Call next to get next row with info about second parameter of FUNC111 function */\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC111\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"B\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(1, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"multiply numbers\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(2, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"FUNC111(NUMBER, NUMBER) RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        /* Assert that there are no more rows left in resultSet */\n        assertFalse(resultSet.next());\n      }\n\n      /* Look at resultSet from calling getFunctionColumns on FUNC112 */\n      try (ResultSet resultSet = metaData.getFunctionColumns(database, schema, \"FUNC112\", \"%\")) {\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"COLA\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"VARCHAR\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(0, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(1, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"COLB\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(2, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"BIN2\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.BINARY, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"BINARY\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(3, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"FUNC112\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"SHAREDCOL\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionColumnResult, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"returns table of 4 columns\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(4, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertThat(\n            \"Columns metadata SPECIFIC_NAME should contains expected columns \",\n            resultSet\n                .getString(\"SPECIFIC_NAME\")\n                .replaceAll(\"\\\\s\", \"\")\n                .matches(\n                    \"^FUNC112.*RETURNTABLE.*COLAVARCHAR.*,COLBNUMBER,BIN2BINARY.*,SHAREDCOLNUMBER.?$\"));\n        assertFalse(resultSet.next());\n      }\n\n      /* Assert that calling getFunctionColumns with no parameters returns empty result set */\n      try (ResultSet resultSet = metaData.getFunctionColumns(\"%\", \"%\", \"%\", \"%\")) {\n        assertFalse(resultSet.next());\n      }\n\n      /* Look at result set from calling getFunctionColumns on total_rows_in_table */\n      try (ResultSet resultSet =\n          metaData.getFunctionColumns(database, schema, \"total_rows_in_table%\", \"%\")) {\n        /* Assert there are 17 columns in result set and 1 row */\n        assertEquals(17, resultSet.getMetaData().getColumnCount());\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n\n      // getSizeofResultSet will mess up the row index of resultSet\n      try (ResultSet resultSet =\n          metaData.getFunctionColumns(database, schema, \"total_rows_in_table%\", \"%\")) {\n        resultSet.next();\n        assertEquals(database, resultSet.getString(\"FUNCTION_CAT\"));\n        assertEquals(schema, resultSet.getString(\"FUNCTION_SCHEM\"));\n        assertEquals(\"TOTAL_ROWS_IN_TABLE\", resultSet.getString(\"FUNCTION_NAME\"));\n        assertEquals(\"\", resultSet.getString(\"COLUMN_NAME\"));\n        assertEquals(DatabaseMetaData.functionReturn, resultSet.getInt(\"COLUMN_TYPE\"));\n        assertEquals(Types.NUMERIC, resultSet.getInt(\"DATA_TYPE\"));\n        assertEquals(\"NUMBER(38,0)\", resultSet.getString(\"TYPE_NAME\"));\n        assertEquals(38, resultSet.getInt(\"PRECISION\"));\n        // length column is not supported and will always be 0\n        assertEquals(0, resultSet.getInt(\"LENGTH\"));\n        assertEquals(0, resultSet.getShort(\"SCALE\"));\n        // radix column is not supported and will always be default of 10 (assumes base 10 system)\n        assertEquals(10, resultSet.getInt(\"RADIX\"));\n        // nullable column is not supported and always returns NullableUnknown\n        assertEquals(DatabaseMetaData.functionNullableUnknown, resultSet.getInt(\"NULLABLE\"));\n        assertEquals(\"user-defined function\", resultSet.getString(\"REMARKS\"));\n        // char octet length column is not supported and always returns 0\n        assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n        assertEquals(0, resultSet.getInt(\"ORDINAL_POSITION\"));\n        // is_nullable column is not supported and always returns empty string\n        assertEquals(\"\", resultSet.getString(\"IS_NULLABLE\"));\n        assertEquals(\"TOTAL_ROWS_IN_TABLE() RETURN NUMBER\", resultSet.getString(\"SPECIFIC_NAME\"));\n        assertFalse(resultSet.next());\n      }\n    }\n  }\n\n  @Test\n  public void testHandlingSpecialChars() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      DatabaseMetaData metaData = connection.getMetaData();\n      String escapeChar = metaData.getSearchStringEscape();\n      // test getColumns with escaped special characters in table name\n      statement.execute(\n          \"create or replace table \\\"TEST\\\\1\\\\_1\\\" (\\\"C%1\\\" integer,\\\"C\\\\1\\\\\\\\11\\\" integer)\");\n      statement.execute(\"INSERT INTO \\\"TEST\\\\1\\\\_1\\\" (\\\"C%1\\\",\\\"C\\\\1\\\\\\\\11\\\") VALUES (0,0)\");\n      // test getColumns with escaped special characters in schema and table name\n      String specialSchemaSuffix = SnowflakeUtil.randomAlphaNumeric(5);\n      String specialSchema =\n          \"\\\"\" + GENERATED_SCHEMA_PREFIX + \"SPECIAL%_\\\\SCHEMA\" + specialSchemaSuffix + \"\\\"\";\n      TestUtil.withSchema(\n          statement,\n          specialSchema,\n          () -> {\n            statement.execute(\n                \"create or replace table \"\n                    + specialSchema\n                    + \".\\\"TEST_1_1\\\" ( \\\"RNUM\\\" integer not null, \"\n                    + \"\\\"C21\\\" integer,\"\n                    + \"\\\"C11\\\" integer,\\\"C%1\\\" integer,\\\"C\\\\1\\\\\\\\11\\\" integer , primary key (\\\"RNUM\\\"))\");\n            statement.execute(\n                \"INSERT INTO \\\"TEST_1_1\\\" (RNUM,C21,C11,\\\"C%1\\\",\\\"C\\\\1\\\\\\\\11\\\") VALUES (0,0,0,0,0)\");\n            String escapedTable1 =\n                \"TEST\" + escapeChar + \"\\\\1\" + escapeChar + \"\\\\\" + escapeChar + \"_1\";\n            try (ResultSet resultSet =\n                metaData.getColumns(database, escapeUnderscore(schema), escapedTable1, null)) {\n              assertTrue(resultSet.next());\n              assertEquals(\"C%1\", resultSet.getString(\"COLUMN_NAME\"));\n              assertTrue(resultSet.next());\n              assertEquals(\"C\\\\1\\\\\\\\11\", resultSet.getString(\"COLUMN_NAME\"));\n              assertFalse(resultSet.next());\n            }\n\n            // Underscore can match to any character, so check that table comes back when underscore\n            // is not escaped.\n            String partiallyEscapedTable1 = \"TEST\" + escapeChar + \"\\\\1\" + escapeChar + \"\\\\_1\";\n            try (ResultSet resultSet =\n                metaData.getColumns(\n                    database, escapeUnderscore(schema), partiallyEscapedTable1, null)) {\n              assertTrue(resultSet.next());\n              assertEquals(\"C%1\", resultSet.getString(\"COLUMN_NAME\"));\n              assertTrue(resultSet.next());\n              assertEquals(\"C\\\\1\\\\\\\\11\", resultSet.getString(\"COLUMN_NAME\"));\n              assertFalse(resultSet.next());\n            }\n\n            String escapedTable2 = \"TEST\" + escapeChar + \"_1\" + escapeChar + \"_1\";\n            String escapedSchema =\n                TestUtil.ESCAPED_GENERATED_SCHEMA_PREFIX\n                    + \"SPECIAL%\"\n                    + escapeChar\n                    + \"_\"\n                    + escapeChar\n                    + \"\\\\SCHEMA\"\n                    + specialSchemaSuffix;\n\n            try (ResultSet resultSet =\n                metaData.getColumns(database, escapedSchema, escapedTable2, null)) {\n              assertTrue(resultSet.next());\n              assertEquals(\"RNUM\", resultSet.getString(\"COLUMN_NAME\"));\n              assertTrue(resultSet.next());\n              assertEquals(\"C21\", resultSet.getString(\"COLUMN_NAME\"));\n              assertTrue(resultSet.next());\n              assertEquals(\"C11\", resultSet.getString(\"COLUMN_NAME\"));\n              assertTrue(resultSet.next());\n              assertEquals(\"C%1\", resultSet.getString(\"COLUMN_NAME\"));\n              assertTrue(resultSet.next());\n              assertEquals(\"C\\\\1\\\\\\\\11\", resultSet.getString(\"COLUMN_NAME\"));\n              assertFalse(resultSet.next());\n            }\n          });\n\n      // test getTables with real special characters and escaped special characters. Unescaped\n      // _\n      // should allow both\n      // tables to be returned, while escaped _ should match up to the _ in both table names.\n      statement.execute(\"create or replace table \" + schema + \".\\\"TABLE_A\\\" (colA string)\");\n      statement.execute(\"create or replace table \" + schema + \".\\\"TABLE_B\\\" (colB number)\");\n      String escapedTable = \"TABLE\" + escapeChar + \"__\";\n      try (ResultSet resultSet =\n          metaData.getColumns(database, escapeUnderscore(schema), escapedTable, null)) {\n        assertTrue(resultSet.next());\n        assertEquals(\"COLA\", resultSet.getString(\"COLUMN_NAME\"));\n        assertTrue(resultSet.next());\n        assertEquals(\"COLB\", resultSet.getString(\"COLUMN_NAME\"));\n        assertFalse(resultSet.next());\n      }\n\n      try (ResultSet resultSet =\n          metaData.getColumns(database, escapeUnderscore(schema), escapedTable, \"COLB\")) {\n        assertTrue(resultSet.next());\n        assertEquals(\"COLB\", resultSet.getString(\"COLUMN_NAME\"));\n        assertFalse(resultSet.next());\n      }\n\n      statement.execute(\"create or replace table \" + schema + \".\\\"special%table\\\" (colA string)\");\n      try (ResultSet resultSet =\n          metaData.getColumns(\n              database, escapeUnderscore(schema), \"special\" + escapeChar + \"%table\", null)) {\n        assertTrue(resultSet.next());\n        assertEquals(\"COLA\", resultSet.getString(\"COLUMN_NAME\"));\n      }\n    }\n  }\n\n  @Test\n  public void testUnderscoreInSchemaNamePatternForPrimaryAndForeignKeys() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = startingDatabase;\n      TestUtil.withRandomSchema(\n          statement,\n          customSchema -> {\n            String escapedSchema = customSchema.replace(\"_\", \"\\\\_\");\n            statement.execute(\"use schema \" + customSchema);\n            statement.execute(\n                \"create or replace table PK_TEST (c1 int PRIMARY KEY, c2 VARCHAR(10))\");\n            statement.execute(\n                \"create or replace table FK_TEST (c1 int REFERENCES PK_TEST(c1), c2 VARCHAR(10))\");\n            DatabaseMetaData metaData = connection.getMetaData();\n            try (ResultSet rs = metaData.getPrimaryKeys(database, escapedSchema, null)) {\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n            try (ResultSet rs = metaData.getImportedKeys(database, escapedSchema, null)) {\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n          });\n    }\n  }\n\n  /**\n   * For driver versions higher than 3.14.5 we can disable the abilty to use pattern searches for\n   * getPrimaryKeys and getImportedKeys functions by setting enablePatternSearch = false.\n   */\n  @Test\n  public void testUnderscoreInSchemaNamePatternForPrimaryAndForeignKeysWithPatternSearchDisabled()\n      throws Exception {\n    Properties properties = new Properties();\n    properties.put(ENABLE_PATTERN_SEARCH, false);\n\n    try (Connection con = getConnection(properties);\n        Statement statement = con.createStatement()) {\n      String database = con.getCatalog();\n      TestUtil.withRandomSchema(\n          statement,\n          customSchema -> {\n            String escapedSchema = customSchema.replace(\"_\", \"\\\\_\");\n            statement.execute(\"use schema \" + customSchema);\n            statement.execute(\n                \"create or replace table PK_TEST (c1 int PRIMARY KEY, c2 VARCHAR(10))\");\n            statement.execute(\n                \"create or replace table FK_TEST (c1 int REFERENCES PK_TEST(c1), c2 VARCHAR(10))\");\n            DatabaseMetaData metaData = con.getMetaData();\n            // We have disabled the pattern search so we should get no results.\n            try (ResultSet rs = metaData.getPrimaryKeys(database, escapedSchema, null)) {\n              assertEquals(0, getSizeOfResultSet(rs));\n            }\n            try (ResultSet rs = metaData.getImportedKeys(database, escapedSchema, null)) {\n              assertEquals(0, getSizeOfResultSet(rs));\n            }\n            // We expect the results to be returned if we use the actual schema name\n            try (ResultSet rs = metaData.getPrimaryKeys(database, customSchema, null)) {\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n            try (ResultSet rs = metaData.getImportedKeys(database, customSchema, null)) {\n              assertEquals(1, getSizeOfResultSet(rs));\n            }\n          });\n    }\n  }\n\n  @Test\n  public void testTimestampWithTimezoneDataType() throws Exception {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.executeQuery(\"create or replace table ts_test(ts timestamp_tz)\");\n      String database = connection.getCatalog();\n      String schema = escapeUnderscore(connection.getSchema());\n      String tableName = escapeUnderscore(\"TS_TEST\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet resultSet = metaData.getColumns(database, schema, tableName, \"TS\")) {\n        resultSet.next();\n        // Assert that TIMESTAMP_TZ type matches java.sql.TIMESTAMP_WITH_TIMEZONE\n        assertEquals(resultSet.getObject(\"DATA_TYPE\"), 2014);\n      }\n\n      SFBaseSession baseSession =\n          connection.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession();\n      Field field = SFBaseSession.class.getDeclaredField(\"enableReturnTimestampWithTimeZone\");\n      field.setAccessible(true);\n      field.set(baseSession, false);\n\n      metaData = connection.getMetaData();\n      try (ResultSet resultSet = metaData.getColumns(database, schema, tableName, \"TS\")) {\n        resultSet.next();\n        // Assert that TIMESTAMP_TZ type matches java.sql.TIMESTAMP when\n        // enableReturnTimestampWithTimeZone is false.\n        assertEquals(resultSet.getObject(\"DATA_TYPE\"), Types.TIMESTAMP);\n      }\n    }\n  }\n\n  @Test\n  public void testGetColumns() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      final String targetTable = \"T0\";\n      try {\n        statement.execute(\n            \"create or replace table \"\n                + targetTable\n                + \"(C1 int, C2 varchar(100), C3 string(16777216) default '', C4 number(18,4), C5 double,\"\n                + \" C6 boolean, C7 date not null, C8 time, C9 timestamp_ntz(7), C10 binary(8388608),C11\"\n                + \" variant, C12 timestamp_ltz(8), C13 timestamp_tz(3))\");\n\n        DatabaseMetaData metaData = connection.getMetaData();\n\n        try (ResultSet resultSet =\n            metaData.getColumns(database, escapeUnderscore(schema), targetTable, \"%\")) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_COLUMNS);\n\n          // C1 metadata\n\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C1\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BIGINT, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(38, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(1, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C2 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C2\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARCHAR\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(100, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(100, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(2, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C3 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C3\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARCHAR\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(EXPECTED_MAX_CHAR_LENGTH, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertEquals(\"\", resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(EXPECTED_MAX_CHAR_LENGTH, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(3, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C4 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C4\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.DECIMAL, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(18, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(4, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(4, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C5 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C5\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.DOUBLE, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"DOUBLE\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(5, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C6 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C6\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BOOLEAN, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"BOOLEAN\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(6, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C7 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C7\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.DATE, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"DATE\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNoNulls, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(7, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C8 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C8\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIME, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIME\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(9, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(8, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C9 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C9\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIMESTAMP, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIMESTAMPNTZ\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(7, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(9, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C10 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C10\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BINARY, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"BINARY\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(EXPECTED_MAX_BINARY_LENGTH, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(10, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C11 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C11\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARIANT\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(11, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C12 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C12\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIMESTAMP, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIMESTAMPLTZ\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(8, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(12, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C13 metadata\n          assertTrue(resultSet.next());\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(3)); // table name (using index)\n          assertEquals(\"C13\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIMESTAMP_WITH_TIMEZONE, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIMESTAMPTZ\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(0, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(3, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(0, resultSet.getInt(\"NUM_PREC_RADIX\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(13, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertNull(resultSet.getString(\"SCOPE_CATALOG\"));\n          assertNull(resultSet.getString(\"SCOPE_SCHEMA\"));\n          assertNull(resultSet.getString(\"SCOPE_TABLE\"));\n          assertEquals((short) 0, resultSet.getShort(\"SOURCE_DATA_TYPE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n        }\n        statement.execute(\n            \"create or replace table \"\n                + targetTable\n                + \"(C1 string, C2 string default '', C3 string default 'apples', C4 string\"\n                + \" default '\\\"apples\\\"', C5 int, C6 int default 5, C7 string default '''', C8\"\n                + \" string default '''apples''''', C9  string default '%')\");\n\n        metaData = connection.getMetaData();\n\n        try (ResultSet resultSet =\n            metaData.getColumns(database, escapeUnderscore(schema), targetTable, \"%\")) {\n          assertTrue(resultSet.next());\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"\", resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"apples\", resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"\\\"apples\\\"\", resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"5\", resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"'\", resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"'apples''\", resultSet.getString(\"COLUMN_DEF\"));\n          assertTrue(resultSet.next());\n          assertEquals(\"%\", resultSet.getString(\"COLUMN_DEF\"));\n          assertThrows(SQLException.class, () -> resultSet.getString(\"INVALID_COLUMN\"));\n        }\n\n        // no column privilege is supported.\n        try (ResultSet resultSet =\n            metaData.getColumnPrivileges(database, schema, targetTable, \"C1\")) {\n          assertEquals(0, super.getSizeOfResultSet(resultSet));\n        }\n      } finally {\n        statement.execute(\"drop table if exists T0\");\n      }\n    }\n  }\n\n  @Test\n  public void testGetStreams() throws SQLException {\n    final String targetStream = \"S0\";\n    final String targetTable = \"T0\";\n    try (Connection conn = getConnectionWithWildcardsDisabled();\n        Statement statement = conn.createStatement()) {\n      String database = conn.getCatalog();\n      String schema = conn.getSchema();\n      String owner = conn.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession().getRole();\n      String tableName = database + \".\" + schema + \".\" + targetTable;\n\n      try {\n        statement.execute(\"create or replace table \" + targetTable + \"(C1 int)\");\n        statement.execute(\"create or replace stream \" + targetStream + \" on table \" + targetTable);\n\n        DatabaseMetaData metaData = conn.getMetaData();\n\n        // match stream\n        try (ResultSet resultSet =\n            metaData.unwrap(SnowflakeDatabaseMetaData.class).getStreams(database, schema, \"%\")) {\n          verifyResultSetMetaDataColumns(resultSet, DBMetadataResultSetMetadata.GET_STREAMS);\n          Set<String> streams = new HashSet<>();\n          while (resultSet.next()) {\n            streams.add(resultSet.getString(1));\n          }\n          assertTrue(streams.contains(\"S0\"));\n        }\n        // match exact stream\n        try (ResultSet resultSet =\n            metaData\n                .unwrap(SnowflakeDatabaseMetaData.class)\n                .getStreams(database, schema, targetStream)) {\n          resultSet.next();\n          assertEquals(targetStream, resultSet.getString(1));\n          assertEquals(database, resultSet.getString(2));\n          assertEquals(schema, resultSet.getString(3));\n          assertEquals(owner, resultSet.getString(4));\n          assertEquals(\"\", resultSet.getString(5));\n          assertEquals(tableName, resultSet.getString(6));\n          assertEquals(\"Table\", resultSet.getString(7));\n          assertEquals(tableName, resultSet.getString(8));\n          assertEquals(\"DELTA\", resultSet.getString(9));\n          assertEquals(\"false\", resultSet.getString(10));\n          assertEquals(\"DEFAULT\", resultSet.getString(11));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable);\n        statement.execute(\"drop stream if exists \" + targetStream);\n      }\n    }\n  }\n\n  /*\n   * This tests that an empty resultset will be returned for getProcedures when using a reader account.\n   */\n  @Test\n  @Disabled\n  public void testGetProceduresWithReaderAccount() throws SQLException {\n    DatabaseMetaData metadata = connection.getMetaData();\n    try (ResultSet rs = metadata.getProcedures(null, null, null)) {\n      assertEquals(0, getSizeOfResultSet(rs));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetProcedureColumns() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String database = startingDatabase;\n      String schema = startingSchema;\n      try {\n        statement.execute(PI_PROCEDURE);\n        DatabaseMetaData metaData = connection.getMetaData();\n        /* Call getProcedureColumns with no parameters for procedure name or column. This should return  all procedures\n        in the current database and schema. It will return all rows as well (1 row per result and 1 row per parameter\n        for each procedure) */\n        try (ResultSet resultSet = metaData.getProcedureColumns(database, schema, \"GETPI\", \"%\")) {\n          verifyResultSetMetaDataColumns(\n              resultSet, DBMetadataResultSetMetadata.GET_PROCEDURE_COLUMNS);\n          resultSet.next();\n          assertEquals(database, resultSet.getString(\"PROCEDURE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"PROCEDURE_SCHEM\"));\n          assertEquals(\"GETPI\", resultSet.getString(\"PROCEDURE_NAME\"));\n          assertEquals(\"\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(DatabaseMetaData.procedureColumnReturn, resultSet.getInt(\"COLUMN_TYPE\"));\n          assertEquals(Types.FLOAT, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"FLOAT\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(38, resultSet.getInt(\"PRECISION\"));\n          // length column is not supported and will always be 0\n          assertEquals(0, resultSet.getInt(\"LENGTH\"));\n          assertEquals(0, resultSet.getShort(\"SCALE\"));\n          // radix column is not supported and will always be default of 10 (assumes base 10 system)\n          assertEquals(10, resultSet.getInt(\"RADIX\"));\n          // nullable column is not supported and always returns NullableUnknown\n          assertEquals(DatabaseMetaData.procedureNoNulls, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"user-defined procedure\", resultSet.getString(\"REMARKS\"));\n          assertNull(resultSet.getString(\"COLUMN_DEF\"));\n          assertEquals(0, resultSet.getInt(\"SQL_DATA_TYPE\"));\n          assertEquals(0, resultSet.getInt(\"SQL_DATETIME_SUB\"));\n          // char octet length column is not supported and always returns 0\n          assertEquals(0, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n          assertEquals(0, resultSet.getInt(\"ORDINAL_POSITION\"));\n          // is_nullable column is not supported and always returns empty string\n          assertEquals(\"NO\", resultSet.getString(\"IS_NULLABLE\"));\n          assertEquals(\"GETPI() RETURN FLOAT\", resultSet.getString(\"SPECIFIC_NAME\"));\n        }\n\n      } finally {\n        statement.execute(\"drop procedure if exists GETPI()\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetProcedureColumnsReturnsResultSet() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table testtable (id int, name varchar(20), address varchar(20));\");\n        statement.execute(\n            \"create or replace procedure PROCTEST()\\n\"\n                + \"returns table (\\\"id\\\" number(38,0), \\\"name\\\" varchar(20), \\\"address\\\" varchar(20))\\n\"\n                + \"language sql\\n\"\n                + \"execute as owner\\n\"\n                + \"as 'declare\\n\"\n                + \"    res resultset default (select * from testtable);\\n\"\n                + \"  begin\\n\"\n                + \"    return table(res);\\n\"\n                + \"  end';\");\n        DatabaseMetaData metaData = connection.getMetaData();\n        try (ResultSet res =\n            metaData.getProcedureColumns(connection.getCatalog(), null, \"PROCTEST\", \"%\")) {\n          res.next();\n          assertEquals(\"PROCTEST\", res.getString(\"PROCEDURE_NAME\"));\n          assertEquals(\"\\\"id\\\"\", res.getString(\"COLUMN_NAME\"));\n          assertEquals(\n              DatabaseMetaData.procedureColumnResult,\n              res.getInt(\"COLUMN_TYPE\")); // procedureColumnResult\n          assertEquals(Types.NUMERIC, res.getInt(\"DATA_TYPE\"));\n          assertEquals(\"NUMBER\", res.getString(\"TYPE_NAME\"));\n          assertEquals(1, res.getInt(\"ORDINAL_POSITION\")); // result set column 1\n          res.next();\n          assertEquals(\"\\\"name\\\"\", res.getString(\"COLUMN_NAME\"));\n          assertEquals(DatabaseMetaData.procedureColumnResult, res.getInt(\"COLUMN_TYPE\"));\n          assertEquals(Types.VARCHAR, res.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARCHAR\", res.getString(\"TYPE_NAME\"));\n          assertEquals(2, res.getInt(\"ORDINAL_POSITION\")); // result set column 2\n          res.next();\n          assertEquals(\"\\\"address\\\"\", res.getString(\"COLUMN_NAME\"));\n          assertEquals(DatabaseMetaData.procedureColumnResult, res.getInt(\"COLUMN_TYPE\"));\n          assertEquals(Types.VARCHAR, res.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARCHAR\", res.getString(\"TYPE_NAME\"));\n          assertEquals(3, res.getInt(\"ORDINAL_POSITION\")); // result set column 3\n        }\n      } finally {\n        statement.execute(\"drop table if exists testtable\");\n      }\n    }\n  }\n\n  @Test\n  public void testGetProcedureColumnsReturnsValue() throws SQLException {\n    try (Statement statement = connection.createStatement(); ) {\n      DatabaseMetaData metaData = connection.getMetaData();\n      String schema = escapeUnderscore(connection.getSchema());\n      // create a procedure with no parameters that has a return value\n      statement.execute(PI_PROCEDURE);\n      try (ResultSet res =\n          metaData.getProcedureColumns(connection.getCatalog(), schema, \"GETPI\", \"%\")) {\n        res.next();\n        assertEquals(\"GETPI\", res.getString(\"PROCEDURE_NAME\"));\n        assertEquals(\"\", res.getString(\"COLUMN_NAME\"));\n        assertEquals(5, res.getInt(\"COLUMN_TYPE\")); // procedureColumnReturn\n        assertEquals(Types.FLOAT, res.getInt(\"DATA_TYPE\"));\n        assertEquals(\"FLOAT\", res.getString(\"TYPE_NAME\"));\n        assertEquals(0, res.getInt(\"ORDINAL_POSITION\"));\n      }\n\n      // create a procedure that returns the value of the argument that is passed in\n      statement.execute(MESSAGE_PROCEDURE);\n      try (ResultSet res =\n          metaData.getProcedureColumns(connection.getCatalog(), schema, \"MESSAGE_PROC\", \"%\")) {\n        res.next();\n        assertEquals(\"MESSAGE_PROC\", res.getString(\"PROCEDURE_NAME\"));\n        assertEquals(\"\", res.getString(\"COLUMN_NAME\"));\n        assertEquals(\n            DatabaseMetaData.procedureColumnReturn,\n            res.getInt(\"COLUMN_TYPE\")); // procedureColumnReturn\n        assertEquals(Types.VARCHAR, res.getInt(\"DATA_TYPE\"));\n        assertEquals(\"VARCHAR\", res.getString(\"TYPE_NAME\"));\n        assertEquals(0, res.getInt(\"ORDINAL_POSITION\"));\n        res.next();\n        assertEquals(\"MESSAGE\", res.getString(\"COLUMN_NAME\"));\n        assertEquals(\n            DatabaseMetaData.procedureColumnIn, res.getInt(\"COLUMN_TYPE\")); // procedureColumnIn\n        assertEquals(Types.VARCHAR, res.getInt(\"DATA_TYPE\"));\n        assertEquals(\"VARCHAR\", res.getString(\"TYPE_NAME\"));\n        assertEquals(1, res.getInt(\"ORDINAL_POSITION\"));\n      }\n    }\n  }\n\n  @Test\n  public void testGetProcedureColumnsReturnsNull() throws SQLException {\n    try (Statement statement = connection.createStatement(); ) {\n      DatabaseMetaData metaData = connection.getMetaData();\n      String schema = escapeUnderscore(connection.getSchema());\n      // The CREATE PROCEDURE statement must include a RETURNS clause that defines a return type,\n      // even\n      // if the procedure does not explicitly return anything.\n      statement.execute(\n          \"create or replace table testtable (id int, name varchar(20), address varchar(20));\");\n      statement.execute(\n          \"create or replace procedure insertproc() \\n\"\n              + \"returns varchar \\n\"\n              + \"language javascript as \\n\"\n              + \"'var sqlcommand = \\n\"\n              + \"`insert into testtable (id, name, address) values (1, \\\\'Tom\\\\', \\\\'Pacific Avenue\\\\');` \\n\"\n              + \"snowflake.execute({sqlText: sqlcommand}); \\n\"\n              + \"';\");\n      try (ResultSet res =\n          metaData.getProcedureColumns(connection.getCatalog(), schema, \"INSERTPROC\", \"%\")) {\n        res.next();\n        // the procedure will return null as the value but column type will be varchar.\n        assertEquals(\"INSERTPROC\", res.getString(\"PROCEDURE_NAME\"));\n        assertEquals(\"\", res.getString(\"COLUMN_NAME\"));\n        assertEquals(\n            DatabaseMetaData.procedureColumnReturn,\n            res.getInt(\"COLUMN_TYPE\")); // procedureColumnReturn\n        assertEquals(Types.VARCHAR, res.getInt(\"DATA_TYPE\"));\n        assertEquals(\"VARCHAR\", res.getString(\"TYPE_NAME\"));\n        assertEquals(0, res.getInt(\"ORDINAL_POSITION\"));\n      }\n    }\n  }\n\n  @Test\n  public void testUpdateLocatorsCopyUnsupported() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n    assertFalse(metaData.locatorsUpdateCopy());\n  }\n\n  /**\n   * For driver versions higher than 3.14.5 we have the enablePatternSearch connection property\n   * which sets whether pattern searches are allowed for certain DatabaseMetaData queries. This test\n   * sets that value to false meaning pattern searches are not allowed for getPrimaryKeys,\n   * getImportedKeys, getExportedKeys, and getCrossReference.\n   */\n  @Test\n  public void testNoPatternSearchAllowedForPrimaryAndForeignKeys() throws Exception {\n    Properties properties = new Properties();\n    properties.put(ENABLE_PATTERN_SEARCH, \"false\");\n    final String table1 = \"PATTERN_SEARCH_TABLE1\";\n    final String table2 = \"PATTERN_SEARCH_TABLE2\";\n\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      String schemaRandomPart = SnowflakeUtil.randomAlphaNumeric(5);\n      String schemaName =\n          \"\\\"\" + GENERATED_SCHEMA_PREFIX + \"TEST_PATTERNS_SCHEMA_\" + schemaRandomPart + \"\\\"\";\n\n      TestUtil.withSchema(\n          statement,\n          schemaName,\n          () -> {\n            String schema = connection.getSchema();\n            statement.execute(\n                \"create or replace table \" + table1 + \"(C1 int primary key, C2 string)\");\n            statement.execute(\n                \"create or replace table \"\n                    + table2\n                    + \"(C1 int primary key, C2 string, C3 int references \"\n                    + table1\n                    + \")\");\n\n            DatabaseMetaData dbmd = connection.getMetaData();\n\n            String schemaPattern = schema.substring(0, schema.length() - 1).concat(\"%\");\n            String tablePattern = \"PATTERN_SEARCH_TABLE%\";\n            String database = connection.getCatalog();\n\n            ExecutorService executor = Executors.newFixedThreadPool(10);\n            List<CompletableFuture<Void>> futures = new ArrayList<>();\n\n            // getPrimaryKeys: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getPrimaryKeys(database, schema, table1)),\n                    result -> assertEquals(1, result)));\n            // getPrimaryKeys: pattern on schema should return empty\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getPrimaryKeys(database, schemaPattern, table1)),\n                    result -> assertEquals(0, result)));\n\n            // getImportedKeys: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getImportedKeys(database, schema, table2)),\n                    result -> assertEquals(1, result)));\n            // getImportedKeys: pattern on table should return empty\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getImportedKeys(database, schema, tablePattern)),\n                    result -> assertEquals(0, result)));\n\n            // getExportedKeys: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getExportedKeys(database, schema, table1)),\n                    result -> assertEquals(1, result)));\n            // getExportedKeys: pattern on schema should return empty\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getExportedKeys(database, schemaPattern, table1)),\n                    result -> assertEquals(0, result)));\n\n            // getCrossReference: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () ->\n                        getSizeOfResultSet(\n                            dbmd.getCrossReference(\n                                database, schema, table1, database, schema, table2)),\n                    result -> assertEquals(1, result)));\n            // getCrossReference: pattern on PK schema should return empty\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () ->\n                        getSizeOfResultSet(\n                            dbmd.getCrossReference(\n                                database, schemaPattern, table1, database, schema, table2)),\n                    result -> assertEquals(0, result)));\n            // getCrossReference: pattern on FK schema should return empty\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () ->\n                        getSizeOfResultSet(\n                            dbmd.getCrossReference(\n                                database, schema, table1, database, schemaPattern, table2)),\n                    result -> assertEquals(0, result)));\n\n            // Wait for all async assertions to complete\n            try {\n              CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();\n            } finally {\n              executor.shutdown();\n            }\n          });\n    }\n  }\n\n  /**\n   * For driver versions higher than 3.14.5 we have the enablePatternSearch connection property\n   * which sets whether pattern searches are allowed for certain DatabaseMetaData queries. This test\n   * uses the default setting for this property which is true.\n   */\n  @Test\n  public void testPatternSearchAllowedForPrimaryAndForeignKeys() throws Exception {\n    final String table1 = \"PATTERN_SEARCH_TABLE1\";\n    final String table2 = \"PATTERN_SEARCH_TABLE2\";\n\n    try (Statement statement = connection.createStatement()) {\n      String schemaRandomPart = SnowflakeUtil.randomAlphaNumeric(5);\n      String schemaName =\n          \"\\\"\" + GENERATED_SCHEMA_PREFIX + \"TEST_PATTERNS_SCHEMA_\" + schemaRandomPart + \"\\\"\";\n\n      TestUtil.withSchema(\n          statement,\n          schemaName,\n          () -> {\n            String schema = connection.getSchema();\n            statement.execute(\n                \"create or replace table \" + table1 + \"(C1 int primary key, C2 string)\");\n            statement.execute(\n                \"create or replace table \"\n                    + table2\n                    + \"(C1 int primary key, C2 string, C3 int references \"\n                    + table1\n                    + \")\");\n\n            DatabaseMetaData dbmd = connection.getMetaData();\n\n            String schemaPattern = schema.substring(0, schema.length() - 1).concat(\"%\");\n            String tablePattern = \"PATTERN_SEARCH_TABLE%\";\n            String database = connection.getCatalog();\n\n            ExecutorService executor = Executors.newFixedThreadPool(10);\n            List<CompletableFuture<Void>> futures = new ArrayList<>();\n\n            // getPrimaryKeys: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getPrimaryKeys(database, schema, table1)),\n                    result -> assertEquals(1, result)));\n            // getPrimaryKeys: schema pattern should also return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getPrimaryKeys(database, schemaPattern, table1)),\n                    result -> assertEquals(1, result)));\n\n            // getImportedKeys: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getImportedKeys(database, schema, table2)),\n                    result -> assertEquals(1, result)));\n            // getImportedKeys: table pattern should also return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getImportedKeys(database, schema, tablePattern)),\n                    result -> assertEquals(1, result)));\n\n            // getExportedKeys: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getExportedKeys(database, schema, table1)),\n                    result -> assertEquals(1, result)));\n            // getExportedKeys: schema pattern should also return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () -> getSizeOfResultSet(dbmd.getExportedKeys(database, schemaPattern, table1)),\n                    result -> assertEquals(1, result)));\n\n            // getCrossReference: exact match should return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () ->\n                        getSizeOfResultSet(\n                            dbmd.getCrossReference(\n                                database, schema, table1, database, schema, table2)),\n                    result -> assertEquals(1, result)));\n            // getCrossReference: PK schema pattern should also return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () ->\n                        getSizeOfResultSet(\n                            dbmd.getCrossReference(\n                                database, schemaPattern, table1, database, schema, table2)),\n                    result -> assertEquals(1, result)));\n            // getCrossReference: FK schema pattern should also return result\n            futures.add(\n                asyncAssert(\n                    executor,\n                    () ->\n                        getSizeOfResultSet(\n                            dbmd.getCrossReference(\n                                database, schema, table1, database, schemaPattern, table2)),\n                    result -> assertEquals(1, result)));\n\n            // Wait for all async assertions to complete\n            try {\n              CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();\n            } finally {\n              executor.shutdown();\n            }\n          });\n    }\n  }\n\n  /**\n   * For driver versions higher than 3.14.5 the driver reports support for JDBC 4.2. For driver\n   * version 3.14.5 and earlier, the driver reports support for JDBC 1.0.\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testGetJDBCVersion() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n\n    // JDBC x.x compatible\n    assertEquals(4, metaData.getJDBCMajorVersion());\n    assertEquals(2, metaData.getJDBCMinorVersion());\n  }\n\n  /** Added in > 3.15.1 */\n  @Test\n  public void testKeywordsCount() throws SQLException {\n    DatabaseMetaData metaData = connection.getMetaData();\n    assertEquals(43, metaData.getSQLKeywords().split(\",\").length);\n  }\n\n  /** Added in > 3.16.1 */\n  @Test\n  public void testVectorDimension() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace table JDBC_VECTOR(text_col varchar(32), float_vec VECTOR(FLOAT, 256), int_vec VECTOR(INT, 16))\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet resultSet =\n          metaData.getColumns(\n              connection.getCatalog(),\n              connection.getSchema().replaceAll(\"_\", \"\\\\\\\\_\"),\n              \"JDBC\\\\_VECTOR\",\n              null)) {\n        assertTrue(resultSet.next());\n        assertEquals(32, resultSet.getObject(\"COLUMN_SIZE\"));\n        assertTrue(resultSet.next());\n        assertEquals(256, resultSet.getObject(\"COLUMN_SIZE\"));\n        assertTrue(resultSet.next());\n        assertEquals(16, resultSet.getObject(\"COLUMN_SIZE\"));\n        assertFalse(resultSet.next());\n      }\n\n      try (ResultSet resultSet =\n          statement.executeQuery(\"Select text_col, float_vec, int_vec from JDBC_VECTOR\")) {\n        SnowflakeResultSetMetaData unwrapResultSetMetadata =\n            resultSet.getMetaData().unwrap(SnowflakeResultSetMetaData.class);\n        assertEquals(0, unwrapResultSetMetadata.getVectorDimension(\"TEXT_COL\"));\n        assertEquals(0, unwrapResultSetMetadata.getVectorDimension(1));\n        assertEquals(256, unwrapResultSetMetadata.getVectorDimension(\"FLOAT_VEC\"));\n        assertEquals(256, unwrapResultSetMetadata.getVectorDimension(2));\n        assertEquals(16, unwrapResultSetMetadata.getVectorDimension(\"INT_VEC\"));\n        assertEquals(16, unwrapResultSetMetadata.getVectorDimension(3));\n      }\n    }\n  }\n\n  /**\n   * Added in > 3.21.0 for SNOW-1619625 - we need to use exact schema when\n   * CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX = true\n   */\n  @Test\n  public void testExactSchemaSearching() throws SQLException {\n    Random random = new Random();\n    int suffix = random.nextInt(Integer.MAX_VALUE);\n    // schemas created in tests must start with GENERATED_SCHEMA_PREFIX value\n    String coreSchemaName = \"ESCAPED_UNDERSCORE%\" + suffix;\n    String schemaName = GENERATED_SCHEMA_PREFIX + coreSchemaName;\n    String alternativeSchemaName =\n        GENERATED_SCHEMA_PREFIX + coreSchemaName.replaceAll(\"_\", \"x\").replaceAll(\"%\", \"y\");\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      for (String scm : Arrays.asList(schemaName, alternativeSchemaName)) {\n        statement.execute(\"CREATE SCHEMA \\\"\" + scm + \"\\\"\");\n        statement.execute(\n            \"CREATE TABLE \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"mytable1\\\" (a text)\");\n        statement.execute(\n            \"CREATE TABLE \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"mytable2\\\" (a text)\");\n\n        statement.execute(\n            \"ALTER TABLE \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"mytable2\\\"\"\n                + \" ADD CONSTRAINT pk_1 PRIMARY KEY (a);\");\n        statement.execute(\n            \"ALTER TABLE \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"mytable1\\\"\"\n                + \" ADD CONSTRAINT fk_1 FOREIGN KEY (a) REFERENCES \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"mytable2\\\"\"\n                + \" (a);\");\n        statement.execute(\n            \"create or replace function \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"myfun1\\\"\"\n                + \"(a number, b number) RETURNS NUMBER as 'a*b'\");\n        statement.execute(\n            \"create or replace procedure \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"myproc1\\\"\"\n                + \"(a varchar, b varchar)\\n\"\n                + \"    returns varchar not null\\n\"\n                + \"    language javascript\\n\"\n                + \"    as\\n\"\n                + \"    $$\\n\"\n                + \"    return a +b;\\n\"\n                + \"    $$\\n\"\n                + \"    ;\");\n        statement.execute(\n            \"create or replace stream \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"stream1\\\"\"\n                + \" on table \\\"\"\n                + connection.getCatalog()\n                + \"\\\".\\\"\"\n                + scm\n                + \"\\\".\\\"mytable1\\\"\");\n      }\n\n      String schemaNameColumnInMetadata = \"TABLE_SCHEM\"; // it's not typo\n\n      try {\n        Properties props = new Properties();\n        props.put(\"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\", true);\n        props.put(\"ENABLE_EXACT_SCHEMA_SEARCH_ENABLED\", true);\n        props.put(\"schema\", schemaName); // we should not escape schema here\n        try (Connection connectionWithContext = getConnection(props)) {\n          try (ResultSet schemas = connectionWithContext.getMetaData().getSchemas()) {\n            assertEquals(schemaName, connectionWithContext.getSchema());\n            assertTrue(schemas.next());\n            assertEquals(schemaName, schemas.getString(schemaNameColumnInMetadata));\n            assertFalse(schemas.next());\n          }\n\n          try (ResultSet schemasWithPattern =\n              connectionWithContext.getMetaData().getSchemas(null, null)) {\n            assertTrue(schemasWithPattern.next());\n            assertEquals(schemaName, schemasWithPattern.getString(schemaNameColumnInMetadata));\n            assertFalse(schemasWithPattern.next());\n          }\n\n          try (ResultSet tables =\n              connectionWithContext.getMetaData().getTables(null, null, null, null)) {\n            assertTrue(tables.next());\n            assertEquals(\"mytable1\", tables.getString(\"TABLE_NAME\"));\n            assertEquals(schemaName, tables.getString(schemaNameColumnInMetadata));\n            assertTrue(tables.next());\n            assertEquals(\"mytable2\", tables.getString(\"TABLE_NAME\"));\n            assertEquals(schemaName, tables.getString(schemaNameColumnInMetadata));\n            assertFalse(tables.next());\n          }\n\n          try (ResultSet tablePrivileges =\n              connectionWithContext.getMetaData().getTablePrivileges(null, null, \"mytable1\")) {\n            assertTrue(tablePrivileges.next());\n            assertEquals(\"mytable1\", tablePrivileges.getString(\"TABLE_NAME\"));\n            assertEquals(schemaName, tablePrivileges.getString(schemaNameColumnInMetadata));\n            assertFalse(tablePrivileges.next());\n          }\n\n          try (ResultSet columns =\n              connectionWithContext.getMetaData().getColumns(null, null, null, null)) {\n            assertTrue(columns.next());\n            assertEquals(\"mytable1\", columns.getString(\"TABLE_NAME\"));\n            assertEquals(schemaName, columns.getString(schemaNameColumnInMetadata));\n            assertEquals(\"A\", columns.getString(\"COLUMN_NAME\"));\n            assertTrue(columns.next());\n            assertEquals(\"mytable2\", columns.getString(\"TABLE_NAME\"));\n            assertEquals(schemaName, columns.getString(schemaNameColumnInMetadata));\n            assertEquals(\"A\", columns.getString(\"COLUMN_NAME\"));\n            assertFalse(columns.next());\n          }\n\n          try (ResultSet primaryKeys =\n              connectionWithContext.getMetaData().getPrimaryKeys(null, null, null)) {\n            assertTrue(primaryKeys.next());\n            assertEquals(\"mytable2\", primaryKeys.getString(\"TABLE_NAME\"));\n            assertEquals(schemaName, primaryKeys.getString(schemaNameColumnInMetadata));\n            assertEquals(\"A\", primaryKeys.getString(\"COLUMN_NAME\"));\n            assertFalse(primaryKeys.next());\n          }\n\n          try (ResultSet importedKeys =\n              connectionWithContext.getMetaData().getImportedKeys(null, null, null)) {\n            assertTrue(importedKeys.next());\n            assertEquals(\"mytable2\", importedKeys.getString(\"PKTABLE_NAME\"));\n            assertEquals(schemaName, importedKeys.getString(\"PKTABLE_SCHEM\"));\n            assertEquals(\"A\", importedKeys.getString(\"PKCOLUMN_NAME\"));\n            assertEquals(\"mytable1\", importedKeys.getString(\"FKTABLE_NAME\"));\n            assertEquals(schemaName, importedKeys.getString(\"FKTABLE_SCHEM\"));\n            assertEquals(\"A\", importedKeys.getString(\"FKCOLUMN_NAME\"));\n            assertFalse(importedKeys.next());\n          }\n\n          try (ResultSet exportedKeys =\n              connectionWithContext.getMetaData().getExportedKeys(null, null, null)) {\n            assertTrue(exportedKeys.next());\n            assertEquals(\"mytable2\", exportedKeys.getString(\"PKTABLE_NAME\"));\n            assertEquals(schemaName, exportedKeys.getString(\"PKTABLE_SCHEM\"));\n            assertEquals(\"A\", exportedKeys.getString(\"PKCOLUMN_NAME\"));\n            assertEquals(\"mytable1\", exportedKeys.getString(\"FKTABLE_NAME\"));\n            assertEquals(schemaName, exportedKeys.getString(\"FKTABLE_SCHEM\"));\n            assertEquals(\"A\", exportedKeys.getString(\"FKCOLUMN_NAME\"));\n            assertFalse(exportedKeys.next());\n          }\n\n          try (ResultSet crossReferences =\n              connectionWithContext\n                  .getMetaData()\n                  .getCrossReference(null, null, null, null, null, null)) {\n            assertTrue(crossReferences.next());\n            assertEquals(\"mytable2\", crossReferences.getString(\"PKTABLE_NAME\"));\n            assertEquals(schemaName, crossReferences.getString(\"PKTABLE_SCHEM\"));\n            assertEquals(\"A\", crossReferences.getString(\"PKCOLUMN_NAME\"));\n            assertEquals(\"mytable1\", crossReferences.getString(\"FKTABLE_NAME\"));\n            assertEquals(schemaName, crossReferences.getString(\"FKTABLE_SCHEM\"));\n            assertEquals(\"A\", crossReferences.getString(\"FKCOLUMN_NAME\"));\n            assertFalse(crossReferences.next());\n          }\n\n          try (ResultSet functions =\n              connectionWithContext.getMetaData().getFunctions(null, null, \"myfun%\")) {\n            assertTrue(functions.next());\n            assertEquals(\"myfun1\", functions.getString(\"FUNCTION_NAME\"));\n            // schema name contains % so it is returned in \"\"\n            assertEquals('\"' + schemaName + '\"', functions.getString(\"FUNCTION_SCHEM\"));\n            assertFalse(functions.next());\n          }\n\n          try (ResultSet functionColumns =\n              connectionWithContext.getMetaData().getFunctionColumns(null, null, \"myfun%\", null)) {\n            assertTrue(functionColumns.next());\n            assertEquals(\"myfun1\", functionColumns.getString(\"FUNCTION_NAME\"));\n            // schema name is empty when getFunctionColumns schema is empty\n            assertEquals(null, functionColumns.getString(\"FUNCTION_SCHEM\"));\n            // first parameter is an empty string\n            assertEquals(\"\", functionColumns.getString(\"COLUMN_NAME\"));\n            assertTrue(functionColumns.next());\n            // schema name is empty when getFunctionColumns schema is empty\n            assertEquals(null, functionColumns.getString(\"FUNCTION_SCHEM\"));\n            assertEquals(\"A\", functionColumns.getString(\"COLUMN_NAME\"));\n            assertTrue(functionColumns.next());\n            assertEquals(\"myfun1\", functionColumns.getString(\"FUNCTION_NAME\"));\n            // schema name is empty when getFunctionColumns schema is empty\n            assertEquals(null, functionColumns.getString(\"FUNCTION_SCHEM\"));\n            assertEquals(\"B\", functionColumns.getString(\"COLUMN_NAME\"));\n            assertFalse(functionColumns.next());\n          }\n\n          try (ResultSet procedures =\n              connectionWithContext.getMetaData().getProcedures(null, null, \"myproc%\")) {\n            assertTrue(procedures.next());\n            // schema name contains % so it is returned in \"\"\n            assertEquals('\"' + schemaName + '\"', procedures.getString(\"PROCEDURE_SCHEM\"));\n            assertEquals(\"myproc1\", procedures.getString(\"PROCEDURE_NAME\"));\n            assertFalse(procedures.next());\n          }\n\n          try (ResultSet procedureColumns =\n              connectionWithContext\n                  .getMetaData()\n                  .getProcedureColumns(null, null, \"myproc%\", null)) {\n            assertTrue(procedureColumns.next());\n            assertEquals(\"myproc1\", procedureColumns.getString(\"PROCEDURE_NAME\"));\n            // schema name contains % so it is returned in \"\"\n            assertEquals('\"' + schemaName + '\"', procedureColumns.getString(\"PROCEDURE_SCHEM\"));\n            // first parameter is an empty string\n            assertEquals(\"\", procedureColumns.getString(\"COLUMN_NAME\"));\n            assertTrue(procedureColumns.next());\n            assertEquals('\"' + schemaName + '\"', procedureColumns.getString(\"PROCEDURE_SCHEM\"));\n            assertEquals(\"A\", procedureColumns.getString(\"COLUMN_NAME\"));\n            assertTrue(procedureColumns.next());\n            assertEquals(\"myproc1\", procedureColumns.getString(\"PROCEDURE_NAME\"));\n            assertEquals('\"' + schemaName + '\"', procedureColumns.getString(\"PROCEDURE_SCHEM\"));\n            assertEquals(\"B\", procedureColumns.getString(\"COLUMN_NAME\"));\n            assertFalse(procedureColumns.next());\n          }\n\n          try (ResultSet streams =\n              connectionWithContext\n                  .getMetaData()\n                  .unwrap(SnowflakeDatabaseMetaData.class)\n                  .getStreams(null, null, null)) {\n            assertTrue(streams.next());\n            // here table name is full qualified name, schema name contains % so it is returned in\n            // \"\", table name is always in \"\"\n            assertEquals(\n                connection.getCatalog() + \".\\\"\" + schemaName + \"\\\".\\\"mytable1\\\"\",\n                streams.getString(\"TABLE_NAME\"));\n            // here schema name does not contain \"\" but maybe should\n            assertEquals(schemaName, streams.getString(\"SCHEMA_NAME\"));\n            assertEquals(\"stream1\", streams.getString(\"STREAM_NAME\"));\n            assertFalse(streams.next());\n          }\n\n          // not supported:\n          // getAttributes\n          // getBestRowIdentifier\n          // getColumnPrivileges\n          // getIndexInfo\n          // getPseudoColumns\n          // getSuperTypes\n          // getSuperTables\n          // getVersionColumns\n          // getUDTs\n        }\n      } finally {\n        for (String scm : Arrays.asList(schemaName, alternativeSchemaName)) {\n          statement.execute(\"DROP SCHEMA \\\"\" + scm + \"\\\"\");\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testGetColumnsWithCommonTypes() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      final String targetTable =\n          \"TESTCOMMONTYPES\" + SnowflakeUtil.randomAlphaNumeric(5).toUpperCase();\n      try {\n        statement.execute(\n            \"create or replace table \"\n                + targetTable\n                + \"(C1 int, C2 varchar(100), C3 number(18,4), C4 double,\"\n                + \" C5 boolean, C6 date not null, C7 time, C8 timestamp_ntz(7),\"\n                + \" C9 timestamp_ltz(8), C10 timestamp_tz(3), C11 binary(100), C12 variant)\");\n\n        DatabaseMetaData metaData = connection.getMetaData();\n\n        try (ResultSet resultSet =\n            metaData.getColumns(database, escapeUnderscore(schema), targetTable, \"%\")) {\n          // C1 - int\n          assertTrue(resultSet.next());\n          assertEquals(\"C1\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BIGINT, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(38, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(0, resultSet.getInt(\"DECIMAL_DIGITS\"));\n          assertEquals(1, resultSet.getInt(\"ORDINAL_POSITION\"));\n\n          // C2 - varchar(100)\n          assertTrue(resultSet.next());\n          assertEquals(\"C2\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARCHAR\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(100, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(100, resultSet.getInt(\"CHAR_OCTET_LENGTH\"));\n\n          // C3 - number(18,4)\n          assertTrue(resultSet.next());\n          assertEquals(\"C3\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.DECIMAL, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(18, resultSet.getInt(\"COLUMN_SIZE\"));\n          assertEquals(4, resultSet.getInt(\"DECIMAL_DIGITS\"));\n\n          // C4 - double\n          assertTrue(resultSet.next());\n          assertEquals(\"C4\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.DOUBLE, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"DOUBLE\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C5 - boolean\n          assertTrue(resultSet.next());\n          assertEquals(\"C5\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BOOLEAN, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"BOOLEAN\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C6 - date not null\n          assertTrue(resultSet.next());\n          assertEquals(\"C6\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.DATE, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"DATE\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(ResultSetMetaData.columnNoNulls, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_NULLABLE\"));\n\n          // C7 - time\n          assertTrue(resultSet.next());\n          assertEquals(\"C7\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIME, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIME\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C8 - timestamp_ntz(7)\n          assertTrue(resultSet.next());\n          assertEquals(\"C8\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIMESTAMP, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIMESTAMPNTZ\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C9 - timestamp_ltz(8)\n          assertTrue(resultSet.next());\n          assertEquals(\"C9\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.TIMESTAMP, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIMESTAMPLTZ\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C10 - timestamp_tz(3)\n          assertTrue(resultSet.next());\n          assertEquals(\"C10\", resultSet.getString(\"COLUMN_NAME\"));\n          int expectedTzType =\n              connection\n                      .unwrap(SnowflakeConnectionImpl.class)\n                      .getSFBaseSession()\n                      .getEnableReturnTimestampWithTimeZone()\n                  ? Types.TIMESTAMP_WITH_TIMEZONE\n                  : Types.TIMESTAMP;\n          assertEquals(expectedTzType, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"TIMESTAMPTZ\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C11 - binary(100)\n          assertTrue(resultSet.next());\n          assertEquals(\"C11\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BINARY, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"BINARY\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(100, resultSet.getInt(\"COLUMN_SIZE\"));\n\n          // C12 - variant\n          assertTrue(resultSet.next());\n          assertEquals(\"C12\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARIANT\", resultSet.getString(\"TYPE_NAME\"));\n\n          assertFalse(resultSet.next());\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable);\n      }\n    }\n  }\n\n  @Test\n  public void testGetColumnsWithUnrecognisedType() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      String database = connection.getCatalog();\n      String schema = connection.getSchema();\n      final String targetTable =\n          \"TESTUUIDTABLE\" + SnowflakeUtil.randomAlphaNumeric(5).toUpperCase();\n      try {\n        statement.execute(\n            \"create or replace table \" + targetTable + \"(C1 int, C2 UUID, C3 varchar(100))\");\n\n        DatabaseMetaData metaData = connection.getMetaData();\n\n        try (ResultSet resultSet =\n            metaData.getColumns(database, escapeUnderscore(schema), targetTable, \"%\")) {\n          // C1 - int column\n          assertTrue(resultSet.next());\n          assertEquals(\"C1\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.BIGINT, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"NUMBER\", resultSet.getString(\"TYPE_NAME\"));\n\n          // C2 - UUID column (unknown type, reported as OTHER with actual type name)\n          assertTrue(resultSet.next());\n          assertEquals(\"C2\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.OTHER, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"UUID\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(database, resultSet.getString(\"TABLE_CAT\"));\n          assertEquals(schema, resultSet.getString(\"TABLE_SCHEM\"));\n          assertEquals(targetTable, resultSet.getString(\"TABLE_NAME\"));\n          assertEquals(ResultSetMetaData.columnNullable, resultSet.getInt(\"NULLABLE\"));\n          assertEquals(\"YES\", resultSet.getString(\"IS_NULLABLE\"));\n          assertEquals(2, resultSet.getInt(\"ORDINAL_POSITION\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_AUTOINCREMENT\"));\n          assertEquals(\"NO\", resultSet.getString(\"IS_GENERATEDCOLUMN\"));\n\n          // C3 - varchar column\n          assertTrue(resultSet.next());\n          assertEquals(\"C3\", resultSet.getString(\"COLUMN_NAME\"));\n          assertEquals(Types.VARCHAR, resultSet.getInt(\"DATA_TYPE\"));\n          assertEquals(\"VARCHAR\", resultSet.getString(\"TYPE_NAME\"));\n          assertEquals(100, resultSet.getInt(\"COLUMN_SIZE\"));\n\n          assertFalse(resultSet.next());\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + targetTable);\n      }\n    }\n  }\n\n  @Nested\n  @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n  class WildcardsInShowMetadataQueries {\n    private String schemaWithUnderscore;\n    private String tableWithUnderscore;\n    private String columnWithUnderscore;\n    private String procedureWithUnderscore;\n    private String functionWithUnderscore;\n\n    private String alternativeSchema;\n    private String alternativeTable;\n    private String alternativeColumn;\n    private String alternativeProcedure;\n    private String alternativeFunction;\n\n    private String database;\n\n    @BeforeAll\n    void setUp() throws Exception {\n      Random random = new Random();\n      int suffix = random.nextInt(Integer.MAX_VALUE);\n\n      database = connection.getCatalog();\n\n      schemaWithUnderscore = GENERATED_SCHEMA_PREFIX + \"MY_SCHEMA_\" + suffix;\n      tableWithUnderscore = \"MY_TABLE_\" + suffix;\n      columnWithUnderscore = \"MY_COLUMN_\" + suffix;\n      procedureWithUnderscore = \"MY_PROCEDURE_\" + suffix;\n      functionWithUnderscore = \"MY_FUNCTION_\" + suffix;\n\n      alternativeSchema = GENERATED_SCHEMA_PREFIX + \"MYXSCHEMAX\" + suffix;\n      alternativeTable = tableWithUnderscore.replace(\"_\", \"X\");\n      alternativeColumn = columnWithUnderscore.replace(\"_\", \"X\");\n      alternativeProcedure = procedureWithUnderscore.replace(\"_\", \"X\");\n      alternativeFunction = functionWithUnderscore.replace(\"_\", \"X\");\n\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\"CREATE SCHEMA \\\"\" + schemaWithUnderscore + \"\\\"\");\n        statement.execute(\"USE SCHEMA \\\"\" + schemaWithUnderscore + \"\\\"\");\n        statement.execute(\n            \"CREATE TABLE \\\"\"\n                + tableWithUnderscore\n                + \"\\\" (\"\n                + columnWithUnderscore\n                + \" VARCHAR(50), col2 INT)\");\n        statement.execute(\n            \"CREATE PROCEDURE \"\n                + procedureWithUnderscore\n                + \"() RETURNS VARCHAR LANGUAGE SQL AS 'BEGIN RETURN ''Hello''; END;'\");\n        statement.execute(\n            \"CREATE FUNCTION \" + functionWithUnderscore + \"() RETURNS VARCHAR AS '''Hello'''\");\n\n        statement.execute(\"CREATE SCHEMA \\\"\" + alternativeSchema + \"\\\"\");\n        statement.execute(\"USE SCHEMA \\\"\" + alternativeSchema + \"\\\"\");\n        statement.execute(\n            \"CREATE TABLE \\\"\"\n                + alternativeTable\n                + \"\\\" (\"\n                + alternativeColumn\n                + \" VARCHAR(50), col2 INT)\");\n        statement.execute(\n            \"CREATE PROCEDURE \"\n                + alternativeProcedure\n                + \"() RETURNS VARCHAR LANGUAGE SQL AS 'BEGIN RETURN ''Hello''; END;'\");\n        statement.execute(\n            \"CREATE FUNCTION \" + alternativeFunction + \"() RETURNS VARCHAR AS '''Hello'''\");\n      }\n    }\n\n    @AfterAll\n    void tearDown() throws Exception {\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\"DROP SCHEMA IF EXISTS \\\"\" + schemaWithUnderscore + \"\\\"\");\n        statement.execute(\"DROP SCHEMA IF EXISTS \\\"\" + alternativeSchema + \"\\\"\");\n      }\n    }\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class WhenWildcardsEnabled {\n      private DatabaseMetaData metaData;\n      private Connection connection;\n\n      @BeforeAll\n      void setUp() throws Exception {\n        Properties props = new Properties();\n        props.put(\"ENABLE_WILDCARDS_IN_SHOW_METADATA_COMMANDS\", \"true\");\n        connection = getConnection(props);\n        metaData = connection.getMetaData();\n      }\n\n      @AfterAll\n      void tearDown() throws Exception {\n        if (connection != null) {\n          connection.close();\n        }\n      }\n\n      @Test\n      void testGetColumns() throws Exception {\n        try (ResultSet rs =\n            metaData.getColumns(\n                database, schemaWithUnderscore, tableWithUnderscore, columnWithUnderscore)) {\n          assertMetadataQueryResult(\n              rs,\n              \"columns like '\" + columnWithUnderscore + \"' in database \\\"\" + database + \"\\\"\",\n              2,\n              Arrays.asList(\n                  Arrays.asList(alternativeTable, alternativeSchema, alternativeColumn),\n                  Arrays.asList(tableWithUnderscore, schemaWithUnderscore, columnWithUnderscore)),\n              handleException(\n                  r ->\n                      Arrays.asList(\n                          r.getString(\"TABLE_NAME\"),\n                          r.getString(\"TABLE_SCHEM\"),\n                          r.getString(\"COLUMN_NAME\"))));\n        }\n      }\n\n      @Test\n      void testGetSchemas() throws Exception {\n        try (ResultSet rs = metaData.getSchemas(database, schemaWithUnderscore)) {\n          assertMetadataQueryResult(\n              rs,\n              \"schemas like '\" + schemaWithUnderscore + \"' in database \\\"\" + database + \"\\\"\",\n              2,\n              Arrays.asList(alternativeSchema, schemaWithUnderscore),\n              handleException(r -> r.getString(\"TABLE_SCHEM\")));\n        }\n      }\n\n      @Test\n      void testGetTables() throws Exception {\n        try (ResultSet rs =\n            metaData.getTables(database, schemaWithUnderscore, tableWithUnderscore, null)) {\n          assertMetadataQueryResult(\n              rs,\n              \"objects like '\" + tableWithUnderscore + \"' in database \\\"\" + database + \"\\\"\",\n              2,\n              Arrays.asList(\n                  Arrays.asList(alternativeTable, alternativeSchema),\n                  Arrays.asList(tableWithUnderscore, schemaWithUnderscore)),\n              handleException(\n                  r -> Arrays.asList(r.getString(\"TABLE_NAME\"), r.getString(\"TABLE_SCHEM\"))));\n        }\n      }\n    }\n\n    @Nested\n    @TestInstance(TestInstance.Lifecycle.PER_CLASS)\n    class WhenWildcardsDisabled {\n      private DatabaseMetaData metaData;\n      private Connection connection;\n\n      @BeforeAll\n      void setUp() throws Exception {\n        connection = getConnectionWithWildcardsDisabled();\n        metaData = connection.getMetaData();\n      }\n\n      @AfterAll\n      void tearDown() throws Exception {\n        if (connection != null) {\n          connection.close();\n        }\n      }\n\n      @Test\n      void testGetColumns() throws Exception {\n        try (ResultSet rs =\n            metaData.getColumns(\n                database, schemaWithUnderscore, tableWithUnderscore, columnWithUnderscore)) {\n          assertMetadataQueryResult(\n              rs,\n              \"columns like '\"\n                  + columnWithUnderscore\n                  + \"' in table \\\"\"\n                  + database\n                  + \"\\\".\\\"\"\n                  + schemaWithUnderscore\n                  + \"\\\".\\\"\"\n                  + tableWithUnderscore\n                  + \"\\\"\",\n              1,\n              Collections.singletonList(\n                  Arrays.asList(tableWithUnderscore, schemaWithUnderscore, columnWithUnderscore)),\n              handleException(\n                  r ->\n                      Arrays.asList(\n                          r.getString(\"TABLE_NAME\"),\n                          r.getString(\"TABLE_SCHEM\"),\n                          r.getString(\"COLUMN_NAME\"))));\n        }\n      }\n\n      @Test\n      void testGetSchemas() throws Exception {\n        try (ResultSet rs = metaData.getSchemas(database, schemaWithUnderscore)) {\n          assertMetadataQueryResult(\n              rs,\n              \"schemas like '\" + schemaWithUnderscore + \"' in database \\\"\" + database + \"\\\"\",\n              2,\n              Arrays.asList(alternativeSchema, schemaWithUnderscore),\n              handleException(r -> r.getString(\"TABLE_SCHEM\")));\n        }\n      }\n\n      @Test\n      void testGetTables() throws Exception {\n        try (ResultSet rs =\n            metaData.getTables(database, schemaWithUnderscore, tableWithUnderscore, null)) {\n          assertMetadataQueryResult(\n              rs,\n              \"objects like '\"\n                  + tableWithUnderscore\n                  + \"' in schema \\\"\"\n                  + database\n                  + \"\\\".\\\"\"\n                  + schemaWithUnderscore\n                  + \"\\\"\",\n              1,\n              Collections.singletonList(Arrays.asList(tableWithUnderscore, schemaWithUnderscore)),\n              handleException(\n                  r -> Arrays.asList(r.getString(\"TABLE_NAME\"), r.getString(\"TABLE_SCHEM\"))));\n        }\n      }\n\n      @Test\n      void testGetProcedures() throws Exception {\n        try (ResultSet rs =\n            metaData.getProcedures(database, schemaWithUnderscore, procedureWithUnderscore)) {\n          assertMetadataQueryResult(\n              rs,\n              \"procedures like '\"\n                  + procedureWithUnderscore\n                  + \"' in schema \\\"\"\n                  + database\n                  + \"\\\".\\\"\"\n                  + schemaWithUnderscore\n                  + \"\\\"\",\n              1,\n              Collections.singletonList(\n                  Arrays.asList(procedureWithUnderscore, schemaWithUnderscore)),\n              handleException(\n                  r ->\n                      Arrays.asList(\n                          r.getString(\"PROCEDURE_NAME\"), r.getString(\"PROCEDURE_SCHEM\"))));\n        }\n      }\n\n      @Test\n      void testGetFunctions() throws Exception {\n        try (ResultSet rs =\n            metaData.getFunctions(database, schemaWithUnderscore, functionWithUnderscore)) {\n          assertMetadataQueryResult(\n              rs,\n              \"functions like '\"\n                  + functionWithUnderscore\n                  + \"' in schema \\\"\"\n                  + database\n                  + \"\\\".\\\"\"\n                  + schemaWithUnderscore\n                  + \"\\\"\",\n              1,\n              Collections.singletonList(\n                  Arrays.asList(functionWithUnderscore, schemaWithUnderscore)),\n              handleException(\n                  r -> Arrays.asList(r.getString(\"FUNCTION_NAME\"), r.getString(\"FUNCTION_SCHEM\"))));\n        }\n      }\n    }\n\n    private <T, R> Function<T, R> handleException(ThrowingFunction<T, R, SQLException> f) {\n      return t -> {\n        try {\n          return f.apply(t);\n        } catch (SQLException e) {\n          throw new RuntimeException(e);\n        }\n      };\n    }\n\n    private <T> void assertMetadataQueryResult(\n        ResultSet rs,\n        String expectedSqlEnding,\n        int expectedRowCount,\n        List<T> expectedRows,\n        Function<ResultSet, T> rowMapper)\n        throws Exception {\n      // 1. Assert the underlying SQL query text\n      String sqlText = getSqlText(rs.unwrap(SnowflakeBaseResultSet.class).getStatement());\n      assertTrue(sqlText.endsWith(expectedSqlEnding));\n\n      // 2. Map ResultSet to a List\n      List<T> actualRows = new ArrayList<>();\n      while (rs.next()) {\n        actualRows.add(rowMapper.apply(rs));\n      }\n\n      // 3. Assert row count and content\n      assertEquals(expectedRowCount, actualRows.size());\n      assertEquals(expectedRows, actualRows);\n    }\n\n    private String getSqlText(Statement statement) throws Exception {\n      Class<?> stmtClass = statement.getClass();\n      Field sfBaseStatementField = stmtClass.getDeclaredField(\"sfBaseStatement\");\n      sfBaseStatementField.setAccessible(true); // Allow access to private field\n      Object sfBaseStatement = sfBaseStatementField.get(statement);\n\n      Class<?> sfBaseStmtClass = sfBaseStatement.getClass();\n      Field sqlTextField = sfBaseStmtClass.getDeclaredField(\"sqlText\");\n      sqlTextField.setAccessible(true); // Allow access to private field\n      return (String) sqlTextField.get(sfBaseStatement);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DatabaseMetaDataResultSetLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.DATABASE_META_DATA)\npublic class DatabaseMetaDataResultSetLatestIT extends BaseJDBCTest {\n\n  @Test\n  public void testGetObjectNotSupported() throws SQLException {\n    try (Connection con = getConnection();\n        Statement st = con.createStatement()) {\n      Object[][] rows = {{1.2F}};\n      List<String> columnNames = Arrays.asList(\"float\");\n      List<String> columnTypeNames = Arrays.asList(\"FLOAT\");\n      List<Integer> columnTypes = Arrays.asList(Types.FLOAT);\n      try (ResultSet resultSet =\n          new SnowflakeDatabaseMetaDataResultSet(\n              columnNames, columnTypeNames, columnTypes, rows, st)) {\n        resultSet.next();\n        assertThrows(\n            SnowflakeLoggedFeatureNotSupportedException.class,\n            () -> assertEquals(1.2F, resultSet.getObject(1)));\n      }\n    }\n  }\n\n  /** Added in > 3.17.0 */\n  @Test\n  public void testObjectColumn() throws SQLException {\n    try (Connection connection = getConnectionWithWildcardsDisabled();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"ALTER SESSION SET ENABLE_STRUCTURED_TYPES_IN_FDN_TABLES = TRUE\");\n      statement.execute(\n          \"CREATE OR REPLACE TABLE TABLEWITHOBJECTCOLUMN (\"\n              + \"    col OBJECT(\"\n              + \"      str VARCHAR,\"\n              + \"      num NUMBER(38,0)\"\n              + \"      )\"\n              + \"   )\");\n      DatabaseMetaData metaData = connection.getMetaData();\n      try (ResultSet resultSet =\n          metaData.getColumns(\n              connection.getCatalog(), connection.getSchema(), \"TABLEWITHOBJECTCOLUMN\", null)) {\n        assertTrue(resultSet.next());\n        assertEquals(\"OBJECT\", resultSet.getObject(\"TYPE_NAME\"));\n        assertFalse(resultSet.next());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DatabaseMetaDataResultsetIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class DatabaseMetaDataResultsetIT extends BaseJDBCWithSharedConnectionIT {\n  private static final int columnCount = 9;\n  private static final int INT_DATA = 1;\n  private static final String TEXT_DATA = \"TEST\";\n  private static final String EPOCH_DATE = \"1970-01-01\";\n  private static final double DOUBLE_DATA = 9.5555555;\n  private static final double DELTA = 0.0001;\n  private static final long NOW = System.currentTimeMillis();\n  private static final Time TIME_DATA = new Time(NOW);\n  private static final Timestamp TIMESTAMP_DATA = new Timestamp(NOW);\n  private static final boolean BOOLEAN_DATA = true;\n  private static final BigDecimal DECIMAL_DATA = new BigDecimal(0.01);\n  private static final long BIGINT_DATA = 10100000L;\n  private static final List<String> columnNames =\n      Arrays.asList(\n          \"int\", \"text\", \"date\", \"double\", \"time\", \"timestamp\", \"bool\", \"decimal\", \"bigint\");\n  private static final List<String> columnTypeNames =\n      Arrays.asList(\n          \"INTEGER\", \"TEXT\", \"DATA\", \"DOUBLE\", \"TIME\", \"TIMESTAMP\", \"BOOLEAN\", \"DECIMAL\", \"BIGINT\");\n  private static final List<Integer> columnTypes =\n      Arrays.asList(\n          Types.INTEGER,\n          Types.VARCHAR,\n          Types.DATE,\n          Types.DOUBLE,\n          Types.TIME,\n          Types.TIMESTAMP,\n          Types.BOOLEAN,\n          Types.DECIMAL,\n          Types.BIGINT);\n  private static final Object[][] rows = {\n    {\n      INT_DATA,\n      TEXT_DATA,\n      Date.valueOf(EPOCH_DATE),\n      DOUBLE_DATA,\n      TIME_DATA,\n      TIMESTAMP_DATA,\n      BOOLEAN_DATA,\n      DECIMAL_DATA,\n      BIGINT_DATA\n    },\n    {0, null, null, 0, null, null, false, null, 0}\n  };\n\n  @Test\n  public void testRowIndex() throws SQLException {\n    try (ResultSet resultSet = getResultSet(false)) {\n\n      assertEquals(columnCount, resultSet.getMetaData().getColumnCount());\n      assertEquals(-1, resultSet.getRow());\n      assertTrue(resultSet.isBeforeFirst());\n      assertFalse(resultSet.isFirst());\n\n      assertTrue(resultSet.next());\n      assertEquals(0, resultSet.getRow());\n\n      assertFalse(resultSet.isBeforeFirst());\n      assertTrue(resultSet.isFirst());\n\n      assertTrue(resultSet.next());\n      assertEquals(1, resultSet.getRow());\n\n      assertTrue(resultSet.isLast());\n      assertFalse(resultSet.isAfterLast());\n\n      assertFalse(resultSet.next());\n      assertEquals(2, resultSet.getRow());\n\n      assertFalse(resultSet.isLast());\n      assertTrue(resultSet.isAfterLast());\n    }\n  }\n\n  private ResultSet getResultSet(boolean doNext) throws SQLException {\n    Statement st = connection.createStatement();\n    ResultSet resultSet =\n        new SnowflakeDatabaseMetaDataResultSet(columnNames, columnTypeNames, columnTypes, rows, st);\n    if (doNext) {\n      resultSet.next();\n    }\n    return resultSet;\n  }\n\n  @Test\n  public void testGetInt() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(INT_DATA, resultSet.getInt(\"int\"));\n    }\n  }\n\n  @Test\n  public void testGetString() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(TEXT_DATA, resultSet.getString(\"text\"));\n    }\n  }\n\n  @Test\n  public void testGetDate() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(Date.valueOf(EPOCH_DATE), resultSet.getDate(\"date\"));\n    }\n  }\n\n  @Test\n  public void testGetDouble() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(DOUBLE_DATA, resultSet.getDouble(\"double\"), DELTA);\n    }\n  }\n\n  @Test\n  public void testGetTime() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(TIME_DATA, resultSet.getTime(\"time\"));\n    }\n  }\n\n  @Test\n  public void testGetTimestamp() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(TIMESTAMP_DATA, resultSet.getTimestamp(\"timestamp\"));\n    }\n  }\n\n  @Test\n  public void testGetBoolean() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(BOOLEAN_DATA, resultSet.getBoolean(\"bool\"));\n    }\n  }\n\n  @Test\n  public void testGetObject() throws SQLException {\n    try (ResultSet resultSet = getResultSet(true)) {\n      assertEquals(INT_DATA, resultSet.getObject(1));\n      assertEquals(TEXT_DATA, resultSet.getObject(2));\n      assertEquals(Date.valueOf(EPOCH_DATE), resultSet.getObject(3));\n      assertEquals(DOUBLE_DATA, resultSet.getObject(4));\n      assertEquals(TIME_DATA, resultSet.getObject(5));\n      assertEquals(TIMESTAMP_DATA, resultSet.getObject(6));\n      assertEquals(BOOLEAN_DATA, resultSet.getObject(7));\n      assertEquals(DECIMAL_DATA, resultSet.getObject(8));\n      assertEquals(BIGINT_DATA, resultSet.getObject(9));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DecfloatTypeLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class DecfloatTypeLatestIT extends BaseJDBCTest {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBasicDecfloatConversions(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultFormat)) {\n        ResultSet rs = stmt.executeQuery(\"SELECT 123.456::DECFLOAT, NULL::DECFLOAT\");\n        assertTrue(rs.next());\n\n        // Test BigDecimal conversions\n        BigDecimal bigDecimalValue = rs.getBigDecimal(1);\n        assertEquals(new BigDecimal(\"123.456\"), bigDecimalValue);\n        BigDecimal nullBigDecimal = rs.getBigDecimal(2);\n        assertNull(nullBigDecimal);\n\n        // Test Double conversions\n        double doubleValue = rs.getDouble(1);\n        assertEquals(123.456, doubleValue, 0.001);\n        double nullDouble = rs.getDouble(2);\n        assertEquals(0.0, nullDouble, 0.001);\n\n        // Test Float conversions\n        float floatValue = rs.getFloat(1);\n        assertEquals(123.456f, floatValue, 0.001f);\n        float nullFloat = rs.getFloat(2);\n        assertEquals(0.0f, nullFloat, 0.001f);\n\n        // Test String conversions\n        String stringValue = rs.getString(1);\n        assertEquals(\"123.456\", stringValue);\n        String nullString = rs.getString(2);\n        assertNull(nullString);\n\n        // Test Object conversions\n        Object objectValue = rs.getObject(1);\n        assertEquals(new BigDecimal(\"123.456\"), objectValue);\n        Object nullObject = rs.getObject(2);\n        assertNull(nullObject);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDecfloatIntegerConversions(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultFormat)) {\n\n        // Test regular integer values without fractions\n        final ResultSet r = stmt.executeQuery(\"SELECT 123::DECFLOAT\");\n        assertTrue(r.next());\n\n        assertEquals(123, r.getInt(1));\n        assertEquals(123L, r.getLong(1));\n        assertEquals((short) 123, r.getShort(1));\n\n        // Test overflow scenarios with large values that fit in Long but overflow int/short\n        final ResultSet rs =\n            stmt.executeQuery(\"SELECT 2147483648::DECFLOAT\"); // 2^31, exceeds int max value\n        assertTrue(rs.next());\n\n        // Long conversion should succeed\n        assertEquals(2147483648L, rs.getLong(1));\n\n        // Int conversion should throw exception (overflow)\n        SnowflakeSQLException e =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () -> rs.getInt(1),\n                \"Expected SnowflakeSQLException for getInt overflow\");\n        assertEquals(\n            ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(),\n            e.getErrorCode(),\n            \"Expected error code INVALID_VALUE_CONVERT for getInt overflow\");\n\n        // Short conversion should throw exception (overflow)\n        e =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () -> rs.getShort(1),\n                \"Expected SnowflakeSQLException for getShort overflow\");\n        assertEquals(\n            ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(),\n            e.getErrorCode(),\n            \"Expected error code INVALID_VALUE_CONVERT for getShort overflow\");\n\n        // Test overflow scenarios with values that exceed Long.MAX_VALUE\n        final ResultSet rsLongOverflow =\n            stmt.executeQuery(\n                \"SELECT 9223372036854775808::DECFLOAT\"); // 2^63, exceeds long max value\n        assertTrue(rsLongOverflow.next());\n\n        // Long conversion should throw exception (overflow)\n        e =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () -> rsLongOverflow.getLong(1),\n                \"Expected SnowflakeSQLException for getLong overflow\");\n        assertEquals(\n            ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(),\n            e.getErrorCode(),\n            \"Expected error code INVALID_VALUE_CONVERT for getLong overflow\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDecfloatBigDecimalConversions(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultFormat)) {\n\n        // Test various DECFLOAT values with precise BigDecimal conversions\n        String[] testValues = {\n          \"0\",\n          \"-1\",\n          \"-1.5\",\n          \"123.4567\",\n          \"-1.2345e2\",\n          \"1.23456e2\",\n          \"-9.8765432099999998623226732747455716901E-250\",\n          \"1.2345678901234567890123456789012345678e37\"\n        };\n\n        BigDecimal[] expectedValues = {\n          new BigDecimal(\"0\"),\n          new BigDecimal(\"-1\"),\n          new BigDecimal(\"-1.5\"),\n          new BigDecimal(\"123.4567\"),\n          new BigDecimal(\"-123.45\"),\n          new BigDecimal(\"123.456\"),\n          new BigDecimal(\n              \"-9.8765432099999998623226732747455716901E-250\"), // Very small negative number with\n          // precision\n          new BigDecimal(\"1.2345678901234567890123456789012345678E+37\") // Very large number\n        };\n\n        for (int i = 0; i < testValues.length; i++) {\n          String query = \"SELECT \" + testValues[i] + \"::DECFLOAT\";\n          ResultSet rs = stmt.executeQuery(query);\n          assertTrue(rs.next(), \"Failed to get result for: \" + testValues[i]);\n\n          BigDecimal actual = rs.getBigDecimal(1);\n          BigDecimal expected = expectedValues[i];\n\n          assertEquals(expected, actual, \"Failed for value: \" + testValues[i]);\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDecfloatBindingBasicTypes(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement ignored = createStatement(con, queryResultFormat)) {\n        try (PreparedStatement ps =\n            con.prepareStatement(\n                \"SELECT ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT\")) {\n\n          ps.setBigDecimal(1, new BigDecimal(\"1234567890.1234567890123456789012345678\"));\n          ps.setDouble(2, 123.45);\n          ps.setString(3, \"678.9\");\n          ps.setInt(4, 1);\n          ps.setFloat(5, 2.5f);\n          ps.setLong(6, 123456789L);\n          ps.setShort(7, (short) 45);\n          ps.setObject(8, new BigDecimal(\"1.2345e4\"), SnowflakeType.EXTRA_TYPES_DECFLOAT);\n          ps.setNull(9, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n\n          try (ResultSet rs = ps.executeQuery()) {\n            assertTrue(rs.next());\n            assertEquals(\n                new BigDecimal(\"1234567890.1234567890123456789012345678\"), rs.getBigDecimal(1));\n            assertEquals(123.45, rs.getDouble(2), 0.001);\n            assertEquals(\"678.9\", rs.getString(3));\n            assertEquals(1, rs.getInt(4));\n            assertEquals(2.5f, rs.getFloat(5), 0.001f);\n            assertEquals(123456789L, rs.getLong(6));\n            assertEquals((short) 45, rs.getShort(7));\n            assertEquals(new BigDecimal(\"1.2345e4\"), rs.getObject(8));\n            assertNull(rs.getObject(9));\n          }\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDecfloatBindingExtremeValues(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement ignored = createStatement(con, queryResultFormat)) {\n        try (PreparedStatement ps =\n            con.prepareStatement(\"SELECT ?::DECFLOAT, ?::DECFLOAT, ?::DECFLOAT\")) {\n\n          BigDecimal veryLargeValue = new BigDecimal(\"1.2345678901234567890123456789012345678e120\");\n          BigDecimal verySmallValue =\n              new BigDecimal(\"-9.8765432099999998623226732747455716901E-250\");\n          BigDecimal expValue = new BigDecimal(\"-1.2345e2\"); // -123.45\n\n          ps.setObject(1, veryLargeValue, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n          ps.setObject(2, verySmallValue, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n          ps.setObject(3, expValue, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n\n          try (ResultSet rs = ps.executeQuery()) {\n            assertTrue(rs.next());\n            BigDecimal result1 = rs.getBigDecimal(1);\n            BigDecimal result2 = rs.getBigDecimal(2);\n            BigDecimal result3 = rs.getBigDecimal(3);\n\n            assertEquals(veryLargeValue, result1);\n            assertEquals(verySmallValue, result2);\n            assertEquals(expValue, result3);\n          }\n        }\n      }\n    }\n  }\n\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @ParameterizedTest\n  public void testDecfloatBindingArray(String queryResultString) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultString)) {\n        try {\n          stmt.execute(\"CREATE OR REPLACE TABLE test_decfloat (value DECFLOAT)\");\n\n          List<BigDecimal> firstArray =\n              Arrays.asList(\n                  new BigDecimal(\"123.45\"),\n                  new BigDecimal(\"1234567890.1234567890123456789012345678\"),\n                  new BigDecimal(\"1.2345678901234567890123456789012345678e120\"));\n\n          try (PreparedStatement ps =\n              con.prepareStatement(\"INSERT INTO test_decfloat VALUES (?)\")) {\n            for (BigDecimal value : firstArray) {\n              ps.setObject(1, value, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n              ps.addBatch();\n            }\n            ps.executeBatch();\n          }\n\n          try (ResultSet rs =\n              stmt.executeQuery(\"SELECT * FROM test_decfloat order by value desc\")) {\n            assertTrue(rs.next());\n            BigDecimal firstValue = rs.getBigDecimal(1);\n            assertEquals(new BigDecimal(\"1.2345678901234567890123456789012345678e120\"), firstValue);\n\n            assertTrue(rs.next());\n            BigDecimal secondValue = rs.getBigDecimal(1);\n            assertEquals(new BigDecimal(\"1234567890.1234567890123456789012345678\"), secondValue);\n\n            assertTrue(rs.next());\n            BigDecimal thirdValue = rs.getBigDecimal(1);\n            assertEquals(new BigDecimal(\"123.45\"), thirdValue);\n          }\n        } finally {\n          // Cleanup\n          stmt.execute(\"DROP TABLE IF EXISTS test_decfloat\");\n        }\n      }\n    }\n  }\n\n  @DontRunOnGithubActions\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @ParameterizedTest\n  public void testDecfloatBindingBatchInserts(String queryResultString) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultString)) {\n        try {\n          stmt.execute(\"CREATE OR REPLACE TABLE test_decfloat (value DECFLOAT)\");\n\n          List<BigDecimal> secondArray =\n              Arrays.asList(\n                  new BigDecimal(\"-987.45e-4\"),\n                  new BigDecimal(\"-1234.423e3\"),\n                  new BigDecimal(\"-9.8765432099999998623226732747455716901E-250\"));\n\n          stmt.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n          try (PreparedStatement ps =\n              con.prepareStatement(\"INSERT INTO test_decfloat VALUES (?)\")) {\n            for (BigDecimal value : secondArray) {\n              ps.setObject(1, value, SnowflakeType.EXTRA_TYPES_DECFLOAT);\n              ps.addBatch();\n            }\n            ps.executeBatch();\n          }\n\n          try (ResultSet rs =\n              stmt.executeQuery(\"SELECT * FROM test_decfloat order by value desc\")) {\n            assertTrue(rs.next());\n            BigDecimal firstValue = rs.getBigDecimal(1);\n            assertEquals(\n                new BigDecimal(\"-9.8765432099999998623226732747455716901E-250\"), firstValue);\n\n            assertTrue(rs.next());\n            BigDecimal fourthValue = rs.getBigDecimal(1);\n            assertEquals(new BigDecimal(\"-0.098745\"), fourthValue);\n\n            assertTrue(rs.next());\n            BigDecimal fifthValue = rs.getBigDecimal(1);\n            assertEquals(new BigDecimal(\"-1234.423e3\"), fifthValue);\n          }\n        } finally {\n          // Cleanup\n          stmt.execute(\"DROP TABLE IF EXISTS test_decfloat\");\n        }\n      }\n    }\n  }\n\n  private Statement createStatement(Connection connection, String queryResultFormat)\n      throws SQLException {\n    Statement stmt = connection.createStatement();\n    stmt.execute(\"ALTER SESSION SET JDBC_QUERY_RESULT_FORMAT = '\" + queryResultFormat + \"'\");\n    return stmt;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DefaultSFConnectionHandlerTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.api.http.HttpHeadersCustomizer.HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyList;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport java.lang.reflect.Field;\nimport java.sql.SQLException;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.SFSession;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\nclass DefaultSFConnectionHandlerTest {\n  private DefaultSFConnectionHandler connectionHandler;\n  private SnowflakeConnectString connectString;\n  private SFSession mockSession;\n  private Properties properties;\n  private ArgumentCaptor<List<HttpHeadersCustomizer>> listCaptor;\n  private HttpHeadersCustomizer mockCustomizer1;\n  private HttpHeadersCustomizer mockCustomizer2;\n\n  @BeforeEach\n  void setUp() throws NoSuchFieldException, IllegalAccessException {\n    listCaptor = ArgumentCaptor.forClass(List.class);\n    mockCustomizer1 = mock();\n    mockCustomizer2 = mock();\n    mockSession = mock();\n\n    Properties prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"test\");\n    prop.put(\"password\", \"test\");\n    connectString =\n        SnowflakeConnectString.parse(\"jdbc:snowflake://testaccount.localhost:8080\", prop);\n    connectionHandler = new DefaultSFConnectionHandler(connectString, true);\n\n    // Use Reflection to inject the mockSession into the private final field\n    Field sessionField = DefaultSFConnectionHandler.class.getDeclaredField(\"sfSession\");\n    sessionField.setAccessible(true);\n    sessionField.set(connectionHandler, mockSession);\n\n    properties = new Properties();\n  }\n\n  @Test\n  void testEmptyHttpHeaderCustomizersWhenPropertyNotSet() throws SQLException {\n    connectionHandler.initialize(connectString, \"\", \"\", properties);\n    verify(mockSession, never()).setHttpHeadersCustomizers(anyList());\n  }\n\n  @Test\n  void testEmptyHttpHeaderCustomizersWhenIsNotList() throws SQLException {\n    properties.put(HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, \"Not a List\");\n    connectionHandler.initialize(connectString, \"\", \"\", properties);\n    verify(mockSession, never()).setHttpHeadersCustomizers(anyList());\n  }\n\n  @Test\n  void testEmptyHttpHeaderCustomizersWhenEmptyListPassed() throws SQLException {\n    properties.put(HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, Collections.emptyList());\n    connectionHandler.initialize(connectString, \"\", \"\", properties);\n    verify(mockSession).setHttpHeadersCustomizers(listCaptor.capture());\n    assertTrue(listCaptor.getValue().isEmpty());\n  }\n\n  @Test\n  void testSetValidCustomizersFromProperty() throws SQLException {\n    List<HttpHeadersCustomizer> inputList = Arrays.asList(mockCustomizer1, mockCustomizer2);\n    properties.put(HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, inputList);\n\n    connectionHandler.initialize(connectString, \"\", \"\", properties);\n\n    verify(mockSession).setHttpHeadersCustomizers(listCaptor.capture());\n    List<HttpHeadersCustomizer> capturedList = listCaptor.getValue();\n    assertEquals(2, capturedList.size());\n    assertTrue(capturedList.contains(mockCustomizer1));\n    assertTrue(capturedList.contains(mockCustomizer2));\n  }\n\n  @Test\n  void testSetsOnlyValidHttpHeaderCustomizersWhenPropertyHasMixedTypes() throws SQLException {\n    List<?> inputList =\n        Arrays.asList(\"String\", mockCustomizer1, null, new Object(), mockCustomizer2);\n    properties.put(HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, inputList);\n\n    connectionHandler.initialize(connectString, \"\", \"\", properties);\n\n    verify(mockSession).setHttpHeadersCustomizers(listCaptor.capture());\n    List<HttpHeadersCustomizer> capturedList = listCaptor.getValue();\n    assertEquals(2, capturedList.size());\n    assertTrue(capturedList.contains(mockCustomizer1));\n    assertTrue(capturedList.contains(mockCustomizer2));\n    assertFalse(capturedList.stream().anyMatch(c -> !(c instanceof HttpHeadersCustomizer)));\n  }\n\n  @Test\n  void testEmptyHttpHeaderCustomizersWhenPropertyHasOnlyInvalidTypes() throws SQLException {\n    List<?> inputList = Arrays.asList(\"String\", new Object(), null);\n    properties.put(HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY, inputList);\n\n    connectionHandler.initialize(connectString, \"\", \"\", properties);\n\n    verify(mockSession).setHttpHeadersCustomizers(listCaptor.capture());\n    assertTrue(listCaptor.getValue().isEmpty());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/DellBoomiCloudIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.io.File;\nimport java.security.Policy;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** A simple run on fetch result under boomi cloud environment's policy file */\n@Tag(TestTags.OTHERS)\npublic class DellBoomiCloudIT extends AbstractDriverIT {\n  @BeforeEach\n  public void setup() {\n    File file = new File(DellBoomiCloudIT.class.getResource(\"boomi.policy\").getFile());\n\n    System.setProperty(\"java.security.policy\", file.getAbsolutePath());\n    Policy.getPolicy().refresh();\n    System.setSecurityManager(new SecurityManager());\n  }\n\n  @Test\n  @Disabled // TODO: SNOW-1805239\n  public void testSelectLargeResultSet() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet =\n            statement.executeQuery(\n                \"select seq4() from table\" + \"(generator\" + \"(rowcount=>10000))\")) {\n\n      while (resultSet.next()) {\n        resultSet.getString(1);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/FileConnectionConfigurationLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.config.SFConnectionConfigParser.SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.driver.AutoConfigurationHelper;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\n/** This test could be run only on environment where file connection.toml is configured */\n@Disabled\npublic class FileConnectionConfigurationLatestIT {\n\n  @AfterEach\n  public void cleanUp() {\n    SnowflakeUtil.systemUnsetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY);\n  }\n\n  @Test\n  public void testThrowExceptionIfConfigurationDoesNotExist() {\n    SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_DEFAULT_CONNECTION_NAME\", \"non-existent\");\n    assertThrows(SnowflakeSQLException.class, () -> SnowflakeDriver.INSTANCE.connect());\n  }\n\n  @Test\n  public void testSimpleConnectionUsingFileConfigurationToken() throws SQLException {\n    verifyConnetionToSnowflake(\"aws-oauth\");\n  }\n\n  @Test\n  public void testSimpleConnectionUsingFileConfigurationTokenFromFile() throws SQLException {\n    verifyConnetionToSnowflake(\"aws-oauth-file\");\n  }\n\n  private static void verifyConnetionToSnowflake(String connectionName) throws SQLException {\n    SnowflakeUtil.systemSetEnv(SNOWFLAKE_DEFAULT_CONNECTION_NAME_KEY, connectionName);\n    try (Connection con =\n            DriverManager.getConnection(AutoConfigurationHelper.AUTO_CONNECTION_PREFIX, null);\n        Statement statement = con.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"show parameters\")) {\n      assertTrue(resultSet.next());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/FileUploaderExpandFileNamesTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.IntStream;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.common.core.SqlState;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n/** Tests for SnowflakeFileTransferAgent.expandFileNames */\npublic class FileUploaderExpandFileNamesTest {\n  @TempDir private File folder;\n  private String localFSFileSep = systemGetProperty(\"file.separator\");\n\n  @Test\n  public void testProcessFileNames() throws Exception {\n    new File(folder, \"TestFileA\").createNewFile();\n    new File(folder, \"TestFileB\").createNewFile();\n\n    String folderName = folder.getCanonicalPath();\n    String originalUserDir = System.getProperty(\"user.dir\");\n    String originalUserHome = System.getProperty(\"user.home\");\n    System.setProperty(\"user.dir\", folderName);\n    System.setProperty(\"user.home\", folderName);\n\n    String[] locations = {\n      folderName + File.separator + \"Tes*Fil*A\",\n      folderName + File.separator + \"TestFil?B\",\n      \"~\" + File.separator + \"TestFileC\",\n      \"TestFileD\",\n      folderName + File.separator + \"TestFileE~\"\n    };\n\n    Set<String> files = SnowflakeFileTransferAgent.expandFileNames(locations, null);\n\n    assertTrue(files.contains(folderName + File.separator + \"TestFileA\"));\n    assertTrue(files.contains(folderName + File.separator + \"TestFileB\"));\n    assertTrue(files.contains(folderName + File.separator + \"TestFileC\"));\n    assertTrue(files.contains(folderName + File.separator + \"TestFileD\"));\n    assertTrue(files.contains(folderName + File.separator + \"TestFileE~\"));\n\n    if (originalUserHome != null) {\n      System.setProperty(\"user.home\", originalUserHome);\n    } else {\n      System.clearProperty(\"user.home\");\n    }\n    if (originalUserDir != null) {\n      System.setProperty(\"user.dir\", originalUserDir);\n    } else {\n      System.clearProperty(\"user.dir\");\n    }\n  }\n\n  @Test\n  public void testProcessFileNamesException() {\n    // inject the Exception\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(new Exception());\n    String[] locations = {\"/Tes*Fil*A\", \"/TestFil?B\", \"~/TestFileC\", \"TestFileD\"};\n\n    SnowflakeSQLException err =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SnowflakeFileTransferAgent.expandFileNames(locations, null));\n    assertEquals(ErrorCode.FAIL_LIST_FILES.getMessageCode(), err.getErrorCode());\n    assertEquals(SqlState.DATA_EXCEPTION, err.getSQLState());\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testSnowflakeFileTransferConfig() throws Exception {\n    SnowflakeFileTransferMetadataV1 metadata =\n        new SnowflakeFileTransferMetadataV1(\n            \"dummy_presigned_url\", null, null, null, null, null, null);\n\n    SnowflakeFileTransferConfig.Builder builder = SnowflakeFileTransferConfig.Builder.newInstance();\n\n    int throwCount = 0;\n    int expectedThrowCount = 3;\n\n    // metadata field is needed.\n    try {\n      builder.build();\n    } catch (IllegalArgumentException ex) {\n      throwCount++;\n    }\n    builder.setSnowflakeFileTransferMetadata(metadata);\n\n    // upload stream is needed.\n    try {\n      builder.build();\n    } catch (IllegalArgumentException ex) {\n      throwCount++;\n    }\n    InputStream input =\n        new InputStream() {\n          @Override\n          public int read() throws IOException {\n            return 0;\n          }\n        };\n    builder.setUploadStream(input);\n\n    // OCSP mode is needed.\n    try {\n      builder.build();\n    } catch (IllegalArgumentException ex) {\n      throwCount++;\n    }\n    builder.setOcspMode(OCSPMode.FAIL_CLOSED);\n\n    // Set the other optional fields\n    builder.setRequireCompress(false);\n    builder.setNetworkTimeoutInMilli(12345);\n    builder.setPrefix(\"dummy_prefix\");\n    Properties props = new Properties();\n    builder.setProxyProperties(props);\n    builder.setDestFileName(\"dummy_dest_file_name\");\n\n    SnowflakeFileTransferConfig config = builder.build();\n\n    // Assert setting fields are in config\n    assertEquals(metadata, config.getSnowflakeFileTransferMetadata());\n    assertEquals(input, config.getUploadStream());\n    assertEquals(OCSPMode.FAIL_CLOSED, config.getOcspMode());\n    assertFalse(config.getRequireCompress());\n    assertEquals(12345, config.getNetworkTimeoutInMilli());\n    assertEquals(props, config.getProxyProperties());\n    assertEquals(\"dummy_prefix\", config.getPrefix());\n    assertEquals(\"dummy_dest_file_name\", config.getDestFileName());\n    assertEquals(expectedThrowCount, throwCount);\n  }\n\n  /**\n   * We have N jobs expanding files with exclusive pattern, processing them and deleting. Expanding\n   * the list should not cause the error when file of another pattern is deleted which may happen\n   * when FileUtils.listFiles is used.\n   *\n   * <p>Fix available after version 3.16.1.\n   *\n   * @throws Exception\n   */\n  @Test\n  public void testFileListingDoesNotFailOnMissingFilesOfAnotherPattern() throws Exception {\n    new File(folder, \"TestFiles\").mkdirs();\n    String folderName = folder.getCanonicalPath();\n\n    int filePatterns = 10;\n    int filesPerPattern = 100;\n    IntStream.range(0, filesPerPattern * filePatterns)\n        .forEach(\n            id -> {\n              try {\n                File file =\n                    new File(\n                        folderName\n                            + localFSFileSep\n                            + \"foo\"\n                            + id % filePatterns\n                            + \"-\"\n                            + UUID.randomUUID());\n                assertTrue(file.createNewFile());\n              } catch (IOException e) {\n                throw new RuntimeException(e);\n              }\n            });\n\n    ExecutorService executorService = Executors.newFixedThreadPool(filePatterns / 3);\n    List<Future<Set<String>>> futures = new ArrayList<>();\n    for (int i = 0; i < filePatterns; ++i) {\n      String[] locations = {\n        folderName + localFSFileSep + \"foo\" + i + \"*\",\n      };\n      Future<Set<String>> future =\n          executorService.submit(\n              () -> {\n                try {\n                  Set<String> strings = SnowflakeFileTransferAgent.expandFileNames(locations, null);\n                  strings.forEach(\n                      fileName -> {\n                        try {\n                          File file = new File(fileName);\n                          Files.delete(file.toPath());\n                        } catch (IOException e) {\n                          throw new RuntimeException(e);\n                        }\n                      });\n                  return strings;\n                } catch (SnowflakeSQLException e) {\n                  throw new RuntimeException(e);\n                }\n              });\n      futures.add(future);\n    }\n    executorService.shutdown();\n    assertTrue(executorService.awaitTermination(60, TimeUnit.SECONDS));\n    assertEquals(filePatterns, futures.size());\n    for (Future<Set<String>> future : futures) {\n      assertTrue(future.isDone());\n      assertEquals(filesPerPattern, future.get().size());\n    }\n  }\n\n  @Test\n  public void testFileListingDoesNotFailOnNotExistingDirectory() throws Exception {\n    new File(folder, \"TestFiles\").mkdirs();\n    String folderName = folder.getCanonicalPath();\n    String[] locations = {\n      folderName + localFSFileSep + \"foo*\",\n    };\n    folder.delete();\n\n    Set<String> files = SnowflakeFileTransferAgent.expandFileNames(locations, null);\n\n    assertTrue(files.isEmpty());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/FileUploaderLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFileAttributes;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.security.NoSuchAlgorithmException;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.Set;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.connection.UploadStreamConfig;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.cloud.storage.S3ObjectMetadata;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeGCSClient;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeStorageClient;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StageInfo;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageClientFactory;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageObjectMetadata;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageProviderException;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.Tag;\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.CsvSource;\nimport software.amazon.awssdk.services.s3.model.PutObjectRequest;\n\n/** Tests for SnowflakeFileTransferAgent that require an active connection */\n@Tag(TestTags.OTHERS)\npublic class FileUploaderLatestIT extends FileUploaderPrep {\n  private static final String OBJ_META_STAGE = \"testObjMeta\";\n  private ObjectMapper mapper = new ObjectMapper();\n  private static final String PUT_COMMAND = \"put file:///dummy/path/file2.gz @testStage\";\n\n  /**\n   * This tests that getStageInfo(JsonNode, session) reflects the boolean value of UseS3RegionalUrl\n   * that has been set via the session.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testGetS3StageDataWithS3Session() throws SQLException {\n    try (Connection con = getConnection(\"s3testaccount\")) {\n      SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      // Set UseRegionalS3EndpointsForPresignedURL to true in session\n      sfSession.setUseRegionalS3EndpointsForPresignedURL(true);\n\n      // Get sample stage info with session\n      StageInfo stageInfo = SnowflakeFileTransferAgent.getStageInfo(exampleS3JsonNode, sfSession);\n      assertEquals(StageInfo.StageType.S3, stageInfo.getStageType());\n      // Assert that true value from session is reflected in StageInfo\n      assertEquals(true, stageInfo.getUseS3RegionalUrl());\n\n      // Set UseRegionalS3EndpointsForPresignedURL to false in session\n      sfSession.setUseRegionalS3EndpointsForPresignedURL(false);\n      stageInfo = SnowflakeFileTransferAgent.getStageInfo(exampleS3JsonNode, sfSession);\n      assertEquals(StageInfo.StageType.S3, stageInfo.getStageType());\n      // Assert that false value from session is reflected in StageInfo\n      assertEquals(false, stageInfo.getUseS3RegionalUrl());\n    }\n  }\n\n  /**\n   * This tests that setting the value of UseS3RegionalUrl for a non-S3 account session has no\n   * effect on the function getStageInfo(JsonNode, session).\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testGetS3StageDataWithAzureSession() throws SQLException {\n    try (Connection con = getConnection(\"azureaccount\")) {\n      SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      // Set UseRegionalS3EndpointsForPresignedURL to true in session. This is redundant since\n      // session\n      // is Azure\n      sfSession.setUseRegionalS3EndpointsForPresignedURL(true);\n\n      // Get sample stage info with session\n      StageInfo stageInfo =\n          SnowflakeFileTransferAgent.getStageInfo(exampleAzureJsonNode, sfSession);\n      assertEquals(StageInfo.StageType.AZURE, stageInfo.getStageType());\n      assertEquals(\"EXAMPLE_LOCATION/\", stageInfo.getLocation());\n      // Assert that UseRegionalS3EndpointsForPresignedURL is false in StageInfo even if it was set\n      // to\n      // true.\n      // The value should always be false for non-S3 accounts\n      assertEquals(false, stageInfo.getUseS3RegionalUrl());\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetObjectMetadataWithGCS() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE \" + OBJ_META_STAGE);\n\n        String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n        String putCommand = \"PUT file://\" + sourceFilePath + \" @\" + OBJ_META_STAGE;\n        statement.execute(putCommand);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n        StageInfo info = sfAgent.getStageInfo();\n        SnowflakeGCSClient client =\n            SnowflakeGCSClient.createSnowflakeGCSClient(\n                info, sfAgent.getEncryptionMaterial().get(0), sfSession);\n\n        String location = info.getLocation();\n        int idx = location.indexOf('/');\n        String remoteStageLocation = location.substring(0, idx);\n        String path = location.substring(idx + 1) + TEST_DATA_FILE + \".gz\";\n        StorageObjectMetadata metadata = client.getObjectMetadata(remoteStageLocation, path);\n        assertEquals(\"gzip\", metadata.getContentEncoding());\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + OBJ_META_STAGE);\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetObjectMetadataFileNotFoundWithGCS() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE \" + OBJ_META_STAGE);\n\n        String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n        String putCommand = \"PUT file://\" + sourceFilePath + \" @\" + OBJ_META_STAGE;\n        statement.execute(putCommand);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n        StageInfo info = sfAgent.getStageInfo();\n        SnowflakeGCSClient client =\n            SnowflakeGCSClient.createSnowflakeGCSClient(\n                info, sfAgent.getEncryptionMaterial().get(0), sfSession);\n\n        String location = info.getLocation();\n        int idx = location.indexOf('/');\n        String remoteStageLocation = location.substring(0, idx);\n        String path = location.substring(idx + 1) + \"wrong_file.csv.gz\";\n        StorageProviderException thrown =\n            assertThrows(\n                StorageProviderException.class,\n                () -> client.getObjectMetadata(remoteStageLocation, path));\n        assertTrue(thrown.getMessage().matches(\".*Blob.*not found in bucket.*\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + OBJ_META_STAGE);\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetObjectMetadataStorageExceptionWithGCS() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE \" + OBJ_META_STAGE);\n\n        String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n        String putCommand = \"PUT file://\" + sourceFilePath + \" @\" + OBJ_META_STAGE;\n        statement.execute(putCommand);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n        StageInfo info = sfAgent.getStageInfo();\n        SnowflakeGCSClient client =\n            SnowflakeGCSClient.createSnowflakeGCSClient(\n                info, sfAgent.getEncryptionMaterial().get(0), sfSession);\n\n        String location = info.getLocation();\n        int idx = location.indexOf('/');\n        String remoteStageLocation = location.substring(0, idx);\n        StorageProviderException thrown =\n            assertThrows(\n                StorageProviderException.class,\n                () -> client.getObjectMetadata(remoteStageLocation, \"\"));\n        assertTrue(thrown.getMessage().matches(\".*Permission.*denied.*\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + OBJ_META_STAGE);\n      }\n    }\n  }\n\n  @Test\n  public void testGetFileTransferCommandType() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(PUT_COMMAND, sfSession, new SFStatement(sfSession));\n        assertEquals(SFBaseFileTransferAgent.CommandType.UPLOAD, sfAgent.getCommandType());\n      } finally {\n        statement.execute(\"drop stage if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testNullCommand() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeSQLException thrown =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () -> new SnowflakeFileTransferAgent(null, sfSession, new SFStatement(sfSession)));\n        assertTrue(\n            thrown\n                .getMessage()\n                .contains(\"JDBC driver internal error: Missing sql for statement execution\"));\n      } finally {\n        statement.execute(\"drop stage if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testCompressStreamWithGzipException() throws Exception {\n    // inject the NoSuchAlgorithmException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(new NoSuchAlgorithmException());\n\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(PUT_COMMAND, sfSession, new SFStatement(sfSession));\n\n        List<SnowflakeFileTransferMetadata> metadataList = sfAgent.getFileTransferMetadatas();\n        SnowflakeFileTransferMetadataV1 metadata =\n            (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n        String srcPath = getFullPathFileInResource(TEST_DATA_FILE);\n        InputStream inputStream = new FileInputStream(srcPath);\n        SnowflakeSQLException thrown =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () ->\n                    SnowflakeFileTransferAgent.uploadWithoutConnection(\n                        SnowflakeFileTransferConfig.Builder.newInstance()\n                            .setSnowflakeFileTransferMetadata(metadata)\n                            .setUploadStream(inputStream)\n                            .setRequireCompress(true)\n                            .setNetworkTimeoutInMilli(0)\n                            .setOcspMode(OCSPMode.FAIL_OPEN)\n                            .setSFSession(sfSession)\n                            .setCommand(PUT_COMMAND)\n                            .build()));\n        assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), thrown.getErrorCode());\n        assertTrue(\n            thrown\n                .getMessage()\n                .contains(\"JDBC driver internal error: error encountered for compression\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testCompressStreamWithGzipNoDigestException() throws Exception {\n    // inject the IOException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(new IOException());\n\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(PUT_COMMAND, sfSession, new SFStatement(sfSession));\n\n        List<SnowflakeFileTransferMetadata> metadataList = sfAgent.getFileTransferMetadatas();\n        SnowflakeFileTransferMetadataV1 metadata =\n            (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n        metadata.setEncryptionMaterial(null, null, null);\n\n        String srcPath = getFullPathFileInResource(TEST_DATA_FILE);\n\n        InputStream inputStream = new FileInputStream(srcPath);\n        SnowflakeSQLException thrown =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () ->\n                    SnowflakeFileTransferAgent.uploadWithoutConnection(\n                        SnowflakeFileTransferConfig.Builder.newInstance()\n                            .setSnowflakeFileTransferMetadata(metadata)\n                            .setUploadStream(inputStream)\n                            .setRequireCompress(true)\n                            .setNetworkTimeoutInMilli(0)\n                            .setOcspMode(OCSPMode.FAIL_OPEN)\n                            .setSFSession(sfSession)\n                            .setCommand(PUT_COMMAND)\n                            .build()));\n        assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), thrown.getErrorCode());\n        assertTrue(\n            thrown\n                .getMessage()\n                .contains(\"JDBC driver internal error: error encountered for compression\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testUploadWithoutConnectionException() throws Exception {\n    // inject the IOException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(\n        new Exception(\"Exception encountered during file upload: failed to push to remote store\"));\n\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(PUT_COMMAND, sfSession, new SFStatement(sfSession));\n\n        List<SnowflakeFileTransferMetadata> metadataList = sfAgent.getFileTransferMetadatas();\n        SnowflakeFileTransferMetadataV1 metadata =\n            (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n        String srcPath = getFullPathFileInResource(TEST_DATA_FILE);\n\n        InputStream inputStream = new FileInputStream(srcPath);\n        Exception thrown =\n            assertThrows(\n                Exception.class,\n                () ->\n                    SnowflakeFileTransferAgent.uploadWithoutConnection(\n                        SnowflakeFileTransferConfig.Builder.newInstance()\n                            .setSnowflakeFileTransferMetadata(metadata)\n                            .setUploadStream(inputStream)\n                            .setRequireCompress(true)\n                            .setNetworkTimeoutInMilli(0)\n                            .setOcspMode(OCSPMode.FAIL_OPEN)\n                            .setSFSession(sfSession)\n                            .setCommand(PUT_COMMAND)\n                            .build()));\n        assertTrue(\n            thrown\n                .getMessage()\n                .contains(\n                    \"Exception encountered during file upload: failed to push to remote store\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testInitFileMetadataFileNotFound() throws Exception {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(PUT_COMMAND, sfSession, new SFStatement(sfSession));\n\n        SnowflakeSQLException thrown = assertThrows(SnowflakeSQLException.class, sfAgent::execute);\n        assertEquals(ErrorCode.FILE_NOT_FOUND.getMessageCode(), thrown.getErrorCode());\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testInitFileMetadataFileIsDirectory() throws Exception {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        String srcPath =\n            getFullPathFileInResource(\"\"); // will pull the resources directory without a file\n        String command = \"put file://\" + srcPath + \" @testStage\";\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n        SnowflakeSQLException thrown = assertThrows(SnowflakeSQLException.class, sfAgent::execute);\n        assertEquals(ErrorCode.FILE_IS_DIRECTORY.getMessageCode(), thrown.getErrorCode());\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testCompareAndSkipFilesException() throws Exception {\n    // inject the NoSuchAlgorithmException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(new NoSuchAlgorithmException());\n\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        String command = \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testStage\";\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n\n        SnowflakeSQLException thrown = assertThrows(SnowflakeSQLException.class, sfAgent::execute);\n        assertEquals(\n            (long) ErrorCode.FILE_OPERATION_UPLOAD_ERROR.getMessageCode(), thrown.getErrorCode());\n        assertInstanceOf(NoSuchAlgorithmException.class, thrown.getCause().getCause());\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testParseCommandException() throws SQLException {\n    // inject the SnowflakeSQLException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(\n        new SnowflakeSQLException(\"invalid data\"));\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        SnowflakeSQLException thrown =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () ->\n                    new SnowflakeFileTransferAgent(\n                        PUT_COMMAND, sfSession, new SFStatement(sfSession)));\n        assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), thrown.getErrorCode());\n        assertTrue(thrown.getMessage().contains(\"Failed to parse the locations\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testPopulateStatusRowsWithSortOn() throws Exception {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        statement.execute(\"set-sf-property sort on\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        // upload files orders_101.csv and orders_100.csv\n        String command =\n            \"PUT file://\"\n                + getFullPathFileInResource(\"\")\n                + File.separator\n                + \"orders_10*.csv @testStage\";\n        SnowflakeFileTransferAgent sfAgent1 =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n        sfAgent1.execute(); // upload files\n\n        // check that source files were sorted\n        assertEquals(2, sfAgent1.statusRows.size());\n        assertEquals(\"orders_100.csv\", sfAgent1.getNextRow().get(0).toString());\n\n        String getCommand = \"GET @testStage file:///tmp\";\n        SnowflakeFileTransferAgent sfAgent2 =\n            new SnowflakeFileTransferAgent(getCommand, sfSession, new SFStatement(sfSession));\n        sfAgent2.execute();\n        // check that files are sorted on download\n        assertEquals(2, sfAgent2.statusRows.size());\n        assertEquals(\"orders_100.csv.gz\", sfAgent2.getNextRow().get(0).toString());\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testListObjectsStorageException() throws Exception {\n    // inject the StorageProviderException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(\n        new StorageProviderException(new Exception(\"could not list objects\")));\n\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace stage testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        String command = \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testStage\";\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n\n        SnowflakeSQLException thrown = assertThrows(SnowflakeSQLException.class, sfAgent::execute);\n        assertEquals(ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(), thrown.getErrorCode());\n        assertTrue(thrown.getMessage().contains(\"Encountered exception during listObjects\"));\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testUploadStreamInterruptedException() throws IOException, SQLException {\n    final String DEST_PREFIX = TEST_UUID + \"/testUploadStream\";\n    // inject the InterruptedException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(new InterruptedException());\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        FileBackedOutputStream outputStream = new FileBackedOutputStream(1000000);\n        outputStream.write(\"hello\".getBytes(StandardCharsets.UTF_8));\n        outputStream.flush();\n\n        // upload the data to user stage under testUploadStream with name hello.txt\n        SnowflakeSQLException thrown =\n            assertThrows(\n                SnowflakeSQLException.class,\n                () ->\n                    connection\n                        .unwrap(SnowflakeConnection.class)\n                        .uploadStream(\n                            \"~\",\n                            \"hello.txt\",\n                            outputStream.asByteSource().openStream(),\n                            UploadStreamConfig.builder()\n                                .setDestPrefix(DEST_PREFIX)\n                                .setCompressData(false)\n                                .build()));\n        assertEquals(ErrorCode.INTERRUPTED.getMessageCode(), thrown.getErrorCode());\n      } finally {\n        statement.execute(\"rm @~/\" + DEST_PREFIX);\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testFileTransferStageInfo() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(PUT_COMMAND, sfSession, new SFStatement(sfSession));\n\n        StageInfo stageInfo = sfAgent.getStageInfo();\n        assertEquals(sfAgent.getStageCredentials(), stageInfo.getCredentials());\n        assertEquals(sfAgent.getStageLocation(), stageInfo.getLocation());\n      } finally {\n        statement.execute(\"drop stage if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testFileTransferMappingFromSourceFile() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE testStage\");\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        String command =\n            \"PUT file://\"\n                + getFullPathFileInResource(\"\")\n                + File.separator\n                + \"orders_10*.csv @testStage\";\n        SnowflakeFileTransferAgent sfAgent1 =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n        sfAgent1.execute();\n\n        SnowflakeFileTransferAgent sfAgent2 =\n            new SnowflakeFileTransferAgent(\n                \"GET @testStage file:///tmp/\", sfSession, new SFStatement(sfSession));\n\n        assertEquals(2, sfAgent2.getSrcToMaterialsMap().size());\n        assertEquals(2, sfAgent2.getSrcToPresignedUrlMap().size());\n      } finally {\n        statement.execute(\"drop stage if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testUploadFileCallableFileNotFound() throws Exception {\n    // inject the FileNotFoundException\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(\n        new FileNotFoundException(\"file does not exist\"));\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE testStage\");\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        String command = \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testStage\";\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n        Exception thrown = assertThrows(Exception.class, sfAgent::execute);\n        assertInstanceOf(FileNotFoundException.class, thrown.getCause());\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n    SnowflakeFileTransferAgent.setInjectedFileTransferException(null);\n  }\n\n  @Test\n  public void testUploadFileStreamWithNoOverwrite() throws Exception {\n    String expectedValue = null;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE testStage\");\n\n        uploadFileToStageUsingStream(connection, false);\n        try (ResultSet resultSet = statement.executeQuery(\"LIST @testStage\")) {\n          assertTrue(resultSet.next());\n          expectedValue = resultSet.getString(\"last_modified\");\n        }\n        Thread.sleep(1000); // add 1 sec delay between uploads.\n\n        uploadFileToStageUsingStream(connection, false);\n        try (ResultSet resultSet = statement.executeQuery(\"LIST @testStage\")) {\n          assertTrue(resultSet.next());\n          String actualValue = resultSet.getString(\"last_modified\");\n          assertEquals(expectedValue, actualValue);\n        }\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  public void testUploadFileStreamWithOverwrite() throws Exception {\n    String expectedValue = null;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE testStage\");\n\n        uploadFileToStageUsingStream(connection, true);\n        try (ResultSet resultSet = statement.executeQuery(\"LIST @testStage\")) {\n          assertTrue(resultSet.next());\n          expectedValue = resultSet.getString(\"last_modified\");\n        }\n        Thread.sleep(1000); // add 1 sec delay between uploads.\n\n        uploadFileToStageUsingStream(connection, true);\n        try (ResultSet resultSet = statement.executeQuery(\"LIST @testStage\")) {\n          assertTrue(resultSet.next());\n          String actualValue = resultSet.getString(\"last_modified\");\n\n          assertFalse(expectedValue.equals(actualValue));\n        }\n      } finally {\n        statement.execute(\"DROP STAGE if exists testStage\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGetS3StorageObjectMetadata() throws Throwable {\n    try (Connection connection = getConnection(\"s3testaccount\");\n        Statement statement = connection.createStatement()) {\n      // create a stage to put the file in\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE \" + OBJ_META_STAGE);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        // Test put file with internal compression\n        String putCommand = \"put file:///dummy/path/file1.gz @\" + OBJ_META_STAGE;\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n        List<SnowflakeFileTransferMetadata> metadata = sfAgent.getFileTransferMetadatas();\n\n        String srcPath = getFullPathFileInResource(TEST_DATA_FILE);\n        for (SnowflakeFileTransferMetadata oneMetadata : metadata) {\n          InputStream inputStream = new FileInputStream(srcPath);\n\n          SnowflakeFileTransferAgent.uploadWithoutConnection(\n              SnowflakeFileTransferConfig.Builder.newInstance()\n                  .setSnowflakeFileTransferMetadata(oneMetadata)\n                  .setUploadStream(inputStream)\n                  .setRequireCompress(true)\n                  .setNetworkTimeoutInMilli(0)\n                  .setOcspMode(OCSPMode.FAIL_OPEN)\n                  .setSFSession(sfSession)\n                  .setCommand(putCommand)\n                  .build());\n\n          SnowflakeStorageClient client =\n              StorageClientFactory.getFactory()\n                  .createClient(\n                      ((SnowflakeFileTransferMetadataV1) oneMetadata).getStageInfo(),\n                      1,\n                      null,\n                      /*session = */ null);\n\n          String location =\n              ((SnowflakeFileTransferMetadataV1) oneMetadata).getStageInfo().getLocation();\n          int idx = location.indexOf('/');\n          String remoteStageLocation = location.substring(0, idx);\n          String path = location.substring(idx + 1) + \"file1.gz\";\n          StorageObjectMetadata meta = client.getObjectMetadata(remoteStageLocation, path);\n\n          PutObjectRequest s3Meta =\n              PutObjectRequest.builder()\n                  .contentLength(meta.getContentLength())\n                  .contentEncoding(meta.getContentEncoding())\n                  .metadata(meta.getUserMetadata())\n                  .build();\n\n          S3ObjectMetadata s3Metadata = new S3ObjectMetadata(s3Meta);\n          RemoteStoreFileEncryptionMaterial encMat = sfAgent.getEncryptionMaterial().get(0);\n          Map<String, String> matDesc =\n              mapper.readValue(s3Metadata.getUserMetadata().get(\"x-amz-matdesc\"), Map.class);\n\n          assertEquals(encMat.getQueryId(), matDesc.get(\"queryId\"));\n          assertEquals(encMat.getSmkId().toString(), matDesc.get(\"smkId\"));\n          assertEquals(1360, s3Metadata.getContentLength());\n          assertEquals(\"gzip\", s3Metadata.getContentEncoding());\n        }\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + OBJ_META_STAGE);\n      }\n    }\n  }\n\n  private void uploadFileToStageUsingStream(Connection connection, boolean overwrite)\n      throws Exception {\n    SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n\n    String putCommand = \"PUT file://\" + sourceFilePath + \" @testStage\";\n\n    if (overwrite) {\n      putCommand += \" overwrite=true\";\n    }\n\n    SnowflakeFileTransferAgent sfAgent =\n        new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n\n    InputStream is = Files.newInputStream(Paths.get(sourceFilePath));\n\n    sfAgent.setSourceStream(is);\n    sfAgent.setDestFileNameForStreamSource(\"test_file\");\n\n    sfAgent.execute();\n  }\n\n  @Test\n  public void testUploadFileWithTildeInFolderName() throws SQLException, IOException {\n    Path topDataDir = null;\n\n    try {\n      topDataDir = Files.createTempDirectory(\"testPutFileTilde\");\n      topDataDir.toFile().deleteOnExit();\n\n      // create sub directory where the name includes ~\n      Path subDir = Files.createDirectories(Paths.get(topDataDir.toString(), \"snowflake~\"));\n\n      // create a test data\n      File dataFile = new File(subDir.toFile(), \"test.txt\");\n      try (Writer writer =\n          new BufferedWriter(\n              new OutputStreamWriter(\n                  Files.newOutputStream(Paths.get(dataFile.getCanonicalPath())),\n                  StandardCharsets.UTF_8))) {\n        writer.write(\"1,test1\");\n      }\n\n      try (Connection connection = getConnection();\n          Statement statement = connection.createStatement()) {\n        try {\n          statement.execute(\"create or replace stage testStage\");\n          String sql = String.format(\"PUT 'file://%s' @testStage\", dataFile.getCanonicalPath());\n\n          // Escape backslashes. This must be done by the application.\n          sql = sql.replaceAll(\"\\\\\\\\\", \"\\\\\\\\\\\\\\\\\");\n          try (ResultSet resultSet = statement.executeQuery(sql)) {\n            while (resultSet.next()) {\n              assertEquals(\"UPLOADED\", resultSet.getString(\"status\"));\n            }\n          }\n        } finally {\n          statement.execute(\"drop stage if exists testStage\");\n        }\n      }\n    } finally {\n      FileUtils.deleteDirectory(topDataDir.toFile());\n    }\n  }\n\n  @Test\n  public void testUploadWithTildeInPath() throws SQLException, IOException {\n    Path subDir = null;\n    try {\n      String homeDir = systemGetProperty(\"user.home\");\n\n      // create sub directory where the name includes ~\n      subDir = Files.createDirectories(Paths.get(homeDir, \"snowflake\"));\n\n      // create a test data\n      File dataFile = new File(subDir.toFile(), \"test.txt\");\n      try (Writer writer =\n          new BufferedWriter(\n              new OutputStreamWriter(\n                  Files.newOutputStream(Paths.get(dataFile.getCanonicalPath())),\n                  StandardCharsets.UTF_8))) {\n        writer.write(\"1,test1\");\n      }\n      try (Connection connection = getConnection();\n          Statement statement = connection.createStatement()) {\n        try {\n          statement.execute(\"create or replace stage testStage\");\n\n          try (ResultSet resultSet =\n              statement.executeQuery(\"PUT 'file://~/snowflake/test.txt' @testStage\")) {\n            while (resultSet.next()) {\n              assertEquals(\"UPLOADED\", resultSet.getString(\"status\"));\n            }\n          }\n        } finally {\n          statement.execute(\"drop stage if exists testStage\");\n        }\n      }\n    } finally {\n      FileUtils.deleteDirectory(subDir.toFile());\n    }\n  }\n\n  @Test\n  public void testUploadWithTripleSlashFilePrefix(@TempDir File tempDir)\n      throws SQLException, IOException {\n    String stageName = \"testStage\" + SnowflakeUtil.randomAlphaNumeric(10);\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        String command =\n            \"PUT file:///\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @\" + stageName;\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n        assertTrue(sfAgent.execute());\n\n        String tempDirPath = tempDir.getCanonicalPath().replace(\"\\\\\", \"/\");\n        String getCommand = \"GET @\" + stageName + \" file:///\" + tempDirPath;\n        SnowflakeFileTransferAgent sfAgent1 =\n            new SnowflakeFileTransferAgent(getCommand, sfSession, new SFStatement(sfSession));\n        assertTrue(sfAgent1.execute());\n        assertEquals(1, sfAgent1.statusRows.size());\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + stageName);\n      }\n    }\n  }\n\n  @DontRunOnWindows\n  @ParameterizedTest\n  @CsvSource({\"true, rwx------, rw-------\", \"false, rwxr-xr-x, rw-r--r--\"})\n  public void testDownloadedFilePermissions(\n      boolean ownerOnlyStageFilePermissionsEnabled,\n      String expectedDirectoryPermissions,\n      String expectedFilePermissions,\n      @TempDir File tempDir)\n      throws SQLException, IOException {\n    String stageName = \"testStage\" + SnowflakeUtil.randomAlphaNumeric(10);\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        sfSession.setOwnerOnlyStageFilePermissionsEnabled(ownerOnlyStageFilePermissionsEnabled);\n\n        String command =\n            \"PUT file:///\"\n                + getFullPathFileInResource(TEST_DATA_FILE)\n                + \" @\"\n                + stageName\n                + \" AUTO_COMPRESS=FALSE\";\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n        assertTrue(sfAgent.execute());\n\n        String tempDirPath = (tempDir.getCanonicalPath() + \"/new_directory\").replace(\"\\\\\", \"/\");\n        String getCommand = \"GET @\" + stageName + \" file://\" + tempDirPath;\n        SnowflakeFileTransferAgent sfAgent1 =\n            new SnowflakeFileTransferAgent(getCommand, sfSession, new SFStatement(sfSession));\n        assertTrue(sfAgent1.execute());\n        assertEquals(1, sfAgent1.statusRows.size());\n        File downloadedFile = new File(tempDirPath, TEST_DATA_FILE);\n        PosixFileAttributes dirAttributes =\n            Files.readAttributes(new File(tempDirPath).toPath(), PosixFileAttributes.class);\n        PosixFileAttributes fileAttributes =\n            Files.readAttributes(downloadedFile.toPath(), PosixFileAttributes.class);\n        Set<PosixFilePermission> tmpDirPermissions = dirAttributes.permissions();\n        Set<PosixFilePermission> filePermissions = fileAttributes.permissions();\n\n        assertEquals(\n            PosixFilePermissions.fromString(expectedDirectoryPermissions), tmpDirPermissions);\n        assertEquals(PosixFilePermissions.fromString(expectedFilePermissions), filePermissions);\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + stageName);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/FileUploaderMimeTypeToCompressionTypeTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Optional;\nimport java.util.stream.Stream;\nimport net.snowflake.common.core.FileCompressionType;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * Tests for SnowflakeFileTransferAgent.mimeTypeToCompressionType See\n * https://github.com/apache/tika/blob/master/tika-core/src/main/resources/org/apache/tika/mime/tika-mimetypes.xml\n * for test cases\n */\npublic class FileUploaderMimeTypeToCompressionTypeTest {\n\n  static class MimeTypesProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      return Stream.of(\n          Arguments.of(\"text/\", null),\n          Arguments.of(\"text/csv\", null),\n          Arguments.of(\"snowflake/orc\", FileCompressionType.ORC),\n          Arguments.of(\"snowflake/orc;p=1\", FileCompressionType.ORC),\n          Arguments.of(\"snowflake/parquet\", FileCompressionType.PARQUET),\n          Arguments.of(\"application/zlib\", FileCompressionType.DEFLATE),\n          Arguments.of(\"application/x-bzip2\", FileCompressionType.BZIP2),\n          Arguments.of(\"application/zstd\", FileCompressionType.ZSTD),\n          Arguments.of(\"application/x-brotli\", FileCompressionType.BROTLI),\n          Arguments.of(\"application/x-lzip\", FileCompressionType.LZIP),\n          Arguments.of(\"application/x-lzma\", FileCompressionType.LZMA),\n          Arguments.of(\"application/x-xz\", FileCompressionType.XZ),\n          Arguments.of(\"application/x-compress\", FileCompressionType.COMPRESS),\n          Arguments.of(\"application/x-gzip\", FileCompressionType.GZIP));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(MimeTypesProvider.class)\n  public void testMimeTypeToCompressionType(String mimeType, FileCompressionType mimeSubType)\n      throws Throwable {\n    Optional<FileCompressionType> foundCompType =\n        SnowflakeFileTransferAgent.mimeTypeToCompressionType(mimeType);\n    if (foundCompType.isPresent()) {\n      assertEquals(mimeSubType, foundCompType.get());\n    } else {\n      assertEquals(mimeSubType, null);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/FileUploaderPrep.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeAll;\n\n/** File uploader test prep reused by IT/connection tests and sessionless tests */\nabstract class FileUploaderPrep extends BaseJDBCTest {\n\n  private static final ObjectMapper mapper = new ObjectMapper();\n\n  static JsonNode exampleS3JsonNode;\n  static JsonNode exampleS3StageEndpointJsonNode;\n  static JsonNode exampleAzureJsonNode;\n  static JsonNode exampleGCSJsonNode;\n  static JsonNode exampleGCSJsonNodeWithUseRegionalUrl;\n  static JsonNode exampleGCSJsonNodeWithEndPoint;\n  static List<JsonNode> exampleNodes;\n\n  private static JsonNode readJsonFromFile(String name) throws IOException {\n    try (InputStream is =\n        FileUploaderPrep.class.getResourceAsStream(\"/FileUploaderPrep/\" + name + \".json\")) {\n      return mapper.readTree(is);\n    }\n  }\n\n  @BeforeAll\n  public static void setup() throws Exception {\n    exampleS3JsonNode = readJsonFromFile(\"exampleS3\");\n    exampleS3StageEndpointJsonNode = readJsonFromFile(\"exampleS3WithStageEndpoint\");\n    exampleAzureJsonNode = readJsonFromFile(\"exampleAzure\");\n    exampleGCSJsonNode = readJsonFromFile(\"exampleGCS\");\n    exampleGCSJsonNodeWithUseRegionalUrl = readJsonFromFile(\"exampleGCSWithUseRegionalUrl\");\n    exampleGCSJsonNodeWithEndPoint = readJsonFromFile(\"exampleGCSWithEndpoint\");\n    exampleNodes =\n        Arrays.asList(\n            exampleS3JsonNode,\n            exampleAzureJsonNode,\n            exampleGCSJsonNode,\n            exampleGCSJsonNodeWithUseRegionalUrl,\n            exampleGCSJsonNodeWithEndPoint);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/FileUploaderSessionlessTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StageInfo;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport org.junit.jupiter.api.Test;\n\n/** Tests for SnowflakeFileTransferAgent.expandFileNames. */\npublic class FileUploaderSessionlessTest extends FileUploaderPrep {\n\n  private ObjectMapper mapper = new ObjectMapper();\n\n  @Test\n  public void testGetEncryptionMaterialMissing() throws Exception {\n    JsonNode modifiedNode = exampleS3JsonNode.deepCopy();\n    ObjectNode foo = (ObjectNode) modifiedNode.path(\"data\");\n    foo.remove(\"encryptionMaterial\");\n\n    List<RemoteStoreFileEncryptionMaterial> encryptionMaterials =\n        SnowflakeFileTransferAgent.getEncryptionMaterial(\n            SFBaseFileTransferAgent.CommandType.UPLOAD, modifiedNode);\n\n    assertEquals(1, encryptionMaterials.size());\n    assertNull(encryptionMaterials.get(0));\n  }\n\n  @Test\n  public void testGetEncryptionMaterial() throws Exception {\n    List<RemoteStoreFileEncryptionMaterial> expected = new ArrayList<>();\n    RemoteStoreFileEncryptionMaterial content =\n        new RemoteStoreFileEncryptionMaterial(\n            \"EXAMPLE_QUERY_STAGE_MASTER_KEY\", \"EXAMPLE_QUERY_ID\", 123L);\n    expected.add(content);\n\n    for (JsonNode exampleNode : exampleNodes) {\n      List<RemoteStoreFileEncryptionMaterial> encryptionMaterials =\n          SnowflakeFileTransferAgent.getEncryptionMaterial(\n              SFBaseFileTransferAgent.CommandType.UPLOAD, exampleNode);\n\n      assertEquals(1, encryptionMaterials.size());\n      assertEquals(\n          expected.get(0).getQueryStageMasterKey(),\n          encryptionMaterials.get(0).getQueryStageMasterKey());\n      assertEquals(expected.get(0).getQueryId(), encryptionMaterials.get(0).getQueryId());\n      assertEquals(expected.get(0).getSmkId(), encryptionMaterials.get(0).getSmkId());\n    }\n  }\n\n  @Test\n  public void testGetS3StageData() throws Exception {\n    StageInfo stageInfo = SnowflakeFileTransferAgent.getStageInfo(exampleS3JsonNode, null);\n    Map<String, String> expectedCreds = new HashMap<>();\n    expectedCreds.put(\"AWS_ID\", \"EXAMPLE_AWS_ID\");\n    expectedCreds.put(\"AWS_KEY\", \"EXAMPLE_AWS_KEY\");\n    expectedCreds.put(\"AWS_KEY_ID\", \"EXAMPLE_AWS_KEY_ID\");\n    expectedCreds.put(\"AWS_SECRET_KEY\", \"EXAMPLE_AWS_SECRET_KEY\");\n    expectedCreds.put(\"AWS_TOKEN\", \"EXAMPLE_AWS_TOKEN\");\n\n    assertEquals(StageInfo.StageType.S3, stageInfo.getStageType());\n    assertEquals(\"stage/location/foo/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"us-west-2\", stageInfo.getRegion());\n    assertEquals(\"null\", stageInfo.getEndPoint());\n    assertEquals(null, stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n    assertEquals(true, stageInfo.getUseS3RegionalUrl());\n  }\n\n  @Test\n  public void testGetS3StageDataWithStageEndpoint() throws Exception {\n    StageInfo stageInfo =\n        SnowflakeFileTransferAgent.getStageInfo(exampleS3StageEndpointJsonNode, null);\n    Map<String, String> expectedCreds = new HashMap<>();\n    expectedCreds.put(\"AWS_ID\", \"EXAMPLE_AWS_ID\");\n    expectedCreds.put(\"AWS_KEY\", \"EXAMPLE_AWS_KEY\");\n    expectedCreds.put(\"AWS_KEY_ID\", \"EXAMPLE_AWS_KEY_ID\");\n    expectedCreds.put(\"AWS_SECRET_KEY\", \"EXAMPLE_AWS_SECRET_KEY\");\n    expectedCreds.put(\"AWS_TOKEN\", \"EXAMPLE_AWS_TOKEN\");\n\n    assertEquals(StageInfo.StageType.S3, stageInfo.getStageType());\n    assertEquals(\"stage/location/foo/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"us-west-2\", stageInfo.getRegion());\n    assertEquals(\"s3-fips.us-east-1.amazonaws.com\", stageInfo.getEndPoint());\n    assertEquals(null, stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n  }\n\n  @Test\n  public void testGetAzureStageData() throws Exception {\n    StageInfo stageInfo = SnowflakeFileTransferAgent.getStageInfo(exampleAzureJsonNode, null);\n    Map<String, String> expectedCreds = new HashMap<>();\n    expectedCreds.put(\"AZURE_SAS_TOKEN\", \"EXAMPLE_AZURE_SAS_TOKEN\");\n\n    assertEquals(StageInfo.StageType.AZURE, stageInfo.getStageType());\n    assertEquals(\"EXAMPLE_LOCATION/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"westus\", stageInfo.getRegion());\n    assertEquals(\"blob.core.windows.net\", stageInfo.getEndPoint());\n    assertEquals(\"EXAMPLE_STORAGE_ACCOUNT\", stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n  }\n\n  @Test\n  public void testGetGCSStageData() throws Exception {\n    StageInfo stageInfo = SnowflakeFileTransferAgent.getStageInfo(exampleGCSJsonNode, null);\n    Map<String, String> expectedCreds = new HashMap<>();\n\n    assertEquals(StageInfo.StageType.GCS, stageInfo.getStageType());\n    assertEquals(\"foo/tables/9224/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"US-WEST1\", stageInfo.getRegion());\n    assertEquals(null, stageInfo.getEndPoint());\n    assertEquals(null, stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasS3() throws Exception {\n    List<SnowflakeFileTransferMetadata> metadataList =\n        SnowflakeFileTransferAgent.getFileTransferMetadatas(exampleS3JsonNode);\n    assertEquals(1, metadataList.size());\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n    // StageInfo check\n    StageInfo stageInfo = metadata.getStageInfo();\n\n    Map<String, String> expectedCreds = new HashMap<>();\n    expectedCreds.put(\"AWS_ID\", \"EXAMPLE_AWS_ID\");\n    expectedCreds.put(\"AWS_KEY\", \"EXAMPLE_AWS_KEY\");\n    expectedCreds.put(\"AWS_KEY_ID\", \"EXAMPLE_AWS_KEY_ID\");\n    expectedCreds.put(\"AWS_SECRET_KEY\", \"EXAMPLE_AWS_SECRET_KEY\");\n    expectedCreds.put(\"AWS_TOKEN\", \"EXAMPLE_AWS_TOKEN\");\n\n    assertEquals(StageInfo.StageType.S3, stageInfo.getStageType());\n    assertEquals(\"stage/location/foo/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"us-west-2\", stageInfo.getRegion());\n    assertEquals(\"null\", stageInfo.getEndPoint());\n    assertEquals(null, stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n\n    // EncryptionMaterial check\n    assertEquals(\"EXAMPLE_QUERY_ID\", metadata.getEncryptionMaterial().getQueryId());\n    assertEquals(\n        \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n        metadata.getEncryptionMaterial().getQueryStageMasterKey());\n    assertEquals(123L, (long) metadata.getEncryptionMaterial().getSmkId());\n\n    // Misc check\n    assertEquals(SFBaseFileTransferAgent.CommandType.UPLOAD, metadata.getCommandType());\n    assertNull(metadata.getPresignedUrl());\n    assertEquals(\"orders_100.csv\", metadata.getPresignedUrlFileName());\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasS3MissingEncryption() throws Exception {\n    JsonNode modifiedNode = exampleS3JsonNode.deepCopy();\n    ObjectNode foo = (ObjectNode) modifiedNode.path(\"data\");\n    foo.remove(\"encryptionMaterial\");\n\n    List<SnowflakeFileTransferMetadata> metadataList =\n        SnowflakeFileTransferAgent.getFileTransferMetadatas(modifiedNode);\n    assertEquals(1, metadataList.size());\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n    // StageInfo check\n    StageInfo stageInfo = metadata.getStageInfo();\n\n    Map<String, String> expectedCreds = new HashMap<>();\n    expectedCreds.put(\"AWS_ID\", \"EXAMPLE_AWS_ID\");\n    expectedCreds.put(\"AWS_KEY\", \"EXAMPLE_AWS_KEY\");\n    expectedCreds.put(\"AWS_KEY_ID\", \"EXAMPLE_AWS_KEY_ID\");\n    expectedCreds.put(\"AWS_SECRET_KEY\", \"EXAMPLE_AWS_SECRET_KEY\");\n    expectedCreds.put(\"AWS_TOKEN\", \"EXAMPLE_AWS_TOKEN\");\n\n    assertEquals(StageInfo.StageType.S3, stageInfo.getStageType());\n    assertEquals(\"stage/location/foo/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"us-west-2\", stageInfo.getRegion());\n    assertEquals(\"null\", stageInfo.getEndPoint());\n    assertEquals(null, stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n\n    // EncryptionMaterial check\n    assertNull(metadata.getEncryptionMaterial().getQueryId());\n    assertNull(metadata.getEncryptionMaterial().getQueryStageMasterKey());\n    assertNull(metadata.getEncryptionMaterial().getSmkId());\n\n    // Misc check\n    assertEquals(SFBaseFileTransferAgent.CommandType.UPLOAD, metadata.getCommandType());\n    assertNull(metadata.getPresignedUrl());\n    assertEquals(\"orders_100.csv\", metadata.getPresignedUrlFileName());\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasAzure() throws Exception {\n    List<SnowflakeFileTransferMetadata> metadataList =\n        SnowflakeFileTransferAgent.getFileTransferMetadatas(exampleAzureJsonNode);\n    assertEquals(1, metadataList.size());\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n    // StageInfo check\n    StageInfo stageInfo = metadata.getStageInfo();\n\n    Map<String, String> expectedCreds = new HashMap<>();\n    expectedCreds.put(\"AZURE_SAS_TOKEN\", \"EXAMPLE_AZURE_SAS_TOKEN\");\n\n    assertEquals(StageInfo.StageType.AZURE, stageInfo.getStageType());\n    assertEquals(\"EXAMPLE_LOCATION/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"westus\", stageInfo.getRegion());\n    assertEquals(\"blob.core.windows.net\", stageInfo.getEndPoint());\n    assertEquals(\"EXAMPLE_STORAGE_ACCOUNT\", stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n\n    // EncryptionMaterial check\n    assertEquals(\"EXAMPLE_QUERY_ID\", metadata.getEncryptionMaterial().getQueryId());\n    assertEquals(\n        \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n        metadata.getEncryptionMaterial().getQueryStageMasterKey());\n    assertEquals(123L, (long) metadata.getEncryptionMaterial().getSmkId());\n\n    // Misc check\n    assertEquals(SFBaseFileTransferAgent.CommandType.UPLOAD, metadata.getCommandType());\n    assertNull(metadata.getPresignedUrl());\n    assertEquals(\"orders_100.csv\", metadata.getPresignedUrlFileName());\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasGCS() throws Exception {\n    List<SnowflakeFileTransferMetadata> metadataList =\n        SnowflakeFileTransferAgent.getFileTransferMetadatas(exampleGCSJsonNode);\n    assertEquals(1, metadataList.size());\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n    // StageInfo check\n    StageInfo stageInfo = metadata.getStageInfo();\n\n    Map<String, String> expectedCreds = new HashMap<>();\n\n    assertEquals(StageInfo.StageType.GCS, stageInfo.getStageType());\n    assertEquals(\"foo/tables/9224/\", stageInfo.getLocation());\n    assertEquals(expectedCreds, stageInfo.getCredentials());\n    assertEquals(\"US-WEST1\", stageInfo.getRegion());\n    assertEquals(null, stageInfo.getEndPoint());\n    assertEquals(null, stageInfo.getStorageAccount());\n    assertEquals(true, stageInfo.getIsClientSideEncrypted());\n    assertEquals(Optional.empty(), stageInfo.gcsCustomEndpoint());\n\n    // EncryptionMaterial check\n    assertEquals(\"EXAMPLE_QUERY_ID\", metadata.getEncryptionMaterial().getQueryId());\n    assertEquals(\n        \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n        metadata.getEncryptionMaterial().getQueryStageMasterKey());\n    assertEquals(123L, (long) metadata.getEncryptionMaterial().getSmkId());\n\n    // Misc check\n    assertEquals(SFBaseFileTransferAgent.CommandType.UPLOAD, metadata.getCommandType());\n    assertEquals(\"EXAMPLE_PRESIGNED_URL\", metadata.getPresignedUrl());\n    assertEquals(\"orders_100.csv\", metadata.getPresignedUrlFileName());\n  }\n\n  @Test\n  public void testGetFileTransferMetadataGCSWithUseRegionalUrl() throws Exception {\n    List<SnowflakeFileTransferMetadata> metadataList =\n        SnowflakeFileTransferAgent.getFileTransferMetadatas(exampleGCSJsonNodeWithUseRegionalUrl);\n    assertEquals(1, metadataList.size());\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n    StageInfo stageInfo = metadata.getStageInfo();\n\n    assertTrue(stageInfo.getUseRegionalUrl());\n    assertEquals(Optional.of(\"storage.us-west1.rep.googleapis.com\"), stageInfo.gcsCustomEndpoint());\n  }\n\n  @Test\n  public void testGetFileTransferMetadataGCSWithEndPoint() throws Exception {\n    List<SnowflakeFileTransferMetadata> metadataList =\n        SnowflakeFileTransferAgent.getFileTransferMetadatas(exampleGCSJsonNodeWithEndPoint);\n    assertEquals(1, metadataList.size());\n\n    SnowflakeFileTransferMetadataV1 metadata =\n        (SnowflakeFileTransferMetadataV1) metadataList.get(0);\n\n    StageInfo stageInfo = metadata.getStageInfo();\n\n    assertFalse(stageInfo.getUseRegionalUrl());\n    assertEquals(Optional.of(\"example.com\"), stageInfo.gcsCustomEndpoint());\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasUploadError() throws Exception {\n    JsonNode downloadNode = mapper.readTree(\"{\\\"data\\\": {\\\"command\\\": \\\"DOWNLOAD\\\"}}\");\n    SnowflakeSQLException err =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SnowflakeFileTransferAgent.getFileTransferMetadatas(downloadNode));\n    assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), err.getErrorCode());\n    assertEquals(\n        \"JDBC driver internal error: This API only supports PUT commands.\", err.getMessage());\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasEncryptionMaterialError() throws Exception {\n    JsonNode garbageNode = mapper.readTree(\"{\\\"data\\\": {\\\"src_locations\\\": [1, 2]}}\");\n    SnowflakeSQLException err =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SnowflakeFileTransferAgent.getFileTransferMetadatas(garbageNode));\n    assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), err.getErrorCode());\n    assertTrue(\n        err.getMessage().contains(\"JDBC driver internal error: Failed to parse the credentials\"));\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasUnsupportedLocationError() {\n    JsonNode modifiedNode = exampleS3JsonNode.deepCopy();\n    ObjectNode foo = (ObjectNode) modifiedNode.path(\"data\").path(\"stageInfo\");\n    foo.put(\"locationType\", \"LOCAL_FS\");\n    SnowflakeSQLException err =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SnowflakeFileTransferAgent.getFileTransferMetadatas(modifiedNode));\n    assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), err.getErrorCode());\n    assertTrue(err.getMessage().contains(\"JDBC driver internal error: This API only supports\"));\n  }\n\n  @Test\n  public void testGetFileTransferMetadatasSrcLocationsArrayError() throws JsonProcessingException {\n    JsonNode garbageNode = mapper.readTree(\"{\\\"data\\\": {\\\"src_locations\\\": \\\"abc\\\"}}\");\n    SnowflakeSQLException err =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SnowflakeFileTransferAgent.getFileTransferMetadatas(garbageNode));\n    assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), err.getErrorCode());\n    assertTrue(\n        err.getMessage().contains(\"JDBC driver internal error: src_locations must be an array\"));\n  }\n\n  @Test\n  public void testGetFileMetadatasEncryptionMaterialsException() {\n    JsonNode modifiedNode = exampleS3JsonNode.deepCopy();\n    ObjectNode foo = (ObjectNode) modifiedNode.path(\"data\");\n    foo.put(\"encryptionMaterial\", \"[1, 2, 3]]\");\n    SnowflakeSQLException err =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () -> SnowflakeFileTransferAgent.getFileTransferMetadatas(modifiedNode));\n    assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), err.getErrorCode());\n    assertTrue(err.getMessage().contains(\"Failed to parse encryptionMaterial\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/GCPLargeResult.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.RESULT_SET)\npublic class GCPLargeResult extends BaseJDBCTest {\n\n  Connection init(String queryResultFormat) throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection(\"gcpaccount\");\n    System.out.println(\"Connected\");\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testLargeResultSetGCP(String queryResultFormat) throws Throwable {\n    try (Connection con = init(queryResultFormat);\n        PreparedStatement stmt =\n            con.prepareStatement(\n                \"select seq8(), randstr(1000, random()) from table(generator(rowcount=>1000))\")) {\n      stmt.setMaxRows(999);\n      try (ResultSet rset = stmt.executeQuery()) {\n        int cnt = 0;\n        while (rset.next()) {\n          ++cnt;\n        }\n        assertEquals(cnt, 999);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/GitRepositoryDownloadLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class GitRepositoryDownloadLatestIT extends BaseJDBCTest {\n\n  /**\n   * Test needs to set up git integration which is not available in GH Action tests and needs\n   * accountadmin role. Added in > 3.19.0\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void shouldDownloadFileAndStreamFromGitRepository() throws Exception {\n    try (Connection connection = getConnection()) {\n      prepareJdbcRepoInSnowflake(connection);\n\n      String stageName =\n          String.format(\"@%s.%s.JDBC\", connection.getCatalog(), connection.getSchema());\n      String fileName = \".pre-commit-config.yaml\";\n      String filePathInGitRepo = \"branches/master/\" + fileName;\n\n      List<String> fetchedFileContent =\n          getContentFromFile(connection, stageName, filePathInGitRepo, fileName);\n\n      List<String> fetchedStreamContent =\n          getContentFromStream(connection, stageName, filePathInGitRepo);\n\n      assertFalse(fetchedFileContent.isEmpty(), \"File content cannot be empty\");\n      assertFalse(fetchedStreamContent.isEmpty(), \"Stream content cannot be empty\");\n      assertEquals(fetchedFileContent, fetchedStreamContent);\n    }\n  }\n\n  private static void prepareJdbcRepoInSnowflake(Connection connection) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n      statement.execute(\n          \"CREATE OR REPLACE API INTEGRATION gh_integration\\n\"\n              + \"  API_PROVIDER = git_https_api\\n\"\n              + \"  API_ALLOWED_PREFIXES = ('https://github.com/snowflakedb/snowflake-jdbc.git')\\n\"\n              + \"  ENABLED = TRUE;\");\n      statement.execute(\n          \"CREATE OR REPLACE GIT REPOSITORY jdbc\\n\"\n              + \"ORIGIN = 'https://github.com/snowflakedb/snowflake-jdbc.git'\\n\"\n              + \"API_INTEGRATION = gh_integration;\");\n    }\n  }\n\n  private static List<String> getContentFromFile(\n      Connection connection, String stageName, String filePathInGitRepo, String fileName)\n      throws IOException, SQLException {\n    Path tempDir = Files.createTempDirectory(\"git\");\n    String stagePath = stageName + \"/\" + filePathInGitRepo;\n    Path downloadedFile = tempDir.resolve(fileName);\n    String command = String.format(\"GET '%s' '%s'\", stagePath, tempDir.toUri());\n\n    try (Statement statement = connection.createStatement();\n        ResultSet rs = statement.executeQuery(command); ) {\n      // then\n      assertTrue(rs.next(), \"has result\");\n      return Files.readAllLines(downloadedFile);\n    } finally {\n      Files.delete(downloadedFile);\n      Files.delete(tempDir);\n    }\n  }\n\n  private static List<String> getContentFromStream(\n      Connection connection, String stageName, String filePathInGitRepo)\n      throws SQLException, IOException {\n    SnowflakeConnection unwrap = connection.unwrap(SnowflakeConnection.class);\n    try (InputStream inputStream =\n        unwrap.downloadStream(\n            stageName,\n            filePathInGitRepo,\n            DownloadStreamConfig.builder().setDecompress(false).build())) {\n      return IOUtils.readLines(inputStream, StandardCharsets.UTF_8);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/HeartbeatAsyncLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.Properties;\nimport java.util.logging.Logger;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.api.resultset.SnowflakeAsyncResultSet;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Test class for using heartbeat with asynchronous querying. This is a \"Latest\" class because old\n * driver versions do not contain the asynchronous querying API.\n */\n@Tag(TestTags.OTHERS)\npublic class HeartbeatAsyncLatestIT extends HeartbeatIT {\n  private static Logger logger = Logger.getLogger(HeartbeatAsyncLatestIT.class.getName());\n\n  /**\n   * create a new connection with or without keep alive parameter and submit an asynchronous query.\n   * The async query will not fail if the session token expires, but the functions fetching the\n   * query results require a valid session token.\n   *\n   * @param useKeepAliveSession Enables/disables client session keep alive\n   * @param queryIdx The query index\n   * @throws SQLException Will be thrown if any of the driver calls fail\n   */\n  @Override\n  protected void submitQuery(boolean useKeepAliveSession, int queryIdx)\n      throws SQLException, InterruptedException {\n    Properties sessionParams = new Properties();\n    sessionParams.put(\n        \"CLIENT_SESSION_KEEP_ALIVE\",\n        useKeepAliveSession ? Boolean.TRUE.toString() : Boolean.FALSE.toString());\n\n    try (Connection connection = getConnection(\"s3testaccount\", sessionParams);\n        Statement stmt = connection.createStatement();\n        // Query will take 5 seconds to run, but ResultSet will be returned immediately\n        ResultSet resultSet =\n            stmt.unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"SELECT count(*) FROM TABLE(generator(timeLimit => 5))\")) {\n      Thread.sleep(61000); // sleep 61 seconds to await original session expiration time\n      QueryStatus queryStatus = resultSet.unwrap(SnowflakeAsyncResultSet.class).getStatus();\n      // Ensure query succeeded. Avoid flaky test failure by waiting until query is complete to\n      // assert the query status is a success.\n      SnowflakeAsyncResultSet rs = resultSet.unwrap(SnowflakeAsyncResultSet.class);\n      await().atMost(Duration.ofSeconds(60)).until(() -> !rs.getStatus().isStillRunning());\n      // Query should succeed eventually. Assert this is the case.\n      assertEquals(QueryStatus.Status.SUCCESS, queryStatus.getStatus());\n\n      // assert we get 1 row\n      assertTrue(resultSet.next());\n      assertFalse(resultSet.next());\n      logger.fine(\"Query \" + queryIdx + \" passed \");\n    }\n  }\n\n  /** Test that isValid() function returns false when session is expired */\n  @Test\n  @DontRunOnGithubActions\n  public void testIsValidWithInvalidSession() throws Exception {\n    try (Connection connection = getConnection(\"s3testaccount\")) {\n      // assert that connection starts out valid\n      assertTrue(connection.isValid(5));\n      Thread.sleep(61000); // sleep 61 seconds to await session expiration time\n      // assert that connection is no longer valid after session has expired\n      assertFalse(connection.isValid(5));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/HeartbeatIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AssumptionUtils.isRunningOnGithubActions;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.logging.Logger;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** This test assumes that GS has been set up */\n@Tag(TestTags.OTHERS)\npublic class HeartbeatIT extends AbstractDriverIT {\n  private static Logger logger = Logger.getLogger(HeartbeatIT.class.getName());\n\n  /**\n   * set up\n   *\n   * <p>change the master token validity to 10 seconds change the session token validity to 5\n   * seconds change the SESSION_RECORD_ACCESS_INTERVAL_SECS to 1 second\n   */\n  @BeforeAll\n  public static void setUpClass() throws Exception {\n    if (!isRunningOnGithubActions()) {\n      try (Connection connection = getSnowflakeAdminConnection();\n          Statement statement = connection.createStatement()) {\n        statement.execute(\n            \"alter system set\"\n                + \" master_token_validity=60\"\n                + \",session_token_validity=20\"\n                + \",SESSION_RECORD_ACCESS_INTERVAL_SECS=1\");\n      }\n    }\n  }\n\n  /**\n   * Reset master_token_validity, session_token_validity, SESSION_RECORD_ACCESS_INTERVAL_SECS to\n   * default.\n   */\n  @AfterAll\n  public static void tearDownClass() throws Exception {\n    if (!isRunningOnGithubActions()) {\n      try (Connection connection = getSnowflakeAdminConnection();\n          Statement statement = connection.createStatement()) {\n        statement.execute(\n            \"alter system set\"\n                + \" master_token_validity=default\"\n                + \",session_token_validity=default\"\n                + \",SESSION_RECORD_ACCESS_INTERVAL_SECS=default\");\n      }\n    }\n  }\n\n  /**\n   * create a new connection with or without keep alive session and submit a query that will take\n   * longer than the master token validity.\n   *\n   * @param useKeepAliveSession Enables/disables client session keep alive\n   * @param queryIdx The query index\n   * @throws SQLException Will be thrown if any of the driver calls fail\n   */\n  protected void submitQuery(boolean useKeepAliveSession, int queryIdx)\n      throws SQLException, InterruptedException {\n    ResultSetMetaData resultSetMetaData;\n\n    Properties sessionParams = new Properties();\n    sessionParams.put(\n        \"CLIENT_SESSION_KEEP_ALIVE\",\n        useKeepAliveSession ? Boolean.TRUE.toString() : Boolean.FALSE.toString());\n\n    try (Connection connection = getConnection(\"s3testaccount\", sessionParams);\n        Statement statement = connection.createStatement()) {\n\n      Thread.sleep(61000); // sleep 61 seconds\n      try (ResultSet resultSet = statement.executeQuery(\"SELECT 1\")) {\n        resultSetMetaData = resultSet.getMetaData();\n\n        // assert column count\n        assertEquals(1, resultSetMetaData.getColumnCount());\n\n        // assert we get 1 row\n        assertTrue(resultSet.next());\n\n        logger.fine(\"Query \" + queryIdx + \" passed \");\n      }\n    }\n  }\n\n  /**\n   * Test heartbeat by starting 10 threads. Each get a connection and wait for a time longer than\n   * master token validity and issue a query to make sure the query succeeds.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testSuccess() throws Exception {\n    int concurrency = 10;\n    ExecutorService executorService = Executors.newFixedThreadPool(10);\n    List<Future<?>> futures = new ArrayList<>();\n    // create 10 threads, each open a connection and submit a query\n    // after sleeping 15 seconds\n    for (int idx = 0; idx < concurrency; idx++) {\n      logger.fine(\"open a new connection and submit query \" + idx);\n      final int queryIdx = idx;\n      futures.add(\n          executorService.submit(\n              () -> {\n                try {\n                  submitQuery(true, queryIdx);\n                } catch (SQLException | InterruptedException e) {\n                  throw new IllegalStateException(\"task interrupted\", e);\n                }\n              }));\n    }\n    executorService.shutdown();\n    for (int idx = 0; idx < concurrency; idx++) {\n      futures.get(idx).get();\n    }\n  }\n\n  /**\n   * Test no heartbeat by starting 1 thread. It gets a connection and wait for a time longer than\n   * master token validity and issue a query to make sure the query fails.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testFailure() throws Exception {\n    ExecutorService executorService = Executors.newFixedThreadPool(1);\n    Future<?> future =\n        executorService.submit(\n            () -> {\n              try {\n                submitQuery(false, 0);\n              } catch (SQLException e) {\n                throw new RuntimeSQLException(\"SQLException\", e);\n              } catch (InterruptedException e) {\n                throw new IllegalStateException(\"task interrupted\", e);\n              }\n            });\n    executorService.shutdown();\n    ExecutionException ex = assertThrows(ExecutionException.class, future::get);\n    Throwable rootCause = ex.getCause();\n    assertThat(\"Runtime Exception\", rootCause, instanceOf(RuntimeSQLException.class));\n\n    rootCause = rootCause.getCause();\n\n    assertThat(\"Root cause class\", rootCause, instanceOf(SnowflakeSQLException.class));\n    assertThat(\"Error code\", ((SnowflakeSQLException) rootCause).getErrorCode(), equalTo(390114));\n  }\n\n  class RuntimeSQLException extends RuntimeException {\n    private static final long serialVersionUID = 1L;\n\n    RuntimeSQLException(String message, SQLException e) {\n      super(message, e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/HeartbeatMultiSessionIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Integration test for HeartbeatRegistry with multiple sessions having different heartbeat\n * intervals.\n *\n * <p>This test verifies that the bug fix works: sessions with short intervals are not expired when\n * sessions with long intervals are added.\n */\n@Tag(TestTags.OTHERS)\npublic class HeartbeatMultiSessionIT extends AbstractDriverIT {\n\n  /**\n   * Set up test environment with short token validity.\n   *\n   * <p>Sets master token validity to 60 seconds and session token validity to 20 seconds to make\n   * tests run faster.\n   */\n  private void setupShortTokenValidity() throws Exception {\n    try (Connection connection = getSnowflakeAdminConnection()) {\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\n            \"alter system set\"\n                + \" master_token_validity=60\"\n                + \",session_token_validity=20\"\n                + \",SESSION_RECORD_ACCESS_INTERVAL_SECS=1\");\n      }\n    }\n  }\n\n  /** Reset master_token_validity, session_token_validity to default. */\n  private void restoreDefaultTokenValidity() throws Exception {\n    try (Connection connection = getSnowflakeAdminConnection()) {\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\n            \"alter system set\"\n                + \" master_token_validity=default\"\n                + \",session_token_validity=default\"\n                + \",SESSION_RECORD_ACCESS_INTERVAL_SECS=default\");\n      }\n    }\n  }\n\n  /**\n   * Test that multiple sessions with different heartbeat intervals work independently.\n   *\n   * <p>This tests the critical bug fix: a session with short heartbeat interval should not expire\n   * when a session with long heartbeat interval is added.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testMultipleSessionsDifferentHeartbeatIntervals() throws Exception {\n    try {\n      setupShortTokenValidity();\n      // Connection 1: Short heartbeat (5 seconds)\n      Properties props1 = new Properties();\n      props1.put(\"CLIENT_SESSION_KEEP_ALIVE\", \"true\");\n      props1.put(\"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\", \"5\");\n\n      // Connection 2: Long heartbeat (30 seconds)\n      Properties props2 = new Properties();\n      props2.put(\"CLIENT_SESSION_KEEP_ALIVE\", \"true\");\n      props2.put(\"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\", \"30\");\n\n      try (Connection conn1 = getConnection(\"s3testaccount\", props1);\n          Connection conn2 = getConnection(\"s3testaccount\", props2)) {\n\n        // Verify both connections are initially valid\n        assertTrue(conn1.isValid(5), \"Connection 1 should be valid initially\");\n        assertTrue(conn2.isValid(5), \"Connection 2 should be valid initially\");\n\n        // Sleep 65 seconds (past master token validity of 60s)\n        Thread.sleep(65000);\n\n        // Both connections should still be valid because:\n        // - Connection 1 got heartbeated every 5 seconds\n        // - Connection 2 got heartbeated every 30 seconds\n        // The bug would have caused connection 1 to expire because its\n        // heartbeat would have been delayed to 30 seconds.\n        assertTrue(conn1.isValid(5), \"Connection 1 should still be valid after 65 seconds\");\n        assertTrue(conn2.isValid(5), \"Connection 2 should still be valid after 65 seconds\");\n      }\n    } finally {\n      restoreDefaultTokenValidity();\n    }\n  }\n\n  /**\n   * Test that multiple sessions with the same interval work correctly.\n   *\n   * <p>This verifies that multiple sessions with the same heartbeat interval can coexist and remain\n   * valid. Sessions with identical intervals should share a thread internally, but this is verified\n   * by unit tests rather than integration tests.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testMultipleSessionsSameInterval_ShareThread() throws Exception {\n    try {\n      setupShortTokenValidity();\n      Properties props = new Properties();\n      props.put(\"CLIENT_SESSION_KEEP_ALIVE\", \"true\");\n      props.put(\"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\", \"10\");\n\n      try (Connection conn1 = getConnection(\"s3testaccount\", props);\n          Connection conn2 = getConnection(\"s3testaccount\", props);\n          Connection conn3 = getConnection(\"s3testaccount\", props)) {\n\n        // All connections should be valid\n        assertTrue(conn1.isValid(5));\n        assertTrue(conn2.isValid(5));\n        assertTrue(conn3.isValid(5));\n\n        // Sleep to let heartbeats run\n        Thread.sleep(65000);\n\n        // All should still be valid\n        assertTrue(conn1.isValid(5), \"Connection 1 should still be valid\");\n        assertTrue(conn2.isValid(5), \"Connection 2 should still be valid\");\n        assertTrue(conn3.isValid(5), \"Connection 3 should still be valid\");\n      }\n    } finally {\n      restoreDefaultTokenValidity();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/IntervalDayTimeTypeLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class IntervalDayTimeTypeLatestIT extends BaseJDBCTest {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testIntervalDayTimeConversions(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultFormat)) {\n        // Test Duration conversions with Interval Day-Time SB16\n        ResultSet rsSB16 =\n            stmt.executeQuery(\n                \"SELECT '999999999 23:59:59.999999999'::INTERVAL DAY TO SECOND, '-999999999 23:59:59.999999999'::INTERVAL DAY TO SECOND, NULL::INTERVAL DAY TO SECOND\");\n        assertTrue(rsSB16.next());\n        Duration durationValueMaxSB16 = rsSB16.getObject(1, Duration.class);\n        assertEquals(Duration.parse(\"P999999999DT23H59M59.999999999S\"), durationValueMaxSB16);\n        Duration durationValueMinSB16 = rsSB16.getObject(2, Duration.class);\n        assertEquals(Duration.parse(\"-P999999999DT23H59M59.999999999S\"), durationValueMinSB16);\n        Duration nullDurationSB16 = rsSB16.getObject(3, Duration.class);\n        assertNull(nullDurationSB16);\n\n        // Test Duration conversions with Interval Day-Time SB8\n        ResultSet rsSB8 =\n            stmt.executeQuery(\n                \"SELECT '0 0:0:0.1'::INTERVAL DAY(3) TO SECOND, NULL::INTERVAL DAY(3) TO SECOND\");\n        assertTrue(rsSB8.next());\n\n        Duration durationValueSB8 = rsSB8.getObject(1, Duration.class);\n        assertEquals(Duration.ofNanos(100_000_000), durationValueSB8);\n        Duration nullDurationSB8 = rsSB8.getObject(2, Duration.class);\n        assertNull(nullDurationSB8);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testIntervalDayTimeBindingBasicTypes(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement ignored = createStatement(con, queryResultFormat)) {\n        try (PreparedStatement ps =\n            con.prepareStatement(\n                \"SELECT ?::INTERVAL DAY TO SECOND, ?::INTERVAL DAY TO MINUTE, ?::INTERVAL DAY TO HOUR, ?::INTERVAL DAY, ?::INTERVAL HOUR TO SECOND, ?::INTERVAL HOUR TO MINUTE, ?::INTERVAL HOUR, ?::INTERVAL MINUTE TO SECOND, ?::INTERVAL MINUTE, ?::INTERVAL SECOND, ?::INTERVAL DAY TO SECOND\")) {\n\n          ps.setObject(1, \"0 0:0:1.2\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(2, \"-999999999 2:3\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(3, \"999999999 2\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(4, \"1\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(5, \"999999999:1:3.56\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(6, \"-999999999:3\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(7, \"5\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(8, \"-999999999:2.999999\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(9, \"4\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setObject(10, \"-999999999.999999\", SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n          ps.setNull(11, SnowflakeType.EXTRA_TYPES_DAY_TIME_INTERVAL);\n\n          try (ResultSet rs = ps.executeQuery()) {\n            assertTrue(rs.next());\n            assertEquals(Duration.ofNanos(1_200_000_000), rs.getObject(1, Duration.class));\n            assertEquals(Duration.parse(\"-P999999999DT2H3M\"), rs.getObject(2, Duration.class));\n            assertEquals(Duration.parse(\"P999999999DT2H\"), rs.getObject(3, Duration.class));\n            assertEquals(Duration.parse(\"P1D\"), rs.getObject(4, Duration.class));\n            assertEquals(Duration.parse(\"PT999999999H1M3.56S\"), rs.getObject(5, Duration.class));\n            assertEquals(Duration.parse(\"-PT999999999H3M\"), rs.getObject(6, Duration.class));\n            assertEquals(Duration.parse(\"PT5H\"), rs.getObject(7, Duration.class));\n            assertEquals(Duration.parse(\"-PT999999999M2.999999S\"), rs.getObject(8, Duration.class));\n            assertEquals(Duration.parse(\"PT4M\"), rs.getObject(9, Duration.class));\n            assertEquals(Duration.parse(\"-PT999999999.999999S\"), rs.getObject(10, Duration.class));\n            assertNull(rs.getObject(11));\n          }\n        }\n      }\n    }\n  }\n\n  private Statement createStatement(Connection connection, String queryResultFormat)\n      throws SQLException {\n    Statement stmt = connection.createStatement();\n    stmt.execute(\"ALTER SESSION SET JDBC_QUERY_RESULT_FORMAT = '\" + queryResultFormat + \"'\");\n    stmt.execute(\"ALTER SESSION SET FEATURE_INTERVAL_TYPES = enabled\");\n    return stmt;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/IntervalYearMonthTypeLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.time.Period;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class IntervalYearMonthTypeLatestIT extends BaseJDBCTest {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testIntervalYearMonthConversions(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement stmt = createStatement(con, queryResultFormat)) {\n        // Test Period conversions with Interval Year-Month SB8\n        ResultSet rsSB8 =\n            stmt.executeQuery(\n                \"SELECT '999999999-11'::INTERVAL YEAR TO MONTH, '-999999999-11'::INTERVAL YEAR TO MONTH, NULL::INTERVAL YEAR TO MONTH\");\n        assertTrue(rsSB8.next());\n\n        Period periodValueMaxSB8 = rsSB8.getObject(1, Period.class);\n        assertEquals(Period.of(999999999, 11, 0), periodValueMaxSB8);\n        Period periodValueMinSB8 = rsSB8.getObject(2, Period.class);\n        assertEquals(Period.of(-999999999, -11, 0), periodValueMinSB8);\n        Period nullPeriodSB8 = rsSB8.getObject(3, Period.class);\n        assertNull(nullPeriodSB8);\n\n        // Test Period conversions with Interval Year-Month SB4\n        ResultSet rsSB4 =\n            stmt.executeQuery(\n                \"SELECT '1-2'::INTERVAL YEAR(7) TO MONTH, NULL::INTERVAL YEAR(7) TO MONTH\");\n        assertTrue(rsSB4.next());\n\n        Period periodValueSB4 = rsSB4.getObject(1, Period.class);\n        assertEquals(Period.of(1, 2, 0), periodValueSB4);\n        Period nullPeriodSB4 = rsSB4.getObject(2, Period.class);\n        assertNull(nullPeriodSB4);\n\n        // Test Period conversions with Interval Year-Month SB2\n        ResultSet rsSB2 =\n            stmt.executeQuery(\n                \"SELECT '1-2'::INTERVAL YEAR(2) TO MONTH, NULL::INTERVAL YEAR(2) TO MONTH\");\n        assertTrue(rsSB2.next());\n\n        Period periodValueSB2 = rsSB2.getObject(1, Period.class);\n        assertEquals(Period.of(1, 2, 0), periodValueSB2);\n        Period nullPeriodSB2 = rsSB2.getObject(2, Period.class);\n        assertNull(nullPeriodSB2);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testIntervalYearMonthBindingBasicTypes(String queryResultFormat) throws SQLException {\n    try (Connection con = getConnection()) {\n      try (Statement ignored = createStatement(con, queryResultFormat)) {\n        try (PreparedStatement ps =\n            con.prepareStatement(\n                \"SELECT ?::INTERVAL YEAR TO MONTH, ?::INTERVAL YEAR TO MONTH, ?::INTERVAL YEAR, ?::INTERVAL MONTH, ?::INTERVAL YEAR TO MONTH\")) {\n\n          ps.setObject(1, \"999999999-11\", SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL);\n          ps.setObject(2, \"-999999999-11\", SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL);\n          ps.setObject(3, \"2\", SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL);\n          ps.setObject(4, \"5\", SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL);\n          ps.setNull(5, SnowflakeType.EXTRA_TYPES_YEAR_MONTH_INTERVAL);\n\n          try (ResultSet rs = ps.executeQuery()) {\n            assertTrue(rs.next());\n            assertEquals(Period.of(999999999, 11, 0), rs.getObject(1, Period.class));\n            assertEquals(Period.of(-999999999, -11, 0), rs.getObject(2, Period.class));\n            assertEquals(Period.of(2, 0, 0), rs.getObject(3, Period.class));\n            assertEquals(Period.of(0, 5, 0), rs.getObject(4, Period.class));\n            assertNull(rs.getObject(5));\n          }\n        }\n      }\n    }\n  }\n\n  private Statement createStatement(Connection connection, String queryResultFormat)\n      throws SQLException {\n    Statement stmt = connection.createStatement();\n    stmt.execute(\"ALTER SESSION SET JDBC_QUERY_RESULT_FORMAT = '\" + queryResultFormat + \"'\");\n    stmt.execute(\"ALTER SESSION SET FEATURE_INTERVAL_TYPES = enabled\");\n    return stmt;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/LobSizeLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.stream.Stream;\nimport net.snowflake.client.annotations.DontRunOnJenkins;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.UUIDUtils;\nimport org.apache.commons.text.RandomStringGenerator;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class LobSizeLatestIT extends BaseJDBCTest {\n\n  private static final Logger logger = Logger.getLogger(SnowflakeDriverIT.class.getName());\n  private static final Map<Integer, String> LobSizeStringValues = new HashMap<>();\n\n  // Max LOB size is testable from version 3.15.0 and above.\n  private static int maxLobSize = 16 * 1024 * 1024; // default value\n  private static int largeLobSize = maxLobSize / 2;\n  private static int mediumLobSize = largeLobSize / 2;\n  private static int smallLobSize = 16;\n  private static int originLobSize = 16 * 1024 * 1024;\n\n  @BeforeAll\n  public static void setUp() throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection()) {\n      // get max LOB size from session\n      maxLobSize = con.getMetaData().getMaxCharLiteralLength();\n      logger.log(Level.INFO, \"Using max lob size: \" + maxLobSize);\n      System.setProperty(\n          // the max json string should be ~1.33 for Arrow response so let's use 1.5 to be sure\n          ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM,\n          Integer.toString((int) (maxLobSize * 1.5)));\n      LobSizeStringValues.put(smallLobSize, generateRandomString(smallLobSize));\n      LobSizeStringValues.put(originLobSize, generateRandomString(originLobSize));\n      LobSizeStringValues.put(mediumLobSize, generateRandomString(mediumLobSize));\n      LobSizeStringValues.put(largeLobSize, generateRandomString(largeLobSize));\n      LobSizeStringValues.put(maxLobSize, generateRandomString(maxLobSize));\n    }\n  }\n\n  static class DataProvider implements ArgumentsProvider {\n\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      int[] lobSizes =\n          new int[] {smallLobSize, originLobSize, mediumLobSize, largeLobSize, maxLobSize};\n      String[] resultFormats = new String[] {\"Arrow\", \"JSON\"};\n      List<Arguments> ret = new ArrayList<>();\n      for (int size : lobSizes) {\n        for (String format : resultFormats) {\n          ret.add(Arguments.of(size, format));\n        }\n      }\n      return ret.stream();\n    }\n  }\n\n  private static String tableName = \"my_lob_test\";\n  private static String executeInsert = \"insert into \" + tableName + \" (c1, c2, c3) values (\";\n  private static String executePreparedStatementInsert = executeInsert + \"?, ?, ?)\";\n  private static String selectQuery = \"select * from \" + tableName + \" where c3=\";\n\n  private static String generateRandomString(int stringSize) {\n    RandomStringGenerator randomStringGenerator =\n        new RandomStringGenerator.Builder().withinRange('a', 'z').build();\n    return randomStringGenerator.generate(stringSize);\n  }\n\n  private static void setResultFormat(Statement stmt, String format) throws SQLException {\n    stmt.execute(\"alter session set jdbc_query_result_format = '\" + format + \"'\");\n  }\n\n  private void createTable(int lobSize, Statement stmt) throws SQLException {\n    String createTableQuery =\n        \"create or replace table \"\n            + tableName\n            + \" (c1 varchar, c2 varchar(\"\n            + lobSize\n            + \"), c3 varchar)\";\n    stmt.execute(createTableQuery);\n  }\n\n  private void insertQuery(String varCharValue, String uuidValue, Statement stmt)\n      throws SQLException {\n    stmt.executeUpdate(executeInsert + \"'abc', '\" + varCharValue + \"', '\" + uuidValue + \"')\");\n  }\n\n  private void preparedInsertQuery(String varCharValue, String uuidValue, Connection con)\n      throws SQLException {\n    try (PreparedStatement pstmt = con.prepareStatement(executePreparedStatementInsert)) {\n      pstmt.setString(1, \"abc\");\n      pstmt.setString(2, varCharValue);\n      pstmt.setString(3, uuidValue);\n\n      pstmt.execute();\n    }\n  }\n\n  @AfterAll\n  public static void tearDown() throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"Drop table if exists \" + tableName);\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnJenkins // the MxLobParameters isn't configured properly on new environment\n  public void testStandardInsertAndSelectWithMaxLobSizeEnabled(int lobSize, String resultFormat)\n      throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      createTable(lobSize, stmt);\n      setResultFormat(stmt, resultFormat);\n\n      String varCharValue = LobSizeStringValues.get(lobSize);\n      String uuidValue = UUIDUtils.getUUID().toString();\n      insertQuery(varCharValue, uuidValue, stmt);\n\n      try (ResultSet rs = stmt.executeQuery(selectQuery + \"'\" + uuidValue + \"'\")) {\n        assertTrue(rs.next());\n        assertEquals(\"abc\", rs.getString(1));\n        assertEquals(varCharValue, rs.getString(2));\n        assertEquals(uuidValue, rs.getString(3));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnJenkins // the MxLobParameters isn't configured properly on new environment\n  public void testPreparedInsertWithMaxLobSizeEnabled(int lobSize, String resultFormat)\n      throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      createTable(lobSize, stmt);\n      setResultFormat(stmt, resultFormat);\n\n      String maxVarCharValue = LobSizeStringValues.get(lobSize);\n      String uuidValue = UUIDUtils.getUUID().toString();\n      preparedInsertQuery(maxVarCharValue, uuidValue, con);\n\n      try (ResultSet rs = stmt.executeQuery(selectQuery + \"'\" + uuidValue + \"'\")) {\n        assertTrue(rs.next());\n        assertEquals(\"abc\", rs.getString(1));\n        assertEquals(maxVarCharValue, rs.getString(2));\n        assertEquals(uuidValue, rs.getString(3));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnJenkins // the MxLobParameters isn't configured properly on new environment\n  public void testPutAndGet(int lobSize, String resultFormat) throws IOException, SQLException {\n    File tempFile = File.createTempFile(\"LobSizeTest\", \".csv\");\n    // Delete file when JVM shuts down\n    tempFile.deleteOnExit();\n\n    String filePath = tempFile.getPath();\n    String filePathEscaped = filePath.replace(\"\\\\\", \"\\\\\\\\\");\n    String fileName = tempFile.getName();\n\n    String varCharValue = LobSizeStringValues.get(lobSize);\n    String uuidValue = UUIDUtils.getUUID().toString();\n    String fileInput = \"abc,\" + varCharValue + \",\" + uuidValue;\n\n    // Print data to new temporary file\n    try (PrintWriter out = new PrintWriter(filePath)) {\n      out.println(fileInput);\n    }\n\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      createTable(lobSize, stmt);\n      setResultFormat(stmt, resultFormat);\n      // Test PUT\n      String sqlPut = \"PUT 'file://\" + filePathEscaped + \"' @%\" + tableName;\n\n      stmt.execute(sqlPut);\n\n      try (ResultSet rsPut = stmt.getResultSet()) {\n        assertTrue(rsPut.next());\n        assertEquals(fileName, rsPut.getString(1));\n        assertEquals(fileName + \".gz\", rsPut.getString(2));\n        assertEquals(\"GZIP\", rsPut.getString(6));\n        assertEquals(\"UPLOADED\", rsPut.getString(7));\n      }\n\n      try (ResultSet rsFiles = stmt.executeQuery(\"ls @%\" + tableName)) {\n        // ResultSet should return a row with the zipped file name\n        assertTrue(rsFiles.next());\n        assertEquals(fileName + \".gz\", rsFiles.getString(1));\n      }\n\n      String copyInto =\n          \"copy into \"\n              + tableName\n              + \" from @%\"\n              + tableName\n              + \" file_format=(type=csv compression='gzip')\";\n      stmt.execute(copyInto);\n\n      // Check that results are copied into table correctly\n      try (ResultSet rsCopy = stmt.executeQuery(selectQuery + \"'\" + uuidValue + \"'\")) {\n        assertTrue(rsCopy.next());\n        assertEquals(\"abc\", rsCopy.getString(1));\n        assertEquals(varCharValue, rsCopy.getString(2));\n        assertEquals(uuidValue, rsCopy.getString(3));\n      }\n\n      // Test Get\n      Path tempDir = Files.createTempDirectory(\"MaxLobTest\");\n      // Delete tempDir when JVM shuts down\n      tempDir.toFile().deleteOnExit();\n      String pathToTempDir = tempDir.toString().replace(\"\\\\\", \"\\\\\\\\\");\n\n      String getSql = \"get @%\" + tableName + \" 'file://\" + pathToTempDir + \"'\";\n      stmt.execute(getSql);\n\n      try (ResultSet rsGet = stmt.getResultSet()) {\n        assertTrue(rsGet.next());\n        assertEquals(fileName + \".gz\", rsGet.getString(1));\n        assertEquals(\"DOWNLOADED\", rsGet.getString(3));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/MaxLobSizeLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.is;\nimport static org.hamcrest.Matchers.not;\nimport static org.hamcrest.text.IsEmptyString.emptyOrNullString;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport org.hamcrest.CoreMatchers;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class MaxLobSizeLatestIT extends BaseJDBCTest {\n\n  /**\n   * Available since 3.17.0\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testIncreasedMaxLobSize() throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"alter session set FEATURE_INCREASED_MAX_LOB_SIZE_IN_MEMORY='ENABLED'\");\n      stmt.execute(\"alter session set ENABLE_LARGE_VARCHAR_AND_BINARY_IN_RESULT=false\");\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> stmt.execute(\"select randstr(20000000, random()) as large_str\"));\n      assertThat(e.getMessage(), CoreMatchers.containsString(\"exceeds supported length\"));\n\n      stmt.execute(\"alter session set ENABLE_LARGE_VARCHAR_AND_BINARY_IN_RESULT=true\");\n      try (ResultSet resultSet =\n          stmt.executeQuery(\"select randstr(20000000, random()) as large_str\")) {\n        assertTrue(resultSet.next());\n        assertThat(resultSet.getString(1), is(not(emptyOrNullString())));\n      } finally {\n        stmt.execute(\"alter session unset ENABLE_LARGE_VARCHAR_AND_BINARY_IN_RESULT\");\n        stmt.execute(\"alter session unset FEATURE_INCREASED_MAX_LOB_SIZE_IN_MEMORY\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/MockConnectionTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.sql.Connection;\nimport java.sql.DriverPropertyInfo;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.SQLNonTransientConnectionException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport java.util.concurrent.Future;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.connection.UploadStreamConfig;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.common.core.SFBinaryFormat;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.QueryContextDTO;\nimport net.snowflake.client.internal.core.ResultUtil;\nimport net.snowflake.client.internal.core.SFBaseResultSet;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFJsonResultSet;\nimport net.snowflake.client.internal.core.SFPreparedStatementMetaData;\nimport net.snowflake.client.internal.core.SFResultSetMetaData;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatementType;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.core.json.Converters;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.common.core.SnowflakeDateTimeFormat;\nimport org.junit.jupiter.api.Test;\n\n/**\n * IT test for testing the \"pluggable\" implementation of SnowflakeConnection, SnowflakeStatement,\n * and ResultSet. These tests will query Snowflake normally, retrieve the JSON result, and replay it\n * back using a custom implementation of these objects that simply echoes a given JSON response.\n */\n// TODO: SNOW-1821554\n// @Tag(TestTags.CONNECTION)\npublic class MockConnectionTest extends BaseJDBCTest {\n\n  // Simple pair class container for the error test.\n  private static class Pair {\n    String first;\n    String second;\n\n    Pair(String first, String second) {\n      this.first = first;\n      this.second = second;\n    }\n  }\n\n  private static final String testTableName = \"test_custom_conn_table\";\n\n  private static SFResultSetMetaData getRSMDFromResponse(JsonNode rootNode, SFBaseSession sfSession)\n      throws SnowflakeSQLException {\n\n    String queryId = rootNode.path(\"data\").path(\"queryId\").asText();\n\n    Map<String, Object> parameters =\n        SessionUtil.getCommonParams(rootNode.path(\"data\").path(\"parameters\"));\n\n    String sqlTimestampFormat =\n        (String) ResultUtil.effectiveParamValue(parameters, \"TIMESTAMP_OUTPUT_FORMAT\");\n\n    // Special handling of specialized formatters, use a helper function\n    SnowflakeDateTimeFormat ntzFormat =\n        ResultUtil.specializedFormatter(\n            parameters, \"timestamp_ntz\", \"TIMESTAMP_NTZ_OUTPUT_FORMAT\", sqlTimestampFormat);\n\n    SnowflakeDateTimeFormat ltzFormat =\n        ResultUtil.specializedFormatter(\n            parameters, \"timestamp_ltz\", \"TIMESTAMP_LTZ_OUTPUT_FORMAT\", sqlTimestampFormat);\n\n    SnowflakeDateTimeFormat tzFormat =\n        ResultUtil.specializedFormatter(\n            parameters, \"timestamp_tz\", \"TIMESTAMP_TZ_OUTPUT_FORMAT\", sqlTimestampFormat);\n\n    String sqlDateFormat =\n        (String) ResultUtil.effectiveParamValue(parameters, \"DATE_OUTPUT_FORMAT\");\n\n    SnowflakeDateTimeFormat dateFormatter =\n        SnowflakeDateTimeFormat.fromSqlFormat(Objects.requireNonNull(sqlDateFormat));\n\n    String sqlTimeFormat =\n        (String) ResultUtil.effectiveParamValue(parameters, \"TIME_OUTPUT_FORMAT\");\n\n    SnowflakeDateTimeFormat timeFormatter =\n        SnowflakeDateTimeFormat.fromSqlFormat(Objects.requireNonNull(sqlTimeFormat));\n\n    List<SnowflakeColumnMetadata> resultColumnMetadata = new ArrayList<>();\n    int columnCount = rootNode.path(\"data\").path(\"rowtype\").size();\n    for (int i = 0; i < columnCount; i++) {\n      JsonNode colNode = rootNode.path(\"data\").path(\"rowtype\").path(i);\n\n      SnowflakeColumnMetadata columnMetadata =\n          SnowflakeUtil.extractColumnMetadata(\n              colNode, sfSession.isJdbcTreatDecimalAsInt(), sfSession);\n\n      resultColumnMetadata.add(columnMetadata);\n    }\n\n    return new SFResultSetMetaData(\n        resultColumnMetadata,\n        queryId,\n        sfSession,\n        false,\n        ntzFormat,\n        ltzFormat,\n        tzFormat,\n        dateFormatter,\n        timeFormatter);\n  }\n\n  private static ObjectNode getJsonFromDataType(DataType dataType) {\n\n    ObjectMapper mapper = new ObjectMapper();\n    ObjectNode type = mapper.createObjectNode();\n\n    if (dataType == DataType.INT) {\n      type.put(\"name\", \"someIntColumn\");\n      type.put(\"database\", \"\");\n      type.put(\"schema\", \"\");\n      type.put(\"table\", \"\");\n      type.put(\"scale\", 0);\n      type.put(\"precision\", 18);\n      type.put(\"type\", \"fixed\");\n      type.put(\"length\", (Integer) null);\n      type.put(\"byteLength\", (Integer) null);\n      type.put(\"nullable\", true);\n      type.put(\"collation\", (String) null);\n    } else if (dataType == DataType.STRING) {\n      type.put(\"name\", \"someStringColumn\");\n      type.put(\"database\", \"\");\n      type.put(\"schema\", \"\");\n      type.put(\"table\", \"\");\n      type.put(\"scale\", (Integer) null);\n      type.put(\"precision\", (Integer) null);\n      type.put(\"length\", 16777216);\n      type.put(\"type\", \"text\");\n      type.put(\"byteLength\", 16777216);\n      type.put(\"nullable\", true);\n      type.put(\"collation\", (String) null);\n    }\n\n    return type;\n  }\n\n  public Connection initMockConnection(SFConnectionHandler implementation) throws SQLException {\n    return new SnowflakeConnectionImpl(implementation);\n  }\n\n  /**\n   * Test running some queries, and plugging in the raw JSON response from those queries into a\n   * MockConnection. The results retrieved from the MockConnection should be the same as those from\n   * the original connection.\n   */\n  @Test\n  public void testMockResponse() throws SQLException, JsonProcessingException {\n    ObjectMapper mapper = new ObjectMapper();\n    JsonNode rawResponse =\n        mapper.readTree(\n            \"{\\n\"\n                + \"   \\\"data\\\":{\\n\"\n                + \"      \\\"parameters\\\":[\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"TIMESTAMP_OUTPUT_FORMAT\\\",\\n\"\n                + \"            \\\"value\\\":\\\"DY, DD MON YYYY HH24:MI:SS TZHTZM\\\"\\n\"\n                + \"         },\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"TIME_OUTPUT_FORMAT\\\",\\n\"\n                + \"            \\\"value\\\":\\\"HH24:MI:SS\\\"\\n\"\n                + \"         },\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"TIMESTAMP_TZ_OUTPUT_FORMAT\\\",\\n\"\n                + \"            \\\"value\\\":\\\"\\\"\\n\"\n                + \"         },\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"TIMESTAMP_NTZ_OUTPUT_FORMAT\\\",\\n\"\n                + \"            \\\"value\\\":\\\"\\\"\\n\"\n                + \"         },\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"DATE_OUTPUT_FORMAT\\\",\\n\"\n                + \"            \\\"value\\\":\\\"YYYY-MM-DD\\\"\\n\"\n                + \"         },\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"TIMESTAMP_LTZ_OUTPUT_FORMAT\\\",\\n\"\n                + \"            \\\"value\\\":\\\"\\\"\\n\"\n                + \"         }\\n\"\n                + \"      ],\\n\"\n                + \"      \\\"rowtype\\\":[\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"COLA\\\",\\n\"\n                + \"            \\\"database\\\":\\\"TESTDB\\\",\\n\"\n                + \"            \\\"schema\\\":\\\"TESTSCHEMA\\\",\\n\"\n                + \"            \\\"table\\\":\\\"TEST_CUSTOM_CONN_TABLE\\\",\\n\"\n                + \"            \\\"scale\\\":null,\\n\"\n                + \"            \\\"precision\\\":null,\\n\"\n                + \"            \\\"length\\\":16777216,\\n\"\n                + \"            \\\"type\\\":\\\"text\\\",\\n\"\n                + \"            \\\"byteLength\\\":16777216,\\n\"\n                + \"            \\\"nullable\\\":true,\\n\"\n                + \"            \\\"collation\\\":null\\n\"\n                + \"         },\\n\"\n                + \"         {\\n\"\n                + \"            \\\"name\\\":\\\"COLB\\\",\\n\"\n                + \"            \\\"database\\\":\\\"TESTDB\\\",\\n\"\n                + \"            \\\"schema\\\":\\\"TESTSCHEMA\\\",\\n\"\n                + \"            \\\"table\\\":\\\"TEST_CUSTOM_CONN_TABLE\\\",\\n\"\n                + \"            \\\"scale\\\":0,\\n\"\n                + \"            \\\"precision\\\":38,\\n\"\n                + \"            \\\"length\\\":null,\\n\"\n                + \"            \\\"type\\\":\\\"fixed\\\",\\n\"\n                + \"            \\\"byteLength\\\":null,\\n\"\n                + \"            \\\"nullable\\\":true,\\n\"\n                + \"            \\\"collation\\\":null\\n\"\n                + \"         }\\n\"\n                + \"      ],\\n\"\n                + \"      \\\"rowset\\\":[\\n\"\n                + \"         [\\n\"\n                + \"            \\\"rowOne\\\",\\n\"\n                + \"            \\\"1\\\"\\n\"\n                + \"         ],\\n\"\n                + \"         [\\n\"\n                + \"            \\\"rowTwo\\\",\\n\"\n                + \"            \\\"2\\\"\\n\"\n                + \"         ]\\n\"\n                + \"      ],\\n\"\n                + \"      \\\"total\\\":2,\\n\"\n                + \"      \\\"returned\\\":2,\\n\"\n                + \"      \\\"queryId\\\":\\\"0199922f-015a-7715-0000-0014000123ca\\\",\\n\"\n                + \"      \\\"databaseProvider\\\":null,\\n\"\n                + \"      \\\"finalDatabaseName\\\":\\\"TESTDB\\\",\\n\"\n                + \"      \\\"finalSchemaName\\\":\\\"TESTSCHEMA\\\",\\n\"\n                + \"      \\\"finalWarehouseName\\\":\\\"DEV\\\",\\n\"\n                + \"      \\\"finalRoleName\\\":\\\"SYSADMIN\\\",\\n\"\n                + \"      \\\"numberOfBinds\\\":0,\\n\"\n                + \"      \\\"arrayBindSupported\\\":false,\\n\"\n                + \"      \\\"statementTypeId\\\":4096,\\n\"\n                + \"      \\\"version\\\":1,\\n\"\n                + \"      \\\"sendResultTime\\\":1610498856446,\\n\"\n                + \"      \\\"queryResultFormat\\\":\\\"json\\\"\\n\"\n                + \"   },\\n\"\n                + \"   \\\"code\\\":null,\\n\"\n                + \"   \\\"message\\\":null,\\n\"\n                + \"   \\\"success\\\":true\\n\"\n                + \"}\");\n\n    SFConnectionHandler mockImpl = new MockSnowflakeConnectionImpl(rawResponse);\n    Connection mockConnection = initMockConnection(mockImpl);\n\n    ResultSet fakeResultSet =\n        mockConnection.prepareStatement(\"select count(*) from \" + testTableName).executeQuery();\n    fakeResultSet.next();\n    String val = fakeResultSet.getString(1);\n    assertEquals(\"rowOne\", val, \"colA value from the mock connection was not what was expected\");\n\n    mockConnection.close();\n  }\n\n  @Test\n  public void testErrorHandlingWithLoggedExceptions() throws SQLException, JsonProcessingException {\n    ObjectMapper mapper = new ObjectMapper();\n    JsonNode ignoredResponse = mapper.readTree(\"{}\");\n\n    SFConnectionHandler mockImpl = new MockSnowflakeConnectionImpl(ignoredResponse);\n    Connection mockConnection = initMockConnection(mockImpl);\n\n    MockSnowflakeSFSession mockSession =\n        (MockSnowflakeSFSession) ((SnowflakeConnectionImpl) mockConnection).getSFBaseSession();\n\n    List<Pair> errors = new ArrayList<>();\n    // First part is the error code, second is the reason/message\n    // Create a bunch of exceptions with code and message\n    errors.add(new Pair(\"12345\", \"message1\"));\n    errors.add(new Pair(\"56789\", \"message2\"));\n    errors.add(new Pair(\"43\", \"foo\"));\n    errors.add(new Pair(\"9\", \"bar\"));\n\n    for (Pair err : errors) {\n      try {\n        throw new SnowflakeSQLLoggedException(mockSession, err.first, err.second);\n      } catch (SQLException e) {\n        // Do nothing here, we just don't want this to crash. We will check the errors list later to\n        // verify\n        // handler operation.\n      }\n    }\n\n    // Grab the errors logged by the session. Our session handler code\n    // will simply append the errors to this list.\n    List<String> loggedErrors = mockSession.getErrorsEncountered();\n    assertEquals(loggedErrors.size(), errors.size());\n\n    // Craft what we expect\n    List<String> expectedErrorMessages =\n        errors.stream()\n            .map(\n                err -> {\n                  return err.second + \"_\" + err.first;\n                })\n            .collect(Collectors.toList());\n\n    List<Pair> zippedList =\n        IntStream.range(0, Math.min(loggedErrors.size(), expectedErrorMessages.size()))\n            .mapToObj(i -> new Pair(expectedErrorMessages.get(i), loggedErrors.get(i)))\n            .collect(Collectors.toList());\n\n    // Check equality of strings in the logged messages.\n    zippedList.forEach(\n        err -> {\n          assertEquals(err.first, err.second);\n        });\n\n    mockConnection.close();\n  }\n\n  /**\n   * Fabricates fake JSON responses with some int data, and asserts the correct results via\n   * retrieval from MockJsonResultSet\n   */\n  @Test\n  public void testMockedResponseWithRows() throws SQLException {\n    // Test with some ints\n    List<DataType> dataTypes = Arrays.asList(DataType.INT, DataType.INT, DataType.INT);\n    List<Object> row1 = Arrays.asList(1, 2, null);\n    List<Object> row2 = Arrays.asList(4, null, 6);\n    List<List<Object>> rowsToTest = Arrays.asList(row1, row2);\n\n    JsonNode responseWithRows = createDummyResponseWithRows(rowsToTest, dataTypes);\n\n    SFConnectionHandler mockImpl = new MockSnowflakeConnectionImpl(responseWithRows);\n    Connection mockConnection = initMockConnection(mockImpl);\n\n    ResultSet fakeResultSet =\n        mockConnection.prepareStatement(\"select * from fakeTable\").executeQuery();\n    compareResultSets(fakeResultSet, rowsToTest, dataTypes);\n\n    mockConnection.close();\n\n    // Now test with some strings\n    dataTypes = Arrays.asList(DataType.STRING, DataType.STRING);\n    row1 = Arrays.asList(\"hi\", \"bye\");\n    row2 = Arrays.asList(null, \"snowflake\");\n    List<Object> row3 = Arrays.asList(\"is\", \"great\");\n    rowsToTest = Arrays.asList(row1, row2, row3);\n\n    responseWithRows = createDummyResponseWithRows(rowsToTest, dataTypes);\n\n    mockImpl = new MockSnowflakeConnectionImpl(responseWithRows);\n    mockConnection = initMockConnection(mockImpl);\n\n    fakeResultSet = mockConnection.prepareStatement(\"select * from fakeTable\").executeQuery();\n    compareResultSets(fakeResultSet, rowsToTest, dataTypes);\n\n    mockConnection.close();\n\n    // Mixed data\n    dataTypes = Arrays.asList(DataType.STRING, DataType.INT);\n    row1 = Arrays.asList(\"foo\", 2);\n    row2 = Arrays.asList(\"bar\", 4);\n    row3 = Arrays.asList(\"baz\", null);\n    rowsToTest = Arrays.asList(row1, row2, row3);\n\n    responseWithRows = createDummyResponseWithRows(rowsToTest, dataTypes);\n\n    mockImpl = new MockSnowflakeConnectionImpl(responseWithRows);\n    mockConnection = initMockConnection(mockImpl);\n\n    fakeResultSet = mockConnection.prepareStatement(\"select * from fakeTable\").executeQuery();\n    compareResultSets(fakeResultSet, rowsToTest, dataTypes);\n\n    mockConnection.close();\n  }\n\n  /** Tests the MockFileTransferInterface with PUT/GET on random byte arrays. */\n  @Test\n  public void testMockTransferAgent() throws SQLException, IOException {\n    SFConnectionHandler mockImpl = new MockSnowflakeConnectionImpl();\n    SnowflakeConnection mockConnection =\n        initMockConnection(mockImpl).unwrap(SnowflakeConnectionImpl.class);\n\n    byte[] inputBytes1 = new byte[] {0, 1, 2};\n    InputStream uploadStream1 = new ByteArrayInputStream(inputBytes1);\n    mockConnection.uploadStream(\n        \"@fakeStage\",\n        \"file1\",\n        uploadStream1,\n        UploadStreamConfig.builder().setDestPrefix(\"\").setCompressData(false).build());\n\n    InputStream downloadStream1 =\n        mockConnection.downloadStream(\n            \"@fakeStage\", \"file1\", DownloadStreamConfig.builder().setDecompress(false).build());\n    byte[] outputBytes1 = new byte[downloadStream1.available()];\n    downloadStream1.read(outputBytes1);\n    assertArrayEquals(outputBytes1, inputBytes1, \"downloaded bytes not what was expected\");\n  }\n\n  private JsonNode createDummyResponseWithRows(List<List<Object>> rows, List<DataType> dataTypes) {\n    ObjectMapper mapper = new ObjectMapper();\n    ObjectNode rootNode = mapper.createObjectNode();\n    ObjectNode dataNode = rootNode.putObject(\"data\");\n\n    createResultSetMetadataResponse(dataNode, dataTypes);\n    createRowsetJson(dataNode, rows, dataTypes);\n\n    return rootNode;\n  }\n\n  /**\n   * Creates the metadata portion of the response, i.e.,\n   *\n   * <p>parameters: [time format, date format, timestamp format, timestamp_ltz format, timestamp_tz\n   * format, timestamp_ntz format, ] queryId rowType\n   *\n   * @param dataNode ObjectNode representing the \"data\" portion of the JSON response\n   * @param dataTypes datatypes of the rows used in the generated response\n   */\n  private void createResultSetMetadataResponse(ObjectNode dataNode, List<DataType> dataTypes) {\n    ArrayNode parameters = dataNode.putArray(\"parameters\");\n\n    parameters.add(createParameterJson(\"TIME_OUTPUT_FORMAT\", \"HH24:MI:SS\"));\n    parameters.add(createParameterJson(\"DATE_OUTPUT_FORMAT\", \"YYYY-MM-DD\"));\n    parameters.add(\n        createParameterJson(\"TIMESTAMP_OUTPUT_FORMAT\", \"DY, DD MON YYYY HH24:MI:SS TZHTZM\"));\n    parameters.add(createParameterJson(\"TIMESTAMP_LTZ_OUTPUT_FORMAT\", \"\"));\n    parameters.add(createParameterJson(\"TIMESTAMP_NTZ_OUTPUT_FORMAT\", \"\"));\n    parameters.add(createParameterJson(\"TIMESTAMP_TZ_OUTPUT_FORMAT\", \"\"));\n\n    dataNode.put(\"queryId\", \"81998ae8-01e5-e08d-0000-10140001201a\");\n\n    ArrayNode rowType = dataNode.putArray(\"rowtype\");\n\n    for (DataType type : dataTypes) {\n      rowType.add(getJsonFromDataType(type));\n    }\n  }\n\n  /**\n   * Creates a parameter key-value pairing in JSON, with name and value\n   *\n   * @return an ObjectNode with the parameter name and value\n   */\n  private ObjectNode createParameterJson(String parameterName, String parameterValue) {\n    ObjectMapper mapper = new ObjectMapper();\n    ObjectNode parameterObject = mapper.createObjectNode();\n    parameterObject.put(\"name\", parameterName);\n    parameterObject.put(\"value\", parameterValue);\n\n    return parameterObject;\n  }\n\n  /**\n   * Adds the data portion of the mocked response JSON\n   *\n   * @param dataNode The ObjectNode representing the \"data\" portion of the JSON response\n   * @param rows The rows to add to the rowset.\n   * @param dataTypes datatypes of the provided set of rows\n   */\n  private void createRowsetJson(\n      ObjectNode dataNode, List<List<Object>> rows, List<DataType> dataTypes) {\n    ArrayNode rowsetNode = dataNode.putArray(\"rowset\");\n\n    if (rows == null || rows.isEmpty()) {\n      return;\n    }\n\n    for (List<Object> row : rows) {\n      Iterator<Object> rowData = row.iterator();\n      ArrayNode rowJson = rowsetNode.addArray();\n      for (DataType type : dataTypes) {\n        if (type == DataType.INT) {\n          rowJson.add((Integer) rowData.next());\n        } else if (type == DataType.STRING) {\n          rowJson.add((String) rowData.next());\n        }\n      }\n    }\n  }\n\n  /**\n   * Utility method to check that the integer result set is equivalent to the given list of list of\n   * ints\n   */\n  private void compareResultSets(\n      ResultSet resultSet, List<List<Object>> expectedRows, List<DataType> dataTypes)\n      throws SQLException {\n    if (expectedRows == null || expectedRows.size() == 0) {\n      assertFalse(resultSet.next());\n      return;\n    }\n\n    int numRows = expectedRows.size();\n\n    int resultSetRows = 0;\n\n    Iterator<List<Object>> rowIterator = expectedRows.iterator();\n\n    while (resultSet.next() && rowIterator.hasNext()) {\n      List<Object> expectedRow = rowIterator.next();\n      int columnIdx = 0;\n      for (DataType type : dataTypes) {\n        Object expected = expectedRow.get(columnIdx);\n        columnIdx++;\n        if (type == DataType.INT) {\n          if (expected == null) {\n            expected = 0;\n          }\n          int actual = resultSet.getInt(columnIdx);\n          assertEquals(expected, actual);\n        } else if (type == DataType.STRING) {\n          String actual = resultSet.getString(columnIdx);\n          assertEquals(expected, actual);\n        }\n      }\n\n      resultSetRows++;\n    }\n\n    // If the result set has more rows than expected, finish the count\n    while (resultSet.next()) {\n      resultSetRows++;\n    }\n\n    assertEquals(numRows, resultSetRows, \"row-count was not what was expected\");\n  }\n\n  // DataTypes supported with mock responses in test:\n  // Currently only String and Integer are supported\n  private enum DataType {\n    INT,\n    STRING\n  }\n\n  private static class MockedSFBaseStatement extends SFBaseStatement {\n    JsonNode mockedResponse;\n    MockSnowflakeSFSession sfSession;\n\n    MockedSFBaseStatement(JsonNode mockedResponse, MockSnowflakeSFSession session) {\n      this.mockedResponse = mockedResponse;\n      this.sfSession = session;\n    }\n\n    @Override\n    public void addProperty(String propertyName, Object propertyValue) {}\n\n    @Override\n    public SFPreparedStatementMetaData describe(String sql) {\n      return null;\n    }\n\n    @Override\n    public SFBaseResultSet execute(\n        String sql,\n        Map<String, ParameterBindingDTO> parametersBinding,\n        CallingMethod caller,\n        ExecTimeTelemetryData execTimeData)\n        throws SQLException, SFException {\n      return new MockJsonResultSet(mockedResponse, sfSession);\n    }\n\n    @Override\n    public SFBaseResultSet asyncExecute(\n        String sql,\n        Map<String, ParameterBindingDTO> parametersBinding,\n        CallingMethod caller,\n        ExecTimeTelemetryData execTimeData)\n        throws SQLException, SFException {\n      return null;\n    }\n\n    @Override\n    public void close() {}\n\n    @Override\n    public void cancel() {}\n\n    @Override\n    public void executeSetProperty(String sql) {}\n\n    @Override\n    public boolean hasChildren() {\n      return false;\n    }\n\n    @Override\n    public SFBaseSession getSFBaseSession() {\n      return sfSession;\n    }\n\n    @Override\n    public SFBaseSession getSFBaseSession(InternalCallMarker internalCallMarker) {\n      return getSFBaseSession();\n    }\n\n    @Override\n    public boolean getMoreResults(int current) {\n      return false;\n    }\n\n    public long getConservativeMemoryLimit() {\n      return 0;\n    }\n\n    public int getConservativePrefetchThreads() {\n      return 0;\n    }\n\n    @Override\n    public SFBaseResultSet getResultSet() {\n      return null;\n    }\n\n    public String[] getChildQueryIds(String queryID) throws SQLException {\n      throw new SQLFeatureNotSupportedException(\"MockedSFBaseStatement.getChildQueryIds\");\n    }\n  }\n\n  private static class MockJsonResultSet extends SFJsonResultSet {\n    private static final TimeZone defaultTimeZone = TimeZone.getTimeZone(\"America/Los_Angeles\");\n\n    JsonNode resultJson;\n    int currentRowIdx = -1;\n    int rowCount;\n\n    public MockJsonResultSet(JsonNode mockedJsonResponse, MockSnowflakeSFSession sfSession)\n        throws SnowflakeSQLException {\n      super(\n          defaultTimeZone,\n          new Converters(\n              defaultTimeZone,\n              new SFSession(),\n              1,\n              true,\n              false,\n              false,\n              false,\n              null,\n              null,\n              null,\n              null,\n              null,\n              null));\n      setSession(sfSession);\n      this.resultJson = mockedJsonResponse.path(\"data\").path(\"rowset\");\n      this.resultSetMetaData = MockConnectionTest.getRSMDFromResponse(mockedJsonResponse, session);\n      this.rowCount = resultJson.size();\n    }\n\n    @Override\n    public boolean next() {\n      currentRowIdx++;\n      return currentRowIdx < rowCount;\n    }\n\n    @Override\n    protected Object getObjectInternal(int columnIndex) {\n      return JsonResultChunk.extractCell(resultJson, currentRowIdx, columnIndex - 1);\n    }\n\n    @Override\n    public boolean isLast() {\n      return (currentRowIdx + 1) == rowCount;\n    }\n\n    @Override\n    public boolean isAfterLast() {\n      return (currentRowIdx >= rowCount);\n    }\n\n    @Override\n    public SFStatementType getStatementType() {\n      return null;\n    }\n\n    @Override\n    public void setStatementType(SFStatementType statementType) {}\n\n    @Override\n    public String getQueryId() {\n      return null;\n    }\n  }\n\n  public static class MockSnowflakeSFSession extends SFBaseSession {\n    private final List<String> errorsEncountered = new ArrayList<>();\n\n    protected MockSnowflakeSFSession(SFConnectionHandler sfConnectionHandler) {\n      super(sfConnectionHandler);\n    }\n\n    public List<String> getErrorsEncountered() {\n      return errorsEncountered;\n    }\n\n    @Override\n    public boolean isSafeToClose() {\n      return false;\n    }\n\n    @Override\n    public List<DriverPropertyInfo> checkProperties() {\n      return null;\n    }\n\n    @Override\n    public void close(InternalCallMarker internalCallMarker) {}\n\n    @Override\n    public QueryStatus getQueryStatus(String queryID) {\n      return null;\n    }\n\n    private Telemetry mockTelemetryClient() {\n      return new Telemetry() {\n        @Override\n        public void addLogToBatch(TelemetryData log) {}\n\n        @Override\n        public void close() {}\n\n        @Override\n        public Future<Boolean> sendBatchAsync() {\n          return null;\n        }\n\n        @Override\n        public void postProcess(String queryId, String sqlState, int vendorCode, Throwable ex) {\n          // For this test, we simply write the message to the list of errors seen using a string.\n          errorsEncountered.add(ex.getMessage() + \"_\" + sqlState);\n        }\n      };\n    }\n\n    @Override\n    public Telemetry getTelemetryClient(InternalCallMarker internalCallMarker) {\n      return mockTelemetryClient();\n    }\n\n    @Override\n    public void callHeartBeat(int timeout) throws SFException, SQLException {}\n\n    @Override\n    public List<SFException> getSqlWarnings() {\n      return null;\n    }\n\n    @Override\n    public void clearSqlWarnings() {}\n\n    public int getNetworkTimeoutInMilli() {\n      return 0;\n    }\n\n    public int getAuthTimeout() {\n      return 0;\n    }\n\n    /**\n     * @return\n     */\n    @Override\n    public int getMaxHttpRetries() {\n      return 7;\n    }\n\n    public SnowflakeConnectString getSnowflakeConnectionString() {\n      return null;\n    }\n\n    @Override\n    public boolean isAsyncSession() {\n      return false;\n    }\n\n    @Override\n    public void setQueryContext(String queryContext) {\n      // Do nothing. Just for overriding.\n    }\n\n    @Override\n    public QueryContextDTO getQueryContextDTO() {\n      // Do nothing. Just for overriding.\n      return null;\n    }\n  }\n\n  private static class MockSFFileTransferAgent extends SFBaseFileTransferAgent {\n\n    private final String filePath;\n    private final Map<String, byte[]> fileMap;\n\n    // Takes the entire command, PUT and all, and encodes it in the file path\n    // We could strip the GET/PUT in front of things, but\n    public MockSFFileTransferAgent(\n        Map<String, byte[]> fileMap, String filePath, CommandType commandType) {\n      this.filePath = filePath;\n      this.fileMap = fileMap;\n      this.commandType = commandType;\n    }\n\n    @Override\n    public boolean execute() throws SQLException {\n      // Uploads a ByteArrayInputStream with available() bytes\n      // to the fake \"file store\" represented by the Map\n      if (commandType == CommandType.UPLOAD) {\n        try {\n          byte[] fileBytes = new byte[sourceStream.available()];\n          sourceStream.read(fileBytes);\n          // string-parsing logic skipped, so we use a dummy key for now\n          fileMap.put(\"fileName\", fileBytes);\n        } catch (IOException e) {\n          e.printStackTrace();\n        }\n      }\n      return false;\n    }\n\n    @Override\n    public InputStream downloadStream(String fileName) throws SnowflakeSQLException {\n      if (commandType == CommandType.DOWNLOAD) {\n        // string-parsing logic skipped, so we use a dummy key for now\n        byte[] bytes = fileMap.get(\"fileName\");\n        return new ByteArrayInputStream(bytes);\n      }\n      return null;\n    }\n  }\n\n  public static class MockSnowflakeConnectionImpl implements SFConnectionHandler {\n    JsonNode jsonResponse;\n    MockSnowflakeSFSession session;\n    // Map to store the bytes that are \"uploaded\"\n    private Map<String, byte[]> fileMap = new HashMap<>();\n\n    public MockSnowflakeConnectionImpl() {\n      this.session = new MockSnowflakeSFSession(this);\n    }\n\n    public MockSnowflakeConnectionImpl(JsonNode jsonResponse) {\n      this();\n      this.jsonResponse = jsonResponse;\n    }\n\n    @Override\n    public boolean supportsAsyncQuery() {\n      return false;\n    }\n\n    @Override\n    public void initializeConnection(String url, Properties info) throws SQLException {}\n\n    @Override\n    public SFBaseSession getSFSession() {\n      return session;\n    }\n\n    @Override\n    public SFBaseStatement getSFStatement() {\n      return new MockedSFBaseStatement(jsonResponse, session);\n    }\n\n    @Override\n    public ResultSet createResultSet(String queryID, Statement statement) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public SnowflakeBaseResultSet createResultSet(SFBaseResultSet resultSet, Statement statement)\n        throws SQLException {\n      Converters convertes =\n          new Converters(\n              null,\n              new SFSession(),\n              0,\n              false,\n              false,\n              false,\n              false,\n              SFBinaryFormat.BASE64,\n              null,\n              null,\n              null,\n              null,\n              null);\n      return new SnowflakeResultSetV1(resultSet, statement);\n    }\n\n    @Override\n    public SnowflakeBaseResultSet createAsyncResultSet(\n        SFBaseResultSet resultSet, Statement statement) throws SQLException {\n      return null;\n    }\n\n    @Override\n    public SFBaseFileTransferAgent getFileTransferAgent(String command, SFBaseStatement statement)\n        throws SQLNonTransientConnectionException, SnowflakeSQLException {\n      SFBaseFileTransferAgent.CommandType commandType =\n          command.substring(0, 3).equalsIgnoreCase(\"PUT\")\n              ? SFBaseFileTransferAgent.CommandType.UPLOAD\n              : SFBaseFileTransferAgent.CommandType.DOWNLOAD;\n      return new MockSFFileTransferAgent(fileMap, \"fileName\", commandType);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/MultiStatementArrowIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\n\n@Tag(TestTags.ARROW)\npublic class MultiStatementArrowIT extends MultiStatementIT {\n\n  public MultiStatementArrowIT() {\n    super();\n    queryResultFormat = \"arrow\";\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/MultiStatementIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.common.core.SqlState;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Multi Statement tests */\n@Tag(TestTags.STATEMENT)\npublic class MultiStatementIT extends BaseJDBCWithSharedConnectionIT {\n  protected static String queryResultFormat = \"json\";\n\n  @BeforeEach\n  public void setQueryResultFormat() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n  }\n\n  @Test\n  public void testMultiStmtExecuteUpdateFail() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String multiStmtQuery =\n          \"select 1;\\n\"\n              + \"create or replace temporary table test_multi (cola int);\\n\"\n              + \"insert into test_multi VALUES (1), (2);\\n\"\n              + \"select cola from test_multi order by cola asc\";\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 4);\n      SQLException ex =\n          assertThrows(SQLException.class, () -> statement.executeUpdate(multiStmtQuery));\n      assertThat(\n          ex.getErrorCode(), is(ErrorCode.UPDATE_FIRST_RESULT_NOT_UPDATE_COUNT.getMessageCode()));\n    }\n  }\n\n  @Test\n  public void testMultiStmtExecuteQueryFail() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String multiStmtQuery =\n          \"create or replace temporary table test_multi (cola int);\\n\"\n              + \"insert into test_multi VALUES (1), (2);\\n\"\n              + \"select cola from test_multi order by cola asc\";\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      SQLException ex =\n          assertThrows(SQLException.class, () -> statement.executeQuery(multiStmtQuery));\n      assertThat(\n          ex.getErrorCode(), is(ErrorCode.QUERY_FIRST_RESULT_NOT_RESULT_SET.getMessageCode()));\n    }\n  }\n\n  @Test\n  public void testMultiStmtSetUnset() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      // setting session variable should propagate outside of query\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"set testvar = 1; select 1\");\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      try (ResultSet rs = statement.executeQuery(\"select $testvar\")) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n\n        // selecting unset variable should cause error\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n        SQLException ex =\n            assertThrows(\n                SQLException.class, () -> statement.execute(\"unset testvar; select $testvar\"));\n        assertEquals(SqlState.PLSQL_ERROR, ex.getSQLState());\n\n        // unsetting session variable should propagate outside of query\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n        ex = assertThrows(SQLException.class, () -> statement.execute(\"select $testvar\"));\n        assertEquals(SqlState.NO_DATA, ex.getSQLState());\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtParseError() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      statement.execute(\"set testvar = 1\");\n      SQLException ex =\n          assertThrows(\n              SQLException.class, () -> statement.execute(\"garbage text; set testvar = 2\"));\n      assertEquals(SqlState.SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, ex.getSQLState());\n\n      try (ResultSet rs = statement.executeQuery(\"select $testvar\")) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtExecError() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      // fails during execution (javascript invokes statement where it gets typechecked)\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  statement.execute(\n                      \"set testvar = 1; select nonexistent_column from nonexistent_table; set testvar = 2\"));\n      assertEquals(SqlState.PLSQL_ERROR, ex.getSQLState());\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      try (ResultSet rs = statement.executeQuery(\"select $testvar\")) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtTempTable() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      String entry = \"success\";\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\n          \"create or replace temporary table test_multi (cola string); insert into test_multi values ('\"\n              + entry\n              + \"')\");\n      // temporary table should persist outside of the above statement\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      try (ResultSet rs = statement.executeQuery(\"select * from test_multi\")) {\n        assertTrue(rs.next());\n        assertEquals(entry, rs.getString(1));\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtUseStmt() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      SFSession session =\n          statement.getConnection().unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n      String originalSchema = session.getSchema();\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"use schema public; select 1\");\n      // current schema change should persist outside of the above statement\n\n      assertEquals(\"PUBLIC\", session.getSchema());\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      try (ResultSet rs = statement.executeQuery(\"select current_schema()\")) {\n        assertTrue(rs.next());\n        assertEquals(\"PUBLIC\", rs.getString(1));\n      }\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(String.format(\"use schema %s; select 1\", originalSchema));\n      // current schema change should persist outside of the above statement\n\n      session = statement.getConnection().unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      assertEquals(originalSchema, session.getSchema());\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      try (ResultSet rs = statement.executeQuery(\"select current_schema()\")) {\n        assertTrue(rs.next());\n        assertEquals(originalSchema, rs.getString(1));\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtAlterSessionParams() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      SFSession session =\n          statement.getConnection().unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n      // we need an arbitrary parameter which is updated by the client after each query for this\n      // test\n      String param = \"AUTOCOMMIT\";\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"alter session set \" + param + \"=false; select 1\");\n      assertFalse(session.getAutoCommit());\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"alter session set \" + param + \"=true; select 1\");\n      assertTrue(session.getAutoCommit());\n    }\n  }\n\n  @Test\n  public void testMultiStmtMultiLine() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // these statements should not fail\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"select 1;\\nselect 2\");\n      statement.execute(\"select \\n 1; select 2\");\n      statement.execute(\"select \\r\\n 1; select 2\");\n    }\n  }\n\n  @Test\n  public void testMultiStmtQuotes() throws SQLException {\n    // test various quotation usage and ensure they succeed\n    try (Statement statement = connection.createStatement()) {\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\n          \"create or replace temporary table \\\"test_multi\\\" (cola string); select * from \\\"test_multi\\\"\");\n      statement.execute(\n          \"create or replace temporary table `test_multi` (cola string); select * from `test_multi`\");\n      statement.execute(\"select 'str'; select 'str2'\");\n      statement.execute(\"select '\\\\` backticks'; select '\\\\\\\\` more `backticks`'\");\n    }\n  }\n\n  @Test\n  public void testMultiStmtCommitRollback() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      statement.execute(\"begin\");\n      statement.execute(\"insert into test_multi_commit_rollback values ('abc')\");\n      // \"commit\" inside multistatement commits previous DML calls\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"insert into test_multi_commit_rollback values ('def'); commit\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"rollback\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n      }\n\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      statement.execute(\"begin\");\n      statement.execute(\"insert into test_multi_commit_rollback values ('abc')\");\n      // \"rollback\" inside multistatement rolls back previous DML calls\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"insert into test_multi_commit_rollback values ('def'); rollback\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"commit\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n      }\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      // open transaction inside multistatement continues after\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"begin; insert into test_multi_commit_rollback values ('abc')\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"insert into test_multi_commit_rollback values ('def')\");\n      statement.execute(\"commit\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n      }\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      // open transaction inside multistatement continues after\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"begin; insert into test_multi_commit_rollback values ('abc')\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"insert into test_multi_commit_rollback values ('def')\");\n      statement.execute(\"rollback\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtCommitRollbackNoAutocommit() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      connection.setAutoCommit(false);\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      statement.execute(\"insert into test_multi_commit_rollback values ('abc')\");\n      // \"commit\" inside multistatement commits previous DML calls\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"insert into test_multi_commit_rollback values ('def'); commit\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"rollback\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n      }\n\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      statement.execute(\"insert into test_multi_commit_rollback values ('abc')\");\n      // \"rollback\" inside multistatement rolls back previous DML calls\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\"insert into test_multi_commit_rollback values ('def'); rollback\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"commit\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n      }\n\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      // open transaction inside multistatement continues after\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\n          \"insert into test_multi_commit_rollback values ('abc'); insert into test_multi_commit_rollback values ('def')\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"commit\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n      }\n      statement.execute(\"create or replace table test_multi_commit_rollback (cola string)\");\n      // open transaction inside multistatement continues after\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      statement.execute(\n          \"insert into test_multi_commit_rollback values ('abc'); insert into test_multi_commit_rollback values ('def')\");\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n      statement.execute(\"rollback\");\n      try (ResultSet rs =\n          statement.executeQuery(\"select count(*) from test_multi_commit_rollback\")) {\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtLarge() throws SQLException {\n    // this test verifies that multiple-statement support does not break\n    // with many statements\n    // it also ensures that results are returned in the correct order\n    try (Statement statement = connection.createStatement()) {\n      StringBuilder multiStmtBuilder = new StringBuilder();\n      String query = \"SELECT %d;\";\n      for (int i = 0; i < 100; i++) {\n        multiStmtBuilder.append(String.format(query, i));\n      }\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 100);\n\n      assertTrue(statement.execute(multiStmtBuilder.toString()));\n      for (int i = 0; i < 100; i++) {\n        try (ResultSet rs = statement.getResultSet()) {\n          assertNotNull(rs);\n          assertEquals(-1, statement.getUpdateCount());\n          assertTrue(rs.next());\n          assertEquals(i, rs.getInt(1));\n          assertFalse(rs.next());\n\n          if (i != 99) {\n            assertTrue(statement.getMoreResults());\n          } else {\n            assertFalse(statement.getMoreResults());\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/MultiStatementLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\nimport java.math.BigDecimal;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\n/**\n * MultiStatement integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the oldest supported driver to examine\n * if the tests still is not applicable. If it is applicable, move tests to MultiStatementIT so that\n * both the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.STATEMENT)\npublic class MultiStatementLatestIT extends BaseJDBCWithSharedConnectionIT {\n  protected static String queryResultFormat = \"json\";\n\n  @BeforeEach\n  public void setQueryResultFormat() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n  }\n\n  @Test\n  public void testMultiStmtExecute() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      String multiStmtQuery =\n          \"create or replace temporary table test_multi (cola int);\\n\"\n              + \"insert into test_multi VALUES (1), (2);\\n\"\n              + \"select cola from test_multi order by cola asc\";\n\n      boolean hasResultSet = statement.execute(multiStmtQuery);\n      // first statement\n      assertFalse(hasResultSet);\n      assertNull(statement.getResultSet());\n      assertEquals(0, statement.getUpdateCount());\n\n      // second statement\n      assertTrue(statement.getMoreResults());\n      assertNull(statement.getResultSet());\n      assertEquals(2, statement.getUpdateCount());\n\n      // third statement\n      assertTrue(statement.getMoreResults());\n      assertEquals(-1, statement.getUpdateCount());\n      try (ResultSet rs = statement.getResultSet()) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n        assertFalse(rs.next());\n\n        assertFalse(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtTransaction() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table test_multi_txn(c1 number, c2 string)\" + \" as select 10, 'z'\");\n\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 4);\n        String multiStmtQuery =\n            \"begin;\\n\"\n                + \"delete from test_multi_txn;\\n\"\n                + \"insert into test_multi_txn values (1, 'a'), (2, 'b');\\n\"\n                + \"commit\";\n\n        boolean hasResultSet = statement.execute(multiStmtQuery);\n        // first statement\n        assertFalse(hasResultSet);\n        assertNull(statement.getResultSet());\n        assertEquals(0, statement.getUpdateCount());\n\n        // second statement\n        assertTrue(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(1, statement.getUpdateCount());\n\n        // third statement\n        assertTrue(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(2, statement.getUpdateCount());\n\n        // fourth statement\n        assertFalse(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(0, statement.getUpdateCount());\n\n        assertFalse(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n\n      } finally {\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n        statement.execute(\"drop table if exists test_multi_txn\");\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtExecuteUpdate() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String multiStmtQuery =\n          \"create or replace temporary table test_multi (cola int);\\n\"\n              + \"insert into test_multi VALUES (1), (2);\\n\"\n              + \"select cola from test_multi order by cola asc\";\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 3);\n      int rowCount = statement.executeUpdate(multiStmtQuery);\n      // first statement\n      assertEquals(0, rowCount);\n      assertNull(statement.getResultSet());\n      assertEquals(0, statement.getUpdateCount());\n\n      // second statement\n      assertTrue(statement.getMoreResults());\n      assertNull(statement.getResultSet());\n      assertEquals(2, statement.getUpdateCount());\n\n      // third statement\n      assertTrue(statement.getMoreResults());\n      assertEquals(-1, statement.getUpdateCount());\n      try (ResultSet rs = statement.getResultSet()) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n        assertFalse(rs.next());\n\n        assertFalse(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtTransactionRollback() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table test_multi_txn_rb(c1 number, c2 string)\"\n                + \" as select 10, 'z'\");\n\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 4);\n        String multiStmtQuery =\n            \"begin;\\n\"\n                + \"delete from test_multi_txn_rb;\\n\"\n                + \"rollback;\\n\"\n                + \"select count(*) from test_multi_txn_rb\";\n\n        boolean hasResultSet = statement.execute(multiStmtQuery);\n        // first statement\n        assertFalse(hasResultSet);\n        assertNull(statement.getResultSet());\n        assertEquals(0, statement.getUpdateCount());\n\n        // second statement\n        assertTrue(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(1, statement.getUpdateCount());\n\n        // third statement\n        assertTrue(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(0, statement.getUpdateCount());\n\n        // fourth statement\n        assertTrue(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n        try (ResultSet rs = statement.getResultSet()) {\n          assertTrue(rs.next());\n          assertEquals(1, rs.getInt(1));\n          assertFalse(rs.next());\n\n          assertFalse(statement.getMoreResults());\n          assertEquals(-1, statement.getUpdateCount());\n        }\n      } finally {\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 1);\n        statement.execute(\"drop table if exists test_multi_txn_rb\");\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtExecuteQuery() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String multiStmtQuery =\n          \"select 1;\\n\"\n              + \"create or replace temporary table test_multi (cola int);\\n\"\n              + \"insert into test_multi VALUES (1), (2);\\n\"\n              + \"select cola from test_multi order by cola asc\";\n\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 4);\n      try (ResultSet rs = statement.executeQuery(multiStmtQuery)) {\n        // first statement\n        assertNotNull(rs);\n        assertNotNull(statement.getResultSet());\n        assertEquals(-1, statement.getUpdateCount());\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n        assertFalse(rs.next());\n\n        // second statement\n        assertTrue(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(0, statement.getUpdateCount());\n\n        // third statement\n        assertTrue(statement.getMoreResults());\n        assertNull(statement.getResultSet());\n        assertEquals(2, statement.getUpdateCount());\n\n        // fourth statement\n        assertTrue(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n      }\n      try (ResultSet rs = statement.getResultSet()) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n        assertTrue(rs.next());\n        assertEquals(2, rs.getInt(1));\n        assertFalse(rs.next());\n\n        assertFalse(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n      }\n    }\n  }\n\n  @Test\n  public void testMultiStmtUpdateCount() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 2);\n      boolean isResultSet =\n          statement.execute(\n              \"CREATE OR REPLACE TEMPORARY TABLE TABLIST AS \"\n                  + \"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES \"\n                  + \"WHERE TABLE_NAME LIKE 'K%' \"\n                  + \"ORDER BY TABLE_SCHEMA, TABLE_NAME; \"\n                  + \"SELECT * FROM TABLIST \"\n                  + \"JOIN INFORMATION_SCHEMA.COLUMNS \"\n                  + \"ON COLUMNS.TABLE_SCHEMA = TABLIST.TABLE_SCHEMA \"\n                  + \"AND COLUMNS.TABLE_NAME = TABLIST.TABLE_NAME;\");\n      assertEquals(isResultSet, false);\n      int statementUpdateCount = statement.getUpdateCount();\n      assertEquals(statementUpdateCount, 0);\n      isResultSet = statement.getMoreResults();\n      assertEquals(isResultSet, true);\n      statementUpdateCount = statement.getUpdateCount();\n      assertEquals(statementUpdateCount, -1);\n    }\n  }\n\n  /** Test use of anonymous blocks (SNOW-758262) */\n  @Test\n  public void testAnonymousBlocksUse() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table tab758262(c1 number)\");\n      // Test anonymous block with multistatement\n      int multistatementcount = 2;\n      statement\n          .unwrap(SnowflakeStatement.class)\n          .setParameter(\"MULTI_STATEMENT_COUNT\", multistatementcount);\n      String multiStmtQuery =\n          \"begin\\n\"\n              + \"insert into tab758262 values (1);\\n\"\n              + \"return 'done';\\n\"\n              + \"end;\\n\"\n              + \"select * from tab758262;\";\n\n      statement.execute(multiStmtQuery);\n      for (int i = 0; i < multistatementcount - 1; i++) {\n        assertTrue(statement.getMoreResults());\n      }\n      try (ResultSet rs = statement.getResultSet()) {\n        assertTrue(rs.next());\n        assertEquals(1, rs.getInt(1));\n      }\n\n      // Test anonymous block in the middle of other queries in multistatement\n      multiStmtQuery =\n          \"insert into tab758262 values (25), (26);\\n\"\n              + \"begin\\n\"\n              + \"insert into tab758262 values (27);\\n\"\n              + \"return 'done';\\n\"\n              + \"end;\\n\"\n              + \"select * from tab758262;\";\n      multistatementcount = 3;\n      statement\n          .unwrap(SnowflakeStatement.class)\n          .setParameter(\"MULTI_STATEMENT_COUNT\", multistatementcount);\n      statement.execute(multiStmtQuery);\n      for (int i = 0; i < multistatementcount - 1; i++) {\n        assertTrue(statement.getMoreResults());\n      }\n      try (ResultSet rs = statement.getResultSet()) {\n        assertEquals(4, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = {\"arrow\", \"json\"})\n  void testMultiStatementResultFormat(String queryResultFormat) throws Exception {\n    // Setup a memory buffer for JUL logs\n    ByteArrayOutputStream logCapture = new ByteArrayOutputStream();\n    PrintStream logStream = new PrintStream(logCapture);\n\n    Logger sfLogger = Logger.getLogger(\"net.snowflake\");\n    sfLogger.setLevel(Level.ALL);\n    sfLogger.setUseParentHandlers(false);\n\n    Handler consoleHandler =\n        new ConsoleHandler() {\n          @Override\n          public synchronized void publish(java.util.logging.LogRecord record) {\n            logStream.println(record.getLevel() + \": \" + record.getMessage());\n          }\n        };\n    consoleHandler.setLevel(Level.ALL);\n    sfLogger.addHandler(consoleHandler);\n\n    try (Statement stmt = connection.createStatement()) {\n      try {\n        stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n        stmt.execute(\"ALTER SESSION SET ENABLE_FIX_1758055_ADD_ARROW_SUPPORT_FOR_MULTI_STMTS=TRUE\");\n      } catch (SQLException ex) {\n        /*\n         * Ingore failure since the test user might not be able to change the parameter.\n         * In such case assume the parameter has been enabled on the test account.\n         */\n        if (!ex.getMessage()\n            .contains(\"invalid parameter 'ENABLE_FIX_1758055_ADD_ARROW_SUPPORT_FOR_MULTI_STMTS'\")) {\n          fail();\n        }\n      }\n\n      stmt.unwrap(SnowflakeStatement.class).setParameter(\"MULTI_STATEMENT_COUNT\", 4);\n\n      String multiStmtQuery =\n          \"select TO_DOUBLE('0.123456789123456789');\\n\"\n              + \"select 456;\\n\"\n              + \"select 789;\\n\"\n              + \"select '000';\";\n\n      stmt.execute(multiStmtQuery);\n\n      int resultSetIndex = 0;\n      boolean hasResults = true;\n\n      while (hasResults) {\n        ResultSet rs = stmt.getResultSet();\n        assertNotNull(rs);\n\n        switch (resultSetIndex) {\n          case 0:\n            assertTrue(rs.next());\n            if (\"arrow\".equals(queryResultFormat)) {\n              assertEquals(0.12345678912345678d, rs.getDouble(1), 0.0);\n            } else {\n              assertEquals(new BigDecimal(\"0.1234567891\"), rs.getBigDecimal(1));\n            }\n            break;\n          case 1:\n            assertTrue(rs.next());\n            assertEquals(456, rs.getInt(1));\n            break;\n          case 2:\n            assertTrue(rs.next());\n            assertEquals(789, rs.getInt(1));\n            break;\n          case 3:\n            assertTrue(rs.next());\n            assertEquals(\"000\", rs.getString(1));\n            break;\n          default:\n            fail(\"Unexpected extra result set\");\n        }\n\n        rs.close();\n        hasResults = stmt.getMoreResults();\n        resultSetIndex++;\n      }\n    }\n\n    try {\n      consoleHandler.flush();\n      String logs = logCapture.toString();\n\n      if (\"arrow\".equals(queryResultFormat) && logs != null) {\n        assertTrue(\n            logs.contains(\n                \"Query result received in ARROW format. Processing with SFArrowResultSet.\"),\n            \"Expected Arrow logs but got: \\n\" + logs);\n      }\n    } finally {\n      sfLogger.removeHandler(consoleHandler);\n      consoleHandler.close();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/OpenGroupCLIFuncIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Test OpenGroup CLI */\n@Tag(TestTags.OTHERS)\npublic class OpenGroupCLIFuncIT extends BaseJDBCWithSharedConnectionIT {\n\n  @BeforeAll\n  public static void setSessionTimezone() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n    }\n  }\n\n  @Test\n  public void testStringFunction() throws SQLException {\n    testFunction(connection, \"select {fn ASCII('snowflake')}\", \"115\");\n    testFunction(connection, \"select {fn CHAR(115)}\", \"s\");\n    testFunction(connection, \"select {fn CONCAT('snow', 'flake')}\", \"snowflake\");\n    // DIFFERENCE is not supported\n    // testFunction(connection, \"select {fn DIFFERENCE('snow', 'flake')}\", \"snowflake\");\n    testFunction(connection, \"select {fn INSERT('snowflake', 2, 3, 'insert')}\", \"sinsertflake\");\n    testFunction(connection, \"select {fn LCASE('SNOWflake')}\", \"snowflake\");\n    testFunction(connection, \"select {fn LEFT('snowflake', 4)}\", \"snow\");\n    testFunction(connection, \"select {fn LENGTH('  snowflake  ')}\", \"11\");\n    testFunction(connection, \"select {fn LOCATE('str', 'strstrstr', 2)}\", \"4\");\n    testFunction(connection, \"select {fn LTRIM('  snowflake  ')}\", \"snowflake  \");\n    testFunction(connection, \"select {fn REPEAT('snow', 3)}\", \"snowsnowsnow\");\n    testFunction(connection, \"select {fn REPLACE('snowssnowsn', 'sn', 'aa')}\", \"aaowsaaowaa\");\n    testFunction(connection, \"select {fn RIGHT('snowflake', 5)}\", \"flake\");\n    testFunction(connection, \"select {fn RTRIM('  snowflake  ')}\", \"  snowflake\");\n    // SOUNDEX is not supported\n    // testFunction(connection, \"select {fn SOUNDEX('snowflake')}\", \"  snowflake\");\n    testFunction(connection, \"select {fn SPACE(4)}\", \"    \");\n    testFunction(connection, \"select {fn SUBSTRING('snowflake', 2, 3)}\", \"now\");\n    testFunction(connection, \"select {fn UCASE('snowflake')}\", \"SNOWFLAKE\");\n  }\n\n  @Test\n  public void testDateTimeFunction() throws SQLException {\n    // testFunction(connection, \"select {fn CURDATE()}\",\"\");\n    // testFunction(connection, \"select {fn CURTIME()}\",\"\");\n    testFunction(connection, \"select {fn DAYNAME('2016-5-25')}\", \"Wed\");\n    testFunction(connection, \"select {fn DAYOFMONTH(to_date('2016-5-25'))}\", \"25\");\n    testFunction(connection, \"select {fn DAYOFWEEK(to_date('2016-5-25'))}\", \"3\");\n    testFunction(connection, \"select {fn DAYOFYEAR(to_date('2016-5-25'))}\", \"146\");\n    testFunction(connection, \"select {fn HOUR(to_timestamp('2016-5-25 12:34:56.789789'))}\", \"12\");\n    testFunction(connection, \"select {fn MINUTE(to_timestamp('2016-5-25 12:34:56.789789'))}\", \"34\");\n    testFunction(connection, \"select {fn MONTH(to_date('2016-5-25'))}\", \"5\");\n    testFunction(connection, \"select {fn MONTHNAME(to_date('2016-5-25'))}\", \"May\");\n    // testFunction(connection, \"select {fn NOW()}\", \"May\");\n    testFunction(connection, \"select {fn QUARTER(to_date('2016-5-25'))}\", \"2\");\n    testFunction(connection, \"select {fn SECOND(to_timestamp('2016-5-25 12:34:56.789789'))}\", \"56\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_FRAC_SECOND, 1000, \"\n            + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Wed, 25 May 2016 12:34:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_SECOND, 1, \"\n            + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Wed, 25 May 2016 12:34:57 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_MINUTE, 1, \"\n            + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Wed, 25 May 2016 12:35:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_HOUR, 1, \" + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Wed, 25 May 2016 13:34:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_DAY, 1, \" + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Thu, 26 May 2016 12:34:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_MONTH, 1, \"\n            + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Sat, 25 Jun 2016 12:34:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_QUARTER, 1, \"\n            + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Thu, 25 Aug 2016 12:34:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPADD(SQL_TSI_YEAR, 1, \" + \"to_timestamp('2016-5-25 12:34:56.789789'))}\",\n        \"Thu, 25 May 2017 12:34:56 -0700\");\n    testFunction(\n        connection,\n        \"select {fn TIMESTAMPDIFF(SQL_TSI_SECOND, \"\n            + \"to_timestamp('2016-5-25 12:34:56.789789'), to_timestamp('2016-5-25 12:34:57.789789'))}\",\n        \"1\");\n    testFunction(connection, \"select {fn WEEK(to_timestamp('2016-5-25 12:34:56.789789'))}\", \"21\");\n    testFunction(connection, \"select {fn YEAR(to_timestamp('2016-5-25 12:34:56.789789'))}\", \"2016\");\n  }\n\n  @Test\n  public void testSystemFunctions() throws SQLException {\n    testFunction(connection, \"select {fn DATABASE()}\", connection.getCatalog());\n    testFunction(connection, \"select {fn IFNULL(NULL, 1)}\", \"1\");\n    testFunction(\n        connection,\n        \"select {fn USER()}\",\n        TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_USER\").toUpperCase());\n  }\n\n  static void testFunction(Connection connection, String sql, String expected) throws SQLException {\n    try (Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(sql)) {\n      assertTrue(resultSet.next());\n      assertEquals(expected, resultSet.getString(1));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/OpenGroupCLIFuncLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.OpenGroupCLIFuncIT.testFunction;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Open Group CLI function integration tests for the latest JDBC driver. This doesn't work for the\n * oldest supported driver. Revisit this tests whenever bumping up the oldest supported driver to\n * examine if the tests still are not applicable. If it is applicable, move tests to\n * OpenGroupCLIFuncIT so that both the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.OTHERS)\npublic class OpenGroupCLIFuncLatestIT extends BaseJDBCTest {\n  /**\n   * Numeric function tests\n   *\n   * @throws SQLException arises if any exception occurs\n   */\n  @Test\n  public void testNumericFunctions() throws SQLException {\n    try (Connection connection = getConnection()) {\n      testFunction(connection, \"select {fn ABS(-1)}\", \"1\");\n      // This doesn't work for the old driver which doesn't support Arrow format yet where the arrow\n      // float data type reserves higher precision.\n      testFunction(connection, \"select {fn ACOS(0.5)}\", \"1.0471975511965979\");\n      testFunction(connection, \"select {fn ASIN(0.5)}\", \"0.5235987755982989\");\n      testFunction(connection, \"select {fn CEILING(1.3)}\", \"2\");\n      testFunction(connection, \"select {fn COS(1.3)}\", \"0.26749882862458735\");\n      testFunction(connection, \"select {fn COT(1.3)}\", \"0.27761564654112514\");\n      testFunction(connection, \"select {fn DEGREES(1.047197551)}\", \"59.99999998873578\");\n      testFunction(connection, \"select {fn EXP(2)}\", \"7.38905609893065\");\n      testFunction(connection, \"select {fn FLOOR(1.2)}\", \"1\");\n      testFunction(connection, \"select {fn LOG(1.2)}\", \"0.1823215567939546\");\n      // LOG10 is not supported\n      // testFunction(connection, \"select {fn LOG10(1.2)}\", \"1\");\n      testFunction(connection, \"select {fn MOD(3, 2)}\", \"1\");\n      testFunction(connection, \"select {fn PI()}\", \"3.141592653589793\");\n      testFunction(connection, \"select {fn POWER(3, 2)}\", \"9.0\");\n      testFunction(connection, \"select {fn RADIANS(1.2)}\", \"0.020943951023931952\");\n      testFunction(connection, \"select {fn RAND(2)}\", \"-1778191858535396788\");\n      testFunction(connection, \"select {fn ROUND(2.234456, 4)}\", \"2.2345\");\n      testFunction(connection, \"select {fn SIGN(-10)}\", \"-1\");\n      testFunction(connection, \"select {fn SQRT(9)}\", \"3.0\");\n      testFunction(connection, \"select {fn TAN(9)}\", \"-0.45231565944180985\");\n      testFunction(connection, \"select {fn TRUNCATE(2.234456, 4)}\", \"2.2344\");\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedMultiStmtIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class PreparedMultiStmtIT extends BaseJDBCWithSharedConnectionIT {\n  private static SnowflakeConnectionImpl sfConnectionV1;\n\n  public PreparedMultiStmtIT() {\n    this.sfConnectionV1 = (SnowflakeConnectionImpl) connection;\n  }\n\n  public void setSessionResultFormat(String queryResultFormat) throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testExecuteUpdateCount(String queryResultFormat) throws Exception {\n    setSessionResultFormat(queryResultFormat);\n    try (Statement statement = sfConnectionV1.createStatement()) {\n      try {\n        statement.execute(\"alter session set MULTI_STATEMENT_COUNT=0\");\n        statement.execute(\"create or replace table test_multi_bind(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            sfConnectionV1.prepareStatement(\n                \"insert into test_multi_bind(c1) values(?); insert into \"\n                    + \"test_multi_bind values (?), (?)\")) {\n\n          assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(3));\n\n          preparedStatement.setInt(1, 20);\n          preparedStatement.setInt(2, 30);\n          preparedStatement.setInt(3, 40);\n\n          // first statement\n          int rowCount = preparedStatement.executeUpdate();\n          assertThat(rowCount, is(1));\n          assertThat(preparedStatement.getResultSet(), is(nullValue()));\n          assertThat(preparedStatement.getUpdateCount(), is(1));\n\n          // second statement\n          assertThat(preparedStatement.getMoreResults(), is(false));\n          assertThat(preparedStatement.getUpdateCount(), is(2));\n\n          try (ResultSet resultSet =\n              statement.executeQuery(\"select c1 from test_multi_bind order by c1 asc\")) {\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getInt(1), is(20));\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getInt(1), is(30));\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getInt(1), is(40));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_multi_bind\");\n      }\n    }\n  }\n\n  /** Less bindings than expected in statement */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testExecuteLessBindings(String queryResultFormat) throws Exception {\n    setSessionResultFormat(queryResultFormat);\n    try (Statement statement = sfConnectionV1.createStatement()) {\n      try {\n        statement.execute(\"alter session set MULTI_STATEMENT_COUNT=0\");\n        statement.execute(\"create or replace table test_multi_bind(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            sfConnectionV1.prepareStatement(\n                \"insert into test_multi_bind(c1) values(?); insert into \"\n                    + \"test_multi_bind values (?), (?)\")) {\n\n          assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(3));\n\n          preparedStatement.setInt(1, 20);\n          preparedStatement.setInt(2, 30);\n\n          // first statement\n          SQLException e =\n              assertThrows(SQLException.class, () -> preparedStatement.executeUpdate());\n          assertThat(e.getMessage(), containsString(\"Bind variable ? not set\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_multi_bind\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testExecuteMoreBindings(String queryResultFormat) throws Exception {\n    setSessionResultFormat(queryResultFormat);\n    try (Statement statement = sfConnectionV1.createStatement()) {\n      try {\n        statement.execute(\"alter session set MULTI_STATEMENT_COUNT=0\");\n        statement.execute(\"create or replace table test_multi_bind(c1 number)\");\n\n        try (PreparedStatement preparedStatement =\n            sfConnectionV1.prepareStatement(\n                \"insert into test_multi_bind(c1) values(?); insert into \"\n                    + \"test_multi_bind values (?), (?)\")) {\n\n          assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(3));\n\n          preparedStatement.setInt(1, 20);\n          preparedStatement.setInt(2, 30);\n          preparedStatement.setInt(3, 40);\n          // 4th binding should be ignored\n          preparedStatement.setInt(4, 50);\n\n          // first statement\n          int rowCount = preparedStatement.executeUpdate();\n          assertThat(rowCount, is(1));\n          assertThat(preparedStatement.getResultSet(), is(nullValue()));\n          assertThat(preparedStatement.getUpdateCount(), is(1));\n\n          // second statement\n          assertThat(preparedStatement.getMoreResults(), is(false));\n          assertThat(preparedStatement.getUpdateCount(), is(2));\n\n          try (ResultSet resultSet =\n              statement.executeQuery(\"select c1 from test_multi_bind order by c1 asc\")) {\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getInt(1), is(20));\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getInt(1), is(30));\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getInt(1), is(40));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_multi_bind\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testExecuteQueryBindings(String queryResultFormat) throws Exception {\n    setSessionResultFormat(queryResultFormat);\n    try (Statement statement = sfConnectionV1.createStatement()) {\n      statement.execute(\"alter session set MULTI_STATEMENT_COUNT=0\");\n\n      try (PreparedStatement preparedStatement =\n          sfConnectionV1.prepareStatement(\"select ?; select ?, ?; select ?, ?, ?\")) {\n\n        assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(6));\n\n        preparedStatement.setInt(1, 10);\n        preparedStatement.setInt(2, 20);\n        preparedStatement.setInt(3, 30);\n        preparedStatement.setInt(4, 40);\n        preparedStatement.setInt(5, 50);\n        preparedStatement.setInt(6, 60);\n\n        // first statement\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          assertThat(resultSet.next(), is(true));\n          assertThat(resultSet.getInt(1), is(10));\n        }\n        // second statement\n        assertThat(preparedStatement.getMoreResults(), is(true));\n        try (ResultSet resultSet = preparedStatement.getResultSet()) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(1), is(20));\n          assertThat(resultSet.getInt(2), is(30));\n        }\n\n        // third statement\n        assertThat(preparedStatement.getMoreResults(), is(true));\n        try (ResultSet resultSet = preparedStatement.getResultSet()) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(1), is(40));\n          assertThat(resultSet.getInt(2), is(50));\n          assertThat(resultSet.getInt(3), is(60));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testExecuteQueryNoBindings(String queryResultFormat) throws Exception {\n    setSessionResultFormat(queryResultFormat);\n    try (Statement statement = sfConnectionV1.createStatement()) {\n      statement.execute(\"alter session set MULTI_STATEMENT_COUNT=0\");\n\n      try (PreparedStatement preparedStatement =\n          sfConnectionV1.prepareStatement(\"select 10; select 20, 30; select 40, 50, 60\")) {\n\n        assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(0));\n\n        // first statement\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          assertThat(resultSet.next(), is(true));\n          assertThat(resultSet.getInt(1), is(10));\n        }\n\n        // second statement\n        assertThat(preparedStatement.getMoreResults(), is(true));\n        try (ResultSet resultSet = preparedStatement.getResultSet()) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(1), is(20));\n          assertThat(resultSet.getInt(2), is(30));\n        }\n\n        // third statement\n        assertThat(preparedStatement.getMoreResults(), is(true));\n        try (ResultSet resultSet = preparedStatement.getResultSet()) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(1), is(40));\n          assertThat(resultSet.getInt(2), is(50));\n          assertThat(resultSet.getInt(3), is(60));\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatement0IT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\n\n/** Prepared statement integration tests */\nabstract class PreparedStatement0IT extends BaseJDBCTest {\n  // Unique table name per test class to prevent race conditions\n  protected final String uniqueTableName = \"test_prepst_\" + SnowflakeUtil.randomAlphaNumeric(10);\n\n  Connection init() throws SQLException {\n    return BaseJDBCTest.getConnection();\n  }\n\n  protected Connection getConn(String queryResultFormat) throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection();\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  final String insertSQL = \"insert into \" + uniqueTableName + \" values(?, ?, ?, ?, ?, ?)\";\n  final String selectAllSQL = \"select * from \" + uniqueTableName;\n  final String updateSQL = \"update \" + uniqueTableName + \" set COLC = 'newString' where ID = ?\";\n  final String deleteSQL = \"delete from \" + uniqueTableName + \" where ID = ?\";\n  final String selectSQL = \"select * from \" + uniqueTableName + \" where ID = ?\";\n  final String createTableSQL =\n      \"create or replace table \"\n          + uniqueTableName\n          + \"(id INTEGER, \"\n          + \"colA DOUBLE, colB FLOAT, colC String,  \"\n          + \"colD NUMBER, col INTEGER)\";\n  final String deleteTableSQL = \"drop table if exists \" + uniqueTableName;\n  final String enableCacheReuse = \"alter session set USE_CACHED_RESULT=true\";\n  final String tableFuncSQL = \"select 1 from table(generator(rowCount => ?))\";\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Connection con = init()) {\n      con.createStatement().execute(createTableSQL);\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    try (Connection con = init()) {\n      con.createStatement().execute(deleteTableSQL);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatement1IT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.api.exception.ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ParameterMetaData;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.statement.SnowflakePreparedStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class PreparedStatement1IT extends PreparedStatement0IT {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetParameterMetaData(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement preparedStatement = connection.prepareStatement(updateSQL)) {\n        /* All binding parameters are of type text and have null precision and scale and are not nullable. Since every\n           binding parameter currently has identical properties, testing is minimal until this changes.\n        */\n        assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(1));\n        assertThat(preparedStatement.getParameterMetaData().getParameterType(1), is(Types.VARCHAR));\n        assertThat(preparedStatement.getParameterMetaData().getPrecision(1), is(0));\n        assertThat(preparedStatement.getParameterMetaData().getScale(1), is(0));\n        assertThat(\n            preparedStatement.getParameterMetaData().isNullable(1),\n            is(ParameterMetaData.parameterNoNulls));\n        assertThat(preparedStatement.getParameterMetaData().getParameterTypeName(1), is(\"text\"));\n      }\n\n      try (PreparedStatement preparedStatement = connection.prepareStatement(insertSQL)) {\n        assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(6));\n        assertThat(preparedStatement.getParameterMetaData().getParameterType(1), is(Types.VARCHAR));\n        assertThat(preparedStatement.getParameterMetaData().getParameterTypeName(1), is(\"text\"));\n        assertThat(preparedStatement.getParameterMetaData().getParameterType(6), is(Types.VARCHAR));\n        assertThat(preparedStatement.getParameterMetaData().getParameterTypeName(6), is(\"text\"));\n      }\n      // test a statement with no binding parameters to ensure the count is 0\n      try (PreparedStatement preparedStatement = connection.prepareStatement(selectAllSQL)) {\n        assertThat(preparedStatement.getParameterMetaData().getParameterCount(), is(0));\n        // try to access a binding parameter that is out of range of the list of parameters (in this\n        // case, the list is\n        // empty so everything is out of range) and ensure an exception is thrown\n        SQLException e =\n            assertThrows(\n                SQLException.class,\n                () -> preparedStatement.getParameterMetaData().getParameterType(3));\n        assertThat(e.getErrorCode(), is(NUMERIC_VALUE_OUT_OF_RANGE.getMessageCode()));\n      }\n    }\n  }\n\n  /** Trigger default stage array binding threshold so that it can be run on travis */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testInsertStageArrayBind(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      connection\n          .createStatement()\n          .execute(\"create or replace table testStageArrayBind(c1 integer)\");\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into testStageArrayBind values (?)\")) {\n\n        for (int i = 0; i < 70000; i++) {\n          prepStatement.setInt(1, i);\n          prepStatement.addBatch();\n        }\n        prepStatement.executeBatch();\n\n        try (ResultSet resultSet =\n            statement.executeQuery(\"select * from testStageArrayBind order by c1 asc\")) {\n          int count = 0;\n          while (resultSet.next()) {\n            assertThat(resultSet.getInt(1), is(count));\n            count++;\n          }\n        }\n      }\n    }\n  }\n\n  static void bindOneParamSet(\n      PreparedStatement prepst, int id, double colA, float colB, String colC, long colD, short colE)\n      throws SQLException {\n    prepst.setInt(1, id);\n    prepst.setDouble(2, colA);\n    prepst.setFloat(3, colB);\n    prepst.setString(4, colC);\n    prepst.setLong(5, colD);\n    prepst.setShort(6, colE);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareStatementWithKeys(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection.createStatement().execute(createTableSQL);\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(insertSQL, Statement.NO_GENERATED_KEYS)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertEquals(1, getSizeOfResultSet(resultSet));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testInsertBatch(String queryResultFormat) throws SQLException {\n    int[] countResult;\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection\n          .createStatement()\n          .execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 0\"); // disable stage bind\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        countResult = prepStatement.executeBatch();\n        assertEquals(1, countResult[0]);\n        assertEquals(1, countResult[1]);\n        assertEquals(2, prepStatement.getUpdateCount());\n        assertEquals(2L, prepStatement.getLargeUpdateCount());\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertEquals(2, getSizeOfResultSet(resultSet));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testInsertBatchStage(String queryResultFormat) throws SQLException {\n    int[] countResult;\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection\n          .createStatement()\n          .execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 12\"); // enable stage bind\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        countResult = prepStatement.executeBatch();\n        assertEquals(1, countResult[0]);\n        assertEquals(1, countResult[1]);\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertEquals(2, getSizeOfResultSet(resultSet));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testInsertBatchStageMultipleTimes(String queryResultFormat) throws SQLException {\n    // using the same statement to run a query multiple times shouldn't result in duplicates\n    int[] countResult;\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection\n          .createStatement()\n          .execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 6\"); // enable stage bind\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        countResult = prepStatement.executeBatch();\n        assertEquals(1, countResult.length);\n        assertEquals(1, countResult[0]);\n        assertEquals(1, prepStatement.getUpdateCount());\n        assertEquals(1L, prepStatement.getLargeUpdateCount());\n\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        countResult = prepStatement.executeBatch();\n        assertEquals(1, countResult.length);\n        assertEquals(1, countResult[0]);\n        assertEquals(1, prepStatement.getUpdateCount());\n        assertEquals(1L, prepStatement.getLargeUpdateCount());\n\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertEquals(2, getSizeOfResultSet(resultSet));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testStageBatchNull(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      int[] thresholds = {0, 6}; // disabled, enabled\n\n      for (int threshold : thresholds) {\n        statement.execute(\"DELETE FROM \" + uniqueTableName + \" WHERE 1=1\"); // clear table\n        statement.execute(\n            String.format(\n                \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = %d\", threshold));\n        try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n          prepStatement.setNull(1, Types.INTEGER);\n          prepStatement.setNull(2, Types.DOUBLE);\n          prepStatement.setNull(3, Types.FLOAT);\n          prepStatement.setNull(4, Types.VARCHAR);\n          prepStatement.setNull(5, Types.NUMERIC);\n          prepStatement.setNull(6, Types.INTEGER);\n          prepStatement.addBatch();\n          int[] countResult = prepStatement.executeBatch();\n          assertEquals(1, countResult.length);\n          assertEquals(1, countResult[0]);\n        }\n\n        try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          String errorMessage =\n              \"Column should be null (\" + (threshold > 0 ? \"stage\" : \"non-stage\") + \")\";\n          resultSet.getInt(1);\n          assertTrue(resultSet.wasNull(), errorMessage);\n          resultSet.getDouble(2);\n          assertTrue(resultSet.wasNull(), errorMessage);\n          resultSet.getFloat(3);\n          assertTrue(resultSet.wasNull(), errorMessage);\n          resultSet.getString(4);\n          assertTrue(resultSet.wasNull(), errorMessage);\n          resultSet.getLong(5);\n          assertTrue(resultSet.wasNull(), errorMessage);\n          resultSet.getShort(6);\n          assertTrue(resultSet.wasNull(), errorMessage);\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testStageString(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      int[] thresholds = {0, 6}; // disabled, enabled\n      String[] rows = {\n        null, \"\", \"\\\"\", \",\", \"\\n\", \"\\r\\n\", \"\\\"\\\"\", \"null\", \"\\\\\\n\", \"\\\",\", \"\\\\\\\",\\\\\\\"\"\n      };\n\n      for (int threshold : thresholds) {\n        statement.execute(\"DELETE FROM \" + uniqueTableName + \" WHERE 1=1\"); // clear table\n        statement.execute(\n            String.format(\n                \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = %d\", threshold));\n        try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n          for (int i = 0; i < rows.length; i++) {\n            bindOneParamSet(prepStatement, i, 0.0, 0.0f, rows[i], 0, (short) 0);\n            prepStatement.addBatch();\n          }\n          prepStatement.executeBatch();\n\n          try (ResultSet resultSet =\n              statement.executeQuery(\"SELECT colC FROM \" + uniqueTableName + \" ORDER BY id ASC\")) {\n            String errorMessage =\n                \"Strings should match (\" + (threshold > 0 ? \"stage\" : \"non-stage\") + \")\";\n            for (String row : rows) {\n              assertTrue(resultSet.next());\n              assertEquals(row, resultSet.getString(1), errorMessage);\n            }\n          }\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testIncorrectTypes(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      int[] thresholds = {0, 6}; // disabled, enabled\n\n      for (int threshold : thresholds) {\n        statement.execute(\"DELETE FROM \" + uniqueTableName + \" WHERE 1=1\"); // clear table\n        statement.execute(\n            String.format(\n                \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = %d\", threshold));\n        try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n\n          prepStatement.setString(1, \"notAnInt\"); // should cause error\n          prepStatement.setDouble(2, 0.0);\n          prepStatement.setFloat(3, 0.0f);\n          prepStatement.setString(4, \"\");\n          prepStatement.setLong(5, 0);\n          prepStatement.setShort(6, (short) 0);\n          prepStatement.addBatch();\n\n          assertThrows(SQLException.class, prepStatement::executeBatch);\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testStageBatchTimestamps(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      Timestamp tsEpoch = new Timestamp(0L);\n      Timestamp tsEpochMinusOneSec = new Timestamp(-1000L); // negative epoch no fraction of seconds\n      Timestamp tsPast = new Timestamp(-2208988800100L); // very large negative epoch\n      Timestamp tsFuture = new Timestamp(32503680000000L); // very large positive epoch\n      Timestamp tsNow = new Timestamp(System.currentTimeMillis());\n      Timestamp tsArbitrary = new Timestamp(862056000000L);\n      Timestamp[] timestamps =\n          new Timestamp[] {tsEpochMinusOneSec, tsEpoch, tsPast, tsFuture, tsNow, tsArbitrary, null};\n      final String[] tsTypes = new String[] {\"TIMESTAMP_LTZ\", \"TIMESTAMP_NTZ\"};\n      int[] countResult;\n\n      try {\n        // Test that stage and non-stage bindings are consistent for each timestamp type\n        for (String tsType : tsTypes) {\n          statement.execute(\"ALTER SESSION SET TIMESTAMP_TYPE_MAPPING = \" + tsType);\n          statement.execute(\"ALTER SESSION SET CLIENT_TIMESTAMP_TYPE_MAPPING = \" + tsType);\n\n          statement.execute(\n              \"CREATE OR REPLACE TABLE \" + uniqueTableName + \"_ts (id INTEGER, tz TIMESTAMP)\");\n          try (PreparedStatement prepStatement =\n              connection.prepareStatement(\n                  \"INSERT INTO \" + uniqueTableName + \"_ts(id, tz) VALUES(?,?)\")) {\n            // First, run with non-stage binding\n            statement.executeQuery(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 0\");\n            for (int i = 0; i < timestamps.length; i++) {\n              prepStatement.setInt(1, i);\n              prepStatement.setTimestamp(2, timestamps[i]);\n              prepStatement.addBatch();\n            }\n            countResult = prepStatement.executeBatch();\n            for (int res : countResult) {\n              assertEquals(1, res);\n            }\n\n            Timestamp[] nonStageResult = new Timestamp[timestamps.length];\n            try (ResultSet rsNonStage =\n                statement.executeQuery(\n                    \"SELECT * FROM \" + uniqueTableName + \"_ts ORDER BY id ASC\")) {\n              for (int i = 0; i < nonStageResult.length; i++) {\n                assertTrue(rsNonStage.next());\n                nonStageResult[i] = rsNonStage.getTimestamp(2);\n              }\n            }\n            statement.execute(\"DELETE FROM \" + uniqueTableName + \"_ts WHERE 1=1\");\n\n            // Now, run with stage binding\n            statement.execute(\n                \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\"); // enable stage\n            // bind\n            for (int i = 0; i < timestamps.length; i++) {\n              prepStatement.setInt(1, i);\n              prepStatement.setTimestamp(2, timestamps[i]);\n              prepStatement.addBatch();\n            }\n            countResult = prepStatement.executeBatch();\n            for (int res : countResult) {\n              assertEquals(1, res);\n            }\n\n            Timestamp[] stageResult = new Timestamp[timestamps.length];\n            try (ResultSet rsStage =\n                statement.executeQuery(\n                    \"SELECT * FROM \" + uniqueTableName + \"_ts ORDER BY id ASC\")) {\n              for (int i = 0; i < stageResult.length; i++) {\n                assertTrue(rsStage.next());\n                stageResult[i] = rsStage.getTimestamp(2);\n              }\n\n              for (int i = 0; i < timestamps.length; i++) {\n                assertEquals(\n                    nonStageResult[i],\n                    stageResult[i],\n                    \"Stage binding timestamp should match non-stage binding timestamp (\"\n                        + tsType\n                        + \")\");\n              }\n            }\n          }\n        }\n      } finally {\n        statement.execute(\"DROP TABLE IF EXISTS \" + uniqueTableName + \"_ts\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testStageBatchTimes(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      Time tMidnight = new Time(0);\n      Time tNeg = new Time(-1);\n      Time tPos = new Time(1);\n      Time tNow = new Time(System.currentTimeMillis());\n      Time tNoon = new Time(12 * 60 * 60 * 1000);\n      Time[] times = new Time[] {tMidnight, tNeg, tPos, tNow, tNoon, null};\n      int[] countResult;\n      try {\n        statement.execute(\n            \"CREATE OR REPLACE TABLE \" + uniqueTableName + \"_time (id INTEGER, tod TIME)\");\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\n                \"INSERT INTO \" + uniqueTableName + \"_time(id, tod) VALUES(?,?)\")) {\n\n          // First, run with non-stage binding\n          statement.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 0\");\n          for (int i = 0; i < times.length; i++) {\n            prepStatement.setInt(1, i);\n            prepStatement.setTime(2, times[i]);\n            prepStatement.addBatch();\n          }\n          countResult = prepStatement.executeBatch();\n          for (int res : countResult) {\n            assertEquals(1, res);\n          }\n\n          Time[] nonStageResult = new Time[times.length];\n          ResultSet rsNonStage =\n              statement.executeQuery(\"SELECT * FROM \" + uniqueTableName + \"_time ORDER BY id ASC\");\n          for (int i = 0; i < nonStageResult.length; i++) {\n            assertTrue(rsNonStage.next());\n            nonStageResult[i] = rsNonStage.getTime(2);\n          }\n\n          statement.execute(\"DELETE FROM \" + uniqueTableName + \"_time WHERE 1=1\");\n\n          // Now, run with stage binding\n          statement.execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\"); // enable stage\n          // bind\n          for (int i = 0; i < times.length; i++) {\n            prepStatement.setInt(1, i);\n            prepStatement.setTime(2, times[i]);\n            prepStatement.addBatch();\n          }\n          countResult = prepStatement.executeBatch();\n          for (int res : countResult) {\n            assertEquals(1, res);\n          }\n\n          Time[] stageResult = new Time[times.length];\n          try (ResultSet rsStage =\n              statement.executeQuery(\n                  \"SELECT * FROM \" + uniqueTableName + \"_time ORDER BY id ASC\")) {\n            for (int i = 0; i < stageResult.length; i++) {\n              assertTrue(rsStage.next());\n              stageResult[i] = rsStage.getTime(2);\n            }\n\n            for (int i = 0; i < times.length; i++) {\n              assertEquals(\n                  nonStageResult[i],\n                  stageResult[i],\n                  \"Stage binding time should match non-stage binding time\");\n            }\n          }\n        }\n      } finally {\n        statement.execute(\"DROP TABLE IF EXISTS \" + uniqueTableName + \"_time\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testClearParameters(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.clearParameters();\n\n        int parameterSize =\n            ((SnowflakePreparedStatementImpl) prepStatement).getParameterBindings().size();\n        assertThat(parameterSize, is(0));\n\n        bindOneParamSet(prepStatement, 3, 1.22, 1.2f, \"hello\", 12222L, (short) 1);\n        prepStatement.executeUpdate();\n\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          assertEquals(3, resultSet.getInt(1));\n          assertFalse(resultSet.next());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testClearBatch(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n\n        // clear batch should remove all batch parameters\n        prepStatement.clearBatch();\n        int batchSize =\n            ((SnowflakePreparedStatementImpl) prepStatement).getBatchParameterBindings().size();\n        assertThat(batchSize, is(0));\n\n        bindOneParamSet(prepStatement, 3, 1.22, 1.2f, \"hello\", 12222L, (short) 1);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n\n        // executeBatch should remove batch as well\n        batchSize =\n            ((SnowflakePreparedStatementImpl) prepStatement).getBatchParameterBindings().size();\n        assertThat(batchSize, is(0));\n\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          assertEquals(3, resultSet.getInt(1));\n          assertFalse(resultSet.next());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testInsertOneRow(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"CREATE OR REPLACE TABLE \" + uniqueTableName + \"_date (id INTEGER, d DATE)\");\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        assertEquals(1, prepStatement.executeUpdate());\n      }\n      try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n        assertEquals(1, getSizeOfResultSet(resultSet));\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        assertFalse(prepStatement.execute());\n        assertEquals(1, prepStatement.getUpdateCount());\n        assertEquals(1L, prepStatement.getLargeUpdateCount());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testUpdateOneRow(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"CREATE OR REPLACE TABLE \" + uniqueTableName + \"_date (id INTEGER, d DATE)\");\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(updateSQL)) {\n        prepStatement.setInt(1, 1);\n        int count = prepStatement.executeUpdate();\n        assertEquals(1, count);\n        try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          assertEquals(\"newString\", resultSet.getString(4));\n        }\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(updateSQL)) {\n        prepStatement.setInt(1, 2);\n        assertFalse(prepStatement.execute());\n        assertEquals(1, prepStatement.getUpdateCount());\n        assertEquals(1L, prepStatement.getLargeUpdateCount());\n        try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          assertTrue(resultSet.next());\n          assertEquals(\"newString\", resultSet.getString(4));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDeleteOneRow(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"CREATE OR REPLACE TABLE \" + uniqueTableName + \"_date (id INTEGER, d DATE)\");\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n      }\n      String qid1;\n      try (PreparedStatement prepStatement = connection.prepareStatement(deleteSQL)) {\n        prepStatement.setInt(1, 1);\n        int count = prepStatement.executeUpdate();\n        assertEquals(1, count);\n        try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n          assertEquals(1, getSizeOfResultSet(resultSet));\n        }\n        // evaluate query ids\n        assertTrue(prepStatement.isWrapperFor(SnowflakePreparedStatement.class));\n        qid1 = prepStatement.unwrap(SnowflakePreparedStatement.class).getQueryID();\n        assertNotNull(qid1);\n      }\n\n      try (PreparedStatement prepStatement = connection.prepareStatement(deleteSQL)) {\n        prepStatement.setInt(1, 2);\n        assertFalse(prepStatement.execute());\n        assertEquals(1, prepStatement.getUpdateCount());\n        assertEquals(1L, prepStatement.getLargeUpdateCount());\n        try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n          assertEquals(0, getSizeOfResultSet(resultSet));\n          // evaluate query ids\n          assertTrue(prepStatement.isWrapperFor(SnowflakePreparedStatement.class));\n          String qid2 = prepStatement.unwrap(SnowflakePreparedStatement.class).getQueryID();\n          assertNotNull(qid2);\n          assertNotEquals(qid1, qid2);\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSelectOneRow(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(selectSQL)) {\n        prepStatement.setInt(1, 2);\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertEquals(1, getSizeOfResultSet(resultSet));\n        }\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(selectSQL)) {\n        prepStatement.setInt(1, 2);\n        assertTrue(prepStatement.execute());\n        try (ResultSet resultSet = prepStatement.getResultSet()) {\n          assertEquals(1, getSizeOfResultSet(resultSet));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testUpdateBatch(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 1);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n      }\n\n      try (PreparedStatement prepStatement = connection.prepareStatement(updateSQL)) {\n        prepStatement.setInt(1, 1);\n        prepStatement.addBatch();\n        prepStatement.setInt(1, 2);\n        prepStatement.addBatch();\n        prepStatement.setInt(1, 3);\n        prepStatement.addBatch();\n\n        int[] counts = prepStatement.executeBatch();\n        assertThat(counts[0], is(1));\n        assertThat(counts[1], is(1));\n        assertThat(counts[2], is(0));\n        assertEquals(0, prepStatement.getUpdateCount());\n        assertEquals(0L, prepStatement.getLargeUpdateCount());\n        try (ResultSet resultSet = connection.createStatement().executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getString(4), is(\"newString\"));\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getString(4), is(\"newString\"));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBatchInsertWithCacheEnabled(String queryResultFormat) throws SQLException {\n    int[] countResult;\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      // ensure enable the cache result use\n      statement.execute(enableCacheReuse);\n\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 1);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 2, 2.22222, (float) 2.2, \"test2\", 1221221123131L, (short) 2);\n        prepStatement.addBatch();\n        countResult = prepStatement.executeBatch();\n        assertEquals(1, countResult[0]);\n        assertEquals(1, countResult[1]);\n\n        prepStatement.clearBatch();\n        bindOneParamSet(prepStatement, 3, 3.3333, (float) 3.2, \"test3\", 1221221123131L, (short) 3);\n        prepStatement.addBatch();\n        bindOneParamSet(prepStatement, 4, 4.4444, (float) 4.2, \"test4\", 1221221123131L, (short) 4);\n        prepStatement.addBatch();\n        countResult = prepStatement.executeBatch();\n        assertEquals(1, countResult[0]);\n        assertEquals(1, countResult[1]);\n\n        try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n          assertTrue(resultSet.next());\n          assertEquals(1, resultSet.getInt(1));\n          assertTrue(resultSet.next());\n          assertEquals(2, resultSet.getInt(1));\n          assertTrue(resultSet.next());\n          assertEquals(3, resultSet.getInt(1));\n          assertTrue(resultSet.next());\n          assertEquals(4, resultSet.getInt(1));\n          assertFalse(resultSet.next());\n        }\n      }\n    }\n  }\n\n  /**\n   * Manual test to ensure proper log file is produced when\n   * CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS is enabled. Look in /tmp folder for\n   * snowflake_jdbc0.log.0 and check that it lists binding params.\n   *\n   * @throws SQLException arises if any exception occurs\n   */\n  @Test\n  @Disabled\n  public void manualTestForPreparedStatementLogging() throws SQLException {\n    Map<String, String> params = getConnectionParameters();\n    Properties props = new Properties();\n    String uri = params.get(\"uri\");\n    props.put(\"account\", params.get(\"account\"));\n    props.put(\"ssl\", params.get(\"ssl\"));\n    props.put(\"database\", params.get(\"database\"));\n    props.put(\"schema\", params.get(\"schema\"));\n    props.put(\"user\", params.get(\"user\"));\n    props.put(\"password\", params.get(\"password\"));\n    props.put(\"tracing\", \"info\");\n    try (Connection con = DriverManager.getConnection(uri, props);\n        Statement statement = con.createStatement()) {\n      statement.executeUpdate(\"alter session set CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS=true\");\n      statement.execute(createTableSQL);\n      try (PreparedStatement prepStatement =\n          con.prepareStatement(insertSQL, Statement.NO_GENERATED_KEYS)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n        statement.executeUpdate(\n            \"alter session set CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS=false\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatement1LatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.PreparedStatement1IT.bindOneParamSet;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.math.BigInteger;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.TimeZone;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * PreparedStatement integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the oldest supported driver to examine\n * if the tests still are not applicable. If it is applicable, move tests to PreparedStatement1IT so\n * that both the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.STATEMENT)\npublic class PreparedStatement1LatestIT extends PreparedStatement0IT {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepStWithCacheEnabled(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      // ensure enable the cache result use\n      statement.execute(enableCacheReuse);\n\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.execute();\n        prepStatement.execute();\n        bindOneParamSet(prepStatement, 100, 1.2222, (float) 1.2, \"testA\", 12122L, (short) 12);\n        prepStatement.execute();\n      }\n\n      try (ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n        assertTrue(resultSet.next());\n        assertEquals(resultSet.getInt(1), 1);\n        assertTrue(resultSet.next());\n        assertEquals(resultSet.getInt(1), 1);\n        assertTrue(resultSet.next());\n        assertEquals(resultSet.getInt(1), 100);\n      }\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\n              \"select id, id + ? from \" + uniqueTableName + \" where id  = ?\")) {\n        prepStatement.setInt(1, 1);\n        prepStatement.setInt(2, 1);\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n          assertEquals(resultSet.getInt(2), 2);\n          prepStatement.setInt(1, 1);\n          prepStatement.setInt(2, 100);\n        }\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n          assertEquals(resultSet.getInt(2), 101);\n        }\n      }\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\n              \"select seq4() from table(generator(rowcount=>100)) limit ?\")) {\n        prepStatement.setInt(1, 1);\n\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n          assertFalse(resultSet.next());\n          prepStatement.setInt(1, 3);\n        }\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n          assertTrue(resultSet.next());\n          assertTrue(resultSet.next());\n          assertFalse(resultSet.next());\n        }\n      }\n    }\n  }\n  /**\n   * Test to ensure it's possible to upload Time values via stage array binding and get proper\n   * values back (SNOW-194437)\n   *\n   * <p>Ignored on GitHub Action because CLIENT_STAGE_ARRAY_BINDING_THRESHOLD parameter is not\n   * available to customers so cannot be set when running on Github Action\n   *\n   * @throws SQLException arises if any exception occurs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testInsertStageArrayBindWithTime(String queryResultFormat) throws SQLException {\n    TimeZone originalTimeZone = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"alter session set CLIENT_STAGE_ARRAY_BINDING_THRESHOLD=2\");\n        statement.execute(\"create or replace table testStageBindTime (c1 time, c2 time)\");\n        PreparedStatement prepSt =\n            connection.prepareStatement(\"insert into testStageBindTime values (?, ?)\");\n        Time[][] timeValues = {\n          {new Time(0), new Time(1)},\n          {new Time(1000), new Time(Integer.MAX_VALUE)},\n          {new Time(123456), new Time(55555)},\n          {Time.valueOf(\"01:02:00\"), new Time(-100)},\n        };\n        for (Time[] value : timeValues) {\n          prepSt.setTime(1, value[0]);\n          prepSt.setTime(2, value[1]);\n          prepSt.addBatch();\n        }\n        prepSt.executeBatch();\n        // check results\n        try (ResultSet rs = statement.executeQuery(\"select * from testStageBindTime\")) {\n          for (Time[] timeValue : timeValues) {\n            assertTrue(rs.next());\n            assertEquals(timeValue[0].toString(), rs.getTime(1).toString());\n            assertEquals(timeValue[1].toString(), rs.getTime(2).toString());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists testStageBindTime\");\n        statement.execute(\"alter session unset CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\");\n        TimeZone.setDefault(originalTimeZone);\n      }\n    }\n  }\n\n  /**\n   * Test that setObject() with additional NTZ and LTZ timestamp types gives same results for stage\n   * array bind and payload binding, regardless of CLIENT_TIMESTAMP_TYPE_MAPPING parameter value.\n   * For SNOW-259255\n   *\n   * <p>Ignored on GitHub Action because CLIENT_STAGE_ARRAY_BINDING_THRESHOLD parameter is not\n   * available to customers so cannot be set when running on Github Action\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testSetObjectForTimestampTypes(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      // set timestamp mapping to default value\n      try {\n        statement.execute(\"ALTER SESSION UNSET CLIENT_TIMESTAMP_TYPE_MAPPING\");\n        statement.execute(\n            \"create or replace table TS (id INTEGER, ntz TIMESTAMP_NTZ, ltz TIMESTAMP_LTZ)\");\n        PreparedStatement prepst = connection.prepareStatement(\"insert into TS values (?, ?, ?)\");\n        String date1 = \"2014-01-01 16:00:00\";\n        String date2 = \"1945-11-12 5:25:00\";\n        Timestamp[] testTzs = {Timestamp.valueOf(date1), Timestamp.valueOf(date2)};\n        int rowId = 0;\n        for (int i = 0; i < testTzs.length; i++) {\n          // Disable stage array binding and insert the timestamp values\n          statement.execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 0\"); // disable stage bind\n          prepst.setInt(1, rowId++);\n          prepst.setObject(2, testTzs[i], SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ);\n          prepst.setObject(3, testTzs[i], SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ);\n          prepst.addBatch();\n          prepst.executeBatch();\n          // Enable stage array binding and insert the same timestamp values as above\n          statement.execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\"); // enable stage bind\n          prepst.setInt(1, rowId++);\n          prepst.setObject(2, testTzs[i], SnowflakeType.EXTRA_TYPES_TIMESTAMP_NTZ);\n          prepst.setObject(3, testTzs[i], SnowflakeType.EXTRA_TYPES_TIMESTAMP_LTZ);\n          prepst.addBatch();\n          prepst.executeBatch();\n        }\n        try (ResultSet rs = statement.executeQuery(\"select * from TS ORDER BY id ASC\")) {\n          // Get results for each timestamp value tested\n          for (int i = 0; i < testTzs.length; i++) {\n            // Assert that the first row of inserts with payload binding matches the second row of\n            // inserts that used stage array binding\n            assertTrue(rs.next());\n            Timestamp expectedNTZTs = rs.getTimestamp(2);\n            Timestamp expectedLTZTs = rs.getTimestamp(3);\n            assertTrue(rs.next());\n            assertEquals(expectedNTZTs, rs.getTimestamp(2));\n            assertEquals(expectedLTZTs, rs.getTimestamp(3));\n          }\n        }\n      } finally {\n        statement.execute(\"ALTER SESSION UNSET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD;\");\n      }\n    }\n  }\n\n  /**\n   * Test to ensure when giving no input batch data, no exceptions will be thrown\n   *\n   * <p>Ignored on GitHub Action because CLIENT_STAGE_ARRAY_BINDING_THRESHOLD parameter is not\n   * available to customers so cannot be set when running on Github Action\n   *\n   * @throws SQLException arises if any exception occurs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testExecuteEmptyBatch(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        // executeBatch shouldn't throw exceptions\n        assertEquals(\n            0, prepStatement.executeBatch().length, \"For empty batch, we should return int[0].\");\n      }\n\n      connection\n          .createStatement()\n          .execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 0\"); // disable stage bind\n      // we need a new PreparedStatement to pick up the changed status (not use stage bind)\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        // executeBatch shouldn't throw exceptions\n        assertEquals(\n            0, prepStatement.executeBatch().length, \"For empty batch, we should return int[0].\");\n      }\n    }\n  }\n\n  /**\n   * Tests that VARBINARY columns can be set in setObject() method using byte[]\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSetObjectMethodWithVarbinaryColumn(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection.createStatement().execute(\"create or replace table test_binary(b VARBINARY)\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into test_binary(b) values (?)\")) {\n        prepStatement.setObject(1, \"HELLO WORLD\".getBytes());\n        prepStatement.execute(); // shouldn't raise an error.\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSetObjectMethodWithBigIntegerColumn(String queryResultFormat) {\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection.createStatement().execute(\"create or replace table test_bigint(id NUMBER)\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into test_bigint(id) values(?)\")) {\n        prepStatement.setObject(1, BigInteger.valueOf(9999));\n        int rows = prepStatement.executeUpdate();\n        assertTrue(rows == 1, \"Row count doesn't match\");\n      }\n    } catch (SQLException e) {\n      e.printStackTrace();\n      fail(\n          \"testSetObjectMethodWithBigIntegerColumn failed with an exception message: \"\n              + e.getMessage());\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSetObjectMethodWithLargeBigIntegerColumn(String queryResultFormat) {\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection.createStatement().execute(\"create or replace table test_bigint(id NUMBER)\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into test_bigint(id) values(?)\")) {\n        BigInteger largeBigInt = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.TEN);\n        prepStatement.setObject(1, largeBigInt);\n        int rows = prepStatement.executeUpdate();\n        assertTrue(rows == 1, \"Row count doesn't match\");\n      }\n    } catch (SQLException e) {\n      e.printStackTrace();\n      fail(\n          \"testSetObjectMethodWithLargeBigIntegerColumn failed with an exception message: \"\n              + e.getMessage());\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBatchInsertWithTimestampInputFormatSet(String queryResultFormat)\n      throws SQLException {\n    TimeZone originalTimeZone = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"alter session set TIMESTAMP_INPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FFTZH'\");\n        statement.execute(\n            \"create or replace table testStageBindTypes (c1 date, c2 datetime, c3 timestamp)\");\n        java.util.Date today = new java.util.Date();\n        java.sql.Date sqldate = new java.sql.Date(today.getDate());\n        java.sql.Timestamp todaySQL = new java.sql.Timestamp(today.getTime());\n        try (PreparedStatement prepSt =\n            connection.prepareStatement(\"insert into testStageBindTypes values (?, ?, ?)\")) {\n          for (int i = 1; i < 30000; i++) {\n            prepSt.setDate(1, sqldate);\n            prepSt.setDate(2, sqldate);\n            prepSt.setTimestamp(3, todaySQL);\n            prepSt.addBatch();\n          }\n          prepSt.executeBatch(); // should not throw a parsing error.\n        }\n      } finally {\n        statement.execute(\"drop table if exists testStageBindTypes\");\n        statement.execute(\"alter session unset TIMESTAMP_INPUT_FORMAT\");\n      }\n    } finally {\n      TimeZone.setDefault(originalTimeZone);\n    }\n  }\n\n  /**\n   * Test the CALL stmt type for stored procedures. Run against test server with\n   * USE_STATEMENT_TYPE_CALL_FOR_STORED_PROC_CALLS enabled.\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @Disabled\n  public void testCallStatement(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.executeQuery(\n            \"ALTER SESSION SET USE_STATEMENT_TYPE_CALL_FOR_STORED_PROC_CALLS=true\");\n        statement.executeQuery(\n            \"create or replace procedure\\n\"\n                + \"TEST_SP_CALL_STMT_ENABLED(in1 float, in2 variant)\\n\"\n                + \"returns string language javascript as $$\\n\"\n                + \"let res = snowflake.execute({sqlText: 'select ? c1, ? c2', binds:[IN1, JSON.stringify(IN2)]});\\n\"\n                + \"res.next();\\n\"\n                + \"return res.getColumnValueAsString(1) + ' ' + res.getColumnValueAsString(2) + ' ' + IN2;\\n\"\n                + \"$$;\");\n\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"call TEST_SP_CALL_STMT_ENABLED(?, to_variant(?))\")) {\n          prepStatement.setDouble(1, 1);\n          prepStatement.setString(2, \"[2,3]\");\n\n          try (ResultSet rs = prepStatement.executeQuery()) {\n            String result = \"1 \\\"[2,3]\\\" [2,3]\";\n            while (rs.next()) {\n              assertEquals(result, rs.getString(1));\n            }\n          }\n        }\n      } finally {\n        statement.executeQuery(\n            \"drop procedure if exists TEST_SP_CALL_STMT_ENABLED(float, variant)\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatement2IT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.google.common.collect.Sets;\nimport java.math.BigDecimal;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.ParameterMetaData;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.Calendar;\nimport java.util.Set;\nimport java.util.TimeZone;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.statement.SnowflakePreparedStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.STATEMENT)\npublic class PreparedStatement2IT extends PreparedStatement0IT {\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testStageBatchDates(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      Date dEpoch = new Date(0);\n      Date dAfterEpoch = new Date(24 * 60 * 60 * 1000);\n      Date dBeforeEpoch = new Date(-1 * 24 * 60 * 60 * 1000);\n      Date dNow = new Date(System.currentTimeMillis());\n      Date dLeapYear = new Date(951782400000L);\n      Date dFuture = new Date(32503680000000L);\n      Date[] dates = new Date[] {dEpoch, dAfterEpoch, dBeforeEpoch, dNow, dLeapYear, dFuture};\n      int[] countResult;\n\n      try {\n        statement.execute(\"CREATE OR REPLACE TABLE test_prepst_date (id INTEGER, d DATE)\");\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"INSERT INTO test_prepst_date(id, d)  VALUES(?,?)\")) {\n\n          // First, run with non-stage binding\n          statement.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 0\");\n          for (int i = 0; i < dates.length; i++) {\n            prepStatement.setInt(1, i);\n            prepStatement.setDate(2, dates[i]);\n            prepStatement.addBatch();\n          }\n          countResult = prepStatement.executeBatch();\n          for (int res : countResult) {\n            assertEquals(1, res);\n          }\n\n          Date[] nonStageResult = new Date[dates.length];\n          try (ResultSet rsNonStage =\n              statement.executeQuery(\"SELECT * FROM test_prepst_date ORDER BY id ASC\")) {\n\n            for (int i = 0; i < nonStageResult.length; i++) {\n              assertTrue(rsNonStage.next());\n              nonStageResult[i] = rsNonStage.getDate(2);\n            }\n          }\n          statement.execute(\"DELETE FROM test_prepst_date WHERE 1=1\");\n\n          // Now, run with stage binding\n          statement.execute(\n              \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\"); // enable stage\n          // bind\n          for (int i = 0; i < dates.length; i++) {\n            prepStatement.setInt(1, i);\n            prepStatement.setDate(2, dates[i]);\n            prepStatement.addBatch();\n          }\n          countResult = prepStatement.executeBatch();\n          for (int res : countResult) {\n            assertEquals(1, res);\n          }\n\n          Date[] stageResult = new Date[dates.length];\n          try (ResultSet rsStage =\n              statement.executeQuery(\"SELECT * FROM test_prepst_date ORDER BY id ASC\")) {\n            for (int i = 0; i < stageResult.length; i++) {\n              assertTrue(rsStage.next());\n              stageResult[i] = rsStage.getDate(2);\n            }\n\n            for (int i = 0; i < dates.length; i++) {\n              assertEquals(\n                  nonStageResult[i],\n                  stageResult[i],\n                  \"Stage binding date should match non-stage binding date\");\n            }\n          }\n        }\n      } finally {\n        statement.execute(\"DROP TABLE IF EXISTS test_prepst_date\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBindWithNullValue(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace table testBindNull(cola date, colb time, colc timestamp, cold number)\");\n\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into testBindNull values (?, ?, ?, ?)\")) {\n        prepStatement.setDate(1, null);\n        prepStatement.setTime(2, null);\n        prepStatement.setTimestamp(3, null);\n        prepStatement.setBigDecimal(4, null);\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testBindNull\")) {\n          assertTrue(resultSet.next());\n          Date date = resultSet.getDate(1);\n          assertNull(date);\n          assertTrue(resultSet.wasNull());\n\n          Time time = resultSet.getTime(2);\n          assertNull(time);\n          assertTrue(resultSet.wasNull());\n\n          Timestamp timestamp = resultSet.getTimestamp(3);\n          assertNull(timestamp);\n          assertTrue(resultSet.wasNull());\n\n          BigDecimal bg = resultSet.getBigDecimal(4);\n          assertNull(bg);\n          assertTrue(resultSet.wasNull());\n        }\n        statement.execute(\"TRUNCATE table testbindnull\");\n        prepStatement.setDate(1, null, Calendar.getInstance());\n        prepStatement.setTime(2, null, Calendar.getInstance());\n        prepStatement.setTimestamp(3, null, Calendar.getInstance());\n        prepStatement.setBigDecimal(4, null);\n\n        prepStatement.addBatch();\n        prepStatement.executeBatch();\n\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testBindNull\")) {\n          assertTrue(resultSet.next());\n          Date date = resultSet.getDate(1);\n          assertNull(date);\n          assertTrue(resultSet.wasNull());\n\n          Time time = resultSet.getTime(2);\n          assertNull(time);\n          assertTrue(resultSet.wasNull());\n\n          Timestamp timestamp = resultSet.getTimestamp(3);\n          assertNull(timestamp);\n          assertTrue(resultSet.wasNull());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareDDL(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"create or replace table testprepareddl(cola number)\")) {\n          prepStatement.execute();\n        }\n        try (ResultSet resultSet = statement.executeQuery(\"show tables like 'testprepareddl'\")) {\n          // result should only have one row since table is created\n          assertThat(resultSet.next(), is(true));\n          assertThat(resultSet.next(), is(false));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testprepareddl\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareSCL(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(\"use SCHEMA  PUBLIC\")) {\n        prepStatement.execute();\n      }\n      try (ResultSet resultSet =\n          connection.createStatement().executeQuery(\"select current_schema()\")) {\n        assertThat(resultSet.next(), is(true));\n        assertThat(resultSet.getString(1), is(\"PUBLIC\"));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareTCL(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection.setAutoCommit(false);\n      String[] testCases = {\"BEGIN\", \"COMMIT\"};\n\n      for (String testCase : testCases) {\n        try (PreparedStatement prepStatement = connection.prepareStatement(testCase)) {\n          try (ResultSet resultSet = prepStatement.executeQuery()) {\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getString(1), is(\"Statement executed successfully.\"));\n          }\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareShowCommand(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(\"show databases\")) {\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n        }\n      }\n    }\n  }\n\n  /**\n   * This test simulates the prepare timeout. When combine describe and execute, combine thread will\n   * wait max to 1 seconds before client coming back with execution request. Sleep thread for 5\n   * second will leads to gs recompiling the statement.\n   *\n   * @throws SQLException Will be thrown if any of driver calls fail\n   * @throws InterruptedException Will be thrown if the sleep is interrupted\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testPrepareTimeout(String queryResultFormat)\n      throws SQLException, InterruptedException {\n    try (Connection adminCon = getSnowflakeAdminConnection();\n        Statement adminStatement = adminCon.createStatement()) {\n      adminStatement.execute(\"alter system set enable_combined_describe=true\");\n      try {\n        try (Connection connection = getConn(queryResultFormat);\n            Statement statement = connection.createStatement()) {\n          statement.execute(\"create or replace table t(c1 string) as select 1\");\n          statement.execute(\"alter session set jdbc_enable_combined_describe=true\");\n          try (PreparedStatement prepStatement =\n              connection.prepareStatement(\"select c1 from t order by c1 limit 1\")) {\n            Thread.sleep(5000);\n            try (ResultSet resultSet = prepStatement.executeQuery()) {\n              assertTrue(resultSet.next());\n              assertThat(resultSet.getInt(1), is(1));\n            }\n          }\n          statement.execute(\"drop table if exists t\");\n        }\n      } finally {\n        adminStatement.execute(\"alter system set enable_combined_describe=default\");\n      }\n    }\n  }\n\n  /** Test case to make sure 2 non null bind refs was not constant folded into one */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSnow36284(String queryResultFormat) throws Exception {\n    String query = \"select * from (values ('a'), ('b')) x where x.COLUMN1 in (?,?);\";\n\n    try (Connection connection = getConn(queryResultFormat);\n        PreparedStatement preparedStatement = connection.prepareStatement(query)) {\n      preparedStatement.setString(1, \"a\");\n      preparedStatement.setString(2, \"b\");\n      try (ResultSet rs = preparedStatement.executeQuery()) {\n        int rowcount = 0;\n        Set<String> valuesReturned = Sets.newHashSetWithExpectedSize(2);\n        while (rs.next()) {\n          rowcount++;\n          valuesReturned.add(rs.getString(1));\n        }\n        assertEquals(2, rowcount, \"Should get back 2 rows\");\n        assertEquals(valuesReturned, Sets.newHashSet(\"a\", \"b\"), \"\");\n      }\n    }\n  }\n\n  /** Test for coalesce with bind and null arguments in a prepared statement */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testSnow35923(String queryResultFormat) throws Exception {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \" + \"optimizer_eliminate_scans_for_constant_select=false\");\n      statement.execute(\"create or replace table inc(a int, b int)\");\n      statement.execute(\"insert into inc(a, b) values (1, 2), \" + \"(NULL, 4), (5,NULL), (7,8)\");\n      // Query used to cause an incident.\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"SELECT coalesce(?, NULL) from inc;\")) {\n        preparedStatement.setInt(1, 0);\n        try (ResultSet rs = preparedStatement.executeQuery()) {}\n      }\n    }\n  }\n\n  /**\n   * Tests binding of object literals, including binding with object names as well as binding with\n   * object IDs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBindObjectLiteral(String queryResultFormat) throws Exception {\n    long t1Id = 0;\n    long t2Id = 0;\n    String t1 = null;\n\n    try (Connection conn = getConn(queryResultFormat);\n        Statement stmt = conn.createStatement()) {\n\n      String sqlText = \"create or replace table identifier(?) (c1 number)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        t1 = \"bindObjectTable1\";\n        // Bind the table name\n        pStmt.setString(1, t1);\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n      // Verify the table has been created and get the table ID\n      stmt.execute(\"select parse_json(system$dict_id('table', '\" + t1 + \"')):entityId;\");\n      try (ResultSet result = stmt.getResultSet()) {\n        if (result.next()) {\n          t1Id = Long.valueOf(result.getString(1));\n        }\n        assertTrue(t1Id != 0);\n      }\n\n      // Mix of object literal binds and value binds\n      sqlText = \"insert into identifier(?) values (1), (2), (3)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setParameter(\"resolve_object_ids\", true);\n        // Bind by object IDs\n        pStmt.setLong(1, t1Id);\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n      // Perform some selection\n      sqlText = \"select * from identifier(?) order by 1\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setString(1, t1);\n        try (ResultSet result = pStmt.executeQuery()) {\n          // Verify 3 rows have been inserted\n          for (int i = 0; i < 3; i++) {\n            assertTrue(result.next());\n          }\n          assertFalse(result.next());\n        }\n      }\n      // Alter Table\n      sqlText = \"alter table identifier(?) add column c2 number\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setParameter(\"resolve_object_ids\", true);\n        pStmt.setLong(1, t1Id);\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n\n      // Describe\n      sqlText = \"desc table identifier(?)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setString(1, t1);\n        try (ResultSet result = pStmt.executeQuery()) {\n          // Verify two columns have been created\n          for (int i = 0; i < 2; i++) {\n            assertTrue(result.next());\n          }\n          assertFalse(result.next());\n        }\n      }\n\n      // Create another table\n      String t2 = \"bindObjectTable2\";\n      sqlText = \"create or replace table identifier(?) (c1 number)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setString(1, t2);\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n      // Verify the table has been created and get the table ID\n      stmt.execute(\"select parse_json(system$dict_id('table', '\" + t2 + \"')):entityId;\");\n      try (ResultSet result = stmt.getResultSet()) {\n        if (result.next()) {\n          t2Id = Long.valueOf(result.getString(1));\n        }\n        assertTrue(t2Id != 0);\n      }\n\n      // Mix object binds with value binds\n      sqlText = \"insert into identifier(?) values (?), (?), (?)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setString(1, t2);\n        pStmt.setInt(2, 1);\n        pStmt.setInt(3, 2);\n        pStmt.setInt(4, 3);\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n      // Verify that 3 rows have been inserted\n      sqlText = \"select * from identifier(?) order by 1\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setParameter(\"resolve_object_ids\", true);\n        pStmt.setLong(1, t2Id);\n        try (ResultSet result = pStmt.executeQuery()) {\n          for (int i = 0; i < 3; i++) {\n            assertTrue(result.next());\n          }\n          assertFalse(result.next());\n        }\n      }\n\n      // Multiple Object Binds\n      sqlText =\n          \"select t2.c1 from identifier(?) as t1, identifier(?) as t2 \"\n              + \"where t1.c1 = t2.c1 and t1.c1 > (?)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setParameter(\"resolve_object_ids\", true);\n        pStmt.setString(1, t1);\n        pStmt.setLong(2, t2Id);\n        pStmt.setInt(3, 1);\n        try (ResultSet result = pStmt.executeQuery()) {\n          for (int i = 0; i < 2; i++) {\n            assertTrue(result.next());\n          }\n          assertFalse(result.next());\n        }\n      }\n\n      // Drop Tables\n      sqlText = \"drop table identifier(?)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setString(1, \"bindObjectTable1\");\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n\n      sqlText = \"drop table identifier(?)\";\n      try (SnowflakePreparedStatementImpl pStmt =\n          (SnowflakePreparedStatementImpl) conn.prepareStatement(sqlText)) {\n        pStmt.setParameter(\"resolve_object_ids\", true);\n        pStmt.setLong(1, t2Id);\n        try (ResultSet result = pStmt.executeQuery()) {}\n      }\n\n      // Verify that the tables have been dropped\n      stmt.execute(\"show tables like 'bindobjecttable%'\");\n      try (ResultSet result = stmt.getResultSet()) {\n        assertFalse(result.next());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBindTimestampTZViaString(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"alter session set timestamp_tz_output_format='YYYY-MM\" + \"-DD HH24:MI:SS.FF9 TZHTZM'\");\n        statement.execute(\"create or replace  table testbindtstz(cola timestamp_tz)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into testbindtstz values(?)\")) {\n          preparedStatement.setString(1, \"2017-11-30T18:17:05.123456789+08:00\");\n          int count = preparedStatement.executeUpdate();\n          assertThat(count, is(1));\n        }\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testbindtstz\")) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getString(1), is(\"2017-11-30 18:17:05.123456789 +0800\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testbindtstz\");\n      }\n    }\n  }\n\n  /**\n   * Ensures binding a string type with TIMESTAMP_TZ works. The customer has to use the specific\n   * timestamp format: YYYY-MM-DD HH24:MI:SS.FF9 TZH:TZM\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBindTimestampTZViaStringBatch(String queryResultFormat) throws SQLException {\n    TimeZone originalTimeZone = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\"); // enable stage bind\n        statement.execute(\n            \"create or replace table testbindtstz(cola timestamp_tz, colb timestamp_ntz)\");\n        statement.execute(\n            \"ALTER SESSION SET TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n        statement.execute(\n            \"ALTER SESSION SET TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into testbindtstz values(?,?)\")) {\n\n          preparedStatement.setString(1, \"2017-11-30 18:17:05.123456789 +08:00\");\n          preparedStatement.setString(2, \"2017-11-30 18:17:05.123456789\");\n          preparedStatement.addBatch();\n          preparedStatement.setString(1, \"2017-05-03 16:44:42.0\");\n          preparedStatement.setString(2, \"2017-05-03 16:44:42.0\");\n          preparedStatement.addBatch();\n          int[] count = preparedStatement.executeBatch();\n          assertThat(count[0], is(1));\n\n          try (ResultSet resultSet =\n              statement.executeQuery(\"select * from testbindtstz order by 1 desc\")) {\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getString(1), is(\"Thu, 30 Nov 2017 18:17:05 +0800\"));\n            assertThat(resultSet.getString(2), is(\"Thu, 30 Nov 2017 18:17:05 Z\"));\n\n            assertTrue(resultSet.next());\n            assertThat(resultSet.getString(1), is(\"Wed, 03 May 2017 16:44:42 -0700\"));\n            assertThat(resultSet.getString(2), is(\"Wed, 03 May 2017 16:44:42 Z\"));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists testbindtstz\");\n      }\n    } finally {\n      TimeZone.setDefault(originalTimeZone);\n    }\n  }\n\n  /**\n   * Test to ensure that when SqlBindRef is in a SqlConstantList the sub-expression remover does not\n   * treat expressions with different bind values are same expressions. SNOW-41620\n   *\n   * @throws Exception raises if any error occurs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSnow41620(String queryResultFormat) throws Exception {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      // Create a table and insert 3 records\n      statement.execute(\"CREATE or REPLACE TABLE SNOW41620 (c1 varchar(20),\" + \"c2 int\" + \"  )\");\n      statement.execute(\"insert into SNOW41620 values('value1', 1), ('value2', 2), ('value3', 3)\");\n\n      String PARAMETERIZED_QUERY =\n          \"SELECT t0.C1, \"\n              + \"CASE WHEN t0.C1 IN (?, ?) THEN t0.C2  ELSE null END, \"\n              + \"CASE WHEN t0.C1 IN (?, ?) THEN t0.C2  ELSE null END \"\n              + \"FROM SNOW41620 t0\";\n\n      try (PreparedStatement pst = connection.prepareStatement(PARAMETERIZED_QUERY)) {\n        // bind values\n        pst.setObject(1, \"value1\");\n        pst.setObject(2, \"value3\");\n        pst.setObject(3, \"value2\");\n        pst.setObject(4, \"value3\");\n        try (ResultSet bindStmtResultSet = pst.executeQuery()) {\n\n          // Execute the same query with bind values replaced in the sql\n          String DIRECT_QUERY =\n              \"SELECT t0.C1, \"\n                  + \"CASE WHEN t0.C1 IN ('value1', 'value3') THEN t0.C2  ELSE null END,\"\n                  + \"CASE WHEN t0.C1 IN ('value2', 'value3') THEN t0.C2  ELSE null END \"\n                  + \"FROM SNOW41620 t0\";\n          try (PreparedStatement pst1 = connection.prepareStatement(DIRECT_QUERY);\n              ResultSet directStmtResultSet = pst1.executeQuery()) {\n            checkResultSetEqual(bindStmtResultSet, directStmtResultSet);\n          }\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSnow50141(String queryResultFormat) throws Exception {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(\"select 1 where true=?\")) {\n        prepStatement.setObject(1, true);\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertThat(resultSet.next(), is(true));\n          assertThat(resultSet.getInt(1), is(1));\n          assertThat(resultSet.next(), is(false));\n        }\n      }\n    }\n  }\n\n  /**\n   * Check if both the {@link ResultSet} passed in has equivalent data. Checks the toString of the\n   * underlying data returned.\n   *\n   * @param rs1 ResultSet1 to compare\n   * @param rs2 ResultSet2 to compare\n   * @throws SQLException Will be thrown if any of the Driver calls fail\n   */\n  private void checkResultSetEqual(ResultSet rs1, ResultSet rs2) throws SQLException {\n    int columns = rs1.getMetaData().getColumnCount();\n    assertEquals(\n        columns,\n        rs2.getMetaData().getColumnCount(),\n        \"Resultsets do not match in the number of columns returned\");\n\n    while (rs1.next() && rs2.next()) {\n      for (int columnIndex = 1; columnIndex <= columns; columnIndex++) {\n        final Object res1 = rs1.getObject(columnIndex);\n        final Object res2 = rs2.getObject(columnIndex);\n\n        assertEquals(\n            res1,\n            res2,\n            String.format(\"%s and %s are not equal values at column %d\", res1, res2, columnIndex));\n      }\n\n      assertEquals(\n          rs1.isLast(), rs2.isLast(), \"Number of records returned by the results does not match\");\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPreparedStatementWithSkipParsing(String queryResultFormat) throws Exception {\n    try (Connection con = getConn(queryResultFormat)) {\n      PreparedStatement stmt =\n          con.unwrap(SnowflakeConnectionImpl.class).prepareStatement(\"select 1\", true);\n      try (ResultSet rs = stmt.executeQuery()) {\n        assertThat(rs.next(), is(true));\n        assertThat(rs.getInt(1), is(1));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPreparedStatementWithSkipParsingAndBinding(String queryResultFormat)\n      throws Exception {\n    try (Connection con = getConn(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      statement.execute(\"create or replace table t(c1 int)\");\n      try {\n        try (PreparedStatement stmt =\n            con.unwrap(SnowflakeConnectionImpl.class)\n                .prepareStatement(\"insert into t(c1) values(?)\", true)) {\n          stmt.setInt(1, 123);\n          int ret = stmt.executeUpdate();\n          assertThat(ret, is(1));\n        }\n        try (PreparedStatement stmt =\n                con.unwrap(SnowflakeConnectionImpl.class)\n                    .prepareStatement(\"select * from t\", true);\n            ResultSet rs = stmt.executeQuery()) {\n          assertThat(rs.next(), is(true));\n          assertThat(rs.getInt(1), is(123));\n        }\n      } finally {\n        statement.execute(\"drop table if exists t\");\n      }\n    }\n  }\n\n  /**\n   * Test prepare mode for to_timestamp(?, 3), compiler right now can not handle this properly. A\n   * workaround is added. More specifically, ErrorCode returned for this statement is caught in\n   * SnowflakePreparedStatementImpl so that execution can continue\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSnow44393(String queryResultFormat) throws Exception {\n    try (Connection con = getConn(queryResultFormat)) {\n      assertFalse(\n          con.createStatement()\n              .execute(\"alter session set timestamp_ntz_output_format='YYYY-MM-DD HH24:MI:SS'\"));\n      try (PreparedStatement stmt = con.prepareStatement(\"select to_timestamp_ntz(?, 3)\")) {\n        stmt.setBigDecimal(1, new BigDecimal(\"1261440000000\"));\n        try (ResultSet resultSet = stmt.executeQuery()) {\n          assertTrue(resultSet.next());\n\n          String res = resultSet.getString(1);\n          assertThat(res, is(\"2009-12-22 00:00:00\"));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testAddBatchNumericNullFloatMixed(String queryResultFormat) throws Exception {\n    try (Connection connection = getConn(queryResultFormat)) {\n      for (int threshold = 0; threshold < 2; ++threshold) {\n        connection\n            .createStatement()\n            .execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = \" + threshold);\n        String sql = \"insert into \" + uniqueTableName + \"(col, colB) values(?,?)\";\n\n        // Null NUMERIC followed by FLOAT.\n        try (PreparedStatement stmt = connection.prepareStatement(sql)) {\n          stmt.setInt(1, 1);\n          stmt.setNull(2, java.sql.Types.NUMERIC);\n          stmt.addBatch();\n          stmt.setInt(1, 2);\n          stmt.setObject(2, 4.0f);\n          stmt.addBatch();\n          stmt.executeBatch();\n\n          // evaluate query ids\n          assertTrue(stmt.isWrapperFor(SnowflakePreparedStatement.class));\n          String qid1 = stmt.unwrap(SnowflakePreparedStatement.class).getQueryID();\n          assertNotNull(qid1);\n        }\n\n        // Null CHAR followed by FLOAT. CHAR type is ignored.\n        try (PreparedStatement stmt = connection.prepareStatement(sql)) {\n          stmt.setInt(1, 1);\n          stmt.setNull(2, java.sql.Types.CHAR);\n          stmt.addBatch();\n          stmt.setInt(1, 2);\n          stmt.setObject(2, 4.0f);\n          stmt.addBatch();\n          stmt.executeBatch();\n        }\n\n        // FLOAT followed by Null NUMERIC. Type for null value is ignored\n        // anyway.\n        try (PreparedStatement stmt = connection.prepareStatement(sql)) {\n          stmt.setInt(1, 2);\n          stmt.setObject(2, 4.0f);\n          stmt.addBatch();\n          stmt.setInt(1, 1);\n          stmt.setNull(2, java.sql.Types.NUMERIC);\n          stmt.addBatch();\n          stmt.setInt(1, 1);\n          stmt.setNull(2, java.sql.Types.BOOLEAN);\n          stmt.addBatch();\n          stmt.executeBatch();\n        }\n\n        // Null NUMERIC followed by FLOAT, but STRING won't be allowed.\n        try (PreparedStatement stmt = connection.prepareStatement(sql)) {\n          stmt.setInt(1, 1);\n          stmt.setNull(2, java.sql.Types.NUMERIC);\n          stmt.addBatch();\n          stmt.setInt(1, 2);\n          stmt.setObject(2, 4.0f);\n          stmt.addBatch();\n          stmt.setInt(1, 1);\n          stmt.setObject(2, \"test1\");\n          SnowflakeSQLException ex = assertThrows(SnowflakeSQLException.class, stmt::addBatch);\n          assertThat(\n              \"Error code is wrong\",\n              ex.getErrorCode(),\n              equalTo(ErrorCode.ARRAY_BIND_MIXED_TYPES_NOT_SUPPORTED.getMessageCode()));\n          assertThat(\"Location\", ex.getMessage(), containsString(\"Column: 2, Row: 3\"));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testInvalidUsageOfApi(String queryResultFormat) throws Exception {\n    try (Connection connection = getConn(queryResultFormat);\n        PreparedStatement preparedStatement = connection.prepareStatement(\"select 1\")) {\n      final int expectedCode =\n          ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API.getMessageCode();\n\n      assertException(\n          new RunnableWithSQLException() {\n            @Override\n            public void run() throws SQLException {\n              preparedStatement.executeUpdate(\"select 1\");\n            }\n          },\n          expectedCode);\n\n      assertException(\n          new RunnableWithSQLException() {\n            @Override\n            public void run() throws SQLException {\n              preparedStatement.execute(\"select 1\");\n            }\n          },\n          expectedCode);\n\n      assertException(\n          new RunnableWithSQLException() {\n            @Override\n            public void run() throws SQLException {\n              preparedStatement.addBatch(\"select 1\");\n            }\n          },\n          expectedCode);\n    }\n  }\n\n  private void assertException(RunnableWithSQLException runnable, int expectedCode) {\n    SQLException e = assertThrows(SQLException.class, runnable::run);\n    assertThat(e.getErrorCode(), is(expectedCode));\n  }\n\n  private interface RunnableWithSQLException {\n    void run() throws SQLException;\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testCreatePreparedStatementWithParameters(String queryResultFormat) throws Throwable {\n    try (Connection connection = getConn(queryResultFormat)) {\n      connection.prepareStatement(\n          \"select 1\", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  connection.prepareStatement(\n                      \"select 1\", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE));\n      assertEquals((int) ErrorCode.FEATURE_UNSUPPORTED.getMessageCode(), ex.getErrorCode());\n      connection.prepareStatement(\n          \"select 1\",\n          ResultSet.TYPE_FORWARD_ONLY,\n          ResultSet.CONCUR_READ_ONLY,\n          ResultSet.CLOSE_CURSORS_AT_COMMIT);\n      ex =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  connection.prepareStatement(\n                      \"select 1\",\n                      ResultSet.TYPE_FORWARD_ONLY,\n                      ResultSet.CONCUR_READ_ONLY,\n                      ResultSet.HOLD_CURSORS_OVER_COMMIT));\n      assertEquals((int) ErrorCode.FEATURE_UNSUPPORTED.getMessageCode(), ex.getErrorCode());\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareAndGetMeta(String queryResultFormat) throws SQLException {\n    try (Connection con = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = con.prepareStatement(\"select 1 where 1 > ?\")) {\n        ResultSetMetaData meta = prepStatement.getMetaData();\n        assertThat(meta.getColumnCount(), is(1));\n      }\n      try (PreparedStatement prepStatement = con.prepareStatement(\"select 1 where 1 > ?\")) {\n        ParameterMetaData parameterMetaData = prepStatement.getParameterMetaData();\n        assertThat(parameterMetaData.getParameterCount(), is(1));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatement2LatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.PreparedStatement1IT.bindOneParamSet;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.api.statement.SnowflakePreparedStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * PreparedStatement integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the oldest supported driver to examine\n * if the tests still are not applicable. If it is applicable, move tests to PreparedStatement2IT so\n * that both the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.STATEMENT)\npublic class PreparedStatement2LatestIT extends PreparedStatement0IT {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testPrepareUDTF(String queryResultFormat) throws Exception {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table employee(id number, address text)\");\n        statement.execute(\n            \"create or replace function employee_detail(sid number, addr text)\\n\"\n                + \" returns table(id number, address text)\\n\"\n                + \"LANGUAGE SQL\\n\"\n                + \"as\\n\"\n                + \"$$\\n\"\n                + \"select *\\n\"\n                + \"from employee\\n\"\n                + \"where  id=sid\\n\"\n                + \"$$;\");\n\n        // should resolve successfully\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"select * from table(employee_detail(?, ?))\")) {\n          prepStatement.setInt(1, 1);\n          prepStatement.setString(2, \"abc\");\n          prepStatement.execute();\n        }\n\n        // should resolve successfully\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"select * from table(employee_detail(?, 'abc'))\")) {\n\n          prepStatement.setInt(1, 1);\n          prepStatement.execute();\n        }\n\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"select * from table(employee_detail(?, 123))\"); ) {\n          // second argument is invalid\n          prepStatement.setInt(1, 1);\n          assertThrows(SQLException.class, prepStatement::execute);\n        }\n\n        // create a udf with same name but different arguments and return type\n        statement.execute(\n            \"create or replace function employee_detail(name text , addr text)\\n\"\n                + \" returns table(id number)\\n\"\n                + \"LANGUAGE SQL\\n\"\n                + \"as\\n\"\n                + \"$$\\n\"\n                + \"select id\\n\"\n                + \"from employee\\n\"\n                + \"$$;\");\n\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"select * from table(employee_detail(?, 'abc'))\")) {\n          prepStatement.setInt(1, 1);\n          prepStatement.execute();\n        }\n      } finally {\n        statement.execute(\"drop function if exists employee_detail(number, text)\");\n        statement.execute(\"drop function if exists employee_detail(text, text)\");\n      }\n    }\n  }\n\n  /**\n   * SNOW-88426: skip bind parameter index check if prepare fails and defer the error checks to\n   * execute\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSelectWithBinding(String queryResultFormat) throws Throwable {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table TESTNULL(created_time timestamp_ntz, mid int)\");\n        // skip bind parameter index check if prepare fails and defer the error checks to execute\n        try (PreparedStatement ps =\n            connection.prepareStatement(\n                \"SELECT 1 FROM TESTNULL WHERE CREATED_TIME = TO_TIMESTAMP(?, 3) and MID = ?\")) {\n          ps.setObject(1, 0);\n          ps.setObject(2, null);\n          try (ResultSet rs = ps.executeQuery()) {\n            assertFalse(rs.next());\n          }\n        }\n\n        // describe is success and do the index range check\n        try (PreparedStatement ps =\n            connection.prepareStatement(\n                \"SELECT 1 FROM TESTNULL WHERE CREATED_TIME = TO_TIMESTAMP(?::NUMBER, 3) and MID = ?\")) {\n          ps.setObject(1, 0);\n          ps.setObject(2, null);\n\n          try (ResultSet rs = ps.executeQuery()) {\n            assertFalse(rs.next());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists TESTNULL\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testLimitBind(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      String stmtStr = \"select seq4() from table(generator(rowcount=>100)) limit ?\";\n      try (PreparedStatement prepStatement = connection.prepareStatement(stmtStr)) {\n        prepStatement.setInt(1, 10);\n        prepStatement.executeQuery(); // ensure no error is raised.\n      }\n    }\n  }\n\n  /** SNOW-31746 */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testConstOptLimitBind(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      String stmtStr = \"select 1 limit ? offset ?\";\n      try (PreparedStatement prepStatement = connection.prepareStatement(stmtStr)) {\n        prepStatement.setInt(1, 10);\n        prepStatement.setInt(2, 0);\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(1), is(1));\n          assertThat(resultSet.next(), is(false));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testTableFuncBindInput(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(tableFuncSQL)) {\n        prepStatement.setInt(1, 2);\n        try (ResultSet resultSet = prepStatement.executeQuery()) {\n          assertEquals(2, getSizeOfResultSet(resultSet));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testExecuteLargeBatch(String queryResultFormat) throws SQLException {\n    try (Connection con = getConn(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table mytab(id int)\");\n        try (PreparedStatement pstatement =\n            con.prepareStatement(\"insert into mytab(id) values (?)\")) {\n          pstatement.setLong(1, 4);\n          pstatement.addBatch();\n          pstatement.setLong(1, 5);\n          pstatement.addBatch();\n          pstatement.setLong(1, 6);\n          pstatement.addBatch();\n          pstatement.executeLargeBatch();\n          con.commit();\n          try (ResultSet resultSet = statement.executeQuery(\"select * from mytab\")) {\n            assertTrue(resultSet.next());\n            assertEquals(4, resultSet.getInt(1));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists mytab\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testRemoveExtraDescribeCalls(String queryResultFormat) throws SQLException {\n    String queryId1 = null;\n    String queryId2 = null;\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_uuid_with_bind(c1 number)\");\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_uuid_with_bind values (?)\")) {\n          preparedStatement.setInt(1, 5);\n          assertEquals(1, preparedStatement.executeUpdate());\n          queryId1 = preparedStatement.unwrap(SnowflakePreparedStatement.class).getQueryID();\n          // Calling getMetadata() should no longer require an additional server call because we\n          // have\n          // the\n          // metadata form the executeUpdate\n          queryId2 =\n              preparedStatement.getMetaData().unwrap(SnowflakeResultSetMetaData.class).getQueryID();\n          // Assert the query IDs are the same. This will be the case if there is no additional\n          // describe\n          // call for getMetadata().\n          assertEquals(queryId1, queryId2);\n\n          preparedStatement.addBatch();\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from test_uuid_with_bind where c1 = ?\")) {\n          assertFalse(\n              preparedStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n          preparedStatement.setInt(1, 5);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertThat(resultSet.next(), is(true));\n            queryId1 = preparedStatement.unwrap(SnowflakePreparedStatement.class).getQueryID();\n            queryId2 =\n                preparedStatement\n                    .getMetaData()\n                    .unwrap(SnowflakeResultSetMetaData.class)\n                    .getQueryID();\n            String queryId3 = resultSet.unwrap(SnowflakeResultSet.class).getQueryID();\n            // Assert all 3 query IDs are the same because only 1 server call was executed\n            assertEquals(queryId1, queryId2);\n            assertEquals(queryId1, queryId3);\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_uuid_with_bind\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testRemoveExtraDescribeCallsSanityCheck(String queryResultFormat)\n      throws SQLException {\n    String queryId1;\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\n              \"create or replace table test_uuid_with_bind(c1 number, c2 string)\")) {\n        preparedStatement.execute();\n        queryId1 = preparedStatement.unwrap(SnowflakePreparedStatement.class).getQueryID();\n      }\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"insert into test_uuid_with_bind values (?, ?)\")) {\n        assertFalse(\n            preparedStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n        preparedStatement.setInt(1, 5);\n        preparedStatement.setString(2, \"hello\");\n        preparedStatement.addBatch();\n        preparedStatement.setInt(1, 7);\n        preparedStatement.setString(2, \"hello1\");\n        preparedStatement.addBatch();\n        String queryId2 =\n            preparedStatement.getMetaData().unwrap(SnowflakeResultSetMetaData.class).getQueryID();\n        // These query IDs should not match because they are from 2 different prepared statements\n        assertNotEquals(queryId1, queryId2);\n        preparedStatement.executeBatch();\n        String queryId3 = preparedStatement.unwrap(SnowflakePreparedStatement.class).getQueryID();\n        // Another execute call was created, so prepared statement has new query ID\n        assertNotEquals(queryId2, queryId3);\n        // Calling getMetadata() should no longer require an additional server call because we\n        // have\n        // the\n        // metadata form the executeUpdate\n        String queryId4 =\n            preparedStatement.getMetaData().unwrap(SnowflakeResultSetMetaData.class).getQueryID();\n        // Assert the query IDs for the 2 identical getMetadata() calls are the same. They should\n        // match\n        // since metadata no longer gets overwritten after successive query calls.\n        assertEquals(queryId2, queryId4);\n        connection.createStatement().execute(\"drop table if exists test_uuid_with_bind\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testAlreadyDescribedMultipleResults(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat)) {\n      try (PreparedStatement prepStatement = connection.prepareStatement(insertSQL)) {\n        bindOneParamSet(prepStatement, 1, 1.22222, (float) 1.2, \"test\", 12121212121L, (short) 12);\n        prepStatement.execute();\n        // The statement above has already been described since it has been executed\n        assertTrue(prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(selectSQL)) {\n        // Assert the statement, once it has been re-created, has already described set to false\n        assertFalse(\n            prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n        prepStatement.setInt(1, 1);\n        try (ResultSet rs = prepStatement.executeQuery()) {\n          assertTrue(rs.next());\n          assertTrue(\n              prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n        }\n      }\n      try (PreparedStatement prepStatement = connection.prepareStatement(selectAllSQL)) {\n        // Assert the statement, once it has been re-created, has already described set to false\n        assertFalse(\n            prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n        try (ResultSet rs = prepStatement.executeQuery()) {\n          assertTrue(rs.next());\n          assertTrue(\n              prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n        }\n      }\n    }\n  }\n\n  /**\n   * Test that consecutive batch inserts can occur. See SNOW-830719. Fixes regression caused by\n   * SNOW-762522.\n   *\n   * @throws Exception\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testConsecutiveBatchInsertError(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table testStageArrayBind(c1 integer, c2 string)\");\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"insert into testStageArrayBind values (?, ?)\")) {\n          // Assert to begin with that before the describe call, array binding is not supported\n          assertFalse(\n              prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isAlreadyDescribed());\n          assertFalse(\n              prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isArrayBindSupported());\n          // Insert enough rows to hit the default binding array threshold\n          for (int i = 0; i < 35000; i++) {\n            prepStatement.setInt(1, i);\n            prepStatement.setString(2, \"test\" + i);\n            prepStatement.addBatch();\n          }\n          prepStatement.executeBatch();\n          // After executing the first batch, verify that array bind support is still true\n          assertTrue(\n              prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isArrayBindSupported());\n          for (int i = 0; i < 35000; i++) {\n            prepStatement.setInt(1, i);\n            prepStatement.setString(2, \"test\" + i);\n            prepStatement.addBatch();\n          }\n          prepStatement.executeBatch();\n          // After executing the second batch, verify that array bind support is still true\n          assertTrue(\n              prepStatement.unwrap(SnowflakePreparedStatementImpl.class).isArrayBindSupported());\n        }\n      } finally {\n        statement.execute(\"drop table if exists testStageArrayBind\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testToString(String queryResultFormat) throws SQLException {\n    try (Connection connection = getConn(queryResultFormat);\n        PreparedStatement prepStatement =\n            connection.prepareStatement(\"select current_version() --testing toString()\")) {\n\n      // Query ID is going to be null since we didn't execute the statement yet\n      assertEquals(\n          \"select current_version() --testing toString() - Query ID: null\",\n          prepStatement.toString());\n\n      prepStatement.executeQuery();\n      assertTrue(\n          prepStatement\n              .toString()\n              .matches(\n                  \"select current_version\\\\(\\\\) --testing toString\\\\(\\\\) - Query ID: (\\\\d|\\\\w)+(-(\\\\d|\\\\w)+)+$\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatementFeatureNotSupportedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.net.URL;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class PreparedStatementFeatureNotSupportedIT extends BaseJDBCTest {\n  @Test\n  public void testFeatureNotSupportedException() throws Throwable {\n    try (Connection connection = getConnection();\n        PreparedStatement preparedStatement = connection.prepareStatement(\"select ?\")) {\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setAsciiStream(1, new BaseJDBCTest.FakeInputStream()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setAsciiStream(1, new BaseJDBCTest.FakeInputStream(), 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setBinaryStream(1, new BaseJDBCTest.FakeInputStream()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setBinaryStream(1, new BaseJDBCTest.FakeInputStream(), 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setCharacterStream(1, new BaseJDBCTest.FakeReader()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setCharacterStream(1, new BaseJDBCTest.FakeReader(), 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setRef(1, new BaseJDBCTest.FakeRef()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setBlob(1, new BaseJDBCTest.FakeBlob()));\n\n      URL fakeURL = new URL(\"http://localhost:8888/\");\n      expectFeatureNotSupportedException(() -> preparedStatement.setURL(1, fakeURL));\n\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setRowId(1, new BaseJDBCTest.FakeRowId()));\n      expectFeatureNotSupportedException(() -> preparedStatement.setNString(1, \"test\"));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setNCharacterStream(1, new BaseJDBCTest.FakeReader()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setNCharacterStream(1, new BaseJDBCTest.FakeReader(), 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setNClob(1, new BaseJDBCTest.FakeNClob()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setNClob(1, new BaseJDBCTest.FakeReader(), 1));\n\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setClob(1, new BaseJDBCTest.FakeReader()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setClob(1, new BaseJDBCTest.FakeReader(), 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setBlob(1, new BaseJDBCTest.FakeInputStream()));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setBlob(1, new BaseJDBCTest.FakeInputStream(), 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.setSQLXML(1, new BaseJDBCTest.FakeSQLXML()));\n\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.execute(\"insert into a values(1)\", 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.execute(\"insert into a values(1)\", new int[] {}));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.execute(\"insert into a values(1)\", new String[] {}));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.executeUpdate(\"insert into a values(1)\", 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.executeUpdate(\"insert into a values(1)\", new int[] {}));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.executeUpdate(\"insert into a values(1)\", new String[] {}));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.executeLargeUpdate(\"insert into a values(1)\", 1));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.executeLargeUpdate(\"insert into a values(1)\", new int[] {}));\n      expectFeatureNotSupportedException(\n          () -> preparedStatement.executeLargeUpdate(\"insert into a values(1)\", new String[] {}));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PreparedStatementLargeUpdateLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.spy;\n\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Map;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\n@Tag(TestTags.STATEMENT)\npublic class PreparedStatementLargeUpdateLatestIT extends BaseJDBCTest {\n\n  /**\n   * Test that large update larger than MAX_INT returns correctly\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testLargeUpdate() throws Throwable {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        long expectedUpdateRows = (long) Integer.MAX_VALUE + 10L;\n        statement.execute(\"create or replace table  test_large_update(c1 boolean)\");\n        try (PreparedStatement st =\n                con.prepareStatement(\n                    \"insert into test_large_update select true from table(generator(rowcount=>\"\n                        + expectedUpdateRows\n                        + \"))\");\n            PreparedStatement spyp = spy(st)) {\n          // Mock internal method which returns rowcount\n          Mockito.doReturn(expectedUpdateRows)\n              .when((SnowflakePreparedStatementImpl) spyp)\n              .executeUpdateInternal(\n                  Mockito.any(String.class),\n                  Mockito.any(Map.class),\n                  Mockito.any(boolean.class),\n                  Mockito.any(ExecTimeTelemetryData.class));\n          long updatedRows = spyp.executeLargeUpdate();\n          assertEquals(expectedUpdateRows, updatedRows);\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_large_update\");\n      }\n    }\n  }\n\n  /**\n   * Test that when a batch size larger than MAX_INT is returned, the result is returned as a long.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testExecuteLargeBatchOverIntMax() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table over_int_table (val string, id int)\");\n        try (PreparedStatement pstmt =\n                connection.prepareStatement(\"UPDATE over_int_table SET ID=200\");\n            PreparedStatement spyp = spy(pstmt)) {\n          long numRows = Integer.MAX_VALUE + 10L;\n          // Mock internal method which returns rowcount\n          Mockito.doReturn(numRows)\n              .when((SnowflakePreparedStatementImpl) spyp)\n              .executeUpdateInternal(\n                  Mockito.any(String.class),\n                  Mockito.any(Map.class),\n                  Mockito.any(boolean.class),\n                  Mockito.any(ExecTimeTelemetryData.class));\n          pstmt.addBatch();\n          long[] queryResult = spyp.executeLargeBatch();\n          assertEquals(1, queryResult.length);\n          assertEquals(numRows, queryResult[0]);\n        }\n      } finally {\n        statement.execute(\"drop table if exists over_int_table\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PrivateKeyAuthenticationExceptionHandlingLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport java.util.stream.Stream;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SecurityUtil;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\n@Tag(TestTags.CONNECTION)\npublic class PrivateKeyAuthenticationExceptionHandlingLatestIT {\n\n  static Stream<String> jwtTimeoutProvider() {\n    return Stream.of(\"10\", \"100\", null);\n  }\n\n  static Stream<String> timeOutSettings() {\n    return Stream.of(\"HTTP_CLIENT_CONNECTION_TIMEOUT\", \"HTTP_CLIENT_SOCKET_TIMEOUT\");\n  }\n\n  @BeforeEach\n  void setup() {\n    HttpUtil.reset();\n  }\n\n  @AfterEach\n  void cleanup() {\n    HttpUtil.reset();\n  }\n\n  /**\n   * Tests the authentication exception and retry JWT renew functionality when retrying login\n   * requests\n   */\n  @ParameterizedTest\n  @MethodSource(\"jwtTimeoutProvider\")\n  public void testPrivateKeyAuthTimeout(String jwtTimeout) {\n    System.setProperty(\n        SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"true\");\n    if (jwtTimeout != null) {\n      System.setProperty(\"JWT_AUTH_TIMEOUT\", jwtTimeout);\n    }\n\n    Properties props = new Properties();\n    props.put(\"user\", \"testUser\");\n    props.put(\"account\", \"testaccount\");\n\n    String privateKeyFile = AbstractDriverIT.getFullPathFileInResource(\"rsa_key.p8\");\n    props.put(\"private_key_file\", privateKeyFile);\n    props.put(\"authenticator\", \"SNOWFLAKE_JWT\");\n\n    try {\n      Exception ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () ->\n                  DriverManager.getConnection(\n                      \"jdbc:snowflake://localhost:8443/?db=TEST_DB&schema=PUBLIC\", props));\n      assertTrue(ex.getMessage().contains(\"JDBC driver encountered communication error\"));\n    } finally {\n      System.setProperty(\n          SecurityUtil.USE_BUNDLED_BOUNCY_CASTLE_FOR_PRIVATE_KEY_DECRYPTION_JVM, \"false\");\n      System.setProperty(\"JWT_AUTH_TIMEOUT\", \"0\");\n    }\n  }\n\n  /**\n   * Test that Connection timeout and socket timout are applied to the httpclient.\n   *\n   * @throws SQLException if any SQL error occurs\n   */\n  @ParameterizedTest\n  @MethodSource(\"timeOutSettings\")\n  public void testConnectionAndSocketTimeout(String timeoutType) throws SQLException {\n    Properties props = new Properties();\n    props.put(\"user\", \"testUser\");\n    props.put(\"account\", \"testaccount\");\n    props.put(\"password\", \"testPasswordt\");\n    props.put(timeoutType, \"10\");\n    long startLoginTime = System.currentTimeMillis();\n    SQLException e =\n        assertThrows(\n            SQLException.class,\n            () -> {\n              DriverManager.getConnection(\n                  \"jdbc:snowflake://fakeaccount.snowflakecomputing.com\", props);\n            });\n    assertThat(e.getErrorCode(), is(ErrorCode.NETWORK_ERROR.getMessageCode()));\n    long endLoginTime = System.currentTimeMillis();\n    // Time lapsed should be less than default socket timeout.\n    assertTrue(endLoginTime - startLoginTime < 30000);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ProxyLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AbstractDriverIT.connectAndVerifySimpleQuery;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.IOException;\nimport java.sql.SQLException;\nimport java.util.Objects;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClients;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class ProxyLatestIT extends BaseWiremockTest {\n\n  @AfterEach\n  public void tearDown() {\n    super.tearDown();\n    unsetJvmProperties();\n  }\n\n  private void addProxyProperties(Properties props) {\n    String proxyProtocol = getProxyProtocol(props);\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyProtocol\", proxyProtocol);\n    props.put(\"proxyHost\", WIREMOCK_HOST);\n    props.put(\"proxyPort\", String.valueOf(getProxyPort(proxyProtocol)));\n  }\n\n  private void setJvmProperties(Properties props) {\n    String proxyProtocol = getProxyProtocol(props);\n    System.setProperty(\"http.useProxy\", \"true\");\n    System.setProperty(\"http.proxyProtocol\", proxyProtocol);\n    if (Objects.equals(proxyProtocol, \"http\")) {\n      System.setProperty(\"http.proxyHost\", WIREMOCK_HOST);\n      System.setProperty(\"http.proxyPort\", String.valueOf(getProxyPort(proxyProtocol)));\n    } else {\n      System.setProperty(\"https.proxyHost\", WIREMOCK_HOST);\n      System.setProperty(\"https.proxyPort\", String.valueOf(getProxyPort(proxyProtocol)));\n    }\n  }\n\n  private void unsetJvmProperties() {\n    System.clearProperty(\"http.useProxy\");\n    System.clearProperty(\"http.proxyProtocol\");\n    System.clearProperty(\"http.proxyHost\");\n    System.clearProperty(\"http.proxyPort\");\n    System.clearProperty(\"https.proxyHost\");\n    System.clearProperty(\"https.proxyPort\");\n  }\n\n  private String getSnowflakeUrl(Properties props) {\n    String protocol = getProxyProtocol(props);\n    return String.format(\"%s://%s:%s\", protocol, props.get(\"host\"), props.get(\"port\"));\n  }\n\n  private void proxyAll(Properties props) {\n    String template =\n        \"{\\n\"\n            + \"  \\\"request\\\": {\\n\"\n            + \"    \\\"method\\\": \\\"ANY\\\",\\n\"\n            + \"    \\\"urlPattern\\\": \\\".*\\\"\\n\"\n            + \"  }\\n,\"\n            + \"  \\\"response\\\": { \\\"proxyBaseUrl\\\": \\\"%s\\\" }\\n\"\n            + \"}\";\n    String body = String.format(template, getSnowflakeUrl(props));\n    addMapping(body);\n  }\n\n  private void verifyProxyWasUsed() {\n    verifyRequestToProxy(\".*login.*\", 1);\n    verifyRequestToProxy(\".*query.*\", 1);\n  }\n\n  private void verifyProxyNotUsed() {\n    verifyRequestToProxy(\".*\", 0);\n  }\n\n  private void verifyRequestToProxy(String pathPattern, int expectedCount) {\n    String body = String.format(\"{ \\\"method\\\":\\\"POST\\\",\\\"urlPattern\\\": \\\".*%s.*\\\" }\", pathPattern);\n    HttpPost postRequest = createWiremockPostRequest(body, \"/__admin/requests/count\");\n    try (CloseableHttpClient client = HttpClients.createDefault();\n        CloseableHttpResponse response = client.execute(postRequest)) {\n      String responseString = EntityUtils.toString(response.getEntity());\n      ObjectMapper mapper = new ObjectMapper();\n      JsonNode json = mapper.readTree(responseString);\n      assertEquals(\n          expectedCount,\n          json.get(\"count\").asInt(),\n          \"expected request count not matched for pattern: \" + pathPattern);\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Test\n  public void testProxyIsUsedWhenSetInProperties() throws SQLException {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    addProxyProperties(props);\n    proxyAll(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyWasUsed();\n  }\n\n  @Test\n  public void testProxyIsUsedWhenSetInJVMParams() throws SQLException {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    setJvmProperties(props);\n    proxyAll(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyWasUsed();\n  }\n\n  @Test\n  public void testProxyNotUsedWhenNonProxyHostsMatchingInProperties() throws SQLException {\n    Properties props = getProperties();\n    addProxyProperties(props);\n    props.put(\"nonProxyHosts\", \"*\");\n    proxyAll(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyNotUsed();\n  }\n\n  @Test\n  public void testProxyIsUsedWhenNonProxyHostsNotMatchingInProperties() throws SQLException {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    addProxyProperties(props);\n    props.put(\"nonProxyHosts\", \"notMatchingHost\");\n    proxyAll(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyWasUsed();\n  }\n\n  @Test\n  public void testProxyNotUsedWhenNonProxyHostsMatchingInJVMParams() throws SQLException {\n    Properties props = getProperties();\n    setJvmProperties(props);\n    System.setProperty(\"http.nonProxyHosts\", \"*\");\n    proxyAll(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyNotUsed();\n  }\n\n  @Test\n  public void testProxyUsedWhenNonProxyHostsNotMatchingInJVMParams() throws SQLException {\n    setCustomTrustStorePropertyPath();\n    Properties props = getProperties();\n    setJvmProperties(props);\n    System.setProperty(\"http.nonProxyHosts\", \"notMatchingHost\");\n    proxyAll(props);\n\n    connectAndVerifySimpleQuery(props);\n    verifyProxyWasUsed();\n  }\n\n  @Override\n  protected Properties getProperties() {\n    Properties props = super.getProperties();\n    // disable telemetry because it introduces race condition which leads to sending requests\n    // outside of current test proxy settings\n    // Scenario:\n    // testProxyIsUsed*** -> success -> telemetry is scheduled for sending\n    // tearDown() resets properties and wiremock\n    // telemetry is sent using HttpClient with configured proxy from the previous test\n    // testProxyIsNotUsed*** -> fails because telemetry request is counted\n    props.put(\"CLIENT_TELEMETRY_ENABLED\", \"false\");\n    return props;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PutFileWithSpaceIncludedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n@Tag(TestTags.OTHERS)\npublic class PutFileWithSpaceIncludedIT extends BaseJDBCTest {\n  @TempDir private File tmpFolder;\n\n  /** Test PUT command to send a data file, which file name contains a space. */\n  @Test\n  @Disabled\n  public void putFileWithSpaceIncluded() throws Exception {\n    String AWS_SECRET_KEY = TestUtil.systemGetEnv(\"AWS_SECRET_ACCESS_KEY\");\n    String AWS_KEY_ID = TestUtil.systemGetEnv(\"AWS_ACCESS_KEY_ID\");\n    String SF_AWS_USER_BUCKET = TestUtil.systemGetEnv(\"SF_AWS_USER_BUCKET\");\n    if (SF_AWS_USER_BUCKET == null) {\n      String userName = TestUtil.systemGetEnv(\"USERNAME\");\n      assertNotNull(userName);\n      SF_AWS_USER_BUCKET = \"sfc-dev1-regression/\" + userName + \"/snow-13400\";\n    }\n\n    assertNotNull(AWS_SECRET_KEY);\n    assertNotNull(AWS_KEY_ID);\n\n    File dataFolder = new File(tmpFolder, \"data\");\n    dataFolder.mkdirs();\n    String tarFile = getFullPathFileInResource(\"snow-13400.tar\");\n    FileInputStream fis = new FileInputStream(tarFile);\n    TarArchiveInputStream tis = new TarArchiveInputStream(fis);\n    TarArchiveEntry tarEntry;\n    while ((tarEntry = tis.getNextTarEntry()) != null) {\n      File outputFile = new File(dataFolder, tarEntry.getName());\n      try (FileOutputStream fos = new FileOutputStream(outputFile)) {\n        IOUtils.copy(tis, fos);\n      }\n    }\n\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace stage snow13400 url='s3://\"\n                + SF_AWS_USER_BUCKET\n                + \"/snow13400'\"\n                + \"credentials=(AWS_KEY_ID='\"\n                + AWS_KEY_ID\n                + \"' AWS_SECRET_KEY='\"\n                + AWS_SECRET_KEY\n                + \"')\");\n\n        try (ResultSet resultSet =\n            statement.executeQuery(\n                \"put file://\"\n                    + dataFolder.getCanonicalPath()\n                    + \"/* @snow13400 auto_compress=false\")) {\n          int cnt = 0;\n          while (resultSet.next()) {\n            cnt++;\n          }\n          assertEquals(cnt, 1);\n        }\n        statement.execute(\"create or replace table snow13400(a string)\");\n        statement.execute(\"copy into snow13400 from @snow13400\");\n        try (ResultSet resultSet = con.createStatement().executeQuery(\"select * from snow13400\")) {\n          int cnt = 0;\n          String output = null;\n          while (resultSet.next()) {\n            output = resultSet.getString(1);\n            cnt++;\n          }\n          assertEquals(cnt, 1);\n          assertEquals(output, \"hello\");\n        }\n      } finally {\n        statement.execute(\"rm @snow13400\");\n        statement.execute(\"drop stage if exists snow13400\");\n        statement.execute(\"drop table if exists snow13400\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/PutUnescapeBackslashIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.startsWith;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class PutUnescapeBackslashIT extends AbstractDriverIT {\n  @BeforeAll\n  public static void setUpClass() throws Exception {}\n\n  /**\n   * Test PUT command for a file name including backslashes. The file name should be unescaped to\n   * match the name. SNOW-25974\n   */\n  @Test\n  public void testPutFileUnescapeBackslashes() throws Exception {\n    String remoteSubDir = \"testPut\";\n    String testDataFileName = \"testdata.txt\";\n\n    Path topDataDir = null;\n    try {\n      topDataDir = Files.createTempDirectory(\"testPutFileUnescapeBackslashes\");\n      topDataDir.toFile().deleteOnExit();\n\n      // create sub directory where the name includes spaces and\n      // backslashes\n      Path subDir = Files.createDirectories(Paths.get(topDataDir.toString(), \"test dir\\\\\\\\3\"));\n\n      // create a test data\n      File dataFile = new File(subDir.toFile(), testDataFileName);\n      try (Writer writer =\n          new BufferedWriter(\n              new OutputStreamWriter(new FileOutputStream(dataFile.getCanonicalPath()), \"UTF-8\"))) {\n        writer.write(\"1,test1\");\n      }\n      // run PUT command\n      try (Connection connection = getConnection();\n          Statement statement = connection.createStatement()) {\n        try {\n          String sql =\n              String.format(\"PUT 'file://%s' @~/%s/\", dataFile.getCanonicalPath(), remoteSubDir);\n\n          // Escape backslashes. This must be done by the application.\n          sql = sql.replaceAll(\"\\\\\\\\\", \"\\\\\\\\\\\\\\\\\");\n          statement.execute(sql);\n\n          try (ResultSet resultSet =\n              connection.createStatement().executeQuery(String.format(\"LS @~/%s/\", remoteSubDir))) {\n            while (resultSet.next()) {\n              assertThat(\n                  \"File name doesn't match\",\n                  resultSet.getString(1),\n                  startsWith(String.format(\"%s/%s\", remoteSubDir, testDataFileName)));\n            }\n          }\n        } finally {\n          statement.execute(String.format(\"RM @~/%s\", remoteSubDir));\n        }\n      }\n    } finally {\n      FileUtils.deleteDirectory(topDataDir.toFile());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/QueryContextWiremockIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.QueryContextDTO;\nimport net.snowflake.client.internal.core.QueryContextEntryDTO;\nimport net.snowflake.client.internal.core.SFSession;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class QueryContextWiremockIT extends BaseWiremockTest {\n\n  private static final String QCC_FAILED_QUERY_MAPPING =\n      \"/wiremock/mappings/querycontext/qcc-merge-on-failed-query.json\";\n\n  /**\n   * SNOW-3063492: Verify that QueryContext is merged from the server response even when the query\n   * fails. The WireMock stub returns a duplicate primary key error with queryContext entries.\n   * Before the fix, the driver would throw the exception without merging QCC.\n   */\n  @Test\n  public void testQueryContextMergedOnFailedQuery() throws Exception {\n    importMappingFromResources(QCC_FAILED_QUERY_MAPPING);\n\n    Properties props = getWiremockProps();\n    String connectStr = String.format(\"jdbc:snowflake://%s:%s\", WIREMOCK_HOST, wiremockHttpPort);\n\n    Connection conn = DriverManager.getConnection(connectStr, props);\n    SFSession session = conn.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    Statement stmt = conn.createStatement();\n\n    assertThrows(\n        SQLException.class,\n        () -> stmt.executeQuery(\"INSERT INTO hybrid_test VALUES ('key1', 'duplicate')\"));\n\n    QueryContextDTO qcc = session.getQueryContextDTO();\n    assertNotNull(qcc, \"QueryContext should have been merged from the failed response\");\n\n    List<QueryContextEntryDTO> entries = qcc.getEntries();\n    assertNotNull(entries);\n    assertEquals(2, entries.size());\n\n    assertEquals(0, entries.get(0).getId());\n    assertEquals(1775206502642407L, entries.get(0).getTimestamp());\n    assertEquals(0, entries.get(0).getPriority());\n\n    assertEquals(69924, entries.get(1).getId());\n    assertEquals(1775206502558790L, entries.get(1).getTimestamp());\n    assertEquals(1, entries.get(1).getPriority());\n  }\n\n  private static Properties getWiremockProps() {\n    Properties props = new Properties();\n    props.put(\"account\", \"testaccount\");\n    props.put(\"user\", \"testuser\");\n    props.put(\"password\", \"testpassword\");\n    props.put(\"warehouse\", \"testwh\");\n    props.put(\"database\", \"testdb\");\n    props.put(\"schema\", \"testschema\");\n    props.put(\"ssl\", \"off\");\n    props.put(\"insecureMode\", \"true\");\n    return props;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/RestRequestTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.AssumptionUtils.assumeRunningOnLinuxMac;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.ByteArrayInputStream;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.SocketException;\nimport java.net.SocketTimeoutException;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Stream;\nimport javax.net.ssl.SSLHandshakeException;\nimport javax.net.ssl.SSLKeyException;\nimport javax.net.ssl.SSLPeerUnverifiedException;\nimport javax.net.ssl.SSLProtocolException;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpExecutingContext;\nimport net.snowflake.client.internal.core.HttpExecutingContextBuilder;\nimport net.snowflake.client.internal.core.HttpResponseContextDto;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFTrustManager;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.InternalApiTelemetryTracker.InternalCallMarker;\nimport net.snowflake.client.internal.jdbc.telemetry.NoOpTelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryUtil;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.client.internal.util.DecorrelatedJitterBackoff;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.http.ProtocolVersion;\nimport org.apache.http.StatusLine;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.entity.BasicHttpEntity;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.message.BasicStatusLine;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\n/** RestRequest unit tests. */\npublic class RestRequestTest {\n\n  static final int DEFAULT_CONNECTION_TIMEOUT = 300000;\n  static final int DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT = 300000; // ms\n\n  private CloseableHttpResponse retryResponse() {\n    StatusLine retryStatusLine = mock(StatusLine.class);\n    when(retryStatusLine.getStatusCode()).thenReturn(503);\n\n    CloseableHttpResponse retryResponse = mock(CloseableHttpResponse.class);\n    when(retryResponse.getStatusLine()).thenReturn(retryStatusLine);\n\n    return retryResponse;\n  }\n\n  private CloseableHttpResponse retryLoginResponse() {\n    StatusLine retryStatusLine = mock(StatusLine.class);\n    when(retryStatusLine.getStatusCode()).thenReturn(429);\n\n    CloseableHttpResponse retryResponse = mock(CloseableHttpResponse.class);\n    when(retryResponse.getStatusLine()).thenReturn(retryStatusLine);\n\n    return retryResponse;\n  }\n\n  private CloseableHttpResponse successResponse() {\n    StatusLine successStatusLine = mock(StatusLine.class);\n    when(successStatusLine.getStatusCode()).thenReturn(200);\n\n    CloseableHttpResponse successResponse = mock(CloseableHttpResponse.class);\n    when(successResponse.getStatusLine()).thenReturn(successStatusLine);\n    InputStream inputStream = new ByteArrayInputStream(\"response body\".getBytes());\n\n    // Create a mock entity and assign the stream to it\n    BasicHttpEntity mockEntity = new BasicHttpEntity();\n    mockEntity.setContent(inputStream);\n    when(successResponse.getEntity()).thenReturn(mockEntity);\n\n    return successResponse;\n  }\n\n  private CloseableHttpResponse socketTimeoutResponse() throws SocketTimeoutException {\n    StatusLine successStatusLine = mock(StatusLine.class);\n    when(successStatusLine.getStatusCode()).thenThrow(new SocketTimeoutException(\"Read timed out\"));\n\n    CloseableHttpResponse successResponse = mock(CloseableHttpResponse.class);\n    when(successStatusLine.getStatusCode()).thenThrow(new SocketTimeoutException(\"Read timed out\"));\n\n    return successResponse;\n  }\n\n  private void execute(\n      CloseableHttpClient client,\n      String uri,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      boolean includeRetryParameters,\n      boolean noRetry)\n      throws IOException, SnowflakeSQLException {\n\n    this.execute(\n        client, uri, retryTimeout, authTimeout, socketTimeout, includeRetryParameters, noRetry, 0);\n  }\n\n  private void execute(\n      CloseableHttpClient client,\n      String uri,\n      int retryTimeout,\n      int authTimeout,\n      int socketTimeout,\n      boolean includeRetryParameters,\n      boolean noRetry,\n      int maxRetries)\n      throws IOException, SnowflakeSQLException {\n\n    RequestConfig.Builder builder =\n        RequestConfig.custom()\n            .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT)\n            .setConnectionRequestTimeout(DEFAULT_CONNECTION_TIMEOUT)\n            .setSocketTimeout(DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT);\n    RequestConfig defaultRequestConfig = builder.build();\n    HttpUtil util = new HttpUtil();\n    util.setRequestConfig(defaultRequestConfig);\n\n    RestRequest.executeWithRetries(\n        client,\n        new HttpGet(uri),\n        retryTimeout, // retry timeout\n        authTimeout,\n        socketTimeout,\n        maxRetries,\n        0, // inject socket timeout\n        new AtomicBoolean(false), // canceling\n        false, // without cookie\n        includeRetryParameters,\n        true,\n        true,\n        noRetry,\n        false,\n        new ExecTimeTelemetryData(),\n        null,\n        null,\n        null,\n        false);\n  }\n\n  @Test\n  public void testRetryParamsInRequest() throws IOException, SnowflakeSQLException {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                HttpUriRequest arg = (HttpUriRequest) invocation.getArguments()[0];\n                String params = arg.getURI().getQuery();\n\n                if (callCount == 0) {\n                  assertFalse(params.contains(\"retryCount=\"));\n                  assertFalse(params.contains(\"retryReason=\"));\n                  assertFalse(params.contains(\"clientStartTime=\"));\n                  assertTrue(params.contains(\"request_guid=\"));\n                } else {\n                  assertTrue(params.contains(\"retryCount=\" + callCount));\n                  assertTrue(params.contains(\"retryReason=503\"));\n                  assertTrue(params.contains(\"clientStartTime=\"));\n                  assertTrue(params.contains(\"request_guid=\"));\n                }\n\n                callCount += 1;\n                if (callCount >= 3) {\n                  return successResponse();\n                } else {\n                  return retryResponse();\n                }\n              }\n            });\n\n    execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, true, false);\n  }\n\n  @Test\n  public void testRetryNoParamsInRequest() throws IOException, SnowflakeSQLException {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                HttpUriRequest arg = (HttpUriRequest) invocation.getArguments()[0];\n                String params = arg.getURI().getQuery();\n\n                assertFalse(params.contains(\"retryCount=\"));\n                assertFalse(params.contains(\"retryReason=\"));\n                assertFalse(params.contains(\"clientStartTime=\"));\n                assertTrue(params.contains(\"request_guid=\"));\n\n                callCount += 1;\n                if (callCount >= 3) {\n                  return successResponse();\n                } else {\n                  return retryResponse();\n                }\n              }\n            });\n\n    execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, false, false);\n  }\n\n  private CloseableHttpResponse anyStatusCodeResponse(int statusCode) {\n    StatusLine successStatusLine = mock(StatusLine.class);\n    when(successStatusLine.getStatusCode()).thenReturn(statusCode);\n\n    CloseableHttpResponse response = mock(CloseableHttpResponse.class);\n    when(response.getStatusLine()).thenReturn(successStatusLine);\n\n    return response;\n  }\n\n  @Test\n  public void testIsNonRetryableHTTPCode() throws Exception {\n    class TestCase {\n      TestCase(int statusCode, boolean retryHTTP403, boolean result) {\n        this.statusCode = statusCode;\n        this.retryHTTP403 = retryHTTP403;\n        this.result = result; // expected result of calling isNonRetryableHTTPCode()\n      }\n\n      public int statusCode;\n      public boolean retryHTTP403;\n      public boolean result;\n    }\n    List<TestCase> testCases = new ArrayList<>();\n    // no retry on HTTP 403 option\n\n    testCases.add(new TestCase(100, false, true));\n    testCases.add(new TestCase(101, false, true));\n    testCases.add(new TestCase(103, false, true));\n    testCases.add(new TestCase(200, false, true));\n    testCases.add(new TestCase(201, false, true));\n    testCases.add(new TestCase(202, false, true));\n    testCases.add(new TestCase(203, false, true));\n    testCases.add(new TestCase(204, false, true));\n    testCases.add(new TestCase(205, false, true));\n    testCases.add(new TestCase(206, false, true));\n    testCases.add(new TestCase(300, false, true));\n    testCases.add(new TestCase(301, false, true));\n    testCases.add(new TestCase(302, false, true));\n    testCases.add(new TestCase(303, false, true));\n    testCases.add(new TestCase(304, false, true));\n    testCases.add(new TestCase(307, false, true));\n    testCases.add(new TestCase(308, false, true));\n    testCases.add(new TestCase(400, false, true));\n    testCases.add(new TestCase(401, false, true));\n    testCases.add(new TestCase(403, false, true)); // no retry on HTTP 403\n    testCases.add(new TestCase(404, false, true));\n    testCases.add(new TestCase(405, false, true));\n    testCases.add(new TestCase(406, false, true));\n    testCases.add(new TestCase(407, false, true));\n    testCases.add(new TestCase(408, false, false)); // do retry on HTTP 408\n    testCases.add(new TestCase(410, false, true));\n    testCases.add(new TestCase(411, false, true));\n    testCases.add(new TestCase(412, false, true));\n    testCases.add(new TestCase(413, false, true));\n    testCases.add(new TestCase(414, false, true));\n    testCases.add(new TestCase(415, false, true));\n    testCases.add(new TestCase(416, false, true));\n    testCases.add(new TestCase(417, false, true));\n    testCases.add(new TestCase(418, false, true));\n    testCases.add(new TestCase(425, false, true));\n    testCases.add(new TestCase(426, false, true));\n    testCases.add(new TestCase(428, false, true));\n    testCases.add(new TestCase(429, false, false)); // do retry on HTTP 429\n    testCases.add(new TestCase(431, false, true));\n    testCases.add(new TestCase(451, false, true));\n    testCases.add(new TestCase(500, false, false));\n    testCases.add(new TestCase(501, false, false));\n    testCases.add(new TestCase(502, false, false));\n    testCases.add(new TestCase(503, false, false));\n    testCases.add(new TestCase(504, false, false));\n    testCases.add(new TestCase(505, false, false));\n    testCases.add(new TestCase(506, false, false));\n    testCases.add(new TestCase(507, false, false));\n    testCases.add(new TestCase(508, false, false));\n    testCases.add(new TestCase(509, false, false));\n    testCases.add(new TestCase(510, false, false));\n    testCases.add(new TestCase(511, false, false));\n    testCases.add(new TestCase(513, false, false));\n    // do retry on HTTP 403 option\n    testCases.add(new TestCase(100, true, true));\n    testCases.add(new TestCase(101, true, true));\n    testCases.add(new TestCase(103, true, true));\n    testCases.add(new TestCase(200, true, true));\n    testCases.add(new TestCase(201, true, true));\n    testCases.add(new TestCase(202, true, true));\n    testCases.add(new TestCase(203, true, true));\n    testCases.add(new TestCase(204, true, true));\n    testCases.add(new TestCase(205, true, true));\n    testCases.add(new TestCase(206, true, true));\n    testCases.add(new TestCase(300, true, true));\n    testCases.add(new TestCase(301, true, true));\n    testCases.add(new TestCase(302, true, true));\n    testCases.add(new TestCase(303, true, true));\n    testCases.add(new TestCase(304, true, true));\n    testCases.add(new TestCase(307, true, true));\n    testCases.add(new TestCase(308, true, true));\n    testCases.add(new TestCase(400, true, true));\n    testCases.add(new TestCase(401, true, true));\n    testCases.add(new TestCase(403, true, false)); // do retry on HTTP 403\n    testCases.add(new TestCase(404, true, true));\n    testCases.add(new TestCase(405, true, true));\n    testCases.add(new TestCase(406, true, true));\n    testCases.add(new TestCase(407, true, true));\n    testCases.add(new TestCase(408, true, false)); // do retry on HTTP 408\n    testCases.add(new TestCase(410, true, true));\n    testCases.add(new TestCase(411, true, true));\n    testCases.add(new TestCase(412, true, true));\n    testCases.add(new TestCase(413, true, true));\n    testCases.add(new TestCase(414, true, true));\n    testCases.add(new TestCase(415, true, true));\n    testCases.add(new TestCase(416, true, true));\n    testCases.add(new TestCase(417, true, true));\n    testCases.add(new TestCase(418, true, true));\n    testCases.add(new TestCase(425, true, true));\n    testCases.add(new TestCase(426, true, true));\n    testCases.add(new TestCase(428, true, true));\n    testCases.add(new TestCase(429, true, false)); // do retry on HTTP 429\n    testCases.add(new TestCase(431, true, true));\n    testCases.add(new TestCase(451, true, true));\n    testCases.add(new TestCase(500, true, false));\n    testCases.add(new TestCase(501, true, false));\n    testCases.add(new TestCase(502, true, false));\n    testCases.add(new TestCase(503, true, false));\n    testCases.add(new TestCase(504, true, false));\n    testCases.add(new TestCase(505, true, false));\n    testCases.add(new TestCase(506, true, false));\n    testCases.add(new TestCase(507, true, false));\n    testCases.add(new TestCase(508, true, false));\n    testCases.add(new TestCase(509, true, false));\n    testCases.add(new TestCase(510, true, false));\n    testCases.add(new TestCase(511, true, false));\n    testCases.add(new TestCase(513, true, false));\n\n    for (TestCase t : testCases) {\n      if (t.result) {\n        assertTrue(\n            RestRequest.isNonRetryableHTTPCode(anyStatusCodeResponse(t.statusCode), t.retryHTTP403),\n            String.format(\n                \"Result must be true but false: HTTP Code: %d, RetryHTTP403: %s\",\n                t.statusCode, t.retryHTTP403));\n      } else {\n        assertFalse(\n            RestRequest.isNonRetryableHTTPCode(anyStatusCodeResponse(t.statusCode), t.retryHTTP403),\n            String.format(\n                \"Result must be false but true: HTTP Code: %d, RetryHTTP403: %s\",\n                t.statusCode, t.retryHTTP403));\n      }\n    }\n  }\n\n  @Test\n  public void testRetryOnTransientSslHandshakeEof() throws Exception {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    // First attempt throws SSLHandshakeException with EOF root cause, second succeeds\n    SSLHandshakeException handshake = new SSLHandshakeException(\"handshake failed\");\n    handshake.initCause(new EOFException(\"remote host closed connection during handshake\"));\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenThrow(handshake)\n        .thenReturn(successResponse());\n\n    // Use helper to execute with maxRetries >= 1 so the loop can retry\n    execute(\n        client,\n        \"fakeurl.com/?requestId=abcd-1234\",\n        /* retryTimeout */ 0,\n        /* authTimeout */ 0,\n        /* socketTimeout */ 0,\n        /* includeRetryParameters */ true,\n        /* noRetry */ false,\n        /* maxRetries */ 2);\n\n    // Verify two executions (first throws, second succeeds)\n    verify(client, times(2)).execute(any(HttpUriRequest.class));\n  }\n\n  @Test\n  public void testNoRetryOnSslHandshakeWithoutEof() throws Exception {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    // SSLHandshakeException without EOF root cause should be treated as non-retryable\n    SSLHandshakeException handshake = new SSLHandshakeException(\"handshake failed (non-EOF)\");\n    when(client.execute(any(HttpUriRequest.class))).thenThrow(handshake);\n\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            execute(\n                client,\n                \"fakeurl.com/?requestId=abcd-1234\",\n                /* retryTimeout */ 0,\n                /* authTimeout */ 0,\n                /* socketTimeout */ 0,\n                /* includeRetryParameters */ true,\n                /* noRetry */ false,\n                /* maxRetries */ 2));\n  }\n\n  @Test\n  public void testExceptionAuthBasedTimeout() throws IOException {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer((Answer<CloseableHttpResponse>) invocation -> retryResponse());\n\n    SnowflakeSQLException ex =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () ->\n                execute(\n                    client, \"login-request.com/?requestId=abcd-1234\", 2, 1, 30000, true, false));\n    assertThat(\n        ex.getErrorCode(), equalTo(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()));\n  }\n\n  @Test\n  public void testExceptionAuthBasedTimeoutFor429ErrorCode() throws IOException {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer((Answer<CloseableHttpResponse>) invocation -> retryLoginResponse());\n\n    SnowflakeSQLException ex =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () ->\n                execute(\n                    client, \"login-request.com/?requestId=abcd-1234\", 2, 1, 30000, true, false));\n    assertThat(\n        ex.getErrorCode(), equalTo(ErrorCode.AUTHENTICATOR_REQUEST_TIMEOUT.getMessageCode()));\n  }\n\n  @Test\n  public void testNoRetry() throws IOException, SnowflakeSQLException {\n    boolean telemetryEnabled = TelemetryService.getInstance().isEnabled();\n    try {\n      TelemetryService.disable(); // disable telemetry for the test\n      CloseableHttpClient client = mock(CloseableHttpClient.class);\n      when(client.execute(any(HttpUriRequest.class)))\n          .thenAnswer(\n              new Answer<CloseableHttpResponse>() {\n                int callCount = 0;\n\n                @Override\n                public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                  callCount += 1;\n                  assertTrue(callCount <= 1);\n                  return retryResponse(); // return a retryable resp on the first attempt\n                }\n              });\n\n      SnowflakeSQLException ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, true, true));\n      assertThat(ex.getErrorCode(), equalTo(ErrorCode.NETWORK_ERROR.getMessageCode()));\n\n    } finally {\n      if (telemetryEnabled) {\n        TelemetryService.enable();\n      } else {\n        TelemetryService.disable();\n      }\n    }\n  }\n\n  /**\n   * Test that after socket timeout, retryReason parameter is set for queries and is set to 0 for\n   * null response.\n   *\n   * @throws SnowflakeSQLException\n   * @throws IOException\n   */\n  @Test\n  public void testRetryParametersWithSocketTimeout() throws IOException, SnowflakeSQLException {\n\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                HttpUriRequest arg = (HttpUriRequest) invocation.getArguments()[0];\n                String params = arg.getURI().getQuery();\n\n                if (callCount == 0) {\n                  assertFalse(params.contains(\"retryCount=\"));\n                  assertFalse(params.contains(\"retryReason=\"));\n                  assertFalse(params.contains(\"clientStartTime=\"));\n                  assertTrue(params.contains(\"request_guid=\"));\n                } else {\n                  assertTrue(params.contains(\"retryCount=\" + callCount));\n                  assertTrue(params.contains(\"retryReason=0\"));\n                  assertTrue(params.contains(\"clientStartTime=\"));\n                  assertTrue(params.contains(\"request_guid=\"));\n                }\n\n                callCount += 1;\n                if (callCount >= 2) {\n                  return successResponse();\n                } else {\n                  return socketTimeoutResponse();\n                }\n              }\n            });\n\n    execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, true, false);\n  }\n\n  @Test\n  public void testMaxRetriesExceeded() throws IOException {\n    boolean telemetryEnabled = TelemetryService.getInstance().isEnabled();\n\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                callCount += 1;\n                if (callCount >= 4) {\n                  return successResponse();\n                } else {\n                  return socketTimeoutResponse();\n                }\n              }\n            });\n\n    try {\n      TelemetryService.disable();\n      assertThrows(\n          SnowflakeSQLException.class,\n          () -> execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, true, false, 1));\n    } finally {\n      if (telemetryEnabled) {\n        TelemetryService.enable();\n      } else {\n        TelemetryService.disable();\n      }\n    }\n  }\n\n  @Test\n  public void testConnectionClosedRetriesSuccessful() throws IOException, SnowflakeSQLException {\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .then(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocationOnMock)\n                  throws Throwable {\n                callCount += 1;\n                if (callCount >= 1) {\n                  return successResponse();\n                } else {\n                  throw new SocketException(\"Connection reset\");\n                }\n              }\n            });\n\n    execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, true, false, 1);\n  }\n\n  @Test\n  public void testLoginMaxRetries() throws IOException {\n    boolean telemetryEnabled = TelemetryService.getInstance().isEnabled();\n\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                callCount += 1;\n                if (callCount >= 4) {\n                  return retryLoginResponse();\n                } else {\n                  return socketTimeoutResponse();\n                }\n              }\n            });\n\n    try {\n      TelemetryService.disable();\n      assertThrows(\n          SnowflakeSQLException.class,\n          () -> execute(client, \"/session/v1/login-request\", 0, 0, 0, true, false, 1));\n    } finally {\n      if (telemetryEnabled) {\n        TelemetryService.enable();\n      } else {\n        TelemetryService.disable();\n      }\n    }\n  }\n\n  @Test\n  public void testLoginTimeout() throws IOException {\n    assumeRunningOnLinuxMac();\n    boolean telemetryEnabled = TelemetryService.getInstance().isEnabled();\n\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                callCount += 1;\n                if (callCount >= 4) {\n                  return retryLoginResponse();\n                } else {\n                  return socketTimeoutResponse();\n                }\n              }\n            });\n\n    try {\n      TelemetryService.disable();\n      assertThrows(\n          SnowflakeSQLException.class,\n          () -> {\n            execute(client, \"/session/v1/login-request\", 1, 0, 0, true, false, 10);\n          });\n    } finally {\n      if (telemetryEnabled) {\n        TelemetryService.enable();\n      } else {\n        TelemetryService.disable();\n      }\n    }\n  }\n\n  @Test\n  public void testMaxRetriesWithSuccessfulResponse() throws IOException, SnowflakeSQLException {\n    boolean telemetryEnabled = TelemetryService.getInstance().isEnabled();\n\n    CloseableHttpClient client = mock(CloseableHttpClient.class);\n    when(client.execute(any(HttpUriRequest.class)))\n        .thenAnswer(\n            new Answer<CloseableHttpResponse>() {\n              int callCount = 0;\n\n              @Override\n              public CloseableHttpResponse answer(InvocationOnMock invocation) throws Throwable {\n                callCount += 1;\n                if (callCount >= 3) {\n                  return successResponse();\n                } else {\n                  return socketTimeoutResponse();\n                }\n              }\n            });\n\n    try {\n      TelemetryService.disable();\n      execute(client, \"fakeurl.com/?requestId=abcd-1234\", 0, 0, 0, true, false, 4);\n    } finally {\n      if (telemetryEnabled) {\n        TelemetryService.enable();\n      } else {\n        TelemetryService.disable();\n      }\n    }\n  }\n\n  @Test\n  public void shouldGenerateBackoffInRangeExceptTheLastBackoff() {\n    int minBackoffInMilli = 1000;\n    int maxBackoffInMilli = 16000;\n    long backoffInMilli = minBackoffInMilli;\n    long elapsedMilliForTransientIssues = 0;\n    DecorrelatedJitterBackoff decorrelatedJitterBackoff =\n        new DecorrelatedJitterBackoff(minBackoffInMilli, maxBackoffInMilli);\n    int retryTimeoutInMilli = 5 * 60 * 1000;\n    while (true) {\n      backoffInMilli =\n          RestRequest.getNewBackoffInMilli(\n              backoffInMilli,\n              true,\n              decorrelatedJitterBackoff,\n              10,\n              retryTimeoutInMilli,\n              elapsedMilliForTransientIssues);\n\n      assertTrue(\n          backoffInMilli <= maxBackoffInMilli,\n          \"Backoff should be lower or equal to max backoff limit\");\n      if (elapsedMilliForTransientIssues + backoffInMilli >= retryTimeoutInMilli) {\n        assertEquals(\n            retryTimeoutInMilli - elapsedMilliForTransientIssues,\n            backoffInMilli,\n            \"Backoff should fill time till retry timeout\");\n        break;\n      } else {\n        assertTrue(\n            backoffInMilli >= minBackoffInMilli,\n            \"Backoff should be higher or equal to min backoff limit\");\n      }\n      elapsedMilliForTransientIssues += backoffInMilli;\n    }\n  }\n\n  /**\n   * Test that IllegalStateException during HTTP request execution triggers a rebuild of the HTTP\n   * client.\n   *\n   * @param statusCode the status code to return from the mock response\n   * @param useDecompression whether to use decompression in the HTTP client\n   * @throws Exception if an error occurs during the test\n   */\n  @ParameterizedTest\n  @MethodSource(\"httpClientRebuildParams\")\n  public void testIllegalStateExceptionTriggersHttpClientRebuildParameterized(\n      int statusCode, boolean useDecompression) throws Exception {\n\n    CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class);\n    HttpRequestBase mockRequest = mock(HttpRequestBase.class);\n    when(mockRequest.getURI()).thenReturn(new URI(\"https://example.com\"));\n    CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);\n    StatusLine mockStatusLine = mock(StatusLine.class);\n    when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);\n    when(mockStatusLine.getStatusCode()).thenReturn(statusCode);\n    HttpClientSettingsKey mockKey = mock(HttpClientSettingsKey.class);\n\n    when(mockHttpClient.execute(any(HttpRequestBase.class)))\n        .thenThrow(new IllegalStateException(\"HttpClient shutdown\"))\n        .thenReturn(mockResponse);\n\n    try (MockedStatic<HttpUtil> httpUtilMockedStatic = mockStatic(HttpUtil.class)) {\n      httpUtilMockedStatic\n          .when(() -> HttpUtil.getHttpClient(any(), any()))\n          .thenReturn(mockHttpClient);\n      httpUtilMockedStatic\n          .when(() -> HttpUtil.getHttpClientWithoutDecompression(any(), any()))\n          .thenReturn(mockHttpClient);\n\n      HttpResponseContextDto result =\n          RestRequest.executeWithRetries(\n              mockHttpClient,\n              mockRequest,\n              10,\n              0,\n              1000,\n              2,\n              0,\n              new AtomicBoolean(false),\n              false,\n              false,\n              false,\n              false,\n              false,\n              new ExecTimeTelemetryData(),\n              null,\n              mockKey,\n              Collections.emptyList(),\n              useDecompression);\n\n      verify(mockHttpClient, times(2)).execute(any(HttpRequestBase.class));\n      assertNotNull(result);\n    }\n  }\n\n  /**\n   * Provides parameters for the parameterized test.\n   *\n   * @return a stream of arguments for the test\n   */\n  private static Stream<Arguments> httpClientRebuildParams() {\n    return Stream.of(Arguments.of(200, false), Arguments.of(200, true));\n  }\n\n  @Test\n  public void testHandlingNotRetryableException_isProtocolVersionError() throws Exception {\n    // Create a mock HttpExecutingContext\n    HttpExecutingContext mockContext =\n        HttpExecutingContextBuilder.withRequest(\"test-request-id\", \"test-request-info\").build();\n\n    // Test case 1: SSL exception with protocol version error - should NOT throw\n    // SnowflakeSQLLoggedException\n    // This should fall through to the general exception handling\n    SSLHandshakeException protocolVersionException =\n        new SSLHandshakeException(\"Received fatal alert: protocol_version\");\n\n    Exception result =\n        RestRequest.handlingNotRetryableException(protocolVersionException, mockContext);\n\n    // Should return the original exception, not throw SnowflakeSQLLoggedException\n    assertEquals(\n        protocolVersionException,\n        result,\n        \"SSL exception with protocol version error should return original exception\");\n\n    // Test case 2: SSL exception without protocol version error - should throw\n    // SnowflakeSQLLoggedException\n    SSLHandshakeException nonProtocolVersionException =\n        new SSLHandshakeException(\"SSL handshake failed for some other reason\");\n\n    assertThrows(\n        SnowflakeSQLLoggedException.class,\n        () -> {\n          RestRequest.handlingNotRetryableException(nonProtocolVersionException, mockContext);\n        },\n        \"SSL exception without protocol version error should throw SnowflakeSQLLoggedException\");\n\n    // Test case 3: Different SSL exception types with protocol version error\n    SSLProtocolException sslProtocolException =\n        new SSLProtocolException(\"Received fatal alert: protocol_version\");\n\n    Exception result2 =\n        RestRequest.handlingNotRetryableException(sslProtocolException, mockContext);\n    assertEquals(\n        sslProtocolException,\n        result2,\n        \"SSLProtocolException with protocol version error should return original exception\");\n\n    // Test case 4: Different SSL exception types without protocol version error\n    SSLKeyException sslKeyException = new SSLKeyException(\"SSL key verification failed\");\n\n    assertThrows(\n        SnowflakeSQLLoggedException.class,\n        () -> {\n          RestRequest.handlingNotRetryableException(sslKeyException, mockContext);\n        },\n        \"SSLKeyException without protocol version error should throw SnowflakeSQLLoggedException\");\n\n    // Test case 5: SSLPeerUnverifiedException with protocol version error\n    SSLPeerUnverifiedException peerUnverifiedException =\n        new SSLPeerUnverifiedException(\"Received fatal alert: protocol_version\");\n\n    Exception result3 =\n        RestRequest.handlingNotRetryableException(peerUnverifiedException, mockContext);\n    assertEquals(\n        peerUnverifiedException,\n        result3,\n        \"SSLPeerUnverifiedException with protocol version error should return original exception\");\n\n    // Test case 6: Non-SSL exception - should return the original exception\n    IOException ioException = new IOException(\"Some IO error\");\n\n    Exception result4 = RestRequest.handlingNotRetryableException(ioException, mockContext);\n    assertEquals(ioException, result4, \"Non-SSL exception should return original exception\");\n  }\n\n  @Test\n  public void testIsProtocolVersionError() {\n    // Test true cases\n    assertTrue(\n        RestRequest.isProtocolVersionError(\n            new SSLHandshakeException(\"Received fatal alert: protocol_version\")),\n        \"Should return true for exception with protocol_version message\");\n\n    assertTrue(\n        RestRequest.isProtocolVersionError(\n            new SSLProtocolException(\n                \"Some prefix Received fatal alert: protocol_version some suffix\")),\n        \"Should return true for exception containing protocol_version message\");\n\n    // Test false cases\n    assertFalse(\n        RestRequest.isProtocolVersionError(new SSLHandshakeException(\"Some other SSL error\")),\n        \"Should return false for exception without protocol_version message\");\n\n    assertFalse(\n        RestRequest.isProtocolVersionError(\n            new SSLHandshakeException(\"Received fatal alert: handshake_failure\")),\n        \"Should return false for different SSL alert type\");\n\n    assertFalse(\n        RestRequest.isProtocolVersionError(new IOException(\"Some IO error\")),\n        \"Should return false for non-SSL exception\");\n\n    // Test null message\n    assertFalse(\n        RestRequest.isProtocolVersionError(new SSLHandshakeException((String) null)),\n        \"Should return false for exception with null message\");\n  }\n\n  @Test\n  public void testIsExceptionInGroup() {\n    // Test SSL exceptions are in the sslExceptions group\n    assertTrue(\n        RestRequest.isExceptionInGroup(\n            new SSLHandshakeException(\"test\"), RestRequest.sslExceptions),\n        \"SSLHandshakeException should be in SSL exceptions group\");\n\n    assertTrue(\n        RestRequest.isExceptionInGroup(new SSLKeyException(\"test\"), RestRequest.sslExceptions),\n        \"SSLKeyException should be in SSL exceptions group\");\n\n    assertTrue(\n        RestRequest.isExceptionInGroup(\n            new SSLPeerUnverifiedException(\"test\"), RestRequest.sslExceptions),\n        \"SSLPeerUnverifiedException should be in SSL exceptions group\");\n\n    assertTrue(\n        RestRequest.isExceptionInGroup(new SSLProtocolException(\"test\"), RestRequest.sslExceptions),\n        \"SSLProtocolException should be in SSL exceptions group\");\n\n    // Test non-SSL exceptions are not in the group\n    assertFalse(\n        RestRequest.isExceptionInGroup(new IOException(\"test\"), RestRequest.sslExceptions),\n        \"IOException should not be in SSL exceptions group\");\n\n    assertFalse(\n        RestRequest.isExceptionInGroup(new RuntimeException(\"test\"), RestRequest.sslExceptions),\n        \"RuntimeException should not be in SSL exceptions group\");\n  }\n\n  /**\n   * SNOW-3463039 - Regression test: when getTelemetryClient() returns a NoOpTelemetryClient (e.g.\n   * because the session URL is not yet set), sendIBHttpErrorEvent must complete without throwing an\n   * NPE. Previously, getTelemetryClient() returned null in this case, causing a\n   * NullPointerException when addLogToBatch() was called on the null reference.\n   */\n  @Test\n  public void testSendIBHttpErrorEventWithNoOpTelemetryClientDoesNotThrow() throws Exception {\n    HttpRequestBase request =\n        new HttpGet(\n            \"https://testaccount.snowflakecomputing.com\"\n                + \"/v1/streamlit/testapp/_stcore/upload_file/12345\");\n    StatusLine statusLine =\n        new BasicStatusLine(new ProtocolVersion(\"HTTP\", 1, 1), 500, \"Internal Server Error\");\n    CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);\n    when(mockResponse.getStatusLine()).thenReturn(statusLine);\n\n    SFBaseSession mockSession = mock(SFBaseSession.class);\n    when(mockSession.getTelemetryClient(any(InternalCallMarker.class)))\n        .thenReturn(new NoOpTelemetryClient());\n\n    HttpExecutingContext mockContext = mock(HttpExecutingContext.class);\n    when(mockContext.getSfSession()).thenReturn(mockSession);\n\n    try (MockedStatic<TelemetryUtil> mockedTelemetryUtil =\n        Mockito.mockStatic(TelemetryUtil.class)) {\n      ObjectNode mockIbValue = mock(ObjectNode.class);\n      TelemetryData mockTelemetryData = mock(TelemetryData.class);\n\n      mockedTelemetryUtil\n          .when(\n              () -> TelemetryUtil.createIBValue(any(), any(), anyInt(), any(), anyString(), any()))\n          .thenReturn(mockIbValue);\n      mockedTelemetryUtil\n          .when(() -> TelemetryUtil.buildJobData(any(ObjectNode.class)))\n          .thenReturn(mockTelemetryData);\n\n      java.lang.reflect.Method method =\n          RestRequest.class.getDeclaredMethod(\n              \"sendIBHttpErrorEvent\",\n              HttpRequestBase.class,\n              CloseableHttpResponse.class,\n              HttpExecutingContext.class);\n      method.setAccessible(true);\n\n      assertDoesNotThrow(() -> method.invoke(null, request, mockResponse, mockContext));\n    }\n  }\n\n  @Test\n  public void testSendIBHttpErrorEventWithNullSessionNoException() throws Exception {\n    HttpRequestBase mockRequest = mock(HttpRequestBase.class);\n    CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);\n    HttpExecutingContext mockContext = mock(HttpExecutingContext.class);\n\n    when(mockContext.getSfSession()).thenReturn(null);\n\n    java.lang.reflect.Method method =\n        RestRequest.class.getDeclaredMethod(\n            \"sendIBHttpErrorEvent\",\n            HttpRequestBase.class,\n            CloseableHttpResponse.class,\n            HttpExecutingContext.class);\n    method.setAccessible(true);\n\n    method.invoke(null, mockRequest, mockResponse, mockContext);\n\n    Mockito.verify(mockContext).getSfSession();\n  }\n\n  @Test\n  public void testSendIBHttpErrorEventWithValidSession() throws Exception {\n    HttpRequestBase mockRequest = mock(HttpRequestBase.class);\n    CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);\n    StatusLine mockStatusLine = mock(StatusLine.class);\n    HttpExecutingContext mockContext = mock(HttpExecutingContext.class);\n    SFBaseSession mockSession = mock(SFBaseSession.class);\n    Telemetry mockTelemetryClient = mock(Telemetry.class);\n\n    when(mockContext.getSfSession()).thenReturn(mockSession);\n    when(mockSession.getTelemetryClient(any(InternalCallMarker.class)))\n        .thenReturn(mockTelemetryClient);\n    when(mockResponse.getStatusLine()).thenReturn(mockStatusLine);\n    when(mockStatusLine.getStatusCode()).thenReturn(500);\n    when(mockStatusLine.getReasonPhrase()).thenReturn(\"Internal Server Error\");\n\n    when(mockRequest.getMethod()).thenReturn(\"POST\");\n    URI mockURI = new URI(\"https://test.snowflakecomputing.com/session/v1/login-request\");\n    when(mockRequest.getURI()).thenReturn(mockURI);\n\n    try (MockedStatic<TelemetryUtil> mockedTelemetryUtil =\n        Mockito.mockStatic(TelemetryUtil.class)) {\n      ObjectNode mockIbValue = mock(ObjectNode.class);\n      TelemetryData mockTelemetryData = mock(TelemetryData.class);\n\n      mockedTelemetryUtil\n          .when(\n              () -> TelemetryUtil.createIBValue(any(), any(), anyInt(), any(), anyString(), any()))\n          .thenReturn(mockIbValue);\n      mockedTelemetryUtil\n          .when(() -> TelemetryUtil.buildJobData(any(ObjectNode.class)))\n          .thenReturn(mockTelemetryData);\n\n      java.lang.reflect.Method method =\n          RestRequest.class.getDeclaredMethod(\n              \"sendIBHttpErrorEvent\",\n              HttpRequestBase.class,\n              CloseableHttpResponse.class,\n              HttpExecutingContext.class);\n      method.setAccessible(true);\n\n      method.invoke(null, mockRequest, mockResponse, mockContext);\n\n      Mockito.verify(mockContext).getSfSession();\n      Mockito.verify(mockSession).getTelemetryClient(any(InternalCallMarker.class));\n      Mockito.verify(mockResponse).getStatusLine();\n      Mockito.verify(mockStatusLine, Mockito.atLeast(1)).getStatusCode();\n      Mockito.verify(mockStatusLine, Mockito.atLeast(1)).getReasonPhrase();\n      Mockito.verify(mockRequest).getMethod();\n      Mockito.verify(mockRequest, Mockito.atLeast(1)).getURI();\n      Mockito.verify(mockTelemetryClient).addLogToBatch(mockTelemetryData);\n\n      mockedTelemetryUtil.verify(\n          () ->\n              TelemetryUtil.createIBValue(\n                  eq(null),\n                  eq(SqlState.INTERNAL_ERROR),\n                  eq(ErrorCode.HTTP_GENERAL_ERROR.getMessageCode() + 500),\n                  eq(TelemetryField.HTTP_EXCEPTION),\n                  eq(\n                      \"HTTP 500 Internal Server Error: POST test.snowflakecomputing.com/session/v1/login-request\"),\n                  eq(null)));\n\n      mockedTelemetryUtil.verify(() -> TelemetryUtil.buildJobData(mockIbValue));\n    }\n  }\n\n  @Test\n  public void testExecuteRequestFailsWithEventEmission() throws IOException {\n    StatusLine statusLine =\n        new BasicStatusLine(new ProtocolVersion(\"HTTP\", 1, 1), 500, \"Internal Server Error\");\n\n    HttpRequestBase mockRequest = mock(HttpRequestBase.class);\n    when(mockRequest.getMethod()).thenReturn(\"POST\");\n    URI mockURI = URI.create(\"https://test.snowflakecomputing.com/session/v1/login-request\");\n    when(mockRequest.getURI()).thenReturn(mockURI);\n\n    HttpExecutingContext mockContext = mock(HttpExecutingContext.class);\n    SFBaseSession mockSession = mock(SFBaseSession.class);\n    TelemetryClient mockTelemetryClient = mock(TelemetryClient.class);\n\n    when(mockSession.getTelemetryClient(any(InternalCallMarker.class)))\n        .thenReturn(mockTelemetryClient);\n    when(mockContext.getSfSession()).thenReturn(mockSession);\n\n    HttpExecutingContext mockHttpExecutingContext = mock(HttpExecutingContext.class);\n    when(mockHttpExecutingContext.getSfSession()).thenReturn(mockSession);\n\n    try (CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class);\n        CloseableHttpClient mockHttpClient = mock(CloseableHttpClient.class); ) {\n      when(mockHttpClient.execute(any(HttpUriRequest.class))).thenReturn(mockResponse);\n      when(mockResponse.getStatusLine()).thenReturn(statusLine);\n      when(mockResponse.getStatusLine())\n          .thenReturn(\n              new BasicStatusLine(new ProtocolVersion(\"HTTP\", 1, 1), 500, \"Internal Server Error\"));\n\n      assertThrows(\n          SnowflakeSQLException.class,\n          () ->\n              RestRequest.executeWithRetries(\n                  mockHttpClient,\n                  mockRequest,\n                  mockHttpExecutingContext,\n                  new ExecTimeTelemetryData(),\n                  null,\n                  null,\n                  null,\n                  false));\n    }\n\n    ArgumentCaptor<TelemetryData> telemetryDataCaptor =\n        ArgumentCaptor.forClass(TelemetryData.class);\n    Mockito.verify(mockSession).getTelemetryClient(any(InternalCallMarker.class));\n    Mockito.verify(mockTelemetryClient).addLogToBatch(telemetryDataCaptor.capture());\n\n    TelemetryData capturedData = telemetryDataCaptor.getValue();\n    assertNotNull(capturedData, \"TelemetryData should not be null\");\n\n    ObjectNode message = capturedData.getMessage();\n    assertNotNull(message, \"TelemetryData message should not be null\");\n\n    assertEquals(\n        \"client_http_exception\",\n        message.get(\"type\").asText(),\n        \"Type should be client_http_exception\");\n    assertEquals(\"JDBC\", message.get(\"DriverType\").asText(), \"DriverType should be JDBC\");\n    assertNotNull(message.get(\"DriverVersion\"), \"DriverVersion should be present\");\n    assertEquals(\n        \"XX000\", message.get(\"SQLState\").asText(), \"SQLState should be XX000 (INTERNAL_ERROR)\");\n    assertEquals(\n        290500, message.get(\"ErrorNumber\").asInt(), \"ErrorNumber should be 290500 (290000 + 500)\");\n    assertEquals(\n        \"HTTP 500 Internal Server Error: POST test.snowflakecomputing.com/session/v1/login-request\",\n        message.get(\"ErrorMessage\").asText(),\n        \"ErrorMessage should match expected format\");\n\n    assertTrue(capturedData.getTimeStamp() > 0, \"Timestamp should be positive\");\n    assertTrue(\n        capturedData.getTimeStamp() <= System.currentTimeMillis(),\n        \"Timestamp should not be in the future\");\n  }\n\n  @Test\n  public void testOCSPCheckFailsWithEventEmission() throws IOException {\n    System.setProperty(SFTrustManager.SF_OCSP_TEST_INJECT_VALIDITY_ERROR, Boolean.TRUE.toString());\n    System.setProperty(\n        SFTrustManager.SF_OCSP_RESPONSE_CACHE_SERVER_ENABLED, Boolean.FALSE.toString());\n\n    try {\n      SFBaseSession mockSession = mock(SFBaseSession.class);\n      TelemetryClient mockTelemetryClient = mock(TelemetryClient.class);\n      when(mockSession.getTelemetryClient(any(InternalCallMarker.class)))\n          .thenReturn(mockTelemetryClient);\n\n      HttpClientSettingsKey settingsKey = new HttpClientSettingsKey(OCSPMode.FAIL_CLOSED);\n      CloseableHttpClient httpClient = HttpUtil.buildHttpClient(settingsKey, null, false);\n\n      HttpRequestBase httpRequest = new HttpGet(\"https://test.snowflakecomputing.com/\");\n\n      HttpExecutingContext context =\n          HttpExecutingContextBuilder.withRequest(\"test-request-id\", \"test-request-info\")\n              .withSfSession(mockSession)\n              .build();\n\n      assertThrows(\n          SnowflakeSQLException.class,\n          () ->\n              RestRequest.executeWithRetries(\n                  httpClient,\n                  httpRequest,\n                  context,\n                  new ExecTimeTelemetryData(),\n                  null,\n                  settingsKey,\n                  null,\n                  false));\n\n      ArgumentCaptor<TelemetryData> telemetryDataCaptor =\n          ArgumentCaptor.forClass(TelemetryData.class);\n      Mockito.verify(mockSession).getTelemetryClient(any(InternalCallMarker.class));\n      Mockito.verify(mockTelemetryClient).addLogToBatch(telemetryDataCaptor.capture());\n\n      TelemetryData capturedData = telemetryDataCaptor.getValue();\n      assertNotNull(capturedData, \"TelemetryData should not be null\");\n\n      ObjectNode message = capturedData.getMessage();\n      assertNotNull(message, \"TelemetryData message should not be null\");\n\n      assertEquals(\n          \"client_ocsp_exception\",\n          message.get(\"type\").asText(),\n          \"Type should be client_ocsp_exception\");\n      assertEquals(\"JDBC\", message.get(\"DriverType\").asText(), \"DriverType should be JDBC\");\n      assertNotNull(message.get(\"DriverVersion\"), \"DriverVersion should be present\");\n      assertEquals(\n          \"XX000\", message.get(\"SQLState\").asText(), \"SQLState should be XX000 (INTERNAL_ERROR)\");\n      assertEquals(254000, message.get(\"ErrorNumber\").asInt(), \"ErrorNumber should be 254000\");\n      assertTrue(\n          message\n              .get(\"ErrorMessage\")\n              .asText()\n              .contains(\"The OCSP response validity is out of range\"),\n          \"ErrorMessage should contain OCSP validity error message\");\n      assertTrue(\n          message.get(\"reason\").asText().contains(\"INVALID_OCSP_RESPONSE_VALIDITY\"),\n          \"Reason should contain INVALID_OCSP_RESPONSE_VALIDITY\");\n\n      assertTrue(capturedData.getTimeStamp() > 0, \"Timestamp should be positive\");\n      assertTrue(\n          capturedData.getTimeStamp() <= System.currentTimeMillis(),\n          \"Timestamp should not be in the future\");\n    } finally {\n      SFTrustManager.cleanTestSystemParameters();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/RestRequestTestRetriesWiremockIT.java",
    "content": "/*\n * Copyright (c) 2012-2022 Snowflake Computing Inc. All rights reserved.\n */\npackage net.snowflake.client.internal.jdbc;\n\nimport static org.junit.Assert.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.util.Stopwatch;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class RestRequestTestRetriesWiremockIT extends BaseWiremockTest {\n\n  private static final String SCENARIOS_BASE_DIR = \"/wiremock/mappings/restrequest\";\n\n  @BeforeEach\n  public void setUp() throws IOException {\n    resetWiremock();\n  }\n\n  @Test\n  public void testRetryWhen503Code() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/response503.json\");\n    Properties props = getWiremockProps();\n    props.setProperty(\"maxHttpRetries\", \"3\");\n    SnowflakeSQLException thrown =\n        assertThrows(SnowflakeSQLException.class, () -> executeServerRequest(props));\n    verifyRequestCount(4, \"/queries/v1/query-request.*\");\n\n    assertTrue(\n        thrown\n            .getMessage()\n            .contains(\"JDBC driver encountered communication error. Message: HTTP status=503.\"));\n  }\n\n  private static Properties getWiremockProps() {\n    Properties props = new Properties();\n    props.put(\"protocol\", \"http://\");\n    props.put(\"host\", WIREMOCK_HOST);\n    props.put(\"port\", String.valueOf(wiremockHttpPort));\n    return props;\n  }\n\n  @Test\n  public void testHttpClientFailedAfterFiveRetries() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/six_malformed_and_correct.json\");\n    Properties props = getWiremockProps();\n    props.setProperty(\"maxHttpRetries\", \"5\");\n\n    SnowflakeSQLException thrown =\n        assertThrows(SnowflakeSQLException.class, () -> executeServerRequest(props));\n    assertTrue(thrown.getMessage().contains(\"Bad chunk header\"));\n    verifyRequestCount(6, \"/queries/v1/query-request.*\");\n  }\n\n  @Test\n  public void testHttpClientSuccessAfterFiveRetries() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/six_malformed_and_correct.json\");\n    try {\n      Properties props = getWiremockProps();\n      props.setProperty(\"maxHttpRetries\", \"7\");\n      executeServerRequest(props);\n      verifyRequestCount(7, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    } finally {\n      System.clearProperty(\"maxHttpRetries\");\n    }\n  }\n\n  @Test\n  public void testHttpClientSuccessAfter307Retry() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/http_307_retry.json\");\n    try {\n      Properties props = getWiremockProps();\n      props.setProperty(\"injectSocketTimeout\", \"200\");\n      executeServerRequest(props);\n      verifyRequestCount(2, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    } finally {\n      System.clearProperty(\"injectSocketTimeout\");\n    }\n  }\n\n  @Test\n  public void testHttpClientSuccessAfter308Retry() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/http_308_retry.json\");\n    try {\n      Properties props = getWiremockProps();\n      props.setProperty(\"injectSocketTimeout\", \"200\");\n      executeServerRequest(props);\n      verifyRequestCount(2, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    } finally {\n      System.clearProperty(\"injectSocketTimeout\");\n    }\n  }\n\n  @Test\n  public void testHttpClientSuccessWithoutRetries() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/correct_response.json\");\n    try {\n      executeServerRequest(getWiremockProps());\n      verifyRequestCount(1, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Test\n  public void testElapsedTimeoutExceeded() {\n    importMappingFromResources(SCENARIOS_BASE_DIR + \"/six_malformed_and_correct.json\");\n    Properties props = getWiremockProps();\n    props.setProperty(\"networkTimeout\", \"1000\");\n    Stopwatch stopwatch = new Stopwatch();\n    stopwatch.start();\n    assertThrows(SnowflakeSQLException.class, () -> executeServerRequest(props));\n    stopwatch.stop();\n    assertTrue(stopwatch.elapsedMillis() > 1000);\n    assertTrue(stopwatch.elapsedMillis() < 3000);\n  }\n\n  private static void executeServerRequest(Properties properties) throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection(properties);\n    Statement stmt = conn.createStatement();\n    stmt.executeQuery(\"SELECT 1\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/RestRequestWiremockLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class RestRequestWiremockLatestIT extends BaseWiremockTest {\n\n  String connectionResetByPeerScenario =\n      \"{\\n\"\n          + \"    \\\"mappings\\\": [\\n\"\n          + \"        {\\n\"\n          + \"            \\\"scenarioName\\\": \\\"Connection reset by peer\\\",\\n\"\n          + \"            \\\"requiredScenarioState\\\": \\\"Started\\\",\\n\"\n          + \"            \\\"newScenarioState\\\": \\\"Connection is stable\\\",\\n\"\n          + \"            \\\"request\\\": {\\n\"\n          + \"                \\\"method\\\": \\\"GET\\\",\\n\"\n          + \"                \\\"url\\\": \\\"/endpoint\\\"\\n\"\n          + \"            },\\n\"\n          + \"            \\\"response\\\": {\\n\"\n          + \"                \\\"fault\\\": \\\"CONNECTION_RESET_BY_PEER\\\"\\n\"\n          + \"            }\\n\"\n          + \"        },\\n\"\n          + \"        {\\n\"\n          + \"            \\\"scenarioName\\\": \\\"Connection reset by peer\\\",\\n\"\n          + \"            \\\"requiredScenarioState\\\": \\\"Connection is stable\\\",\\n\"\n          + \"            \\\"request\\\": {\\n\"\n          + \"                \\\"method\\\": \\\"GET\\\",\\n\"\n          + \"                \\\"url\\\": \\\"/endpoint\\\"\\n\"\n          + \"            },\\n\"\n          + \"            \\\"response\\\": {\\n\"\n          + \"                \\\"status\\\": 200\\n\"\n          + \"            }\\n\"\n          + \"        }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"importOptions\\\": {\\n\"\n          + \"        \\\"duplicatePolicy\\\": \\\"IGNORE\\\",\\n\"\n          + \"        \\\"deleteAllNotInImport\\\": true\\n\"\n          + \"    }\"\n          + \"}\";\n\n  @Test\n  public void testProxyIsUsedWhenSetInProperties() throws Exception {\n    importMapping(connectionResetByPeerScenario);\n    HttpClientBuilder httpClientBuilder = HttpClientBuilder.create().disableAutomaticRetries();\n    try (CloseableHttpClient httpClient = httpClientBuilder.build()) {\n      HttpGet request =\n          new HttpGet(String.format(\"http://%s:%d/endpoint\", WIREMOCK_HOST, wiremockHttpPort));\n      RestRequest.executeWithRetries(\n          httpClient,\n          request,\n          0,\n          0,\n          0,\n          0,\n          0,\n          new AtomicBoolean(false),\n          false,\n          false,\n          false,\n          false,\n          false,\n          new ExecTimeTelemetryData(),\n          null,\n          null,\n          null,\n          false);\n\n      CloseableHttpResponse response = httpClient.execute(request);\n      assert (response.getStatusLine().getStatusCode() == 200);\n    }\n  }\n\n  @Test\n  public void testStickyHeaderPropagatedFromLogin() throws Exception {\n    importMappingFromResources(\"/wiremock/mappings/restrequest/sticky_header_from_login.json\");\n    Properties props = getWiremockProps();\n\n    try {\n      String connectStr = String.format(\"jdbc:snowflake://%s:%s\", WIREMOCK_HOST, wiremockHttpPort);\n      Connection conn = DriverManager.getConnection(connectStr, props);\n      Statement stmt = conn.createStatement();\n      // Execute first query - should have sticky header from login\n      stmt.executeQuery(\"SELECT 1\");\n      // Execute second query - should also have sticky header from login\n      stmt.executeQuery(\"SELECT 1\");\n\n      verifyRequestCount(1, \"/session/v1/login-request.*\");\n      verifyRequestCount(2, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Test\n  public void testStickyHeaderPropagatedFromFirstQuery() throws Exception {\n    importMappingFromResources(\n        \"/wiremock/mappings/restrequest/sticky_header_from_first_query.json\");\n    Properties props = getWiremockProps();\n\n    try {\n      String connectStr = String.format(\"jdbc:snowflake://%s:%s\", WIREMOCK_HOST, wiremockHttpPort);\n      Connection conn = DriverManager.getConnection(connectStr, props);\n      Statement stmt = conn.createStatement();\n      // Execute first query - should get the sticky header from response\n      stmt.executeQuery(\"SELECT 1\");\n      // Execute second query - should send the sticky header in request\n      stmt.executeQuery(\"SELECT 1\");\n\n      // Verify login request was made\n      verifyRequestCount(1, \"/session/v1/login-request.*\");\n      // Verify two query requests were made, second one with the sticky header\n      verifyRequestCount(2, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Test\n  public void testStickyHeaderValueUpdated() throws Exception {\n    importMappingFromResources(\"/wiremock/mappings/restrequest/sticky_header_updated.json\");\n    Properties props = getWiremockProps();\n\n    try {\n      String connectStr = String.format(\"jdbc:snowflake://%s:%s\", WIREMOCK_HOST, wiremockHttpPort);\n      Connection conn = DriverManager.getConnection(connectStr, props);\n      Statement stmt = conn.createStatement();\n      // Execute first query - should send session-ABC from login\n      stmt.executeQuery(\"SELECT 1\");\n      // Execute second query - should send session-ABC, receive session-XYZ\n      stmt.executeQuery(\"SELECT 1\");\n      // Execute third query - should send updated session-XYZ\n      stmt.executeQuery(\"SELECT 1\");\n\n      verifyRequestCount(1, \"/session/v1/login-request.*\");\n      verifyRequestCount(3, \"/queries/v1/query-request.*\");\n    } catch (SQLException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private static Properties getWiremockProps() {\n    Properties props = new Properties();\n    props.put(\"account\", \"testaccount\");\n    props.put(\"user\", \"testuser\");\n    props.put(\"password\", \"testpassword\");\n    props.put(\"warehouse\", \"testwh\");\n    props.put(\"database\", \"testdb\");\n    props.put(\"schema\", \"testschema\");\n    props.put(\"ssl\", \"off\");\n    props.put(\"insecureMode\", \"true\");\n    return props;\n  }\n\n  private static void executeServerRequest(Properties properties) throws SQLException {\n    String connectStr = String.format(\"jdbc:snowflake://%s:%s\", WIREMOCK_HOST, wiremockHttpPort);\n    Connection conn = DriverManager.getConnection(connectStr, properties);\n    Statement stmt = conn.createStatement();\n    stmt.executeQuery(\"SELECT 1\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultJsonParserV2Test.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.SFSession;\nimport org.apache.commons.text.StringEscapeUtils;\nimport org.junit.jupiter.api.Test;\n\n/** This is the unit tests for ResultJsonParserV2 */\npublic class ResultJsonParserV2Test {\n  @Test\n  public void simpleTest() throws SnowflakeSQLException {\n    SFSession session = null;\n    String simple =\n        \"[\\\"1\\\", \\\"1.01\\\"],\"\n            + \"[null, null],\"\n            + \"[\\\"2\\\", \\\"0.13\\\"],\"\n            + \"[\\\"\\\", \\\"\\\"],\"\n            + \"[\\\"\\\\\\\"escape\\\\\\\"\\\", \\\"\\\\\\\"escape\\\\\\\"\\\"],\"\n            + \"[\\\"\\\\u2605\\\", \\\"\\\\u263A\\\\u263A\\\"],\"\n            + \"[\\\"\\\\ud841\\\\udf0e\\\", \\\"\\\\ud841\\\\udf31\\\\ud841\\\\udf79\\\"],\"\n            + \"[\\\"{\\\\\\\"date\\\\\\\" : \\\\\\\"2017-04-28\\\\\\\",\\\\\\\"dealership\\\\\\\" : \\\\\\\"Tindel Toyota\\\\\\\"}\\\", \\\"[1,2,3,4,5]\\\"]\";\n    byte[] data = simple.getBytes(StandardCharsets.UTF_8);\n    JsonResultChunk chunk = new JsonResultChunk(\"\", 8, 2, data.length, session);\n    ResultJsonParserV2 jp = new ResultJsonParserV2();\n    jp.startParsing(chunk, session);\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data);\n    jp.continueParsing(byteBuffer, session);\n    byte[] remaining = new byte[byteBuffer.remaining()];\n    byteBuffer.get(remaining);\n    jp.endParsing(ByteBuffer.wrap(remaining), session);\n    assertEquals(\"1\", chunk.getCell(0, 0).toString());\n    assertEquals(\"1.01\", chunk.getCell(0, 1).toString());\n    assertNull(chunk.getCell(1, 0));\n    assertNull(chunk.getCell(1, 1));\n    assertEquals(\"2\", chunk.getCell(2, 0).toString());\n    assertEquals(\"0.13\", chunk.getCell(2, 1).toString());\n    assertEquals(\"\", chunk.getCell(3, 0).toString());\n    assertEquals(\"\", chunk.getCell(3, 1).toString());\n    assertEquals(\"\\\"escape\\\"\", chunk.getCell(4, 0).toString());\n    assertEquals(\"\\\"escape\\\"\", chunk.getCell(4, 1).toString());\n    assertEquals(\"★\", chunk.getCell(5, 0).toString());\n    assertEquals(\"☺☺\", chunk.getCell(5, 1).toString());\n    assertEquals(\"𠜎\", chunk.getCell(6, 0).toString());\n    assertEquals(\"𠜱𠝹\", chunk.getCell(6, 1).toString());\n    assertEquals(\n        \"{\\\"date\\\" : \\\"2017-04-28\\\",\\\"dealership\\\" : \\\"Tindel Toyota\\\"}\",\n        chunk.getCell(7, 0).toString());\n    assertEquals(\"[1,2,3,4,5]\", chunk.getCell(7, 1).toString());\n  }\n\n  @Test\n  public void simpleStreamingTest() throws SnowflakeSQLException {\n    SFSession session = null;\n    String simple =\n        \"[\\\"1\\\", \\\"1.01\\\"],\"\n            + \"[null, null],\"\n            + \"[\\\"2\\\", \\\"0.13\\\"],\"\n            + \"[\\\"\\\", \\\"\\\"],\"\n            + \"[\\\"\\\\\\\"escape\\\\\\\"\\\", \\\"\\\\\\\"escape\\\\\\\"\\\"],\"\n            + \"[\\\"☺☺\\\", \\\"☺☺☺\\\"], \"\n            + \"[\\\"\\\\ud841\\\\udf0e\\\", \\\"\\\\ud841\\\\udf31\\\\ud841\\\\udf79\\\"],\"\n            + \"[\\\"{\\\\\\\"date\\\\\\\" : \\\\\\\"2017-04-28\\\\\\\",\\\\\\\"dealership\\\\\\\" : \\\\\\\"Tindel Toyota\\\\\\\"}\\\", \\\"[1,2,3,4,5]\\\"]\";\n    byte[] data = simple.getBytes(StandardCharsets.UTF_8);\n    JsonResultChunk chunk = new JsonResultChunk(\"\", 8, 2, data.length, session);\n    ResultJsonParserV2 jp = new ResultJsonParserV2();\n    jp.startParsing(chunk, session);\n    int len = 15;\n    ByteBuffer byteBuffer = null;\n    for (int i = 0; i < data.length; i += len) {\n      if (i + len < data.length) {\n        byteBuffer = ByteBuffer.wrap(data, i, len);\n        jp.continueParsing(byteBuffer, session);\n      } else {\n        byteBuffer = ByteBuffer.wrap(data, i, data.length - i);\n        jp.continueParsing(byteBuffer, session);\n      }\n    }\n    byte[] remaining = new byte[byteBuffer.remaining()];\n    byteBuffer.get(remaining);\n    jp.endParsing(ByteBuffer.wrap(remaining), session);\n\n    assertEquals(\"1\", chunk.getCell(0, 0).toString());\n    assertEquals(\"1.01\", chunk.getCell(0, 1).toString());\n    assertNull(chunk.getCell(1, 0));\n    assertNull(chunk.getCell(1, 1));\n    assertEquals(\"2\", chunk.getCell(2, 0).toString());\n    assertEquals(\"0.13\", chunk.getCell(2, 1).toString());\n    assertEquals(\"\", chunk.getCell(3, 0).toString());\n    assertEquals(\"\", chunk.getCell(3, 1).toString());\n    assertEquals(\"\\\"escape\\\"\", chunk.getCell(4, 0).toString());\n    assertEquals(\"\\\"escape\\\"\", chunk.getCell(4, 1).toString());\n    assertEquals(\"☺☺\", chunk.getCell(5, 0).toString());\n    assertEquals(\"☺☺☺\", chunk.getCell(5, 1).toString());\n    assertEquals(\"𠜎\", chunk.getCell(6, 0).toString());\n    assertEquals(\"𠜱𠝹\", chunk.getCell(6, 1).toString());\n    assertEquals(\n        \"{\\\"date\\\" : \\\"2017-04-28\\\",\\\"dealership\\\" : \\\"Tindel Toyota\\\"}\",\n        chunk.getCell(7, 0).toString());\n    assertEquals(\"[1,2,3,4,5]\", chunk.getCell(7, 1).toString());\n  }\n\n  /**\n   * Test the largest column size 16 MB\n   *\n   * @throws SnowflakeSQLException Will be thrown if parsing fails\n   */\n  @Test\n  public void LargestColumnTest() throws SnowflakeSQLException {\n    SFSession session = null;\n    StringBuilder sb = new StringBuilder();\n    StringBuilder a = new StringBuilder();\n    for (int i = 0; i < 16 * 1024 * 1024; i++) {\n      a.append(\"a\");\n    }\n    StringBuilder b = new StringBuilder();\n    for (int i = 0; i < 16 * 1024 * 1024; i++) {\n      b.append(\"b\");\n    }\n    StringBuilder c = new StringBuilder();\n    for (int i = 0; i < 16 * 1024 * 1024; i++) {\n      c.append(\"c\");\n    }\n    StringBuilder s = new StringBuilder();\n    for (int i = 0; i < 16 * 1024 * 1024 - 5; i += 6) {\n      s.append(\"\\\\u263A\");\n    }\n    sb.append(\"[\\\"\")\n        .append(a)\n        .append(\"\\\",\\\"\")\n        .append(b)\n        .append(\"\\\"],[\\\"\")\n        .append(c)\n        .append(\"\\\",\\\"\")\n        .append(s)\n        .append(\"\\\"]\");\n\n    byte[] data = sb.toString().getBytes(StandardCharsets.UTF_8);\n    JsonResultChunk chunk = new JsonResultChunk(\"\", 2, 2, data.length, session);\n    ResultJsonParserV2 jp = new ResultJsonParserV2();\n    jp.startParsing(chunk, session);\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data);\n    jp.continueParsing(byteBuffer, session);\n    byte[] remaining = new byte[byteBuffer.remaining()];\n    byteBuffer.get(remaining);\n    jp.endParsing(ByteBuffer.wrap(remaining), session);\n    assertEquals(a.toString(), chunk.getCell(0, 0).toString());\n    assertEquals(b.toString(), chunk.getCell(0, 1).toString());\n    assertEquals(c.toString(), chunk.getCell(1, 0).toString());\n    assertEquals(StringEscapeUtils.unescapeJava(s.toString()), chunk.getCell(1, 1).toString());\n  }\n\n  // SNOW-802910: Test to cover edge case '\\u0000\\u0000' where null could be dropped.\n  @Test\n  public void testAsciiSequential() throws SnowflakeSQLException {\n    SFSession session = null;\n    String ascii = \"[\\\"\\\\u0000\\\\u0000\\\\u0000\\\"]\";\n    byte[] data = ascii.getBytes(StandardCharsets.UTF_8);\n    JsonResultChunk chunk = new JsonResultChunk(\"\", 1, 1, data.length, session);\n    ResultJsonParserV2 jp = new ResultJsonParserV2();\n    jp.startParsing(chunk, session);\n\n    // parse the first null\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data, 0, 14);\n    jp.continueParsing(byteBuffer, session);\n    // parse the rest of the string\n    byteBuffer = ByteBuffer.wrap(data, 9, 13);\n    jp.continueParsing(byteBuffer, session);\n    byteBuffer = ByteBuffer.wrap(data, 15, 7);\n    jp.continueParsing(byteBuffer, session);\n\n    // finish parsing\n    byte[] remaining = new byte[byteBuffer.remaining()];\n    byteBuffer.get(remaining);\n    jp.endParsing(ByteBuffer.wrap(remaining), session);\n\n    // check null is not dropped\n    assertEquals(\"00 00 00 \", stringToHex(chunk.getCell(0, 0).toString()));\n  }\n\n  // SNOW-802910: Test to cover edge case '\\u0003ä\\u0000' where null could be dropped.\n  @Test\n  public void testAsciiCharacter() throws SnowflakeSQLException {\n    SFSession session = null;\n    String ascii = \"[\\\"\\\\u0003ä\\\\u0000\\\"]\";\n    byte[] data = ascii.getBytes(StandardCharsets.UTF_8);\n    JsonResultChunk chunk = new JsonResultChunk(\"\", 1, 1, data.length, session);\n    ResultJsonParserV2 jp = new ResultJsonParserV2();\n    jp.startParsing(chunk, session);\n\n    // parse ETX and UTF-8 character\n    ByteBuffer byteBuffer = ByteBuffer.wrap(data, 0, data.length);\n    jp.continueParsing(byteBuffer, session);\n\n    int position = byteBuffer.position();\n    // try to parse null\n    byteBuffer = ByteBuffer.wrap(data, position, data.length - position);\n    jp.continueParsing(byteBuffer, session);\n\n    byte[] remaining = new byte[byteBuffer.remaining()];\n    byteBuffer.get(remaining);\n    jp.endParsing(ByteBuffer.wrap(remaining), session);\n\n    // \u0003Ã00 is returned\n    assertEquals(\"03 C3 A4 00 \", stringToHex(chunk.getCell(0, 0).toString()));\n  }\n\n  public static String stringToHex(String input) {\n    byte[] byteArray = input.getBytes(StandardCharsets.UTF_8);\n    StringBuilder sb = new StringBuilder();\n    char[] hexBytes = new char[2];\n    for (int i = 0; i < byteArray.length; i++) {\n      hexBytes[0] = Character.forDigit((byteArray[i] >> 4) & 0xF, 16);\n      hexBytes[1] = Character.forDigit((byteArray[i] & 0xF), 16);\n      sb.append(hexBytes);\n      sb.append(\" \"); // Space every two characters to make it easier to read visually\n    }\n    return sb.toString().toUpperCase();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSet0IT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\n\n/** Result set test base class. */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSet0IT extends BaseJDBCWithSharedConnectionIT {\n  public Connection init(Properties paramProperties, String queryResultFormat) throws SQLException {\n    Connection conn =\n        BaseJDBCTest.getConnection(DONT_INJECT_SOCKET_TIMEOUT, paramProperties, false, false);\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  protected final String uniqueTableName =\n      \"orders_jdbc_resultset0it_\" + SnowflakeUtil.randomAlphaNumeric(10);\n\n  protected final String uniqueTestRsTableName =\n      \"test_rs_resultset0it_\" + SnowflakeUtil.randomAlphaNumeric(10);\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      // TEST_RS\n      statement.execute(\"create or replace table \" + uniqueTestRsTableName + \" (colA string)\");\n      statement.execute(\"insert into \" + uniqueTestRsTableName + \" values('rowOne')\");\n      statement.execute(\"insert into \" + uniqueTestRsTableName + \" values('rowTwo')\");\n      statement.execute(\"insert into \" + uniqueTestRsTableName + \" values('rowThree')\");\n\n      // ORDERS_JDBC with unique name to prevent race conditions\n      statement.execute(\n          \"create or replace table \"\n              + uniqueTableName\n              + \"(C1 STRING NOT NULL COMMENT 'JDBC', \"\n              + \"C2 STRING, C3 STRING, C4 STRING, C5 STRING, C6 STRING, \"\n              + \"C7 STRING, C8 STRING, C9 STRING) \"\n              + \"stage_file_format = (field_delimiter='|' \"\n              + \"error_on_column_count_mismatch=false)\");\n      // put files\n      assertTrue(\n          statement.execute(\n              \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @%\" + uniqueTableName),\n          \"Failed to put a file\");\n      assertTrue(\n          statement.execute(\n              \"PUT file://\"\n                  + getFullPathFileInResource(TEST_DATA_FILE_2)\n                  + \" @%\"\n                  + uniqueTableName),\n          \"Failed to put a file\");\n\n      int numRows = statement.executeUpdate(\"copy into \" + uniqueTableName);\n\n      assertEquals(73, numRows, \"Unexpected number of rows copied: \" + numRows);\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"drop table if exists \" + uniqueTableName);\n      statement.execute(\"drop table if exists \" + uniqueTestRsTableName);\n    }\n  }\n\n  ResultSet numberCrossTesting(String queryResultFormat) throws SQLException {\n    Statement statement = createStatement(queryResultFormat);\n    statement.execute(\n        \"create or replace table test_types(c1 number, c2 integer, c3 float, c4 boolean,\"\n            + \"c5 char, c6 varchar, c7 date, c8 datetime, c9 time, c10 timestamp_ltz, \"\n            + \"c11 timestamp_tz, c12 binary)\");\n    statement.execute(\n        \"insert into test_types values (null, null, null, null, null, null, null, null, null, null, \"\n            + \"null, null)\");\n    statement.execute(\n        \"insert into test_types values(2, 5, 3.5, true,\"\n            + \"'1','1', '1994-12-27', \"\n            + \"'1994-12-27 05:05:05', '05:05:05', '1994-12-27 05:05:05', '1994-12-27 05:05:05', '48454C4C4F')\");\n    statement.execute(\"insert into test_types (c5, c6) values('h', 'hello')\");\n    return statement.executeQuery(\"select * from test_types\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetAlreadyClosedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.sql.DatabaseMetaData;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Calendar;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetAlreadyClosedIT extends BaseJDBCWithSharedConnectionIT {\n\n  @Test\n  public void testQueryResultSetAlreadyClosed() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      ResultSet resultSet = statement.executeQuery(\"select 1\");\n      resultSet.close();\n      checkAlreadyClosed(resultSet);\n    }\n  }\n\n  @Test\n  public void testMetadataResultSetAlreadyClosed() throws Throwable {\n    String database = connection.getCatalog();\n    String schema = connection.getSchema();\n    DatabaseMetaData metaData = connection.getMetaData();\n\n    checkAlreadyClosed(metaData.getCatalogs());\n    checkAlreadyClosed(metaData.getSchemas());\n    checkAlreadyClosed(metaData.getSchemas(database, null));\n    checkAlreadyClosed(metaData.getTables(database, schema, null, null));\n    checkAlreadyClosed(metaData.getColumns(database, schema, null, null));\n  }\n\n  @Test\n  public void testResultSetAlreadyClosed() throws Throwable {\n    try (Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"SELECT 1\")) {\n      checkAlreadyClosed(resultSet);\n    }\n  }\n\n  @Test\n  public void testEmptyResultSetAlreadyClosed() throws Throwable {\n    try (SnowflakeResultSetV1.EmptyResultSet resultSet =\n        new SnowflakeResultSetV1.EmptyResultSet()) {\n      checkAlreadyClosedEmpty(resultSet);\n    }\n  }\n\n  private void checkAlreadyClosed(ResultSet resultSet) throws SQLException {\n    resultSet.close();\n    resultSet.close(); // second close won't raise exception\n    assertTrue(resultSet.isClosed());\n    assertFalse(resultSet.next()); // next after close should return false.\n\n    expectResultSetAlreadyClosedException(resultSet::wasNull);\n    expectResultSetAlreadyClosedException(() -> resultSet.getString(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBoolean(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getByte(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getShort(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getInt(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getLong(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getFloat(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getDouble(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBigDecimal(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBytes(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getDate(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTime(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTimestamp(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getObject(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getCharacterStream(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getClob(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getDate(1, Calendar.getInstance()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTime(1, Calendar.getInstance()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTimestamp(1, Calendar.getInstance()));\n\n    expectResultSetAlreadyClosedException(() -> resultSet.getString(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBoolean(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getByte(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getShort(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getInt(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getLong(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getFloat(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getDouble(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBigDecimal(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBytes(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getString(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getDate(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTime(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTimestamp(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getObject(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getCharacterStream(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getClob(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getDate(\"col1\", Calendar.getInstance()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getTime(\"col1\", Calendar.getInstance()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.getTimestamp(\"col1\", Calendar.getInstance()));\n\n    expectResultSetAlreadyClosedException(() -> resultSet.getBigDecimal(1, 28));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBigDecimal(\"col1\", 38));\n\n    expectResultSetAlreadyClosedException(resultSet::getWarnings);\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.unwrap(SnowflakeBaseResultSet.class).getWarnings());\n\n    expectResultSetAlreadyClosedException(resultSet::clearWarnings);\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.unwrap(SnowflakeBaseResultSet.class).clearWarnings());\n\n    expectResultSetAlreadyClosedException(resultSet::getMetaData);\n\n    expectResultSetAlreadyClosedException(() -> resultSet.findColumn(\"col1\"));\n\n    expectResultSetAlreadyClosedException(resultSet::isBeforeFirst);\n    expectResultSetAlreadyClosedException(resultSet::isAfterLast);\n    expectResultSetAlreadyClosedException(resultSet::isFirst);\n    expectResultSetAlreadyClosedException(resultSet::isLast);\n    expectResultSetAlreadyClosedException(resultSet::getRow);\n\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.setFetchDirection(ResultSet.FETCH_FORWARD));\n    expectResultSetAlreadyClosedException(() -> resultSet.setFetchSize(10));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.unwrap(SnowflakeBaseResultSet.class).setFetchSize(10));\n\n    expectResultSetAlreadyClosedException(resultSet::getFetchDirection);\n    expectResultSetAlreadyClosedException(resultSet::getFetchSize);\n    expectResultSetAlreadyClosedException(resultSet::getType);\n    expectResultSetAlreadyClosedException(resultSet::getConcurrency);\n    expectResultSetAlreadyClosedException(\n        resultSet.unwrap(SnowflakeBaseResultSet.class)::getConcurrency);\n\n    expectResultSetAlreadyClosedException(resultSet::getHoldability);\n    expectResultSetAlreadyClosedException(\n        resultSet.unwrap(SnowflakeBaseResultSet.class)::getHoldability);\n\n    expectResultSetAlreadyClosedException(resultSet::getStatement);\n  }\n\n  /**\n   * These tests are specific to an empty resultset object\n   *\n   * @param resultSet\n   * @throws SQLException\n   */\n  private void checkAlreadyClosedEmpty(SnowflakeResultSetV1.EmptyResultSet resultSet)\n      throws SQLException {\n    resultSet.close();\n    resultSet.close(); // second close won't raise exception\n    assertTrue(resultSet.isClosed());\n    assertFalse(resultSet.next()); // next after close should return false.\n\n    expectResultSetAlreadyClosedException(resultSet::beforeFirst);\n    expectResultSetAlreadyClosedException(resultSet::afterLast);\n    expectResultSetAlreadyClosedException(resultSet::first);\n    expectResultSetAlreadyClosedException(resultSet::last);\n    expectResultSetAlreadyClosedException(resultSet::getRow);\n    expectResultSetAlreadyClosedException(resultSet::previous);\n    expectResultSetAlreadyClosedException(resultSet::rowUpdated);\n    expectResultSetAlreadyClosedException(resultSet::rowInserted);\n    expectResultSetAlreadyClosedException(resultSet::rowDeleted);\n\n    expectResultSetAlreadyClosedException(() -> resultSet.absolute(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.relative(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.setFetchDirection(1));\n    expectResultSetAlreadyClosedException(resultSet::getFetchDirection);\n    expectResultSetAlreadyClosedException(() -> resultSet.setFetchSize(1));\n    expectResultSetAlreadyClosedException(resultSet::getFetchSize);\n    expectResultSetAlreadyClosedException(resultSet::getType);\n    expectResultSetAlreadyClosedException(resultSet::getConcurrency);\n\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNull(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNull(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBoolean(2, true));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBoolean(\"col2\", true));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateByte(3, (byte) 0));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateByte(\"col3\", (byte) 0));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateShort(4, (short) 0));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateShort(\"col4\", (short) 0));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateInt(5, 0));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateInt(\"col5\", 0));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateLong(6, 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateLong(\"col6\", 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateFloat(6, 4F));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateFloat(\"col6\", 4F));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateDouble(7, 12.5));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateDouble(\"col7\", 12.5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBigDecimal(8, BigDecimal.valueOf(12.5)));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBigDecimal(\"col8\", BigDecimal.valueOf(12.5)));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateString(9, \"hello\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateString(\"col9\", \"hello\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBytes(10, new byte[0]));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBytes(\"col10\", new byte[0]));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateDate(11, new java.sql.Date(System.currentTimeMillis())));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateDate(\"col11\", new java.sql.Date(System.currentTimeMillis())));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateTime(12, new java.sql.Time(System.currentTimeMillis())));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateTime(\"col12\", new java.sql.Time(System.currentTimeMillis())));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateTimestamp(13, new java.sql.Timestamp(System.currentTimeMillis())));\n    expectResultSetAlreadyClosedException(\n        () ->\n            resultSet.updateTimestamp(\"col13\", new java.sql.Timestamp(System.currentTimeMillis())));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateAsciiStream(14, new FakeInputStream()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateAsciiStream(14, new FakeInputStream(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateAsciiStream(14, new FakeInputStream(), 5L));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateAsciiStream(\"col14\", new FakeInputStream()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateAsciiStream(\"col14\", new FakeInputStream(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateAsciiStream(\"col14\", new FakeInputStream(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.getAsciiStream(14));\n    expectResultSetAlreadyClosedException(() -> resultSet.getAsciiStream(\"col14\"));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBinaryStream(15, new FakeInputStream()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBinaryStream(15, new FakeInputStream(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBinaryStream(15, new FakeInputStream(), 5L));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBinaryStream(\"col15\", new FakeInputStream()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBinaryStream(\"col15\", new FakeInputStream(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBinaryStream(\"col15\", new FakeInputStream(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBinaryStream(15));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBinaryStream(\"col15\"));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateCharacterStream(16, new FakeReader()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateCharacterStream(16, new FakeReader(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateCharacterStream(16, new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateCharacterStream(\"col16\", new FakeReader()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateCharacterStream(\"col16\", new FakeReader(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateCharacterStream(\"col16\", new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateObject(17, new Object()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateObject(17, new Object(), 5));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateObject(\"col17\", new Object()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateObject(\"col17\", new Object(), 5));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.getObject(17, SnowflakeResultSetV1.class));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.getObject(\"col17\", SnowflakeResultSetV1.class));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.getObject(17, SnowflakeResultSetV1.class));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.getObject(\"col17\", SnowflakeResultSetV1.class));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBlob(18, new FakeBlob()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBlob(18, new FakeInputStream()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBlob(18, new FakeInputStream(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateBlob(\"col18\", new FakeBlob()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBlob(\"col18\", new FakeInputStream()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateBlob(\"col18\", new FakeInputStream(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBlob(18));\n    expectResultSetAlreadyClosedException(() -> resultSet.getBlob(\"col18\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNull(19));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNull(\"col19\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getUnicodeStream(20));\n    expectResultSetAlreadyClosedException(() -> resultSet.getUnicodeStream(\"col20\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateRef(21, new FakeRef()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateRef(\"col21\", new FakeRef()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getRef(21));\n    expectResultSetAlreadyClosedException(() -> resultSet.getRef(\"col21\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateArray(22, new FakeArray()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateArray(\"col22\", new FakeArray()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getArray(22));\n    expectResultSetAlreadyClosedException(() -> resultSet.getArray(\"col22\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getURL(23));\n    expectResultSetAlreadyClosedException(() -> resultSet.getURL(\"col23\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateClob(24, new FakeNClob()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateClob(24, new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateClob(24, new FakeReader()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateClob(\"col24\", new FakeNClob()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateClob(\"col24\", new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateClob(\"col24\", new FakeReader()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNString(25, \"hello\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNString(\"col25\", \"hello\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.getNString(25));\n    expectResultSetAlreadyClosedException(() -> resultSet.getNString(\"col25\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNClob(26, new FakeNClob()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNClob(26, new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNClob(26, new FakeReader()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNClob(\"col26\", new FakeNClob()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateNClob(\"col26\", new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateNClob(\"col26\", new FakeReader()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getNClob(26));\n    expectResultSetAlreadyClosedException(() -> resultSet.getNClob(\"col26\"));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateNCharacterStream(27, new FakeReader()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateNCharacterStream(27, new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateNCharacterStream(\"col26\", new FakeReader()));\n    expectResultSetAlreadyClosedException(\n        () -> resultSet.updateNCharacterStream(\"col26\", new FakeReader(), 5L));\n    expectResultSetAlreadyClosedException(() -> resultSet.getNCharacterStream(26));\n    expectResultSetAlreadyClosedException(() -> resultSet.getNCharacterStream(\"col26\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateSQLXML(27, new FakeSQLXML()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateSQLXML(\"col27\", new FakeSQLXML()));\n    expectResultSetAlreadyClosedException(() -> resultSet.getSQLXML(27));\n    expectResultSetAlreadyClosedException(() -> resultSet.getSQLXML(\"col27\"));\n\n    expectResultSetAlreadyClosedException(() -> resultSet.getRowId(1));\n    expectResultSetAlreadyClosedException(() -> resultSet.getRowId(\"col1\"));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateRowId(1, new FakeRowId()));\n    expectResultSetAlreadyClosedException(() -> resultSet.updateRowId(\"col1\", new FakeRowId()));\n\n    expectResultSetAlreadyClosedException(resultSet::insertRow);\n    expectResultSetAlreadyClosedException(resultSet::updateRow);\n    expectResultSetAlreadyClosedException(resultSet::deleteRow);\n    expectResultSetAlreadyClosedException(resultSet::refreshRow);\n    expectResultSetAlreadyClosedException(resultSet::cancelRowUpdates);\n    expectResultSetAlreadyClosedException(resultSet::moveToInsertRow);\n    expectResultSetAlreadyClosedException(resultSet::moveToCurrentRow);\n    expectResultSetAlreadyClosedException(resultSet::cancelRowUpdates);\n\n    expectResultSetAlreadyClosedException(() -> resultSet.isWrapperFor(SnowflakeResultSetV1.class));\n    expectResultSetAlreadyClosedException(() -> resultSet.unwrap(SnowflakeResultSetV1.class));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetArrowForce0MultiTimeZone.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport java.util.TimeZone;\nimport net.snowflake.client.providers.ProvidersUtil;\nimport net.snowflake.client.providers.ScaleProvider;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport net.snowflake.client.providers.SnowflakeArgumentsProvider;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\nabstract class ResultSetArrowForce0MultiTimeZone extends BaseJDBCTest {\n  protected static class DataProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(\n          context, new SimpleResultFormatProvider(), new TimezoneProvider(3));\n    }\n  }\n\n  protected static class DataWithScaleProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(context, new DataProvider(), new ScaleProvider());\n    }\n  }\n\n  private static TimeZone origTz;\n\n  @BeforeAll\n  public static void setUp() {\n    origTz = TimeZone.getDefault();\n  }\n\n  @AfterAll\n  public static void tearDown() {\n    TimeZone.setDefault(origTz);\n  }\n\n  protected static void setTimezone(String tz) {\n    TimeZone.setDefault(TimeZone.getTimeZone(tz));\n  }\n\n  Connection init(String table, String column, String values, String queryResultFormat)\n      throws SQLException {\n    Connection con = BaseJDBCTest.getConnection();\n\n    try (Statement statement = con.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n\n      statement.execute(\n          \"alter session set jdbc_query_result_format\" + \" = '\" + queryResultFormat + \"'\");\n      statement.execute(\"create or replace table \" + table + \" \" + column);\n      statement.execute(\"insert into \" + table + \" values \" + values);\n    }\n    return con;\n  }\n\n  protected void finish(String table, Connection con) throws SQLException {\n    con.createStatement().execute(\"drop table \" + table);\n    con.close();\n    System.clearProperty(\"user.timezone\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetArrowForceLTZMultiTimeZoneIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.text.SimpleDateFormat;\nimport java.util.TimeZone;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/** Compare json and arrow resultSet behaviors 1/2 */\n@Tag(TestTags.ARROW)\npublic class ResultSetArrowForceLTZMultiTimeZoneIT extends ResultSetArrowForce0MultiTimeZone {\n\n  @ParameterizedTest\n  @ArgumentsSource(DataWithScaleProvider.class)\n  public void testTimestampLTZWithScale(String queryResultFormat, String tz, int scale)\n      throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00 Z\",\n      \"2014-01-02 16:00:00 Z\",\n      \"2014-01-02 12:34:56 Z\",\n      \"1970-01-01 00:00:00 Z\",\n      \"1970-01-01 00:00:01 Z\",\n      \"1969-12-31 11:59:59 Z\",\n      \"0000-01-01 00:00:01 Z\",\n      \"0001-12-31 11:59:59 Z\"\n    };\n\n    long[] times = {\n      1483272000000L,\n      1388678400000L,\n      1388666096000L,\n      0,\n      1000,\n      -43201000,\n      -62167391999000L,\n      -62104276801000L\n    };\n\n    SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    dateFormat.setTimeZone(TimeZone.getDefault());\n\n    String table = \"test_arrow_ts_ltz\";\n\n    String column = \"(a timestamp_ltz(\" + scale + \"))\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'),('\") + \"'), (null)\";\n    Connection con = init(table, column, values, queryResultFormat);\n    ResultSet rs = con.createStatement().executeQuery(\"select * from \" + table);\n    int i = 0;\n    while (i < cases.length) {\n      assertTrue(rs.next());\n      assertEquals(times[i++], rs.getTimestamp(1).getTime());\n      assertEquals(0, rs.getTimestamp(1).getNanos());\n    }\n    assertTrue(rs.next());\n    assertNull(rs.getString(1));\n    finish(table, con);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampLTZOutputFormat(String queryResultFormat, String tz)\n      throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\"2017-01-01 12:00:00 Z\", \"2014-01-02 16:00:00 Z\", \"2014-01-02 12:34:56 Z\"};\n\n    long[] times = {1483272000000L, 1388678400000L, 1388666096000L};\n\n    SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    dateFormat.setTimeZone(TimeZone.getDefault());\n\n    String table = \"test_arrow_ts_ltz\";\n\n    String column = \"(a timestamp_ltz)\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'),('\") + \"')\";\n    try (Connection con = init(table, column, values, queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        // use initialized ltz output format\n        try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n          for (int i = 0; i < cases.length; i++) {\n            assertTrue(rs.next());\n            assertEquals(times[i], rs.getTimestamp(1).getTime());\n            String weekday = rs.getString(1).split(\",\")[0];\n            assertEquals(3, weekday.length());\n          }\n        }\n        // change ltz output format\n        statement.execute(\n            \"alter session set TIMESTAMP_LTZ_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS TZH:TZM'\");\n        try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n          for (int i = 0; i < cases.length; i++) {\n            assertTrue(rs.next());\n            assertEquals(times[i], rs.getTimestamp(1).getTime());\n            String year = rs.getString(1).split(\"-\")[0];\n            assertEquals(4, year.length());\n          }\n        }\n\n        // unset ltz output format, then it should use timestamp_output_format\n        statement.execute(\"alter session unset TIMESTAMP_LTZ_OUTPUT_FORMAT\");\n        try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n          for (int i = 0; i < cases.length; i++) {\n            assertTrue(rs.next());\n            assertEquals(times[i], rs.getTimestamp(1).getTime());\n            String weekday = rs.getString(1).split(\",\")[0];\n            assertEquals(3, weekday.length());\n          }\n        }\n        // set ltz output format back to init value\n        statement.execute(\n            \"alter session set TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n        try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n          for (int i = 0; i < cases.length; i++) {\n            assertTrue(rs.next());\n            assertEquals(times[i], rs.getTimestamp(1).getTime());\n            String weekday = rs.getString(1).split(\",\")[0];\n            assertEquals(3, weekday.length());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampLTZWithNulls(String queryResultFormat, String tz) throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00 Z\",\n      \"2014-01-02 16:00:00 Z\",\n      \"2014-01-02 12:34:56 Z\",\n      \"1970-01-01 00:00:00 Z\",\n      \"1970-01-01 00:00:01 Z\",\n      \"1969-12-31 11:59:59 Z\",\n      \"0000-01-01 00:00:01 Z\",\n      \"0001-12-31 11:59:59 Z\"\n    };\n\n    long[] times = {\n      1483272000000L,\n      1388678400000L,\n      1388666096000L,\n      0,\n      1000,\n      -43201000,\n      -62167391999000L,\n      -62104276801000L\n    };\n\n    SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    dateFormat.setTimeZone(TimeZone.getDefault());\n\n    String table = \"test_arrow_ts_ltz\";\n\n    String column = \"(a timestamp_ltz)\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'), (null),('\") + \"')\";\n    try (Connection con = init(table, column, values, queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        int i = 0;\n        while (i < 2 * cases.length - 1) {\n          assertTrue(rs.next());\n          if (i % 2 != 0) {\n            assertNull(rs.getTimestamp(1));\n          } else {\n            assertEquals(times[i / 2], rs.getTimestamp(1).getTime());\n            assertEquals(0, rs.getTimestamp(1).getNanos());\n          }\n          i++;\n        }\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampLTZWithNanos(String queryResultFormat, String tz) throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00.123456789\",\n      \"2014-01-02 16:00:00.000000001\",\n      \"2014-01-02 12:34:56.1\",\n      \"1969-12-31 23:59:59.000000001\",\n      \"1970-01-01 00:00:00.123412423\",\n      \"1970-01-01 00:00:01.000001\",\n      \"1969-12-31 11:59:59.001\",\n      \"0001-12-31 11:59:59.11\"\n    };\n\n    long[] times = {\n      1483272000123L, 1388678400000L, 1388666096100L, -1000, 123, 1000, -43200999, -62104276800890L\n    };\n\n    int[] nanos = {123456789, 1, 100000000, 1, 123412423, 1000, 1000000, 110000000};\n\n    String table = \"test_arrow_ts_ltz\";\n\n    String column = \"(a timestamp_ltz)\";\n\n    String values = \"('\" + StringUtils.join(cases, \" Z'),('\") + \" Z'), (null)\";\n    try (Connection con = init(table, column, values, queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          assertEquals(times[i], rs.getTimestamp(1).getTime());\n          assertEquals(nanos[i++], rs.getTimestamp(1).getNanos());\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetArrowForceTZMultiTimeZoneIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.category.TestTags;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/** Compare json and arrow resultSet behaviors 2/2 */\n@Tag(TestTags.ARROW)\npublic class ResultSetArrowForceTZMultiTimeZoneIT extends ResultSetArrowForce0MultiTimeZone {\n\n  @ParameterizedTest\n  @ArgumentsSource(DataWithScaleProvider.class)\n  public void testTimestampTZWithScale(String queryResultFormat, String tz, int scale)\n      throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00 Z\",\n      \"2014-01-02 16:00:00 Z\",\n      \"2014-01-02 12:34:56 Z\",\n      \"1970-01-01 00:00:00 Z\",\n      \"1970-01-01 00:00:01 Z\",\n      \"1969-12-31 11:59:59 Z\",\n      \"0000-01-01 00:00:01 Z\",\n      \"0001-12-31 11:59:59 Z\"\n    };\n\n    long[] times = {\n      1483272000000L,\n      1388678400000L,\n      1388666096000L,\n      0,\n      1000,\n      -43201000L,\n      -62167391999000L,\n      -62104276801000L\n    };\n\n    String table = \"test_arrow_ts_tz\";\n\n    String column = \"(a timestamp_tz(\" + scale + \"))\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'),('\") + \"'), (null)\";\n    try (Connection con = init(table, column, values, queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          assertEquals(times[i++], rs.getTimestamp(1).getTime());\n          assertEquals(0, rs.getTimestamp(1).getNanos());\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampTZWithNanos(String queryResultFormat, String tz) throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00.1\",\n      \"2014-01-02 16:00:00.123456789\",\n      \"2014-01-02 12:34:56.999999999\",\n      \"1969-12-31 23:59:59.000000001\",\n      \"1970-01-01 00:00:00.000000001\",\n      \"1970-01-01 00:00:01.0000001\",\n      \"1969-12-31 11:59:59.134\",\n      \"0001-12-31 11:59:59.234141\",\n      \"0000-01-01 00:00:01.790870987\"\n    };\n\n    long[] times = {\n      1483272000100L,\n      1388678400123L,\n      1388666096999L,\n      -1000,\n      0,\n      1000,\n      -43200866,\n      -62104276800766L,\n      -62167391998210L\n    };\n\n    int[] nanos = {100000000, 123456789, 999999999, 1, 1, 100, 134000000, 234141000, 790870987};\n\n    String table = \"test_arrow_ts_tz\";\n\n    String column = \"(a timestamp_tz)\";\n\n    String values = \"('\" + StringUtils.join(cases, \" Z'),('\") + \" Z'), (null)\";\n    try (Connection con = init(table, column, values, queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          if (i == cases.length - 1 && tz.equalsIgnoreCase(\"utc\")) {\n            // TODO: Is this a JDBC bug which happens in both arrow and json cases?\n            assertEquals(\"0001-01-01 00:00:01.790870987\", rs.getTimestamp(1).toString());\n          }\n\n          assertEquals(times[i], rs.getTimestamp(1).getTime());\n          assertEquals(nanos[i++], rs.getTimestamp(1).getNanos());\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampTZWithMicros(String queryResultFormat, String tz) throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00.1\",\n      \"2014-01-02 16:00:00.123456\",\n      \"2014-01-02 12:34:56.999999\",\n      \"1969-12-31 23:59:59.000001\",\n      \"1970-01-01 00:00:00.000001\",\n      \"1970-01-01 00:00:01.00001\",\n      \"1969-12-31 11:59:59.134\",\n      \"0001-12-31 11:59:59.234141\",\n      \"0000-01-01 00:00:01.79087\"\n    };\n\n    long[] times = {\n      1483272000100L,\n      1388678400123L,\n      1388666096999L,\n      -1000,\n      0,\n      1000,\n      -43200866,\n      -62104276800766L,\n      -62167391998210L\n    };\n\n    int[] nanos = {\n      100000000, 123456000, 999999000, 1000, 1000, 10000, 134000000, 234141000, 790870000\n    };\n\n    String table = \"test_arrow_ts_tz\";\n\n    String column = \"(a timestamp_tz(6))\";\n\n    String values = \"('\" + StringUtils.join(cases, \" Z'),('\") + \" Z'), (null)\";\n    try (Connection con = init(table, column, values, queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          if (i == cases.length - 1 && tz.equalsIgnoreCase(\"utc\")) {\n            // TODO: Is this a JDBC bug which happens in both arrow and json cases?\n            assertEquals(\"0001-01-01 00:00:01.79087\", rs.getTimestamp(1).toString());\n          }\n\n          assertEquals(times[i], rs.getTimestamp(1).getTime());\n          assertEquals(nanos[i++], rs.getTimestamp(1).getNanos());\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetAsyncIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.text.DecimalFormat;\nimport java.text.SimpleDateFormat;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.common.core.SqlState;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Test AsyncResultSet */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetAsyncIT extends BaseJDBCWithSharedConnectionIT {\n\n  @Test\n  public void testAsyncResultSetFunctionsWithNewSession() throws SQLException {\n    final Map<String, String> params = getConnectionParameters();\n    String queryID = null;\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_rsmd(colA number(20, 5), colB string)\");\n        statement.execute(\"insert into test_rsmd values(1.00, 'str'),(2.00, 'str2')\");\n        String createTableSql = \"select * from test_rsmd\";\n        try (ResultSet rs =\n            statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(createTableSql)) {\n          queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_rsmd\");\n      }\n    }\n    try (Connection connection = getConnection();\n        // open a new connection and create a result set\n        ResultSet resultSet =\n            connection.unwrap(SnowflakeConnection.class).createResultSet(queryID)) {\n      ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n      // getCatalogName(), getSchemaName(), and getTableName() are empty\n      // when session is re-opened\n      assertEquals(\"\", resultSetMetaData.getCatalogName(1).toUpperCase());\n      assertEquals(\"\", resultSetMetaData.getSchemaName(1).toUpperCase());\n      assertEquals(\"\", resultSetMetaData.getTableName(1));\n      assertEquals(String.class.getName(), resultSetMetaData.getColumnClassName(2));\n      assertEquals(2, resultSetMetaData.getColumnCount());\n      assertEquals(22, resultSetMetaData.getColumnDisplaySize(1));\n      assertEquals(\"COLA\", resultSetMetaData.getColumnLabel(1));\n      assertEquals(\"COLA\", resultSetMetaData.getColumnName(1));\n      assertEquals(3, resultSetMetaData.getColumnType(1));\n      assertEquals(\"NUMBER\", resultSetMetaData.getColumnTypeName(1));\n      assertEquals(20, resultSetMetaData.getPrecision(1));\n      assertEquals(5, resultSetMetaData.getScale(1));\n      assertFalse(resultSetMetaData.isAutoIncrement(1));\n      assertFalse(resultSetMetaData.isCaseSensitive(1));\n      assertFalse(resultSetMetaData.isCurrency(1));\n      assertFalse(resultSetMetaData.isDefinitelyWritable(1));\n      assertEquals(ResultSetMetaData.columnNullable, resultSetMetaData.isNullable(1));\n      assertTrue(resultSetMetaData.isReadOnly(1));\n      assertTrue(resultSetMetaData.isSearchable(1));\n      assertTrue(resultSetMetaData.isSigned(1));\n\n      SnowflakeResultSetMetaData secretMetaData =\n          resultSetMetaData.unwrap(SnowflakeResultSetMetaData.class);\n      List<String> colNames = secretMetaData.getColumnNames();\n      assertEquals(\"COLA\", colNames.get(0));\n      assertEquals(\"COLB\", colNames.get(1));\n      assertEquals(Types.DECIMAL, secretMetaData.getInternalColumnType(1));\n      assertEquals(Types.VARCHAR, secretMetaData.getInternalColumnType(2));\n      TestUtil.assertValidQueryId(secretMetaData.getQueryID());\n      assertEquals(\n          secretMetaData.getQueryID(), resultSet.unwrap(SnowflakeResultSet.class).getQueryID());\n    }\n  }\n\n  @Test\n  public void testResultSetMetadata() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_rsmd(colA number(20, 5), colB string)\");\n        statement.execute(\"insert into test_rsmd values(1.00, 'str'),(2.00, 'str2')\");\n        try (ResultSet resultSet =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"select * from test_rsmd\")) {\n          ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n          assertEquals(\"\", resultSetMetaData.getCatalogName(1).toUpperCase());\n          assertEquals(\"\", resultSetMetaData.getSchemaName(1).toUpperCase());\n          assertEquals(\"\", resultSetMetaData.getTableName(1));\n          assertEquals(String.class.getName(), resultSetMetaData.getColumnClassName(2));\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(22, resultSetMetaData.getColumnDisplaySize(1));\n          assertEquals(\"COLA\", resultSetMetaData.getColumnLabel(1));\n          assertEquals(\"COLA\", resultSetMetaData.getColumnName(1));\n          assertEquals(3, resultSetMetaData.getColumnType(1));\n          assertEquals(\"NUMBER\", resultSetMetaData.getColumnTypeName(1));\n          assertEquals(20, resultSetMetaData.getPrecision(1));\n          assertEquals(5, resultSetMetaData.getScale(1));\n          assertFalse(resultSetMetaData.isAutoIncrement(1));\n          assertFalse(resultSetMetaData.isCaseSensitive(1));\n          assertFalse(resultSetMetaData.isCurrency(1));\n          assertFalse(resultSetMetaData.isDefinitelyWritable(1));\n          assertEquals(ResultSetMetaData.columnNullable, resultSetMetaData.isNullable(1));\n          assertTrue(resultSetMetaData.isReadOnly(1));\n          assertTrue(resultSetMetaData.isSearchable(1));\n          assertTrue(resultSetMetaData.isSigned(1));\n          SnowflakeResultSetMetaData secretMetaData =\n              resultSetMetaData.unwrap(SnowflakeResultSetMetaData.class);\n          List<String> colNames = secretMetaData.getColumnNames();\n          assertEquals(\"COLA\", colNames.get(0));\n          assertEquals(\"COLB\", colNames.get(1));\n          assertEquals(Types.DECIMAL, secretMetaData.getInternalColumnType(1));\n          assertEquals(Types.VARCHAR, secretMetaData.getInternalColumnType(2));\n          TestUtil.assertValidQueryId(secretMetaData.getQueryID());\n          assertEquals(\n              secretMetaData.getQueryID(), resultSet.unwrap(SnowflakeResultSet.class).getQueryID());\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_rsmd\");\n      }\n    }\n  }\n\n  @Test\n  public void testOrderAndClosureFunctions() throws SQLException {\n    // Set up environment\n    String queryID = null;\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table test_rsmd(colA number(20, 5), colB string)\");\n      statement.execute(\"insert into test_rsmd values(1.00, 'str'),(2.00, 'str2')\");\n      try {\n        ResultSet resultSet =\n            statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(\"select * from test_rsmd\");\n\n        // test isFirst, isBeforeFirst\n        assertTrue(resultSet.isBeforeFirst(), \"should be before the first\");\n        assertFalse(resultSet.isFirst(), \"should not be the first\");\n        resultSet.next();\n        assertFalse(resultSet.isBeforeFirst(), \"should not be before the first\");\n        assertTrue(resultSet.isFirst(), \"should be the first\");\n\n        // test isClosed functions\n        queryID = resultSet.unwrap(SnowflakeResultSet.class).getQueryID();\n        assertFalse(resultSet.isClosed());\n        // close resultSet and test again\n        resultSet.close();\n        assertTrue(resultSet.isClosed());\n      } finally {\n        statement.execute(\"drop table if exists test_rsmd\");\n      }\n    }\n    try (Connection connection = getConnection()) {\n      ResultSet resultSet = connection.unwrap(SnowflakeConnection.class).createResultSet(queryID);\n      // test out isClosed, isLast, and isAfterLast\n      assertFalse(resultSet.isClosed());\n      resultSet.next();\n      resultSet.next();\n      // cursor should be on last row\n      assertTrue(resultSet.isLast());\n      resultSet.next();\n      // cursor is after last row\n      assertTrue(resultSet.isAfterLast());\n      resultSet.close();\n      // resultSet should be closed\n      assertTrue(resultSet.isClosed());\n    }\n  }\n\n  @Test\n  public void testWasNull() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        Clob emptyClob = connection.createClob();\n        emptyClob.setString(1, \"\");\n        statement.execute(\n            \"create or replace table test_null(colA number, colB string, colNull string, emptyClob string)\");\n        try (PreparedStatement prepst =\n            connection.prepareStatement(\"insert into test_null values (?, ?, ?, ?)\")) {\n          prepst.setNull(1, Types.INTEGER);\n          prepst.setString(2, \"hello\");\n          prepst.setString(3, null);\n          prepst.setClob(4, emptyClob);\n          prepst.execute();\n\n          try (ResultSet resultSet =\n              statement\n                  .unwrap(SnowflakeStatement.class)\n                  .executeAsyncQuery(\"select * from test_null\")) {\n            resultSet.next();\n            resultSet.getInt(1);\n            assertTrue(resultSet.wasNull()); // integer value is null\n            resultSet.getString(2);\n            assertFalse(resultSet.wasNull()); // string value is not null\n            assertNull(resultSet.getClob(3));\n            assertNull(resultSet.getClob(\"COLNULL\"));\n            assertEquals(\"\", resultSet.getClob(\"EMPTYCLOB\").toString());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_null\");\n      }\n    }\n  }\n\n  @Test\n  public void testGetMethods() throws Throwable {\n    String prepInsertString =\n        \"insert into test_get values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\";\n    int bigInt = Integer.MAX_VALUE;\n    long bigLong = Long.MAX_VALUE;\n    short bigShort = Short.MAX_VALUE;\n    String str = \"hello\";\n    double bigDouble = Double.MAX_VALUE;\n    float bigFloat = Float.MAX_VALUE;\n    byte[] bytes = {(byte) 0xAB, (byte) 0xCD, (byte) 0x12};\n    BigDecimal bigDecimal = new BigDecimal(\"10000000000\");\n    byte oneByte = (byte) 1;\n    Date date = new Date(44);\n    Time time = new Time(500);\n    Timestamp ts = new Timestamp(333);\n\n    Clob clob = connection.createClob();\n    clob.setString(1, \"hello world\");\n    try (Statement statement = connection.createStatement()) {\n      try {\n        // TODO structuredType - add to test when WRITE is ready - SNOW-1157904\n        statement.execute(\n            \"create or replace table test_get(colA integer, colB number, colC number, colD string, colE double, colF float, colG boolean, colH text, colI binary(3), colJ number(38,9), colK int, colL date, colM time, colN timestamp_ltz)\");\n\n        try (PreparedStatement prepStatement = connection.prepareStatement(prepInsertString)) {\n          prepStatement.setInt(1, bigInt);\n          prepStatement.setLong(2, bigLong);\n          prepStatement.setLong(3, bigShort);\n          prepStatement.setString(4, str);\n          prepStatement.setDouble(5, bigDouble);\n          prepStatement.setFloat(6, bigFloat);\n          prepStatement.setBoolean(7, true);\n          prepStatement.setClob(8, clob);\n          prepStatement.setBytes(9, bytes);\n          prepStatement.setBigDecimal(10, bigDecimal);\n          prepStatement.setByte(11, oneByte);\n          prepStatement.setDate(12, date);\n          prepStatement.setTime(13, time);\n          prepStatement.setTimestamp(14, ts);\n          prepStatement.execute();\n\n          try (ResultSet resultSet =\n              statement\n                  .unwrap(SnowflakeStatement.class)\n                  .executeAsyncQuery(\"select * from test_get\")) {\n            resultSet.next();\n            assertEquals(bigInt, resultSet.getInt(1));\n            assertEquals(bigInt, resultSet.getInt(\"COLA\"));\n            assertEquals(bigLong, resultSet.getLong(2));\n            assertEquals(bigLong, resultSet.getLong(\"COLB\"));\n            assertEquals(bigShort, resultSet.getShort(3));\n            assertEquals(bigShort, resultSet.getShort(\"COLC\"));\n            assertEquals(str, resultSet.getString(4));\n            assertEquals(str, resultSet.getString(\"COLD\"));\n            Reader reader = resultSet.getCharacterStream(\"COLD\");\n            char[] sample = new char[str.length()];\n\n            assertEquals(str.length(), reader.read(sample));\n            assertEquals(str.charAt(0), sample[0]);\n            assertEquals(str, new String(sample));\n\n            assertEquals(bigDouble, resultSet.getDouble(5), 0);\n            assertEquals(bigDouble, resultSet.getDouble(\"COLE\"), 0);\n            assertEquals(bigFloat, resultSet.getFloat(6), 0);\n            assertEquals(bigFloat, resultSet.getFloat(\"COLF\"), 0);\n            assertTrue(resultSet.getBoolean(7));\n            assertTrue(resultSet.getBoolean(\"COLG\"));\n            assertEquals(\"hello world\", resultSet.getClob(\"COLH\").toString());\n\n            // TODO: figure out why getBytes returns an offset.\n            // assertEquals(bytes, resultSet.getBytes(9));\n            // assertEquals(bytes, resultSet.getBytes(\"COLI\"));\n\n            DecimalFormat df = new DecimalFormat(\"#.00\");\n            assertEquals(df.format(bigDecimal), df.format(resultSet.getBigDecimal(10)));\n            assertEquals(df.format(bigDecimal), df.format(resultSet.getBigDecimal(\"COLJ\")));\n\n            assertEquals(oneByte, resultSet.getByte(11));\n            assertEquals(oneByte, resultSet.getByte(\"COLK\"));\n\n            SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd\");\n            assertEquals(sdf.format(date), sdf.format(resultSet.getDate(12)));\n            assertEquals(sdf.format(date), sdf.format(resultSet.getDate(\"COLL\")));\n            assertEquals(time, resultSet.getTime(13));\n            assertEquals(time, resultSet.getTime(\"COLM\"));\n            assertEquals(ts, resultSet.getTimestamp(14));\n            assertEquals(ts, resultSet.getTimestamp(\"COLN\"));\n\n            // test getObject\n            assertEquals(str, resultSet.getObject(4).toString());\n            assertEquals(str, resultSet.getObject(\"COLD\").toString());\n\n            // test getStatement method\n            assertEquals(statement, resultSet.getStatement());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists table_get\");\n      }\n    }\n  }\n\n  /**\n   * This is a corner case for if a user forgets to call one of these functions before attempting to\n   * fetch real data. An empty ResultSet is initially returned form executeAsyncQuery() but is\n   * replaced by a real ResultSet when next() or getMetaData() is called. If neither is called, user\n   * can try to get results from empty result set but exceptions are thrown whenever a column name\n   * cannot be found.\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testEmptyResultSet() throws SQLException {\n    try (Statement statement = connection.createStatement();\n        ResultSet rs =\n            statement\n                .unwrap(SnowflakeStatement.class)\n                .executeAsyncQuery(\"select * from empty_table\")) {\n      // if user never calls getMetadata() or next(), empty result set is used to get results.\n      // empty ResultSet returns all nulls, 0s, and empty values.\n      assertFalse(rs.isClosed());\n      assertEquals(0, rs.getInt(1));\n      SQLException e = assertThrows(SQLException.class, () -> rs.getInt(\"col1\"));\n      assertEquals(SqlState.UNDEFINED_COLUMN, e.getSQLState());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetAsyncLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Test AsyncResultSet */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetAsyncLatestIT extends BaseJDBCTest {\n  @Test\n  public void testAsyncResultSet() throws SQLException {\n    String queryID;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_rsmd(colA number(20, 5), colB string)\");\n        statement.execute(\"insert into test_rsmd values(1.00, 'str'),(2.00, 'str2')\");\n        String createTableSql = \"select * from test_rsmd\";\n        try (ResultSet rs =\n            statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(createTableSql)) {\n          queryID = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_rsmd\");\n      }\n    }\n    // Close and reopen connection\n\n    try (Connection connection = getConnection();\n        // open a new connection and create a result set\n        ResultSet resultSet =\n            connection.unwrap(SnowflakeConnection.class).createResultSet(queryID)) {\n      // Process result set\n      ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n      SnowflakeResultSetMetaData secretMetaData =\n          resultSetMetaData.unwrap(SnowflakeResultSetMetaData.class);\n      assertEquals(\n          secretMetaData.getQueryID(), resultSet.unwrap(SnowflakeResultSet.class).getQueryID());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetFeatureNotSupportedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.math.BigDecimal;\nimport java.sql.DatabaseMetaData;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.util.Collections;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetFeatureNotSupportedIT extends BaseJDBCWithSharedConnectionIT {\n  @Test\n  public void testQueryResultSetNotSupportedException() throws Throwable {\n    try (Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n      checkFeatureNotSupportedException(resultSet);\n    }\n  }\n\n  @Test\n  public void testMetadataResultSetNotSupportedException() throws Throwable {\n    DatabaseMetaData metaData = connection.getMetaData();\n    String database = connection.getCatalog();\n    String schema = connection.getSchema();\n\n    checkFeatureNotSupportedException(metaData.getCatalogs());\n    checkFeatureNotSupportedException(metaData.getSchemas());\n    checkFeatureNotSupportedException(metaData.getSchemas(database, null));\n    checkFeatureNotSupportedException(metaData.getTables(database, schema, null, null));\n    checkFeatureNotSupportedException(metaData.getColumns(database, schema, null, null));\n  }\n\n  private void checkFeatureNotSupportedException(ResultSet resultSet) throws SQLException {\n    expectFeatureNotSupportedException(() -> resultSet.getAsciiStream(1));\n    expectFeatureNotSupportedException(() -> resultSet.getBinaryStream(1));\n\n    expectFeatureNotSupportedException(() -> resultSet.getAsciiStream(\"col1\"));\n    expectFeatureNotSupportedException(() -> resultSet.getBinaryStream(\"col2\"));\n\n    expectFeatureNotSupportedException(resultSet::getCursorName);\n    expectFeatureNotSupportedException(resultSet::beforeFirst);\n    expectFeatureNotSupportedException(resultSet::afterLast);\n    expectFeatureNotSupportedException(resultSet::first);\n    expectFeatureNotSupportedException(resultSet::last);\n    expectFeatureNotSupportedException(resultSet::previous);\n\n    expectFeatureNotSupportedException(() -> resultSet.absolute(0));\n    expectFeatureNotSupportedException(() -> resultSet.relative(0));\n    expectFeatureNotSupportedException(() -> resultSet.relative(0));\n\n    expectFeatureNotSupportedException(resultSet::rowUpdated);\n    expectFeatureNotSupportedException(resultSet::rowInserted);\n    expectFeatureNotSupportedException(resultSet::rowDeleted);\n    expectFeatureNotSupportedException(resultSet::rowUpdated);\n\n    expectFeatureNotSupportedException(() -> resultSet.updateNull(1));\n    expectFeatureNotSupportedException(() -> resultSet.updateBoolean(1, true));\n    expectFeatureNotSupportedException(() -> resultSet.updateByte(1, (byte) 1));\n    expectFeatureNotSupportedException(() -> resultSet.updateShort(1, (short) 2));\n    expectFeatureNotSupportedException(() -> resultSet.updateInt(1, 3));\n    expectFeatureNotSupportedException(() -> resultSet.updateLong(1, 4L));\n    expectFeatureNotSupportedException(() -> resultSet.updateFloat(1, (float) 5.0));\n    expectFeatureNotSupportedException(() -> resultSet.updateDouble(1, 6.0));\n    expectFeatureNotSupportedException(() -> resultSet.updateBigDecimal(1, new BigDecimal(7)));\n    expectFeatureNotSupportedException(() -> resultSet.updateString(1, \"test1\"));\n    expectFeatureNotSupportedException(() -> resultSet.updateBytes(1, new byte[] {}));\n    expectFeatureNotSupportedException(() -> resultSet.updateDate(1, new Date(1)));\n    expectFeatureNotSupportedException(() -> resultSet.updateTime(1, new Time(0)));\n    expectFeatureNotSupportedException(() -> resultSet.updateTimestamp(1, new Timestamp(3)));\n    expectFeatureNotSupportedException(() -> resultSet.updateAsciiStream(1, null));\n    expectFeatureNotSupportedException(() -> resultSet.updateBinaryStream(1, null));\n    expectFeatureNotSupportedException(() -> resultSet.updateCharacterStream(1, null));\n    expectFeatureNotSupportedException(() -> resultSet.updateAsciiStream(1, null));\n    expectFeatureNotSupportedException(() -> resultSet.updateObject(1, new Object(), 124));\n    expectFeatureNotSupportedException(() -> resultSet.updateObject(1, new Object()));\n\n    expectFeatureNotSupportedException(() -> resultSet.updateNull(\"col1\"));\n    expectFeatureNotSupportedException(() -> resultSet.updateBoolean(\"col1\", true));\n    expectFeatureNotSupportedException(() -> resultSet.updateByte(\"col1\", (byte) 1));\n    expectFeatureNotSupportedException(() -> resultSet.updateShort(\"col1\", (short) 2));\n    expectFeatureNotSupportedException(() -> resultSet.updateInt(\"col1\", 3));\n    expectFeatureNotSupportedException(() -> resultSet.updateLong(\"col1\", 4L));\n    expectFeatureNotSupportedException(() -> resultSet.updateFloat(\"col1\", (float) 5.0));\n    expectFeatureNotSupportedException(() -> resultSet.updateDouble(\"col1\", 6.0));\n    expectFeatureNotSupportedException(() -> resultSet.updateBigDecimal(\"col1\", new BigDecimal(7)));\n    expectFeatureNotSupportedException(() -> resultSet.updateString(\"col1\", \"test1\"));\n    expectFeatureNotSupportedException(() -> resultSet.updateBytes(\"col1\", new byte[] {}));\n    expectFeatureNotSupportedException(() -> resultSet.updateDate(\"col1\", new Date(0)));\n    expectFeatureNotSupportedException(() -> resultSet.updateTime(\"col1\", new Time(0)));\n    expectFeatureNotSupportedException(() -> resultSet.updateTimestamp(\"col1\", new Timestamp(3)));\n    expectFeatureNotSupportedException(() -> resultSet.updateAsciiStream(\"col1\", null));\n    expectFeatureNotSupportedException(() -> resultSet.updateBinaryStream(\"col1\", null));\n    expectFeatureNotSupportedException(() -> resultSet.updateCharacterStream(\"col1\", null));\n    expectFeatureNotSupportedException(() -> resultSet.updateAsciiStream(\"col1\", null));\n    expectFeatureNotSupportedException(() -> resultSet.updateObject(\"col1\", new Object(), 124));\n    expectFeatureNotSupportedException(() -> resultSet.updateObject(\"col1\", new Object()));\n\n    expectFeatureNotSupportedException(resultSet::insertRow);\n    expectFeatureNotSupportedException(resultSet::updateRow);\n    expectFeatureNotSupportedException(resultSet::deleteRow);\n    expectFeatureNotSupportedException(resultSet::refreshRow);\n    expectFeatureNotSupportedException(resultSet::cancelRowUpdates);\n    expectFeatureNotSupportedException(resultSet::moveToInsertRow);\n    expectFeatureNotSupportedException(resultSet::moveToCurrentRow);\n\n    expectFeatureNotSupportedException(() -> resultSet.getObject(1, Collections.emptyMap()));\n    expectFeatureNotSupportedException(() -> resultSet.getRef(1));\n    expectFeatureNotSupportedException(() -> resultSet.getBlob(1));\n    expectFeatureNotSupportedException(() -> resultSet.getURL(1));\n    expectFeatureNotSupportedException(() -> resultSet.getRowId(1));\n    expectFeatureNotSupportedException(() -> resultSet.getNClob(1));\n    expectFeatureNotSupportedException(() -> resultSet.getSQLXML(1));\n    expectFeatureNotSupportedException(() -> resultSet.getNString(1));\n    expectFeatureNotSupportedException(() -> resultSet.getNCharacterStream(1));\n    expectFeatureNotSupportedException(() -> resultSet.getNClob(1));\n\n    expectFeatureNotSupportedException(() -> resultSet.updateRef(1, new FakeRef()));\n    expectFeatureNotSupportedException(() -> resultSet.updateBlob(1, new FakeBlob()));\n    expectFeatureNotSupportedException(() -> resultSet.updateClob(1, new SnowflakeClob()));\n    expectFeatureNotSupportedException(() -> resultSet.updateArray(1, new FakeArray()));\n    expectFeatureNotSupportedException(() -> resultSet.updateRowId(1, new FakeRowId()));\n    expectFeatureNotSupportedException(() -> resultSet.updateNString(1, \"testN\"));\n    expectFeatureNotSupportedException(() -> resultSet.updateNClob(1, new FakeNClob()));\n    expectFeatureNotSupportedException(() -> resultSet.updateSQLXML(1, new FakeSQLXML()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateNCharacterStream(1, new FakeReader(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateNCharacterStream(1, new FakeReader()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateAsciiStream(1, new FakeInputStream(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateAsciiStream(1, new FakeInputStream()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateBinaryStream(1, new FakeInputStream(), 100));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateBinaryStream(1, new FakeInputStream()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateCharacterStream(1, new FakeReader(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateCharacterStream(1, new FakeReader()));\n    expectFeatureNotSupportedException(() -> resultSet.updateBlob(1, new FakeInputStream(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateBlob(1, new FakeInputStream()));\n    expectFeatureNotSupportedException(() -> resultSet.updateClob(1, new FakeReader(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateClob(1, new FakeReader()));\n    expectFeatureNotSupportedException(() -> resultSet.updateNClob(1, new FakeReader(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateNClob(1, new FakeReader()));\n\n    expectFeatureNotSupportedException(() -> resultSet.getObject(\"col1\", Collections.emptyMap()));\n    expectFeatureNotSupportedException(() -> resultSet.getRef(\"col1\"));\n    expectFeatureNotSupportedException(() -> resultSet.getBlob(\"col1\"));\n    expectFeatureNotSupportedException(() -> resultSet.getArray(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getURL(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getRowId(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getNClob(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getSQLXML(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getNString(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getNCharacterStream(\"col2\"));\n    expectFeatureNotSupportedException(() -> resultSet.getNClob(\"col2\"));\n\n    expectFeatureNotSupportedException(() -> resultSet.updateRef(\"col2\", new FakeRef()));\n    expectFeatureNotSupportedException(() -> resultSet.updateBlob(\"col2\", new FakeBlob()));\n    expectFeatureNotSupportedException(() -> resultSet.updateClob(\"col2\", new SnowflakeClob()));\n    expectFeatureNotSupportedException(() -> resultSet.updateArray(\"col2\", new FakeArray()));\n    expectFeatureNotSupportedException(() -> resultSet.updateRowId(\"col2\", new FakeRowId()));\n    expectFeatureNotSupportedException(() -> resultSet.updateNString(\"col2\", \"testN\"));\n    expectFeatureNotSupportedException(() -> resultSet.updateNClob(\"col2\", new FakeNClob()));\n    expectFeatureNotSupportedException(() -> resultSet.updateSQLXML(\"col2\", new FakeSQLXML()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateNCharacterStream(\"col2\", new FakeReader(), 100));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateNCharacterStream(\"col2\", new FakeReader()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateAsciiStream(\"col2\", new FakeInputStream(), 100));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateAsciiStream(\"col2\", new FakeInputStream()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateBinaryStream(\"col2\", new FakeInputStream(), 100));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateBinaryStream(\"col2\", new FakeInputStream()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateCharacterStream(\"col2\", new FakeReader(), 100));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateCharacterStream(\"col2\", new FakeReader()));\n    expectFeatureNotSupportedException(\n        () -> resultSet.updateBlob(\"col2\", new FakeInputStream(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateBlob(\"col2\", new FakeInputStream()));\n    expectFeatureNotSupportedException(() -> resultSet.updateClob(\"col2\", new FakeReader(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateClob(\"col2\", new FakeReader()));\n    expectFeatureNotSupportedException(() -> resultSet.updateNClob(\"col2\", new FakeReader(), 100));\n    expectFeatureNotSupportedException(() -> resultSet.updateNClob(\"col2\", new FakeReader()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetFormatType.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\npublic enum ResultSetFormatType {\n  JSON(\"JSON\"),\n  ARROW_WITH_JSON_STRUCTURED_TYPES(\"ARROW\"),\n  NATIVE_ARROW(\"ARROW\");\n  public final String sessionParameterTypeValue;\n\n  ResultSetFormatType(String sessionParameterTypeValue) {\n    this.sessionParameterTypeValue = sessionParameterTypeValue;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.math.BigDecimal;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.Properties;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/** Test ResultSet */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetIT extends ResultSet0IT {\n  private final String selectAllSQL = \"select * from \" + uniqueTestRsTableName;\n\n  private static final byte[] byteArrayTestCase1 = new byte[0];\n  private static final byte[] byteArrayTestCase2 = {(byte) 0xAB, (byte) 0xCD, (byte) 0x12};\n  private static final byte[] byteArrayTestCase3 = {\n    (byte) 0x00, (byte) 0xFF, (byte) 0x42, (byte) 0x01\n  };\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testFindColumn(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat);\n        ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n      assertEquals(1, resultSet.findColumn(\"COLA\"));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetColumnClassNameForBinary(String queryResultFormat) throws Throwable {\n    try (Statement statement = createStatement(queryResultFormat); ) {\n      try {\n        statement.execute(\"create or replace table bintable (b binary)\");\n        statement.execute(\"insert into bintable values ('00f1f2')\");\n        try (ResultSet resultSet = statement.executeQuery(\"select * from bintable\")) {\n          ResultSetMetaData metaData = resultSet.getMetaData();\n          assertEquals(SnowflakeTypeHelper.BINARY_CLASS_NAME, metaData.getColumnClassName(1));\n          assertTrue(resultSet.next());\n          Class<?> klass = Class.forName(SnowflakeTypeHelper.BINARY_CLASS_NAME);\n          Object ret0 = resultSet.getObject(1);\n          assertEquals(ret0.getClass(), klass);\n          byte[] ret = (byte[]) ret0;\n          assertEquals(3, ret.length);\n          assertEquals(ret[0], (byte) 0);\n          assertEquals(ret[1], (byte) -15);\n          assertEquals(ret[2], (byte) -14);\n        }\n      } finally {\n        statement.execute(\"drop table if exists bintable\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetMethod(String queryResultFormat) throws Throwable {\n    String prepInsertString = \"insert into test_get values(?, ?, ?, ?, ?, ?, ?, ?)\";\n    int bigInt = Integer.MAX_VALUE;\n    long bigLong = Long.MAX_VALUE;\n    short bigShort = Short.MAX_VALUE;\n    String str = \"hello\";\n    double bigDouble = Double.MAX_VALUE;\n    float bigFloat = Float.MAX_VALUE;\n\n    Clob clob = connection.createClob();\n    clob.setString(1, \"hello world\");\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\n            \"create or replace table test_get(colA integer, colB number, colC number, \"\n                + \"colD string, colE double, colF float, colG boolean, colH text)\");\n\n        try (PreparedStatement prepStatement = connection.prepareStatement(prepInsertString)) {\n          prepStatement.setInt(1, bigInt);\n          prepStatement.setLong(2, bigLong);\n          prepStatement.setLong(3, bigShort);\n          prepStatement.setString(4, str);\n          prepStatement.setDouble(5, bigDouble);\n          prepStatement.setFloat(6, bigFloat);\n          prepStatement.setBoolean(7, true);\n          prepStatement.setClob(8, clob);\n          prepStatement.execute();\n\n          statement.execute(\"select * from test_get\");\n          try (ResultSet resultSet = statement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(bigInt, resultSet.getInt(1));\n            assertEquals(bigInt, resultSet.getInt(\"COLA\"));\n            assertEquals(bigLong, resultSet.getLong(2));\n            assertEquals(bigLong, resultSet.getLong(\"COLB\"));\n            assertEquals(bigShort, resultSet.getShort(3));\n            assertEquals(bigShort, resultSet.getShort(\"COLC\"));\n            assertEquals(str, resultSet.getString(4));\n            assertEquals(str, resultSet.getString(\"COLD\"));\n            Reader reader = resultSet.getCharacterStream(\"COLD\");\n            char[] sample = new char[str.length()];\n\n            assertEquals(str.length(), reader.read(sample));\n            assertEquals(str.charAt(0), sample[0]);\n            assertEquals(str, new String(sample));\n\n            // assertEquals(bigDouble, resultSet.getDouble(5), 0);\n            // assertEquals(bigDouble, resultSet.getDouble(\"COLE\"), 0);\n            assertEquals(bigFloat, resultSet.getFloat(6), 0);\n            assertEquals(bigFloat, resultSet.getFloat(\"COLF\"), 0);\n            assertTrue(resultSet.getBoolean(7));\n            assertTrue(resultSet.getBoolean(\"COLG\"));\n            assertEquals(\"hello world\", resultSet.getClob(\"COLH\").toString());\n\n            // test getStatement method\n            assertEquals(statement, resultSet.getStatement());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists table_get\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetObjectOnDatabaseMetadataResultSet(String queryResultFormat)\n      throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {}\n    DatabaseMetaData databaseMetaData = connection.getMetaData();\n    try (ResultSet resultSet = databaseMetaData.getTypeInfo()) {\n      assertTrue(resultSet.next());\n      // SNOW-21375 \"NULLABLE\" Column is a SMALLINT TYPE\n      assertEquals(DatabaseMetaData.typeNullable, resultSet.getObject(\"NULLABLE\"));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetShort(String queryResultFormat) throws SQLException {\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      // assert that 0 is returned for null values for every type of value\n      for (int i = 1; i < 13; i++) {\n        assertEquals(0, resultSet.getShort(i));\n      }\n\n      assertTrue(resultSet.next());\n      assertEquals(2, resultSet.getShort(1));\n      assertEquals(5, resultSet.getShort(2));\n      assertEquals(3, resultSet.getShort(3));\n      assertEquals(1, resultSet.getShort(4));\n      assertEquals(1, resultSet.getShort(5));\n      assertEquals(1, resultSet.getShort(6));\n      assertEquals(9126, resultSet.getShort(7));\n\n      for (int i = 8; i < 13; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getShort(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n      assertTrue(resultSet.next());\n      // certain column types can only have certain values when called by getShort() or else a\n      // SQLexception is thrown.\n      // These column types are varchar, char, and float.\n\n      for (int i = 5; i < 7; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getShort(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetInt(String queryResultFormat) throws SQLException {\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      // assert that 0 is returned for null values for every type of value\n      for (int i = 1; i < 13; i++) {\n        assertEquals(0, resultSet.getInt(i));\n      }\n\n      assertTrue(resultSet.next());\n      assertEquals(2, resultSet.getInt(1));\n      assertEquals(5, resultSet.getInt(2));\n      assertEquals(3, resultSet.getInt(3));\n      assertEquals(1, resultSet.getInt(4));\n      assertEquals(1, resultSet.getInt(5));\n      assertEquals(1, resultSet.getInt(6));\n      assertEquals(9126, resultSet.getInt(7));\n\n      for (int i = 8; i < 13; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getInt(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n      assertTrue(resultSet.next());\n      // certain column types can only have certain values when called by getInt() or else a\n      // SQLException is thrown.\n      // These column types are varchar, char, and float.\n      for (int i = 5; i < 7; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getInt(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetLong(String queryResultFormat) throws SQLException {\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      // assert that 0 is returned for null values for every type of value\n      for (int i = 1; i < 13; i++) {\n        assertEquals(0, resultSet.getLong(i));\n      }\n\n      assertTrue(resultSet.next());\n      assertEquals(2, resultSet.getLong(1));\n      assertEquals(5, resultSet.getLong(2));\n      assertEquals(3, resultSet.getLong(3));\n      assertEquals(1, resultSet.getLong(4));\n      assertEquals(1, resultSet.getLong(5));\n      assertEquals(1, resultSet.getLong(6));\n      assertEquals(9126, resultSet.getLong(7));\n\n      for (int i = 8; i < 13; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getLong(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n      assertTrue(resultSet.next());\n      // certain column types can only have certain values when called by getLong() or else a\n      // SQLexception is thrown.\n      // These column types are varchar, char, and float.\n      for (int i = 5; i < 7; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getLong(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetFloat(String queryResultFormat) throws SQLException {\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      // assert that 0 is returned for null values for every type of value\n      for (int i = 1; i < 13; i++) {\n        assertEquals(0, resultSet.getFloat(i), .1);\n      }\n\n      assertTrue(resultSet.next());\n      assertEquals(2, resultSet.getFloat(1), .1);\n      assertEquals(5, resultSet.getFloat(2), .1);\n      assertEquals(3.5, resultSet.getFloat(3), .1);\n      assertEquals(1, resultSet.getFloat(4), .1);\n      assertEquals(1, resultSet.getFloat(5), .1);\n      assertEquals(1, resultSet.getFloat(6), .1);\n      assertEquals(9126, resultSet.getFloat(7), .1);\n\n      for (int i = 8; i < 13; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getFloat(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n      assertTrue(resultSet.next());\n      // certain column types can only have certain values when called by getFloat() or else a\n      // SQLexception is thrown.\n      // These column types are varchar and char.\n      for (int i = 5; i < 7; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getFloat(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetDouble(String queryResultFormat) throws SQLException {\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      // assert that 0 is returned for null values for every type of value\n      for (int i = 1; i < 13; i++) {\n        assertEquals(0, resultSet.getDouble(i), .1);\n      }\n\n      assertTrue(resultSet.next());\n      assertEquals(2, resultSet.getDouble(1), .1);\n      assertEquals(5, resultSet.getDouble(2), .1);\n      assertEquals(3.5, resultSet.getDouble(3), .1);\n      assertEquals(1, resultSet.getDouble(4), .1);\n      assertEquals(1, resultSet.getDouble(5), .1);\n      assertEquals(1, resultSet.getDouble(6), .1);\n      assertEquals(9126, resultSet.getDouble(7), .1);\n\n      for (int i = 8; i < 13; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getDouble(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n      assertTrue(resultSet.next());\n      // certain column types can only have certain values when called by getDouble() or else a\n      // SQLexception is thrown.\n      // These column types are varchar and char.\n      for (int i = 5; i < 7; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getDouble(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetBigDecimal(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"create or replace table test_get(colA number(38,9))\");\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"insert into test_get values(?)\")) {\n        BigDecimal bigDecimal1 = new BigDecimal(\"10000000000\");\n        preparedStatement.setBigDecimal(1, bigDecimal1);\n        preparedStatement.executeUpdate();\n\n        BigDecimal bigDecimal2 = new BigDecimal(\"100000000.123456789\");\n        preparedStatement.setBigDecimal(1, bigDecimal2);\n        preparedStatement.execute();\n\n        statement.execute(\"select * from test_get order by 1\");\n        try (ResultSet resultSet = statement.getResultSet()) {\n          assertTrue(resultSet.next());\n          assertEquals(bigDecimal2, resultSet.getBigDecimal(1));\n          assertEquals(bigDecimal2, resultSet.getBigDecimal(\"COLA\"));\n        }\n      }\n      statement.execute(\"drop table if exists test_get\");\n    }\n\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      for (int i = 1; i < 13; i++) {\n        assertNull(resultSet.getBigDecimal(i));\n      }\n      assertTrue(resultSet.next());\n      assertEquals(new BigDecimal(2), resultSet.getBigDecimal(1));\n      assertEquals(new BigDecimal(5), resultSet.getBigDecimal(2));\n      assertEquals(new BigDecimal(3.5), resultSet.getBigDecimal(3));\n      assertEquals(new BigDecimal(1), resultSet.getBigDecimal(4));\n      assertEquals(new BigDecimal(1), resultSet.getBigDecimal(5));\n      assertEquals(new BigDecimal(1), resultSet.getBigDecimal(6));\n      assertEquals(new BigDecimal(9126), resultSet.getBigDecimal(7));\n      for (int i = 8; i < 13; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(\n                SQLException.class, () -> resultSet.getBigDecimal(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n      assertTrue(resultSet.next());\n      for (int i = 5; i < 7; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(\n                SQLException.class, () -> resultSet.getBigDecimal(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetBigDecimalNegative(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\"create or replace table test_dec(colA time)\");\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into test_dec values(?)\")) {\n          java.sql.Time time = new java.sql.Time(System.currentTimeMillis());\n          preparedStatement.setTime(1, time);\n          preparedStatement.executeUpdate();\n\n          statement.execute(\"select * from test_dec order by 1\");\n          try (ResultSet resultSet = statement.getResultSet(); ) {\n            assertTrue(resultSet.next());\n            SQLException ex =\n                assertThrows(SQLException.class, () -> resultSet.getBigDecimal(2, 38));\n            assertEquals(ErrorCode.COLUMN_DOES_NOT_EXIST.getMessageCode(), ex.getErrorCode());\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_dec\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testCursorPosition(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(selectAllSQL);\n      try (ResultSet resultSet = statement.getResultSet()) {\n        assertTrue(resultSet.next());\n        assertTrue(resultSet.isFirst());\n        assertEquals(1, resultSet.getRow());\n        assertTrue(resultSet.next());\n        assertFalse(resultSet.isFirst());\n        assertEquals(2, resultSet.getRow());\n        assertFalse(resultSet.isLast());\n        assertTrue(resultSet.next());\n        assertEquals(3, resultSet.getRow());\n        assertTrue(resultSet.isLast());\n        assertFalse(resultSet.next());\n        assertTrue(resultSet.isAfterLast());\n      }\n    }\n  }\n\n  /**\n   * Gets bytes in HEX form.\n   *\n   * @throws SQLException arises if any exception occurs.\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetBytes(String queryResultFormat) throws SQLException {\n    Properties props = new Properties();\n    try (Connection connection = init(props, queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        ingestBinaryTestData(connection);\n\n        // Get results in hex format (default).\n        try (ResultSet resultSet = statement.executeQuery(\"select * from bin\")) {\n          assertTrue(resultSet.next());\n          assertArrayEquals(byteArrayTestCase1, resultSet.getBytes(1));\n          assertEquals(\"\", resultSet.getString(1));\n          assertTrue(resultSet.next());\n          assertArrayEquals(byteArrayTestCase2, resultSet.getBytes(1));\n          assertEquals(\"ABCD12\", resultSet.getString(1));\n          assertTrue(resultSet.next());\n          assertArrayEquals(byteArrayTestCase3, resultSet.getBytes(1));\n          assertEquals(\"00FF4201\", resultSet.getString(1));\n        }\n      } finally {\n        statement.execute(\"drop table if exists bin\");\n      }\n    }\n  }\n\n  /**\n   * Ingests the byte test data\n   *\n   * @param connection Connection\n   * @throws SQLException arises if any exception occurs\n   */\n  private void ingestBinaryTestData(Connection connection) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table bin (b Binary)\");\n      try (PreparedStatement prepStatement =\n          connection.prepareStatement(\"insert into bin values (?), (?), (?)\")) {\n        prepStatement.setBytes(1, byteArrayTestCase1);\n        prepStatement.setBytes(2, byteArrayTestCase2);\n        prepStatement.setBytes(3, byteArrayTestCase3);\n        prepStatement.execute();\n      }\n    }\n  }\n\n  /**\n   * Get bytes in Base64\n   *\n   * @throws Exception arises if any error occurs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetBytesInBase64(String queryResultFormat) throws Exception {\n    Properties props = new Properties();\n    props.setProperty(\"binary_output_format\", \"BAse64\");\n    try (Connection connection = init(props, queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        ingestBinaryTestData(connection);\n\n        try (ResultSet resultSet = statement.executeQuery(\"select * from bin\")) {\n          assertTrue(resultSet.next());\n          assertArrayEquals(byteArrayTestCase1, resultSet.getBytes(1));\n          assertEquals(\"\", resultSet.getString(1));\n          assertTrue(resultSet.next());\n          assertArrayEquals(byteArrayTestCase2, resultSet.getBytes(1));\n          assertEquals(\"q80S\", resultSet.getString(1));\n          assertTrue(resultSet.next());\n          assertArrayEquals(byteArrayTestCase3, resultSet.getBytes(1));\n          assertEquals(\"AP9CAQ==\", resultSet.getString(1));\n        }\n      } finally {\n        statement.execute(\"drop table if exists bin\");\n      }\n    }\n  }\n\n  // SNOW-31647\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testColumnMetaWithZeroPrecision(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\n            \"create or replace table testColDecimal(cola number(38, 0), \" + \"colb number(17, 5))\");\n\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testColDecimal\")) {\n          ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n          assertThat(resultSetMetaData.getColumnType(1), is(Types.BIGINT));\n          assertThat(resultSetMetaData.getColumnType(2), is(Types.DECIMAL));\n          assertThat(resultSetMetaData.isSigned(1), is(true));\n          assertThat(resultSetMetaData.isSigned(2), is(true));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testColDecimal\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetObjectOnFixedView(String queryResultFormat) throws Exception {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\n            \"create or replace table testFixedView\"\n                + \"(C1 STRING NOT NULL COMMENT 'JDBC', \"\n                + \"C2 STRING, C3 STRING, C4 STRING, C5 STRING, C6 STRING, \"\n                + \"C7 STRING, C8 STRING, C9 STRING) \"\n                + \"stage_file_format = (field_delimiter='|' \"\n                + \"error_on_column_count_mismatch=false)\");\n\n        // put files\n        assertTrue(\n            statement.execute(\n                \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @%testFixedView\"),\n            \"Failed to put a file\");\n\n        try (ResultSet resultSet =\n            statement.executeQuery(\n                \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE_2) + \" @%testFixedView\")) {\n\n          ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n          while (resultSet.next()) {\n            for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {\n              assertNotNull(resultSet.getObject(i + 1));\n            }\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists testFixedView\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testGetColumnDisplaySizeAndPrecision(String queryResultFormat) throws SQLException {\n    ResultSetMetaData resultSetMetaData = null;\n    try (Statement statement = createStatement(queryResultFormat)) {\n\n      try (ResultSet resultSet = statement.executeQuery(\"select cast(1 as char)\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(1, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(1, resultSetMetaData.getPrecision(1));\n      }\n\n      try (ResultSet resultSet = statement.executeQuery(\"select cast(1 as number(38, 0))\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(39, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(38, resultSetMetaData.getPrecision(1));\n      }\n\n      try (ResultSet resultSet = statement.executeQuery(\"select cast(1 as decimal(25, 15))\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(27, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(25, resultSetMetaData.getPrecision(1));\n      }\n\n      try (ResultSet resultSet = statement.executeQuery(\"select cast(1 as string)\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(1, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(1, resultSetMetaData.getPrecision(1));\n      }\n\n      try (ResultSet resultSet = statement.executeQuery(\"select cast(1 as string(30))\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(1, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(1, resultSetMetaData.getPrecision(1));\n      }\n\n      try (ResultSet resultSet =\n          statement.executeQuery(\"select to_date('2016-12-13', 'YYYY-MM-DD')\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(10, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(10, resultSetMetaData.getPrecision(1));\n      }\n\n      try (ResultSet resultSet =\n          statement.executeQuery(\"select to_time('12:34:56', 'HH24:MI:SS')\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        assertEquals(8, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(8, resultSetMetaData.getPrecision(1));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetBoolean(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"create or replace table testBoolean(cola boolean)\");\n      statement.execute(\"insert into testBoolean values(false)\");\n      try (ResultSet resultSet = statement.executeQuery(\"select * from testBoolean\")) {\n        assertTrue(resultSet.next());\n        assertFalse(resultSet.getBoolean(1));\n      }\n      statement.execute(\"insert into testBoolean values(true)\");\n      try (ResultSet resultSet = statement.executeQuery(\"select * from testBoolean\")) {\n        assertTrue(resultSet.next());\n        assertFalse(resultSet.getBoolean(1));\n        assertTrue(resultSet.next());\n        assertTrue(resultSet.getBoolean(1));\n      }\n      statement.execute(\"drop table if exists testBoolean\");\n\n      statement.execute(\n          \"create or replace table test_types(c1 number, c2 integer,  c3 varchar, c4 char, \"\n              + \"c5 boolean, c6 float, c7 binary, c8 date, c9 datetime, c10 time, c11 timestamp_ltz, \"\n              + \"c12 timestamp_tz)\");\n      statement.execute(\n          \"insert into test_types values (null, null, null, null, null, null, null, null, null, null, \"\n              + \"null, null)\");\n      statement.execute(\n          \"insert into test_types (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) values(1, 1, '1',\"\n              + \"'1', true, 1.0, '48454C4C4F', '1994-12-27', \"\n              + \"'1994-12-27 05:05:05', '05:05:05', '1994-12-27 05:05:05 +00:05', '1994-12-27 05:05:05')\");\n      statement.execute(\"insert into test_types (c1, c2, c3, c4) values(2, 3, '4', '5')\");\n      try (ResultSet resultSet = statement.executeQuery(\"select * from test_types\")) {\n\n        assertTrue(resultSet.next());\n        // assert that getBoolean returns false for null values\n        for (int i = 1; i < 13; i++) {\n          assertFalse(resultSet.getBoolean(i));\n        }\n        // do the other columns that are out of order\n        // go to next row of result set column\n        assertTrue(resultSet.next());\n        // assert that getBoolean returns true for values that equal 1\n        assertTrue(resultSet.getBoolean(1));\n        assertTrue(resultSet.getBoolean(2));\n        assertTrue(resultSet.getBoolean(3));\n        assertTrue(resultSet.getBoolean(4));\n        assertTrue(resultSet.getBoolean(5));\n        for (int i = 6; i < 13; i++) {\n          int finalI = i;\n          SQLException ex =\n              assertThrows(\n                  SQLException.class, () -> resultSet.getBoolean(finalI), \"Failing on \" + i);\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n        }\n\n        assertTrue(resultSet.next());\n        for (int i = 1; i < 5; i++) {\n          int finalI = i;\n          SQLException ex =\n              assertThrows(\n                  SQLException.class, () -> resultSet.getBoolean(finalI), \"Failing on \" + i);\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetClob(String queryResultFormat) throws Throwable {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"create or replace table testClob(cola text)\");\n      statement.execute(\"insert into testClob values('hello world')\");\n      statement.execute(\"insert into testClob values('hello world1')\");\n      statement.execute(\"insert into testClob values('hello world2')\");\n      statement.execute(\"insert into testClob values('hello world3')\");\n      try (ResultSet resultSet = statement.executeQuery(\"select * from testClob\")) {\n        assertTrue(resultSet.next());\n        // test reading Clob\n        char[] chars = new char[100];\n        Reader reader = resultSet.getClob(1).getCharacterStream();\n        int charRead;\n        charRead = reader.read(chars, 0, chars.length);\n        assertEquals(charRead, 11);\n        assertEquals(\"hello world\", resultSet.getClob(1).toString());\n\n        // test reading truncated clob\n        assertTrue(resultSet.next());\n        Clob clob = resultSet.getClob(1);\n        assertEquals(clob.length(), 12);\n        clob.truncate(5);\n        reader = clob.getCharacterStream();\n\n        charRead = reader.read(chars, 0, chars.length);\n        assertEquals(charRead, 5);\n\n        // read from input stream\n        assertTrue(resultSet.next());\n        final InputStream input = resultSet.getClob(1).getAsciiStream();\n\n        Reader in = new InputStreamReader(input, StandardCharsets.UTF_8);\n        charRead = in.read(chars, 0, chars.length);\n        assertEquals(charRead, 12);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testFetchOnClosedResultSet(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      ResultSet resultSet = statement.executeQuery(selectAllSQL);\n      assertFalse(resultSet.isClosed());\n      resultSet.close();\n      assertTrue(resultSet.isClosed());\n      assertFalse(resultSet.next());\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testReleaseDownloaderCurrentMemoryUsage(String queryResultFormat)\n      throws SQLException {\n    final long initialMemoryUsage = SnowflakeChunkDownloader.getCurrentMemoryUsage();\n\n    try (Statement statement = createStatement(queryResultFormat)) {\n\n      statement.executeQuery(\n          \"select current_date(), true,2345234, 2343.0, 'testrgint\\\\n\\\\t' from table(generator(rowcount=>1000000))\");\n\n      assertThat(\n          \"hold memory usage for the resultSet before close\",\n          SnowflakeChunkDownloader.getCurrentMemoryUsage() - initialMemoryUsage >= 0);\n    }\n    assertThat(\n        \"closing statement didn't release memory allocated for result\",\n        SnowflakeChunkDownloader.getCurrentMemoryUsage(),\n        equalTo(initialMemoryUsage));\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testResultColumnSearchCaseSensitiveOld(String queryResultFormat) throws Exception {\n    subTestResultColumnSearchCaseSensitive(\"JDBC_RS_COLUMN_CASE_INSENSITIVE\", queryResultFormat);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testResultColumnSearchCaseSensitive(String queryResultFormat) throws Exception {\n    subTestResultColumnSearchCaseSensitive(\n        \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\", queryResultFormat);\n  }\n\n  private void subTestResultColumnSearchCaseSensitive(\n      String parameterName, String queryResultFormat) throws Exception {\n    Properties prop = new Properties();\n    prop.put(\"tracing\", \"FINEST\");\n    try (Connection connection = init(prop, queryResultFormat);\n        Statement statement = connection.createStatement()) {\n\n      try (ResultSet resultSet = statement.executeQuery(\"select 1 AS TESTCOL\")) {\n\n        assertTrue(resultSet.next());\n        assertEquals(\"1\", resultSet.getString(\"TESTCOL\"));\n        assertEquals(\"1\", resultSet.getString(\"TESTCOL\"));\n        SQLException e = assertThrows(SQLException.class, () -> resultSet.getString(\"testcol\"));\n        assertEquals(\"Column not found: testcol\", e.getMessage());\n      }\n      // try to do case-insensitive search\n      statement.executeQuery(String.format(\"alter session set %s=true\", parameterName));\n\n      try (ResultSet resultSet = statement.executeQuery(\"select 1 AS TESTCOL\")) {\n        assertTrue(resultSet.next());\n\n        // get twice so that the code path can hit the place where\n        // we use cached key pair (columnName, index)\n        assertEquals(\"1\", resultSet.getString(\"TESTCOL\"));\n        assertEquals(\"1\", resultSet.getString(\"TESTCOL\"));\n        assertEquals(\"1\", resultSet.getString(\"testcol\"));\n        assertEquals(\"1\", resultSet.getString(\"testcol\"));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testInvalidColumnIndex(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat);\n        ResultSet resultSet = statement.executeQuery(selectAllSQL)) {\n\n      assertTrue(resultSet.next());\n      SQLException e = assertThrows(SQLException.class, () -> resultSet.getString(0));\n      assertEquals(ErrorCode.COLUMN_DOES_NOT_EXIST.getMessageCode(), e.getErrorCode());\n      e = assertThrows(SQLException.class, () -> resultSet.getString(2));\n      assertEquals(ErrorCode.COLUMN_DOES_NOT_EXIST.getMessageCode(), e.getErrorCode());\n    }\n  }\n\n  /** SNOW-28882: wasNull was not set properly */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testWasNull(String queryResultFormat) throws Exception {\n    try (ResultSet ret =\n        createStatement(queryResultFormat)\n            .executeQuery(\n                \"select cast(1/nullif(0,0) as double),\"\n                    + \"cast(1/nullif(0,0) as int), 100, \"\n                    + \"cast(1/nullif(0,0) as number(8,2))\")) {\n      assertTrue(ret.next());\n      assertThat(\"Double value cannot be null\", ret.getDouble(1), equalTo(0.0));\n      assertThat(\"wasNull should be true\", ret.wasNull());\n      assertThat(\"Integer value cannot be null\", ret.getInt(2), equalTo(0));\n      assertThat(\"wasNull should be true\", ret.wasNull());\n      assertThat(\"Non null column\", ret.getInt(3), equalTo(100));\n      assertThat(\"wasNull should be false\", !ret.wasNull());\n      assertThat(\"BigDecimal value must be null\", ret.getBigDecimal(4), nullValue());\n      assertThat(\"wasNull should be true\", ret.wasNull());\n    }\n  }\n\n  /** SNOW-28390 */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testParseInfAndNaNNumber(String queryResultFormat) throws Exception {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try (ResultSet ret = statement.executeQuery(\"select to_double('inf'), to_double('-inf')\")) {\n        assertTrue(ret.next());\n        assertThat(\"Positive Infinite Number\", ret.getDouble(1), equalTo(Double.POSITIVE_INFINITY));\n        assertThat(\"Negative Infinite Number\", ret.getDouble(2), equalTo(Double.NEGATIVE_INFINITY));\n        assertThat(\"Positive Infinite Number\", ret.getFloat(1), equalTo(Float.POSITIVE_INFINITY));\n        assertThat(\"Negative Infinite Number\", ret.getFloat(2), equalTo(Float.NEGATIVE_INFINITY));\n      }\n      try (ResultSet ret = statement.executeQuery(\"select to_double('nan')\")) {\n        assertTrue(ret.next());\n        assertThat(\"Parse NaN\", ret.getDouble(1), equalTo(Double.NaN));\n        assertThat(\"Parse NaN\", ret.getFloat(1), equalTo(Float.NaN));\n      }\n    }\n  }\n\n  /** SNOW-33227 */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testTreatDecimalAsInt(String queryResultFormat) throws Exception {\n    ResultSetMetaData metaData;\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try (ResultSet ret = statement.executeQuery(\"select 1\")) {\n\n        metaData = ret.getMetaData();\n        assertThat(metaData.getColumnType(1), equalTo(Types.BIGINT));\n      }\n      statement.execute(\"alter session set jdbc_treat_decimal_as_int = false\");\n\n      try (ResultSet ret = statement.executeQuery(\"select 1\")) {\n        metaData = ret.getMetaData();\n        assertThat(metaData.getColumnType(1), equalTo(Types.DECIMAL));\n      }\n      statement.execute(\"alter session set jdbc_treat_decimal_as_int = true\");\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testIsLast(String queryResultFormat) throws Exception {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try (ResultSet ret = statement.executeQuery(\"select * from \" + uniqueTableName)) {\n        assertTrue(ret.isBeforeFirst(), \"should be before the first\");\n        assertFalse(ret.isFirst(), \"should not be the first\");\n\n        assertTrue(ret.next());\n\n        assertFalse(ret.isBeforeFirst(), \"should not be before the first\");\n        assertTrue(ret.isFirst(), \"should be the first\");\n\n        int cnt = 0;\n        while (ret.next()) {\n          cnt++;\n          if (cnt == 72) {\n            assertTrue(ret.isLast(), \"should be the last\");\n            assertFalse(ret.isAfterLast(), \"should not be after the last\");\n          }\n        }\n        assertEquals(72, cnt);\n\n        assertFalse(ret.next());\n\n        assertFalse(ret.isLast(), \"should not be the last\");\n        assertTrue(ret.isAfterLast(), \"should be afterthe last\");\n      }\n      // PUT one file\n      try (ResultSet ret =\n          statement.executeQuery(\n              \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @~\")) {\n\n        assertTrue(ret.isBeforeFirst(), \"should be before the first\");\n        assertFalse(ret.isFirst(), \"should not be the first\");\n\n        assertTrue(ret.next());\n\n        assertFalse(ret.isBeforeFirst(), \"should not be before the first\");\n        assertTrue(ret.isFirst(), \"should be the first\");\n\n        assertTrue(ret.isLast(), \"should be the last\");\n        assertFalse(ret.isAfterLast(), \"should not be after the last\");\n\n        assertFalse(ret.next());\n\n        assertFalse(ret.isLast(), \"should not be the last\");\n        assertTrue(ret.isAfterLast(), \"should be after the last\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testUpdateCountOnCopyCmd(String queryResultFormat) throws Exception {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\"create or replace table testcopy(cola string)\");\n\n        // stage table has no file. Should return 0.\n        int rowCount = statement.executeUpdate(\"copy into testcopy\");\n        assertThat(rowCount, is(0));\n\n        // copy one file into table stage\n        statement.execute(\"copy into @%testcopy from (select 'test_string')\");\n        rowCount = statement.executeUpdate(\"copy into testcopy\");\n        assertThat(rowCount, is(1));\n      } finally {\n        // cleanup\n        statement.execute(\"drop table if exists testcopy\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetTimeNullTimestampAndTimestampNullTime(String queryResultFormat)\n      throws Throwable {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\"create or replace table testnullts(c1 timestamp, c2 time)\");\n        statement.execute(\"insert into testnullts(c1, c2) values(null, null)\");\n        try (ResultSet rs = statement.executeQuery(\"select * from testnullts\")) {\n          assertTrue(rs.next(), \"should return result\");\n          assertNull(rs.getTime(1), \"return value must be null\");\n          assertNull(rs.getTimestamp(2), \"return value must be null\");\n        }\n      } finally {\n        statement.execute(\"drop table if exists testnullts\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testNextNegative(String queryResultFormat) throws SQLException {\n    try (ResultSet rs = createStatement(queryResultFormat).executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      System.setProperty(\"snowflake.enable_incident_test2\", \"true\");\n      SQLException ex = assertThrows(SQLException.class, rs::next);\n      assertEquals(ErrorCode.MAX_RESULT_LIMIT_EXCEEDED.getMessageCode(), ex.getErrorCode());\n      System.setProperty(\"snowflake.enable_incident_test2\", \"false\");\n    }\n  }\n\n  /** SNOW-1416051; Added in > 3.16.0 */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void shouldSerializeArrayAndObjectAsStringOnGetObject(String queryResultFormat)\n      throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat);\n        ResultSet resultSet =\n            statement.executeQuery(\n                \"select ARRAY_CONSTRUCT(1,2,3), OBJECT_CONSTRUCT('a', 4, 'b', 'test')\")) {\n      assertTrue(resultSet.next());\n      String expectedArrayAsString = \"[\\n  1,\\n  2,\\n  3\\n]\";\n      assertEquals(expectedArrayAsString, resultSet.getObject(1));\n      assertEquals(expectedArrayAsString, resultSet.getString(1));\n      String expectedObjectAsString = \"{\\n  \\\"a\\\": 4,\\n  \\\"b\\\": \\\"test\\\"\\n}\";\n      assertEquals(expectedObjectAsString, resultSet.getObject(2));\n      assertEquals(expectedObjectAsString, resultSet.getString(2));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetJsonVsArrowIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.IsInstanceOf.instanceOf;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertInstanceOf;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.nio.ByteBuffer;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.TimeZone;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.apache.arrow.vector.BigIntVector;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/** Completely compare json and arrow resultSet behaviors */\n@Tag(TestTags.ARROW)\npublic class ResultSetJsonVsArrowIT extends BaseJDBCTest {\n\n  public Connection init(String queryResultFormat) throws SQLException {\n    Connection conn = getConnection(BaseJDBCTest.DONT_INJECT_SOCKET_TIMEOUT);\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGSResult(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs =\n            statement.executeQuery(\n                \"select 1, 128, 65500, 10000000000000, \"\n                    + \"1000000000000000000000000000000000000, NULL, \"\n                    + \"current_timestamp, current_timestamp(0), current_timestamp(5),\"\n                    + \"current_date, current_time, current_time(0), current_time(5);\")) {\n      assertTrue(rs.next());\n      assertEquals((byte) 1, rs.getByte(1));\n      assertEquals((short) 128, rs.getShort(2));\n      assertEquals(65500, rs.getInt(3));\n      assertEquals(10000000000000l, rs.getLong(4));\n      assertEquals(new BigDecimal(\"1000000000000000000000000000000000000\"), rs.getBigDecimal(5));\n      assertNull(rs.getString(6));\n      assertNotNull(rs.getTimestamp(7));\n      assertNotNull(rs.getTimestamp(8));\n      assertNotNull(rs.getTimestamp(9));\n\n      assertNotNull(rs.getDate(10));\n      assertNotNull(rs.getTime(11));\n      assertNotNull(rs.getTime(12));\n      assertNotNull(rs.getTime(13));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGSResultReal(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table t (a real)\");\n        statement.execute(\"insert into t values (123.456)\");\n        try (ResultSet rs = statement.executeQuery(\"select * from t;\")) {\n          assertTrue(rs.next());\n          assertEquals(123.456, rs.getFloat(1), 0.001);\n        }\n      } finally {\n        statement.execute(\"drop table if exists t\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGSResultScan(String queryResultFormat) throws SQLException {\n    String queryId = null;\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table t (a text)\");\n        statement.execute(\"insert into t values ('test')\");\n        try (ResultSet rs = statement.executeQuery(\"select count(*) from t;\")) {\n          assertTrue(rs.next());\n          assertEquals(1, rs.getInt(1));\n          queryId = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        }\n        try (ResultSet rs =\n            statement.executeQuery(\"select * from table(result_scan('\" + queryId + \"'))\")) {\n          assertTrue(rs.next());\n          assertEquals(1, rs.getInt(1));\n        }\n      } finally {\n        statement.execute(\"drop table if exists t\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGSResultForEmptyAndSmallTable(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table t (a int)\");\n        try (ResultSet rs = statement.executeQuery(\"select * from t;\")) {\n          assertFalse(rs.next());\n        }\n        statement.execute(\"insert into t values (1)\");\n        try (ResultSet rs = statement.executeQuery(\"select * from t;\")) {\n          assertTrue(rs.next());\n          assertEquals(1, rs.getInt(1));\n        }\n      } finally {\n        statement.execute(\"drop table if exists t\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSNOW89737(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table test_types(c1 number, c2 integer, c3 float, c4 varchar, c5 char, c6 \"\n                + \"binary, c7 boolean, c8 date, c9 datetime, c10 time, c11 timestamp_ltz, c12 timestamp_tz, c13 \"\n                + \"variant, c14 object, c15 array)\");\n        statement.execute(\n            \"insert into test_types values (null, null, null, null, null, null, null, null, null, null, \"\n                + \"null, null, null, null, null)\");\n        statement.execute(\n            \"insert into test_types (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) values(5, 5, 5.0,\"\n                + \"'hello', 'h', '48454C4C4F', true, '1994-12-27', \"\n                + \"'1994-12-27 05:05:05', '05:05:05', '1994-12-27 05:05:05 +00:05', '1994-12-27 05:05:05')\");\n        statement.execute(\n            \"insert into test_types(c13) select parse_json(' { \\\"key1\\\\x00\\\":\\\"value1\\\" } ')\");\n        statement.execute(\n            \"insert into test_types(c14) select parse_json(' { \\\"key1\\\\x00\\\":\\\"value1\\\" } ')\");\n        statement.execute(\n            \"insert into test_types(c15) select parse_json('{\\\"fruits\\\" : [\\\"apples\\\", \\\"pears\\\", \"\n                + \"\\\"oranges\\\"]}')\");\n        ResultSet resultSet = statement.executeQuery(\"select * from test_types\");\n        // test first row of result set against all \"get\" methods\n        assertTrue(resultSet.next());\n        // test getString method against all other data types\n        assertEquals(null, resultSet.getString(1));\n        assertEquals(null, resultSet.getString(2));\n        assertEquals(null, resultSet.getString(3));\n        assertEquals(null, resultSet.getString(4));\n        assertEquals(null, resultSet.getString(5));\n        assertEquals(null, resultSet.getString(6));\n        assertEquals(null, resultSet.getString(7));\n        assertEquals(null, resultSet.getString(8));\n        assertEquals(null, resultSet.getString(9));\n        assertEquals(null, resultSet.getString(10));\n        assertEquals(null, resultSet.getString(11));\n        assertEquals(null, resultSet.getString(12));\n        assertEquals(null, resultSet.getString(13));\n        assertEquals(null, resultSet.getString(14));\n        assertEquals(null, resultSet.getString(15));\n      } finally {\n        statement.execute(\"drop table if exists t\");\n      }\n    }\n  }\n\n  /**\n   * Note: Arrow format does not include space and \\n in the string values\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSemiStructuredData(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement();\n        ResultSet rs =\n            statement.executeQuery(\n                \"select array_construct(10, 20, 30), \"\n                    + \"array_construct(null, 'hello', 3::double, 4, 5), \"\n                    + \"array_construct(), \"\n                    + \"object_construct('a',1,'b','BBBB', 'c',null),\"\n                    + \"object_construct('Key_One', parse_json('NULL'), 'Key_Two', null, 'Key_Three', 'null'),\"\n                    + \"to_variant(3.2),\"\n                    + \"parse_json('{ \\\"a\\\": null}'),\"\n                    + \" 100::variant;\")) {\n      while (rs.next()) {\n        assertEquals(\"[\\n\" + \"  10,\\n\" + \"  20,\\n\" + \"  30\\n\" + \"]\", rs.getString(1));\n        assertEquals(\n            \"[\\n\"\n                + \"  undefined,\\n\"\n                + \"  \\\"hello\\\",\\n\"\n                + \"  3.000000000000000e+00,\\n\"\n                + \"  4,\\n\"\n                + \"  5\\n\"\n                + \"]\",\n            rs.getString(2));\n        assertEquals(\"{\\n\" + \"  \\\"a\\\": 1,\\n\" + \"  \\\"b\\\": \\\"BBBB\\\"\\n\" + \"}\", rs.getString(4));\n        assertEquals(\n            \"{\\n\" + \"  \\\"Key_One\\\": null,\\n\" + \"  \\\"Key_Three\\\": \\\"null\\\"\\n\" + \"}\",\n            rs.getString(5));\n        assertEquals(\"{\\n\" + \"  \\\"a\\\": null\\n\" + \"}\", rs.getString(7));\n        assertEquals(\"[]\", rs.getString(3));\n        assertEquals(\"3.2\", rs.getString(6));\n        assertEquals(\"100\", rs.getString(8));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testStructuredTypes(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"alter session set feature_structured_types = 'ENABLED';\");\n\n      try (ResultSet rs =\n          stmt.executeQuery(\n              \"select array_construct(10, 20, 30)::array(int), \"\n                  + \"object_construct_keep_null('a', 1, 'b', 'BBBB', 'c', null)::object(a int, b varchar, c int), \"\n                  + \"object_construct_keep_null('k1', 'v1', 'k2', null)::map(varchar, varchar);\")) {\n        while (rs.next()) {\n          assertEquals(\"[\\n\" + \"  10,\\n\" + \"  20,\\n\" + \"  30\\n\" + \"]\", rs.getString(1));\n          assertEquals(\n              \"{\\n\" + \"  \\\"a\\\": 1,\\n\" + \"  \\\"b\\\": \\\"BBBB\\\",\\n\" + \"  \\\"c\\\": null\\n\" + \"}\",\n              rs.getString(2));\n          assertEquals(\"{\\n\" + \"  \\\"k1\\\": \\\"v1\\\",\\n\" + \"  \\\"k2\\\": null\\n\" + \"}\", rs.getString(3));\n        }\n      }\n    }\n  }\n\n  private Connection init(String queryResultFormat, String table, String column, String values)\n      throws SQLException {\n    Connection con = init(queryResultFormat);\n    try (Statement statement = con.createStatement()) {\n      statement.execute(\"create or replace table \" + table + \" \" + column);\n      statement.execute(\"insert into \" + table + \" values \" + values);\n    }\n    return con;\n  }\n\n  private boolean isJSON(String queryResultFormat) {\n    return queryResultFormat.equalsIgnoreCase(\"json\");\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No -----------------------------------------------------------------------\n   * getColumnType BIGINT 0 getInt same 0 getShort same 0 getLong same 0 getString same null\n   * getFloat same 0 getDouble same 0 getBigDecimal same null getObject same null getByte same 0\n   * getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testTinyInt(String queryResultFormat) throws SQLException {\n    int[] cases = {0, 1, -1, 127, -128};\n    String table = \"test_arrow_tiny_int\";\n    String column = \"(a int)\";\n    String values = \"(\" + StringUtils.join(ArrayUtils.toObject(cases), \"),(\") + \"), (NULL)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.1;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.BIGINT, columnType);\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          assertEquals(cases[i], rs.getInt(1));\n          assertEquals((short) cases[i], rs.getShort(1));\n          assertEquals((long) cases[i], rs.getLong(1));\n          assertEquals((Integer.toString(cases[i])), rs.getString(1));\n          assertEquals((float) cases[i], rs.getFloat(1), delta);\n          double val = cases[i];\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(Integer.toString(cases[i])), rs.getBigDecimal(1));\n          assertEquals(rs.getLong(1), rs.getObject(1));\n          assertEquals(cases[i], rs.getByte(1));\n\n          byte[] bytes = new byte[1];\n          bytes[0] = (byte) cases[i];\n          assertArrayEquals(bytes, rs.getBytes(1));\n        }\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertNull(rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ------------------------------------------------------------------------------------------------\n   * getColumnType DECIMAL 0 getInt java.NumberFormatException vs SQLException.INVALID_VALUE_CONVERT\n   * 0 getShort java.NumberFormatException vs SQLException.INVALID_VALUE_CONVERT 0 getLong same 0\n   * getString same null getFloat same 0 getDouble same 0 getBigDecimal same null getObject same\n   * null getByte INTERNAL_ERROR vs SUCCESS 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * --------------------------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testScaledTinyInt(String queryResultFormat) throws SQLException {\n    float[] cases = {0.0f, 0.11f, -0.11f, 1.27f, -1.28f};\n    String table = \"test_arrow_tiny_int\";\n    String column = \"(a number(3,2))\";\n    String values = \"(\" + StringUtils.join(ArrayUtils.toObject(cases), \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = con.createStatement().executeQuery(\"select * from test_arrow_tiny_int\")) {\n      try {\n        double delta = 0.001;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.DECIMAL, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals((String.format(\"%.2f\", cases[i])), rs.getString(1));\n          assertEquals(cases[i], rs.getFloat(1), delta);\n          double val = cases[i];\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n          if (isJSON(queryResultFormat)) {\n            Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            assertEquals(((byte) (cases[i] * 100)), rs.getByte(1));\n          }\n\n          if (!isJSON(queryResultFormat)) {\n            byte[] bytes = new byte[1];\n            bytes[0] = rs.getByte(1);\n            assertArrayEquals(bytes, rs.getBytes(1));\n          }\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertNull(rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No -----------------------------------------------------------------------\n   * getColumnType BIGINT 0 getInt same 0 getShort same 0 getLong same 0 getString same null\n   * getFloat same 0 getDouble same 0 getBigDecimal same null getObject same null getByte\n   * INTERNAL_ERROR vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSmallInt(String queryResultFormat) throws SQLException {\n    short[] cases = {0, 1, -1, 127, -128, 128, -129, 32767, -32768};\n    String table = \"test_arrow_small_int\";\n    String column = \"(a int)\";\n    String values = \"(\" + StringUtils.join(ArrayUtils.toObject(cases), \"),(\") + \"), (NULL)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.1;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.BIGINT, columnType);\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          assertEquals(cases[i], rs.getInt(1));\n          assertEquals(cases[i], rs.getShort(1));\n          assertEquals((long) cases[i], rs.getLong(1));\n          assertEquals((Integer.toString(cases[i])), rs.getString(1));\n          assertEquals((float) cases[i], rs.getFloat(1), delta);\n          double val = cases[i];\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(Integer.toString(cases[i])), rs.getBigDecimal(1));\n          assertEquals(rs.getLong(1), rs.getObject(1));\n          if (cases[i] <= 127 && cases[i] >= -128) {\n            assertEquals(cases[i], rs.getByte(1));\n          } else {\n            Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n            if (isJSON(queryResultFormat)) {\n              // Note: not caught by SQLException!\n              assertTrue(e.toString().contains(\"NumberFormatException\"));\n            } else {\n              SQLException se = assertInstanceOf(SQLException.class, e);\n              assertEquals(\n                  (int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n              assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n            }\n          }\n          ByteBuffer bb = ByteBuffer.allocate(2);\n          bb.putShort(cases[i]);\n          if (isJSON(queryResultFormat)) {\n            byte[] res = rs.getBytes(1);\n            for (int j = res.length - 1; j >= 0; j--) {\n              assertEquals(bb.array()[2 - res.length + j], res[j]);\n            }\n          } else {\n            assertArrayEquals(bb.array(), rs.getBytes(1));\n          }\n        }\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertNull(rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ----------------------------------------------------------------------------- getColumnType\n   * DECIMAL 0 getInt NumberFormatException vs INVALID_VALUE_CONVERT 0 getShort\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getLong same 0 getString same null getFloat\n   * same 0 getDouble same 0 getBigDecimal same null getObject same null getByte\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testScaledSmallInt(String queryResultFormat) throws SQLException {\n    float[] cases = {0, 2.0f, -2.0f, 32.767f, -32.768f};\n    short[] shortCompact = {0, 2000, -2000, 32767, -32768};\n    String table = \"test_arrow_small_int\";\n    String column = \"(a number(5,3))\";\n    String values = \"(\" + StringUtils.join(ArrayUtils.toObject(cases), \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = con.createStatement().executeQuery(\"select * from test_arrow_small_int\")) {\n      try {\n        double delta = 0.0001;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.DECIMAL, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals((String.format(\"%.3f\", cases[i])), rs.getString(1));\n          assertEquals(cases[i], rs.getFloat(1), delta);\n          double val = cases[i];\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n          Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n          if (isJSON(queryResultFormat)) {\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            se = assertInstanceOf(SQLException.class, e);\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n\n          ByteBuffer byteBuffer = ByteBuffer.allocate(2);\n          byteBuffer.putShort(shortCompact[i]);\n          if (isJSON(queryResultFormat)) {\n            se = assertThrows(SQLException.class, () -> rs.getBytes(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          } else {\n            assertArrayEquals(byteBuffer.array(), rs.getBytes(1));\n          }\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * -------------------------------------------------------------------------- getColumnType BIGINT\n   * 0 getInt same 0 getShort same 0 getLong same 0 getString same null getFloat same 0 getDouble\n   * same 0 getBigDecimal same null getObject same null getByte INTERNAL_ERROR vs\n   * INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR OR vs SUCCESS null Return wrong result\n   * -------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testInt(String queryResultFormat) throws SQLException {\n    int[] cases = {\n      0, 1, -1, 127, -128, 128, -129, 32767, -32768, 32768, -32769, 2147483647, -2147483648\n    };\n    String table = \"test_arrow_int\";\n    String column = \"(a int)\";\n    String values = \"(\" + StringUtils.join(ArrayUtils.toObject(cases), \"),(\") + \"), (NULL)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = con.createStatement().executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.1;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.BIGINT, columnType);\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          assertEquals(cases[i], rs.getInt(1));\n          if (cases[i] >= Short.MIN_VALUE && cases[i] <= Short.MAX_VALUE) {\n            assertEquals((short) cases[i], rs.getShort(1));\n          } else {\n            SQLException se = assertThrows(SQLException.class, () -> rs.getShort(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n          assertEquals((long) cases[i], rs.getLong(1));\n          assertEquals((Integer.toString(cases[i])), rs.getString(1));\n          assertEquals((float) cases[i], rs.getFloat(1), delta);\n          double val = cases[i];\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(Integer.toString(cases[i])), rs.getBigDecimal(1));\n          assertEquals(rs.getLong(1), rs.getObject(1));\n          if (cases[i] <= 127 && cases[i] >= -128) {\n            assertEquals(cases[i], rs.getByte(1));\n          } else {\n            Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n            if (isJSON(queryResultFormat)) {\n              // Note: not caught by SQLException!\n              assertTrue(e.toString().contains(\"NumberFormatException\"));\n            } else {\n              SQLException se = assertInstanceOf(SQLException.class, e);\n              assertEquals(\n                  (int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n              assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n            }\n          }\n          ByteBuffer bb = ByteBuffer.allocate(4);\n          bb.putInt(cases[i]);\n          if (isJSON(queryResultFormat)) {\n            byte[] res = rs.getBytes(1);\n            for (int j = res.length - 1; j >= 0; j--) {\n              assertEquals(bb.array()[4 - res.length + j], res[j]);\n            }\n          } else {\n            assertArrayEquals(bb.array(), rs.getBytes(1));\n          }\n        }\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertNull(rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ----------------------------------------------------------------------------- getColumnType\n   * DECIMAL 0 getInt NumberFormatException vs INVALID_VALUE_CONVERT 0 getShort\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getLong same 0 getString same null getFloat\n   * same 0 getDouble same 0 getBigDecimal same null getObject same null getByte\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testScaledInt(String queryResultFormat) throws SQLException {\n    int scale = 9;\n    int[] intCompacts = {0, 123456789, -123456789, 2147483647, -2147483647};\n    List<BigDecimal> caseList =\n        Arrays.stream(intCompacts)\n            .mapToObj(x -> BigDecimal.valueOf(x, scale))\n            .collect(Collectors.toList());\n\n    BigDecimal[] cases = caseList.stream().toArray(BigDecimal[]::new);\n\n    String table = \"test_arrow_int\";\n\n    String column = String.format(\"(a number(10,%d))\", scale);\n    String values = \"(\" + StringUtils.join(cases, \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = con.createStatement().executeQuery(\"select * from test_arrow_int\")) {\n      try {\n        double delta = 0.0000000001;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.DECIMAL, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals(cases[i].toPlainString(), rs.getString(1));\n          assertEquals(Float.parseFloat(cases[i].toString()), rs.getFloat(1), delta);\n          double val = Double.parseDouble(cases[i].toString());\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n\n          Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n          if (isJSON(queryResultFormat)) {\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            se = assertInstanceOf(SQLException.class, e);\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n\n          if (isJSON(queryResultFormat)) {\n            se = assertThrows(SQLException.class, () -> rs.getBytes(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          } else {\n            ByteBuffer byteBuffer = ByteBuffer.allocate(4);\n            byteBuffer.putInt(intCompacts[i]);\n            assertArrayEquals(byteBuffer.array(), rs.getBytes(1));\n          }\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * -------------------------------------------------------------------------- getColumnType BIGINT\n   * 0 getInt same 0 getShort same 0 getLong same 0 getString same null getFloat same 0 getDouble\n   * same 0 getBigDecimal same null getObject same null getByte INTERNAL_ERROR vs\n   * INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR OR vs SUCCESS null Return wrong result\n   * -------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBigInt(String queryResultFormat) throws SQLException {\n    long[] cases = {\n      0,\n      1,\n      -1,\n      127,\n      -128,\n      128,\n      -129,\n      32767,\n      -32768,\n      32768,\n      -32769,\n      2147483647,\n      -2147483648,\n      2147483648l,\n      -2147483649l,\n      Long.MAX_VALUE,\n      Long.MIN_VALUE\n    };\n    String table = \"test_arrow_big_int\";\n    String column = \"(a int)\";\n    String values = \"(\" + StringUtils.join(ArrayUtils.toObject(cases), \"),(\") + \"), (NULL)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.1;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.BIGINT, columnType);\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n\n          if (cases[i] >= Integer.MIN_VALUE && cases[i] <= Integer.MAX_VALUE) {\n            assertEquals(cases[i], rs.getInt(1));\n          } else {\n            SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n\n          if (cases[i] >= Short.MIN_VALUE && cases[i] <= Short.MAX_VALUE) {\n            assertEquals((short) cases[i], rs.getShort(1));\n          } else {\n            SQLException se = assertThrows(SQLException.class, () -> rs.getShort(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n          assertEquals(cases[i], rs.getLong(1));\n          assertEquals((Long.toString(cases[i])), rs.getString(1));\n          assertEquals((float) cases[i], rs.getFloat(1), delta);\n          double val = cases[i];\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(Long.toString(cases[i])), rs.getBigDecimal(1));\n          assertEquals(rs.getLong(1), rs.getObject(1));\n          if (cases[i] <= 127 && cases[i] >= -128) {\n            assertEquals(cases[i], rs.getByte(1));\n          } else {\n            Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n            if (isJSON(queryResultFormat)) {\n              // Note: not caught by SQLException!\n              assertTrue(e.toString().contains(\"NumberFormatException\"));\n            } else {\n              SQLException se = assertInstanceOf(SQLException.class, e);\n              assertEquals(\n                  (int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n              assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n            }\n          }\n          ByteBuffer bb = ByteBuffer.allocate(8);\n          bb.putLong(cases[i]);\n          byte[] res = rs.getBytes(1);\n          for (int j = res.length - 1; j >= 0; j--) {\n            assertEquals(bb.array()[8 - res.length + j], res[j]);\n          }\n        }\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ----------------------------------------------------------------------------- getColumnType\n   * DECIMAL 0 getInt NumberFormatException vs INVALID_VALUE_CONVERT 0 getShort\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getLong same 0 getString same null getFloat\n   * same 0 getDouble same 0 getBigDecimal same null getObject same null getByte\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testScaledBigInt(String queryResultFormat) throws SQLException {\n    int scale = 18;\n    long[] longCompacts = {\n      0, 123456789, -123456789, 2147483647, -2147483647, Long.MIN_VALUE, Long.MAX_VALUE\n    };\n    List<BigDecimal> caseList =\n        Arrays.stream(longCompacts)\n            .mapToObj(x -> BigDecimal.valueOf(x, scale))\n            .collect(Collectors.toList());\n\n    BigDecimal[] cases = caseList.stream().toArray(BigDecimal[]::new);\n\n    String table = \"test_arrow_big_int\";\n\n    String column = String.format(\"(a number(38,%d))\", scale);\n    String values = \"(\" + StringUtils.join(cases, \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.0000000000000000001;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.DECIMAL, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals(cases[i].toPlainString(), rs.getString(1));\n          assertEquals(Float.parseFloat(cases[i].toString()), rs.getFloat(1), delta);\n          double val = Double.parseDouble(cases[i].toString());\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n\n          Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n          if (isJSON(queryResultFormat)) {\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            se = assertInstanceOf(SQLException.class, e);\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n\n          if (isJSON(queryResultFormat)) {\n            se = assertThrows(SQLException.class, () -> rs.getBytes(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          } else {\n            ByteBuffer byteBuffer = ByteBuffer.allocate(BigIntVector.TYPE_WIDTH);\n            byteBuffer.putLong(longCompacts[i]);\n            assertArrayEquals(byteBuffer.array(), rs.getBytes(1));\n          }\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ----------------------------------------------------------------------------- getColumnType\n   * DECIMAL 0 getInt NumberFormatException vs INVALID_VALUE_CONVERT 0 getShort\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getLong same 0 getString same null getFloat\n   * same 0 getDouble same 0 getBigDecimal same null getObject INTERNAL_ERROR vs SUCCESS null\n   * getByte NumberFormatException vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS\n   * null -------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDecimalNoScale(String queryResultFormat) throws SQLException {\n    int scale = 0;\n    String[] longCompacts = {\n      \"10000000000000000000000000000000000000\",\n      \"12345678901234567890123456789012345678\", // pragma: allowlist secret\n      \"99999999999999999999999999999999999999\"\n    };\n    List<BigDecimal> caseList =\n        Arrays.stream(longCompacts).map(x -> new BigDecimal(x)).collect(Collectors.toList());\n\n    BigDecimal[] cases = caseList.stream().toArray(BigDecimal[]::new);\n\n    String table = \"test_arrow_decimal\";\n\n    String column = String.format(\"(a number(38,%d))\", scale);\n    String values = \"(\" + StringUtils.join(cases, \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.1;\n        int columnType = rs.getMetaData().getColumnType(1);\n\n        assertEquals(Types.BIGINT, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals(cases[i].toPlainString(), rs.getString(1));\n          assertEquals(Float.parseFloat(cases[i].toString()), rs.getFloat(1), delta);\n          double val = Double.parseDouble(cases[i].toString());\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n\n          Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n          if (isJSON(queryResultFormat)) {\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            se = assertInstanceOf(SQLException.class, e);\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n          assertArrayEquals(cases[i].toBigInteger().toByteArray(), rs.getBytes(1));\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow)\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ----------------------------------------------------------------------------- getColumnType\n   * DECIMAL 0 getInt NumberFormatException vs INVALID_VALUE_CONVERT 0 getShort\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getLong same 0 getString same null getFloat\n   * same 0 getDouble same 0 getBigDecimal same null getObject same null getByte\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDecimalWithLargeScale(String queryResultFormat) throws SQLException {\n    int scale = 37;\n    String[] longCompacts = {\n      \"1.0000000000000000000000000000000000000\",\n      \"1.2345678901234567890123456789012345678\",\n      \"9.9999999999999999999999999999999999999\"\n    };\n    List<BigDecimal> caseList =\n        Arrays.stream(longCompacts).map(x -> new BigDecimal(x)).collect(Collectors.toList());\n\n    BigDecimal[] cases = caseList.stream().toArray(BigDecimal[]::new);\n\n    String table = \"test_arrow_decimal\";\n\n    String column = String.format(\"(a number(38,%d))\", scale);\n    String values = \"(\" + StringUtils.join(cases, \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.00000000000000000000000000000000000001;\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.DECIMAL, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals(cases[i].toPlainString(), rs.getString(1));\n          assertEquals(Float.parseFloat(cases[i].toString()), rs.getFloat(1), delta);\n          double val = Double.parseDouble(cases[i].toString());\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n\n          Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n          if (isJSON(queryResultFormat)) {\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            se = assertInstanceOf(SQLException.class, e);\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n\n          if (isJSON(queryResultFormat)) {\n            se = assertThrows(SQLException.class, () -> rs.getBytes(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          } else {\n            assertArrayEquals(cases[i].toBigInteger().toByteArray(), rs.getBytes(1));\n          }\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * compare behaviors (json vs arrow) decimal values stored in bigInt\n   *\n   * <p>VALUE_IS_NULL Yes No\n   * ----------------------------------------------------------------------------- getColumnType\n   * DECIMAL 0 getInt NumberFormatException vs INVALID_VALUE_CONVERT 0 getShort\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getLong same 0 getString same null getFloat\n   * same 0 getDouble same 0 getBigDecimal same null getObject same null getByte\n   * NumberFormatException vs INVALID_VALUE_CONVERT 0 getBytes INTERNAL_ERROR vs SUCCESS null\n   * -------------------------------------------------------------------------------\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testDecimal(String queryResultFormat) throws SQLException {\n    int scale = 37;\n    long[] longCompacts = {\n      0, 123456789, -123456789, 2147483647, -2147483647, Long.MIN_VALUE, Long.MAX_VALUE\n    };\n    List<BigDecimal> caseList =\n        Arrays.stream(longCompacts)\n            .mapToObj(x -> BigDecimal.valueOf(x, scale))\n            .collect(Collectors.toList());\n\n    BigDecimal[] cases = caseList.stream().toArray(BigDecimal[]::new);\n\n    String table = \"test_arrow_big_int\";\n\n    String column = String.format(\"(a number(38,%d))\", scale);\n    String values = \"(\" + StringUtils.join(cases, \"),(\") + \"), (null)\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = con.createStatement().executeQuery(\"select * from \" + table)) {\n      try {\n        double delta = 0.00000000000000000000000000000000000001;\n        ByteBuffer byteBuf = ByteBuffer.allocate(BigIntVector.TYPE_WIDTH);\n        int columnType = rs.getMetaData().getColumnType(1);\n        assertEquals(Types.DECIMAL, columnType);\n\n        for (int i = 0; i < cases.length; i++) {\n          assertTrue(rs.next());\n\n          SQLException se = assertThrows(SQLException.class, () -> rs.getInt(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getShort(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          se = assertThrows(SQLException.class, () -> rs.getLong(1));\n          assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n          assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n\n          assertEquals(cases[i].toPlainString(), rs.getString(1));\n          assertEquals(Float.parseFloat(cases[i].toString()), rs.getFloat(1), delta);\n          double val = Double.parseDouble(cases[i].toString());\n          assertEquals(val, rs.getDouble(1), delta);\n          assertEquals(new BigDecimal(rs.getString(1)), rs.getBigDecimal(1));\n          assertEquals(rs.getBigDecimal(1), rs.getObject(1));\n\n          Exception e = assertThrows(Exception.class, () -> rs.getByte(1));\n          if (isJSON(queryResultFormat)) {\n            // Note: not caught by SQLException!\n            assertTrue(e.toString().contains(\"NumberFormatException\"));\n          } else {\n            se = assertInstanceOf(SQLException.class, e);\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          }\n\n          if (isJSON(queryResultFormat)) {\n            se = assertThrows(SQLException.class, () -> rs.getBytes(1));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), se.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), se.getSQLState());\n          } else {\n            assertArrayEquals(byteBuf.putLong(0, longCompacts[i]).array(), rs.getBytes(1));\n          }\n        }\n\n        // null value\n        assertTrue(rs.next());\n        assertEquals(0, rs.getInt(1));\n        assertEquals((short) 0, rs.getShort(1));\n        assertEquals((long) 0, rs.getLong(1));\n        assertNull(rs.getString(1));\n        assertEquals((float) 0, rs.getFloat(1), delta);\n        double val = 0;\n        assertEquals(val, rs.getDouble(1), delta);\n        assertNull(rs.getBigDecimal(1));\n        assertEquals(null, rs.getObject(1));\n        assertEquals(0, rs.getByte(1));\n        assertNull(rs.getBytes(1));\n        assertTrue(rs.wasNull());\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  /**\n   * Arrow can make sure no precision loss for double values\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testDoublePrecision(String queryResultFormat) throws SQLException {\n    String[] cases = {\n      // SNOW-31249\n      \"-86.6426540296895\",\n      \"3.14159265359\",\n      // SNOW-76269\n      \"1.7976931348623157E308\",\n      \"1.7E308\",\n      \"1.7976931348623151E308\",\n      \"-1.7976931348623151E308\",\n      \"-1.7E308\",\n      \"-1.7976931348623157E308\"\n    };\n\n    // json results have been truncated to maxScale = 9\n    String[] json_results = {\n      \"-86.64265403\",\n      \"3.141592654\",\n      \"Infinity\",\n      \"1.7E308\",\n      \"Infinity\",\n      \"-Infinity\",\n      \"-1.7E308\",\n      \"-Infinity\"\n    };\n    String table = \"test_arrow_double\";\n\n    String column = \"(a double)\";\n    String values = \"(\" + StringUtils.join(cases, \"),(\") + \")\";\n    try (Connection con = init(queryResultFormat, table, column, values);\n        Statement statement = con.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      try {\n        int i = 0;\n        if (isJSON(queryResultFormat)) {\n          while (rs.next()) {\n            assertEquals(json_results[i++], Double.toString(rs.getDouble(1)));\n          }\n        } else {\n          // Arrow results has no precision loss\n          while (rs.next()) {\n            assertEquals(cases[i++], Double.toString(rs.getDouble(1)));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBoolean(String queryResultFormat) throws SQLException {\n    String table = \"test_arrow_boolean\";\n    String column = \"(a boolean)\";\n    String values = \"(true),(null),(false)\";\n    try (Connection conn = init(queryResultFormat, table, column, values);\n        Statement statement = conn.createStatement();\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      assertTrue(rs.next());\n      assertTrue(rs.getBoolean(1));\n      assertEquals(\"TRUE\", rs.getString(1));\n      assertTrue(rs.next());\n      assertFalse(rs.getBoolean(1));\n      assertTrue(rs.next());\n      assertFalse(rs.getBoolean(1));\n      assertEquals(\"FALSE\", rs.getString(1));\n      assertFalse(rs.next());\n      statement.execute(\"drop table if exists \" + table);\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testClientSideSorting(String queryResultFormat) throws SQLException {\n    String table = \"test_arrow_sort_on\";\n    String column = \"( a int, b double, c string)\";\n    String values = \"(1,2.0,'test'),(0,2.0, 'test'),(1,2.0,'abc')\";\n    try (Connection conn = init(queryResultFormat, table, column, values);\n        Statement statement = conn.createStatement()) {\n      try {\n        // turn on sorting mode\n        statement.execute(\"set-sf-property sort on\");\n\n        try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n          assertTrue(rs.next());\n          assertEquals(\"0\", rs.getString(1));\n          assertTrue(rs.next());\n          assertEquals(\"1\", rs.getString(1));\n          assertTrue(rs.next());\n          assertEquals(\"test\", rs.getString(3));\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + table);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testClientSideSortingOnBatchedChunk(String queryResultFormat) throws SQLException {\n    // in this test, the first chunk contains multiple batches when the format is Arrow\n    String[] queries = {\n      \"set-sf-property sort on\",\n      \"alter session set populate_change_tracking_columns = true;\",\n      \"alter session set create_change_tracking_columns = true;\",\n      \"alter session set enable_stream = true;\",\n      \"alter session set qa_mode=true;  -- used for row_id\",\n      \"create or replace schema stream_get_table_timestamp;\",\n      \"create or replace  table T(id int);\",\n      \"create stream S on table T;\",\n      \"select system$stream_get_table_timestamp('S');\",\n      \"select system$last_change_commit_time('T');\",\n      \"insert into T values (1);\",\n      \"insert into T values (2);\",\n      \"insert into T values (3);\",\n    };\n\n    try (Connection conn = init(queryResultFormat);\n        Statement stat = conn.createStatement()) {\n      try {\n        for (String q : queries) {\n          stat.execute(q);\n        }\n\n        try (ResultSet rs = stat.executeQuery(\"select * from S\")) {\n          assertTrue(rs.next());\n          assertEquals(1, rs.getInt(1));\n          assertTrue(rs.next());\n          assertEquals(2, rs.getInt(1));\n          assertTrue(rs.next());\n          assertEquals(3, rs.getInt(1));\n          assertFalse(rs.next());\n        }\n      } finally {\n        stat.execute(\"drop stream S\");\n        stat.execute(\"drop table T\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testTimestampNTZAreAllNulls(String queryResultFormat) throws SQLException {\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.executeQuery(\n            \"create or replace table test_null_ts_ntz (a timestampntz(9)) as select null from table(generator\"\n                + \"(rowcount => 1000000)) v \"\n                + \"order by 1;\");\n        try (ResultSet rs = statement.executeQuery(\"select * from test_null_ts_ntz\")) {\n          while (rs.next()) {\n            rs.getObject(1);\n          }\n        }\n      } finally {\n        statement.executeQuery(\"drop table if exists test_null_ts_ntz\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void TestArrowStringRoundTrip(String queryResultFormat) throws SQLException {\n    String big_number = \"11111111112222222222333333333344444444\";\n    try (Connection con = init(queryResultFormat);\n        Statement st = con.createStatement()) {\n      try {\n        for (int i = 0; i < 38; i++) {\n          StringBuilder to_insert = new StringBuilder(big_number);\n          if (i != 0) {\n            int insert_to = 38 - i;\n            to_insert.insert(insert_to, \".\");\n          }\n          st.execute(\"create or replace table test_arrow_string (a NUMBER(38, \" + i + \") )\");\n          st.execute(\"insert into test_arrow_string values (\" + to_insert + \")\");\n          try (ResultSet rs = st.executeQuery(\"select * from test_arrow_string\")) {\n            assertTrue(rs.next());\n            assertEquals(to_insert.toString(), rs.getString(1));\n          }\n        }\n      } finally {\n        st.execute(\"drop table if exists test_arrow_string\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void TestArrowFloatRoundTrip(String queryResultFormat) throws SQLException {\n    float[] cases = {Float.MAX_VALUE, Float.MIN_VALUE};\n    try (Connection con = init(queryResultFormat);\n        Statement st = con.createStatement()) {\n      try {\n        for (float f : cases) {\n          st.executeQuery(\"create or replace table test_arrow_float (a FLOAT)\");\n          st.executeQuery(\"insert into test_arrow_float values (\" + f + \")\");\n          try (ResultSet rs = st.executeQuery(\"select * from test_arrow_float\")) {\n            assertTrue(rs.next());\n            assertEquals(f, rs.getFloat(1), Float.MIN_VALUE);\n          }\n        }\n      } finally {\n        st.executeQuery(\"drop table if exists test_arrow_float\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void TestTimestampNTZWithDLS(String queryResultFormat) throws SQLException {\n    TimeZone origTz = TimeZone.getDefault();\n    String[] timeZones = new String[] {\"America/New_York\", \"America/Los_Angeles\"};\n    try (Connection con = init(queryResultFormat);\n        Statement st = con.createStatement()) {\n      for (String timeZone : timeZones) {\n        TimeZone.setDefault(TimeZone.getTimeZone(timeZone));\n        st.execute(\"alter session set JDBC_USE_SESSION_TIMEZONE=false\");\n        st.execute(\"alter session set JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true\");\n        st.execute(\"alter session set TIMEZONE='\" + timeZone + \"'\");\n        st.execute(\n            \"create or replace table src_ts(col1 TIMESTAMP_NTZ, col2 TIMESTAMP_LTZ, col3 TIMESTAMP_TZ)\");\n        List<String> testTimestampNTZValues =\n            Arrays.asList(\n                // DLS start in 2018\n                \"2018-03-11 01:10:34.0123456\",\n                \"2018-03-11 02:10:34.0\",\n                \"2018-03-11 03:10:34.0\",\n                // DLS end in 2018\n                \"2018-11-04 01:10:34.0\",\n                \"2018-11-04 02:10:34.0\",\n                \"2018-11-04 03:10:34.0\",\n                // DLS start in 2020\n                \"2020-03-11 01:10:34.0\",\n                \"2020-03-11 02:10:34.0\",\n                \"2020-03-11 03:10:34.0\",\n                // DLS end in 2020\n                \"2020-11-01 01:10:34.0\",\n                \"2020-11-01 02:10:34.0\",\n                \"2020-11-01 03:10:34.0\");\n\n        List<String[]> testTimestampLTZValues =\n            Arrays.asList(\n                // DLS start in 2018\n                new String[] {\"2018-03-11 01:10:34.0123456\", \"2018-03-11 01:10:34.0123456\"},\n                new String[] {\n                  \"2018-03-11 02:10:34.0\", \"2018-03-11 01:10:34.0\"\n                }, // only this has an impact\n                new String[] {\"2018-03-11 03:10:34.0\", \"2018-03-11 03:10:34.0\"},\n                // DLS end in 2018\n                new String[] {\"2018-11-04 01:10:34.0\", \"2018-11-04 01:10:34.0\"},\n                new String[] {\"2018-11-04 02:10:34.0\", \"2018-11-04 02:10:34.0\"},\n                new String[] {\"2018-11-04 03:10:34.0\", \"2018-11-04 03:10:34.0\"},\n                // DLS start in 2020\n                new String[] {\"2020-03-11 01:10:34.0\", \"2020-03-11 01:10:34.0\"},\n                new String[] {\"2020-03-11 02:10:34.0\", \"2020-03-11 02:10:34.0\"},\n                new String[] {\"2020-03-11 03:10:34.0\", \"2020-03-11 03:10:34.0\"},\n                // DLS end in 2020\n                new String[] {\"2020-11-01 01:10:34.0\", \"2020-11-01 01:10:34.0\"},\n                new String[] {\"2020-11-01 02:10:34.0\", \"2020-11-01 02:10:34.0\"},\n                new String[] {\"2020-11-01 03:10:34.0\", \"2020-11-01 03:10:34.0\"});\n        List<String> testTimestampTZValues =\n            Arrays.asList(\n                // DLS start in 2018\n                \"2018-03-11 01:10:34.0 +0200\",\n                \"2018-03-11 02:10:34.0 +0200\",\n                \"2018-03-11 03:10:34.0 +0200\",\n                // DLS end in 2018\n                \"2018-11-04 01:10:34.0 +0200\",\n                \"2018-11-04 02:10:34.0 +0200\",\n                \"2018-11-04 03:10:34.0 +0200\",\n                // DLS start in 2020\n                \"2020-03-11 01:10:34.0 +0200\",\n                \"2020-03-11 02:10:34.0 +0200\",\n                \"2020-03-11 03:10:34.0 +0200\",\n                // DLS end in 2020\n                \"2020-11-01 01:10:34.0 +0200\",\n                \"2020-11-01 02:10:34.0 +0200\",\n                \"2020-11-01 03:10:34.0 +0200\");\n\n        for (int i = 0; i < testTimestampNTZValues.size(); i++) {\n          st.execute(\n              \"insert into src_ts(col1,col2,col3) values('\"\n                  + testTimestampNTZValues.get(i)\n                  + \"', '\"\n                  + testTimestampLTZValues.get(i)[0]\n                  + \"', '\"\n                  + testTimestampTZValues.get(i)\n                  + \"')\");\n        }\n\n        try (ResultSet resultSet = st.executeQuery(\"select col1, col2, col3 from src_ts\")) {\n          int j = 0;\n          while (resultSet.next()) {\n            Object data1 = resultSet.getObject(1);\n            assertEquals(testTimestampNTZValues.get(j), data1.toString());\n\n            Object data2 = resultSet.getObject(2);\n            assertEquals(testTimestampLTZValues.get(j)[1], data2.toString());\n\n            Object data3 = resultSet.getObject(3);\n            assertThat(data3, instanceOf(Timestamp.class));\n            assertEquals(\n                parseTimestampTZ(testTimestampTZValues.get(j)).toEpochSecond(),\n                ((Timestamp) data3).getTime() / 1000);\n            j++;\n          }\n        }\n      }\n    } finally {\n      TimeZone.setDefault(origTz);\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void TestTimestampNTZBinding(String queryResultFormat) throws SQLException {\n    TimeZone origTz = TimeZone.getDefault();\n    try (Connection con = init(queryResultFormat)) {\n      TimeZone.setDefault(TimeZone.getTimeZone(\"PST\"));\n      try (Statement st = con.createStatement()) {\n        st.execute(\"alter session set CLIENT_TIMESTAMP_TYPE_MAPPING=TIMESTAMP_NTZ\");\n        st.execute(\"alter session set JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true\");\n        st.execute(\"create or replace table src_ts(col1 TIMESTAMP_NTZ)\");\n        try (PreparedStatement prepst = con.prepareStatement(\"insert into src_ts values(?)\")) {\n          Timestamp tz = Timestamp.valueOf(\"2018-03-11 01:10:34.0\");\n          prepst.setTimestamp(1, tz);\n          prepst.execute();\n        }\n        try (ResultSet resultSet = st.executeQuery(\"SELECT COL1 FROM SRC_TS\")) {\n          Object data;\n          int i = 1;\n          while (resultSet.next()) {\n            data = resultSet.getObject(i);\n            System.out.println(data.toString());\n          }\n        }\n      }\n    }\n    TimeZone.setDefault(origTz);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetJsonVsArrowMultiTZIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.ProvidersUtil;\nimport net.snowflake.client.providers.ScaleProvider;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport net.snowflake.client.providers.SnowflakeArgumentsProvider;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/** Completely compare json and arrow resultSet behaviors */\n@Tag(TestTags.ARROW)\npublic class ResultSetJsonVsArrowMultiTZIT extends BaseJDBCWithSharedConnectionIT {\n  static String originalTz;\n\n  static class DataProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(\n          context, new SimpleResultFormatProvider(), new TimezoneProvider(3));\n    }\n  }\n\n  static class DataWithScaleProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(context, new DataProvider(), new ScaleProvider());\n    }\n  }\n\n  @BeforeEach\n  public void setSessionTimezone() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n    }\n  }\n\n  private static void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  @BeforeAll\n  public static void saveTimezone() {\n    originalTz = System.getProperty(\"user.timezone\");\n  }\n\n  @AfterAll\n  public static void restoreTimezone() {\n    if (originalTz != null) {\n      System.setProperty(\"user.timezone\", originalTz);\n    } else {\n      System.clearProperty(\"user.timezone\");\n    }\n  }\n\n  private void init(String table, String column, String values, String queryResultFormat)\n      throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n      statement.execute(\"create or replace table \" + table + \" \" + column);\n      statement.execute(\"insert into \" + table + \" values \" + values);\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataWithScaleProvider.class)\n  public void testTime(String queryResultFormat, String tz, int scale) throws SQLException {\n    setTimezone(tz);\n    String[] times = {\n      \"00:01:23\",\n      \"00:01:23.1\",\n      \"00:01:23.12\",\n      \"00:01:23.123\",\n      \"00:01:23.1234\",\n      \"00:01:23.12345\",\n      \"00:01:23.123456\",\n      \"00:01:23.1234567\",\n      \"00:01:23.12345678\",\n      \"00:01:23.123456789\"\n    };\n    testTimeWithScale(times, scale, queryResultFormat);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testDate(String queryResultFormat, String tz) throws Exception {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01\",\n      \"2014-01-02\",\n      \"2014-01-02\",\n      \"1970-01-01\",\n      \"1970-01-01\",\n      \"1969-12-31\",\n      \"0200-02-27\",\n      \"0200-02-28\",\n      \"0000-01-01\",\n      \"0001-12-31\"\n    };\n    String table = \"test_arrow_date\";\n\n    String column = \"(a date)\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'),('\") + \"'), (null)\";\n    init(table, column, values, queryResultFormat);\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          if (i == cases.length - 2) {\n            assertEquals(\"0001-01-01\", rs.getDate(1).toString());\n          } else {\n            assertEquals(cases[i], rs.getDate(1).toString());\n          }\n          i++;\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      }\n      statement.execute(\"drop table \" + table);\n      System.clearProperty(\"user.timezone\");\n    }\n  }\n\n  public void testTimeWithScale(String[] times, int scale, String queryResultFormat)\n      throws SQLException {\n    String table = \"test_arrow_time\";\n    String column = \"(a time(\" + scale + \"))\";\n    String values = \"('\" + StringUtils.join(times, \"'),('\") + \"'), (null)\";\n    init(table, column, values, queryResultFormat);\n    try (Statement statement = createStatement(queryResultFormat);\n        ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n      for (int i = 0; i < times.length; i++) {\n        assertTrue(rs.next());\n        // Java Time class does not have nanoseconds\n        assertEquals(\"00:01:23\", rs.getString(1));\n      }\n      assertTrue(rs.next());\n      assertNull(rs.getTime(1));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataWithScaleProvider.class)\n  public void testTimestampNTZWithScale(String queryResultFormat, String tz, int scale)\n      throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00\",\n      \"2014-01-02 16:00:00\",\n      \"2014-01-02 12:34:56\",\n      \"0001-01-02 16:00:00\",\n      \"1969-12-31 23:59:59\",\n      \"1970-01-01 00:00:00\",\n      \"1970-01-01 00:00:01\",\n      \"9999-01-01 00:00:00\"\n    };\n\n    String[] results = {\n      \"Sun, 01 Jan 2017 12:00:00 Z\",\n      \"Thu, 02 Jan 2014 16:00:00 Z\",\n      \"Thu, 02 Jan 2014 12:34:56 Z\",\n      \"Sun, 02 Jan 0001 16:00:00 Z\",\n      \"Wed, 31 Dec 1969 23:59:59 Z\",\n      \"Thu, 01 Jan 1970 00:00:00 Z\",\n      \"Thu, 01 Jan 1970 00:00:01 Z\",\n      \"Fri, 01 Jan 9999 00:00:00 Z\"\n    };\n\n    String table = \"test_arrow_ts_ntz\";\n\n    String column = \"(a timestamp_ntz(\" + scale + \"))\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'),('\") + \"'), (null)\";\n    init(table, column, values, queryResultFormat);\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          assertEquals(results[i++], rs.getString(1));\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      }\n      statement.execute(\"drop table \" + table);\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampNTZWithNanos(String queryResultFormat, String tz) throws SQLException {\n    setTimezone(tz);\n    String[] cases = {\n      \"2017-01-01 12:00:00.123456789\",\n      \"2014-01-02 16:00:00.0123\",\n      \"2014-01-02 12:34:56.234241234\",\n      \"0001-01-02 16:00:00.999999999\",\n      \"1969-12-31 23:59:59.000000001\",\n      \"1970-01-01 00:00:00.111111111\",\n      \"1970-01-01 00:00:01.00000001\",\n      \"9999-01-01 00:00:00.1\"\n    };\n\n    String table = \"test_arrow_ts_ntz\";\n\n    String column = \"(a timestamp_ntz)\";\n\n    String values = \"('\" + StringUtils.join(cases, \"'),('\") + \"'), (null)\";\n    init(table, column, values, queryResultFormat);\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try (ResultSet rs = statement.executeQuery(\"select * from \" + table)) {\n        int i = 0;\n        while (i < cases.length) {\n          assertTrue(rs.next());\n          assertEquals(cases[i++], rs.getTimestamp(1).toString());\n        }\n        assertTrue(rs.next());\n        assertNull(rs.getString(1));\n      } finally {\n        statement.execute(\"drop table \" + table);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.TestUtil.expectSnowflakeLoggedFeatureNotSupportedException;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport java.lang.reflect.Field;\nimport java.math.BigDecimal;\nimport java.math.BigInteger;\nimport java.math.RoundingMode;\nimport java.nio.ByteBuffer;\nimport java.sql.CallableStatement;\nimport java.sql.Clob;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.util.ArrayList;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport java.util.concurrent.ExecutionException;\nimport java.util.regex.Pattern;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.common.core.SFBinary;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryClient;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryData;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryField;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.apache.arrow.vector.Float8Vector;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * ResultSet integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the oldest supported driver to examine\n * if the tests still is not applicable. If it is applicable, move tests to ResultSetIT so that both\n * the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetLatestIT extends ResultSet0IT {\n  private static void setQueryResultFormat(Statement stmt, String queryResultFormat)\n      throws SQLException {\n    stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n  }\n\n  private String createTableSql =\n      \"Create or replace table get_object_for_numeric_types (c1 INT, c2 BIGINT, c3 SMALLINT, c4 TINYINT) \";\n  private String insertStmt =\n      \"Insert into get_object_for_numeric_types (c1, c2, c3, c4) values (1000000000, 2000000000000000000000000, 3, 4)\";\n  private String selectQuery = \"Select * from get_object_for_numeric_types\";\n  private String setJdbcTreatDecimalAsIntFalse =\n      \"alter session set JDBC_TREAT_DECIMAL_AS_INT = false\";\n\n  /**\n   * Test that when closing of results is interrupted by Thread.Interrupt(), the memory is released\n   * safely before driver execution ends.\n   *\n   * @throws Throwable\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testMemoryClearingAfterInterrupt(String queryResultFormat) throws Throwable {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      final long initialMemoryUsage = SnowflakeChunkDownloader.getCurrentMemoryUsage();\n      // Inject an InterruptedException into the SnowflakeChunkDownloader.terminate() function\n      SnowflakeChunkDownloader.setInjectedDownloaderException(new InterruptedException());\n      // 10000 rows should be enough to force result into multiple chunks\n\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () -> {\n                try (ResultSet resultSet =\n                    statement.executeQuery(\n                        \"select seq8(), randstr(1000, random()) from table(generator(rowcount => 10000))\")) {\n                  assertThat(\n                      \"hold memory usage for the resultSet before close\",\n                      SnowflakeChunkDownloader.getCurrentMemoryUsage() - initialMemoryUsage >= 0);\n                  // Result closure should catch InterruptedException and throw a SQLException after\n                  // its caught\n                }\n              });\n      assertEquals((int) ErrorCode.INTERRUPTED.getMessageCode(), ex.getErrorCode());\n      // Assert all memory was released\n      assertThat(\n          \"closing statement didn't release memory allocated for result\",\n          SnowflakeChunkDownloader.getCurrentMemoryUsage(),\n          equalTo(initialMemoryUsage));\n      // Unset the exception injection so statement and connection can close without exceptions\n      SnowflakeChunkDownloader.setInjectedDownloaderException(null);\n    }\n  }\n\n  /**\n   * This tests that the SnowflakeChunkDownloader doesn't hang when memory limits are low. Opening\n   * multiple statements concurrently uses a lot of memory. This checks that chunks download even\n   * when there is not enough memory available for concurrent prefetching.\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testChunkDownloaderNoHang(String queryResultFormat) throws SQLException {\n    int stmtCount = 30;\n    int rowCount = 170000;\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      List<ResultSet> rsList = new ArrayList<>();\n      // Set memory limit to low number\n      connection\n          .unwrap(SnowflakeConnectionImpl.class)\n          .getSFBaseSession()\n          .setMemoryLimitForTesting(2000000);\n      // open multiple statements concurrently to overwhelm current memory allocation\n      for (int i = 0; i < stmtCount; ++i) {\n        ResultSet resultSet =\n            stmt.executeQuery(\n                \"select randstr(100, random()) from table(generator(rowcount => \"\n                    + rowCount\n                    + \"))\");\n        rsList.add(resultSet);\n      }\n      // Assert that all resultSets exist and can successfully download the needed chunks without\n      // hanging\n      for (int i = 0; i < stmtCount; i++) {\n        rsList.get(i).next();\n        assertTrue(Pattern.matches(\"[a-zA-Z0-9]{100}\", rsList.get(i).getString(1)));\n        rsList.get(i).close();\n      }\n      // set memory limit back to default invalid value so it does not get used\n      connection\n          .unwrap(SnowflakeConnectionImpl.class)\n          .getSFBaseSession()\n          .setMemoryLimitForTesting(SFBaseSession.MEMORY_LIMIT_UNSET);\n    }\n  }\n\n  /** This tests that the SnowflakeChunkDownloader doesn't hang when memory limits are low. */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testChunkDownloaderSetRetry(String queryResultFormat) throws SQLException {\n    int stmtCount = 3;\n    int rowCount = 170000;\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      connection\n          .unwrap(SnowflakeConnectionImpl.class)\n          .getSFBaseSession()\n          .setMemoryLimitForTesting(1 * 1024 * 1024);\n      connection\n          .unwrap(SnowflakeConnectionImpl.class)\n          .getSFBaseSession()\n          .setOtherParameter(SessionUtil.JDBC_CHUNK_DOWNLOADER_MAX_RETRY, 1);\n      // Set memory limit to low number\n      // open multiple statements concurrently to overwhelm current memory allocation\n      for (int i = 0; i < stmtCount; ++i) {\n        try (ResultSet resultSet =\n            stmt.executeQuery(\n                \"select randstr(100, random()) from table(generator(rowcount => \"\n                    + rowCount\n                    + \"))\")) {\n          // consume half of the results and go to the next statement\n          for (int j = 0; j < rowCount / 2; j++) {\n            resultSet.next();\n          }\n          assertTrue(Pattern.matches(\"[a-zA-Z0-9]{100}\", resultSet.getString(1)));\n        }\n      }\n      // reset retry to MAX_NUM_OF_RETRY, which is 10\n      connection\n          .unwrap(SnowflakeConnectionImpl.class)\n          .getSFBaseSession()\n          .setOtherParameter(SessionUtil.JDBC_CHUNK_DOWNLOADER_MAX_RETRY, 10);\n      // set memory limit back to default invalid value so it does not get used\n      connection\n          .unwrap(SnowflakeConnectionImpl.class)\n          .getSFBaseSession()\n          .setMemoryLimitForTesting(SFBaseSession.MEMORY_LIMIT_UNSET);\n    }\n  }\n\n  /**\n   * Metadata API metric collection. The old driver didn't collect metrics.\n   *\n   * @throws SQLException arises if any exception occurs\n   * @throws ExecutionException arises if error occurred when sending telemetry events\n   * @throws InterruptedException arises if error occurred when sending telemetry events\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testMetadataAPIMetricCollection(String queryResultFormat)\n      throws SQLException, ExecutionException, InterruptedException {\n    Statement stmt = createStatement(queryResultFormat);\n    stmt.close();\n    Telemetry telemetry =\n        connection.unwrap(SnowflakeConnectionImpl.class).getSfSession().getTelemetryClient();\n    DatabaseMetaData metadata = connection.getMetaData();\n    // Call one of the DatabaseMetadata API functions but for simplicity, ensure returned\n    // ResultSet\n    // is empty\n    metadata.getColumns(\"fakecatalog\", \"fakeschema\", null, null);\n    LinkedList<TelemetryData> logs = ((TelemetryClient) telemetry).logBuffer();\n    // No result set has been downloaded from server so no chunk downloader metrics have been\n    // collected\n    // Logs should contain 1 item: the data about the getColumns() parameters\n    assertEquals(logs.size(), 1);\n    // Assert the log is of type client_metadata_api_metrics\n    assertEquals(\n        logs.get(0).getMessage().get(TelemetryField.TYPE.toString()).textValue(),\n        TelemetryField.METADATA_METRICS.toString());\n    // Assert function name and params match and that query id exists\n    assertEquals(logs.get(0).getMessage().get(\"function_name\").textValue(), \"getColumns\");\n    TestUtil.assertValidQueryId(logs.get(0).getMessage().get(\"query_id\").textValue());\n    JsonNode parameterValues = logs.get(0).getMessage().get(\"function_parameters\");\n    assertEquals(parameterValues.get(\"catalog\").textValue(), \"fakecatalog\");\n    assertEquals(parameterValues.get(\"schema\").textValue(), \"fakeschema\");\n    assertNull(parameterValues.get(\"general_name_pattern\").textValue());\n    assertNull(parameterValues.get(\"specific_name_pattern\").textValue());\n\n    // send data to clear log for next test\n    telemetry.sendBatchAsync().get();\n    assertEquals(0, ((TelemetryClient) telemetry).logBuffer().size());\n\n    String catalog = connection.getCatalog();\n    String schema = connection.getSchema();\n    metadata.getColumns(catalog, schema, null, null);\n    logs = ((TelemetryClient) telemetry).logBuffer();\n    assertEquals(logs.size(), 2);\n    // first item in log buffer is metrics on time to consume first result set chunk\n    assertEquals(\n        logs.get(0).getMessage().get(TelemetryField.TYPE.toString()).textValue(),\n        TelemetryField.TIME_CONSUME_FIRST_RESULT.toString());\n    // second item in log buffer is metrics on getProcedureColumns() parameters\n    // Assert the log is of type client_metadata_api_metrics\n    assertEquals(\n        logs.get(1).getMessage().get(TelemetryField.TYPE.toString()).textValue(),\n        TelemetryField.METADATA_METRICS.toString());\n    // Assert function name and params match and that query id exists\n    assertEquals(logs.get(1).getMessage().get(\"function_name\").textValue(), \"getColumns\");\n    TestUtil.assertValidQueryId(logs.get(1).getMessage().get(\"query_id\").textValue());\n    parameterValues = logs.get(1).getMessage().get(\"function_parameters\");\n    assertEquals(parameterValues.get(\"catalog\").textValue(), catalog);\n    assertEquals(parameterValues.get(\"schema\").textValue(), schema);\n    assertNull(parameterValues.get(\"general_name_pattern\").textValue());\n    assertNull(parameterValues.get(\"specific_name_pattern\").textValue());\n  }\n\n  /**\n   * Test that there is no nullptr exception thrown when null value is retrieved from character\n   * stream\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetCharacterStreamNull(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"create or replace table JDBC_NULL_CHARSTREAM (col1 varchar(16))\");\n      statement.execute(\"insert into JDBC_NULL_CHARSTREAM values(NULL)\");\n      try (ResultSet rs = statement.executeQuery(\"select * from JDBC_NULL_CHARSTREAM\")) {\n        rs.next();\n        assertNull(rs.getCharacterStream(1));\n      }\n    }\n  }\n\n  /**\n   * Large result chunk metrics tests. The old driver didn't collect metrics.\n   *\n   * @throws SQLException arises if any exception occurs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testMultipleChunks(String queryResultFormat) throws Exception {\n    try (Statement statement = createStatement(queryResultFormat);\n\n        // 10000 rows should be enough to force result into multiple chunks\n        ResultSet resultSet =\n            statement.executeQuery(\n                \"select seq8(), randstr(1000, random()) from table(generator(rowcount => 10000))\")) {\n      int cnt = 0;\n      while (resultSet.next()) {\n        ++cnt;\n      }\n      assertTrue(cnt >= 0);\n      Telemetry telemetry =\n          connection.unwrap(SnowflakeConnectionImpl.class).getSfSession().getTelemetryClient();\n      LinkedList<TelemetryData> logs = ((TelemetryClient) telemetry).logBuffer();\n\n      // These fields are always expected when results span multiple chunks\n      TelemetryField[] expectedFields = {\n        TelemetryField.TIME_CONSUME_LAST_RESULT, TelemetryField.TIME_WAITING_FOR_CHUNKS,\n        TelemetryField.TIME_DOWNLOADING_CHUNKS, TelemetryField.TIME_PARSING_CHUNKS\n      };\n      // TIME_CONSUME_FIRST_RESULT is only logged when the server provides sendResultTime,\n      // which is not guaranteed, so it is not asserted here.\n\n      for (TelemetryField expectedField : expectedFields) {\n        boolean found = false;\n        for (TelemetryData log : logs) {\n          if (log.getMessage()\n              .get(TelemetryField.TYPE.toString())\n              .textValue()\n              .equals(expectedField.field)) {\n            found = true;\n            break;\n          }\n        }\n        assertThat(\n            String.format(\"%s field not found in telemetry logs\\n\", expectedField.field), found);\n      }\n      telemetry.sendBatchAsync();\n    }\n  }\n\n  /**\n   * Result set metadata\n   *\n   * @throws SQLException arises if any exception occurs\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testResultSetMetadata(String queryResultFormat) throws SQLException {\n    final Map<String, String> params = getConnectionParameters();\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\"create or replace table test_rsmd(colA number(20, 5), colB string)\");\n        statement.execute(\"insert into test_rsmd values(1.00, 'str'),(2.00, 'str2')\");\n        ResultSet resultSet = statement.executeQuery(\"select * from test_rsmd\");\n        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n        assertEquals(\n            params.get(\"database\").toUpperCase(),\n            resultSetMetaData.getCatalogName(1).toUpperCase());\n        assertEquals(\n            params.get(\"schema\").toUpperCase(), resultSetMetaData.getSchemaName(1).toUpperCase());\n        assertEquals(\"TEST_RSMD\", resultSetMetaData.getTableName(1));\n        assertEquals(String.class.getName(), resultSetMetaData.getColumnClassName(2));\n        assertEquals(2, resultSetMetaData.getColumnCount());\n        assertEquals(22, resultSetMetaData.getColumnDisplaySize(1));\n        assertEquals(\"COLA\", resultSetMetaData.getColumnLabel(1));\n        assertEquals(\"COLA\", resultSetMetaData.getColumnName(1));\n        assertEquals(3, resultSetMetaData.getColumnType(1));\n        assertEquals(\"NUMBER\", resultSetMetaData.getColumnTypeName(1));\n        assertEquals(20, resultSetMetaData.getPrecision(1));\n        assertEquals(5, resultSetMetaData.getScale(1));\n        assertFalse(resultSetMetaData.isAutoIncrement(1));\n        assertFalse(resultSetMetaData.isCaseSensitive(1));\n        assertFalse(resultSetMetaData.isCurrency(1));\n        assertFalse(resultSetMetaData.isDefinitelyWritable(1));\n        assertEquals(ResultSetMetaData.columnNullable, resultSetMetaData.isNullable(1));\n        assertTrue(resultSetMetaData.isReadOnly(1));\n        assertTrue(resultSetMetaData.isSearchable(1));\n        assertTrue(resultSetMetaData.isSigned(1));\n        SnowflakeResultSetMetaData secretMetaData =\n            resultSetMetaData.unwrap(SnowflakeResultSetMetaData.class);\n        List<String> colNames = secretMetaData.getColumnNames();\n        assertEquals(\"COLA\", colNames.get(0));\n        assertEquals(\"COLB\", colNames.get(1));\n        assertEquals(Types.DECIMAL, secretMetaData.getInternalColumnType(1));\n        assertEquals(Types.VARCHAR, secretMetaData.getInternalColumnType(2));\n        TestUtil.assertValidQueryId(secretMetaData.getQueryID());\n      } finally {\n        statement.execute(\"drop table if exists test_rsmd\");\n      }\n    }\n  }\n\n  /**\n   * Tests behavior of empty ResultSet. Only callable from getGeneratedKeys().\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testEmptyResultSet(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat);\n        // the only function that returns ResultSetV1.emptyResultSet()\n        ResultSet rs = statement.getGeneratedKeys()) {\n      assertFalse(rs.next());\n      assertFalse(rs.isClosed());\n      assertEquals(0, rs.getInt(1));\n      assertEquals(0, rs.getInt(\"col1\"));\n      assertEquals(0L, rs.getLong(2));\n      assertEquals(0L, rs.getLong(\"col2\"));\n      assertEquals(0, rs.getShort(3));\n      assertEquals(0, rs.getShort(\"col3\"));\n      assertEquals(\"\", rs.getString(4));\n      assertEquals(\"\", rs.getString(\"col4\"));\n      assertEquals(0, rs.getDouble(5), 0);\n      assertEquals(0, rs.getDouble(\"col5\"), 0);\n      assertEquals(0, rs.getFloat(6), 0);\n      assertEquals(0, rs.getFloat(\"col6\"), 0);\n      assertEquals(false, rs.getBoolean(7));\n      assertEquals(false, rs.getBoolean(\"col7\"));\n      assertEquals((byte) 0, rs.getByte(8));\n      assertEquals((byte) 0, rs.getByte(\"col8\"));\n      assertEquals(null, rs.getBinaryStream(9));\n      assertEquals(null, rs.getBinaryStream(\"col9\"));\n      assertEquals(null, rs.getDate(10));\n      assertEquals(null, rs.getDate(10, new FakeCalendar()));\n      assertEquals(null, rs.getDate(\"col10\"));\n      assertEquals(null, rs.getDate(\"col10\", new FakeCalendar()));\n      assertEquals(null, rs.getTime(11));\n      assertEquals(null, rs.getTime(11, new FakeCalendar()));\n      assertEquals(null, rs.getTime(\"col11\"));\n      assertEquals(null, rs.getTime(\"col11\", new FakeCalendar()));\n      assertEquals(null, rs.getTimestamp(12));\n      assertEquals(null, rs.getTimestamp(12, new FakeCalendar()));\n      assertEquals(null, rs.getTimestamp(\"col12\"));\n      assertEquals(null, rs.getTimestamp(\"col12\", new FakeCalendar()));\n      assertEquals(null, rs.getDate(13));\n      assertEquals(null, rs.getDate(\"col13\"));\n      assertEquals(null, rs.getAsciiStream(14));\n      assertEquals(null, rs.getAsciiStream(\"col14\"));\n      assertArrayEquals(new byte[0], rs.getBytes(15));\n      assertArrayEquals(new byte[0], rs.getBytes(\"col15\"));\n      assertNull(rs.getBigDecimal(16));\n      assertNull(rs.getBigDecimal(16, 38));\n      assertNull(rs.getBigDecimal(\"col16\"));\n      assertNull(rs.getBigDecimal(\"col16\", 38));\n      assertNull(rs.getRef(17));\n      assertNull(rs.getRef(\"col17\"));\n      assertNull(rs.getArray(18));\n      assertNull(rs.getArray(\"col18\"));\n      assertNull(rs.getBlob(19));\n      assertNull(rs.getBlob(\"col19\"));\n      assertNull(rs.getClob(20));\n      assertNull(rs.getClob(\"col20\"));\n      assertEquals(0, rs.findColumn(\"col1\"));\n      assertNull(rs.getUnicodeStream(21));\n      assertNull(rs.getUnicodeStream(\"col21\"));\n      assertNull(rs.getURL(22));\n      assertNull(rs.getURL(\"col22\"));\n      assertNull(rs.getObject(23));\n      assertNull(rs.getObject(\"col24\"));\n      assertNull(rs.getObject(23, SnowflakeResultSetV1.class));\n      assertNull(rs.getObject(\"col23\", SnowflakeResultSetV1.class));\n      assertNull(rs.getNString(25));\n      assertNull(rs.getNString(\"col25\"));\n      assertNull(rs.getNClob(26));\n      assertNull(rs.getNClob(\"col26\"));\n      assertNull(rs.getNCharacterStream(27));\n      assertNull(rs.getNCharacterStream(\"col27\"));\n      assertNull(rs.getCharacterStream(28));\n      assertNull(rs.getCharacterStream(\"col28\"));\n      assertNull(rs.getSQLXML(29));\n      assertNull(rs.getSQLXML(\"col29\"));\n      assertNull(rs.getStatement());\n      assertNull(rs.getWarnings());\n      assertNull(rs.getCursorName());\n      assertNull(rs.getMetaData());\n      assertNull(rs.getRowId(1));\n      assertNull(rs.getRowId(\"col1\"));\n      assertEquals(0, rs.getRow());\n      assertEquals(0, rs.getFetchDirection());\n      assertEquals(0, rs.getFetchSize());\n      assertEquals(0, rs.getType());\n      assertEquals(0, rs.getConcurrency());\n      assertEquals(0, rs.getHoldability());\n      assertNull(rs.unwrap(SnowflakeResultSetV1.class));\n      assertFalse(rs.isWrapperFor(SnowflakeResultSetV1.class));\n      assertFalse(rs.wasNull());\n      assertFalse(rs.isFirst());\n      assertFalse(rs.isBeforeFirst());\n      assertFalse(rs.isLast());\n      assertFalse(rs.isAfterLast());\n      assertFalse(rs.first());\n      assertFalse(rs.last());\n      assertFalse(rs.previous());\n      assertFalse(rs.rowUpdated());\n      assertFalse(rs.rowInserted());\n      assertFalse(rs.rowDeleted());\n      assertFalse(rs.absolute(1));\n      assertFalse(rs.relative(1));\n    }\n  }\n\n  /**\n   * Gets bytes from other data types\n   *\n   * @throws Exception arises if any exception occurs.\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testBytesCrossTypeTests(String queryResultFormat) throws Exception {\n    try (ResultSet resultSet = numberCrossTesting(queryResultFormat)) {\n      assertTrue(resultSet.next());\n      // assert that 0 is returned for null values for every type of value\n      for (int i = 1; i < 13; i++) {\n        assertArrayEquals(null, resultSet.getBytes(i));\n      }\n      assertTrue(resultSet.next());\n      assertArrayEquals(intToByteArray(2), resultSet.getBytes(1));\n      assertArrayEquals(intToByteArray(5), resultSet.getBytes(2));\n      assertArrayEquals(floatToByteArray(3.5f), resultSet.getBytes(3));\n      assertArrayEquals(new byte[] {1}, resultSet.getBytes(4));\n      assertArrayEquals(new byte[] {(byte) '1'}, resultSet.getBytes(5));\n      assertArrayEquals(\"1\".getBytes(), resultSet.getBytes(6));\n\n      for (int i = 7; i < 12; i++) {\n        int finalI = i;\n        SQLException ex =\n            assertThrows(SQLException.class, () -> resultSet.getBytes(finalI), \"Failing on \" + i);\n        assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), ex.getErrorCode());\n      }\n\n      byte[] decoded = SFBinary.fromHex(\"48454C4C4F\").getBytes();\n\n      assertArrayEquals(decoded, resultSet.getBytes(12));\n    }\n  }\n\n  // SNOW-204185\n  // 30s for timeout. This test usually finishes in around 10s.\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @Timeout(30)\n  public void testResultChunkDownloaderException(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n\n      // The generated resultSet must be big enough for triggering result chunk downloader\n      String query =\n          \"select current_date(), true,2345234, 2343.0, 'testrgint\\\\n\"\n              + \"\\\\t' from table(generator(rowcount=>10000))\";\n\n      try (ResultSet resultSet = statement.executeQuery(query)) {\n        assertTrue(resultSet.next()); // should finish successfully\n      }\n\n      try {\n        SnowflakeChunkDownloader.setInjectedDownloaderException(\n            new OutOfMemoryError(\"Fake OOM error for testing\"));\n        try (ResultSet resultSet = statement.executeQuery(query)) {\n          // Normally this step won't cause too long. Because we will get exception once trying to\n          // get\n          // result from the first chunk downloader\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> {\n                while (resultSet.next()) {}\n              },\n              \"Should not reach here. Last next() command is supposed to throw an exception\");\n        }\n      } finally {\n        SnowflakeChunkDownloader.setInjectedDownloaderException(null);\n      }\n    }\n  }\n\n  /**\n   * SNOW-165204 Fix exception that resulted from fetching too large a number with getObject().\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testGetObjectWithBigInt() throws SQLException {\n    try (Statement statement = createStatement(\"json\")) {\n      // test with greatest possible number and greatest negative possible number\n      String[] extremeNumbers = {\n        \"99999999999999999999999999999999999999\", \"-99999999999999999999999999999999999999\"\n      };\n      for (int i = 0; i < extremeNumbers.length; i++) {\n        try (ResultSet resultSet = statement.executeQuery(\"select \" + extremeNumbers[i])) {\n          assertTrue(resultSet.next());\n          assertEquals(Types.BIGINT, resultSet.getMetaData().getColumnType(1));\n          assertEquals(new BigDecimal(extremeNumbers[i]), resultSet.getObject(1));\n        }\n      }\n    }\n  }\n\n  private byte[] intToByteArray(int i) {\n    return BigInteger.valueOf(i).toByteArray();\n  }\n\n  private byte[] floatToByteArray(float i) {\n    return ByteBuffer.allocate(Float8Vector.TYPE_WIDTH).putDouble(0, i).array();\n  }\n\n  /**\n   * Test getBigDecimal(int colIndex, int scale) works properly and doesn't throw any\n   * NullPointerExceptions (SNOW-334161)\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetBigDecimalWithScale(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"create or replace table test_get(colA number(38,9))\");\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"insert into test_get values(?)\")) {\n        preparedStatement.setBigDecimal(1, null);\n        preparedStatement.addBatch();\n        BigDecimal bigDecimal = new BigDecimal(\"100000000.123456789\");\n        preparedStatement.setBigDecimal(1, bigDecimal);\n        preparedStatement.addBatch();\n        preparedStatement.executeBatch();\n\n        try (ResultSet resultSet = statement.executeQuery(\"select * from test_get\")) {\n          assertTrue(resultSet.next());\n          assertEquals(null, resultSet.getBigDecimal(1, 5));\n          assertEquals(null, resultSet.getBigDecimal(\"COLA\", 5));\n          assertTrue(resultSet.next());\n          assertEquals(bigDecimal.setScale(5, RoundingMode.HALF_UP), resultSet.getBigDecimal(1, 5));\n          assertEquals(\n              bigDecimal.setScale(5, RoundingMode.HALF_UP), resultSet.getBigDecimal(\"COLA\", 5));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetDataTypeWithTimestampTz(String queryResultFormat) throws Exception {\n    try (Connection connection = getConnection()) {\n      ResultSetMetaData resultSetMetaData = null;\n      try (Statement statement = connection.createStatement()) {\n        setQueryResultFormat(statement, queryResultFormat);\n        statement.executeQuery(\"create or replace table ts_test(ts timestamp_tz)\");\n        try (ResultSet resultSet = statement.executeQuery(\"select * from ts_test\")) {\n          resultSetMetaData = resultSet.getMetaData();\n          // Assert that TIMESTAMP_TZ type matches java.sql.TIMESTAMP_WITH_TIMEZONE\n          assertEquals(resultSetMetaData.getColumnType(1), 2014);\n          // Assert that TIMESTAMP_TZ column returns Timestamp class name\n          assertEquals(resultSetMetaData.getColumnClassName(1), Timestamp.class.getName());\n        }\n      }\n      SFBaseSession baseSession =\n          connection.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession();\n      Field field = SFBaseSession.class.getDeclaredField(\"enableReturnTimestampWithTimeZone\");\n      field.setAccessible(true);\n      field.set(baseSession, false);\n\n      try (Statement statement = connection.createStatement();\n          ResultSet resultSet = statement.executeQuery(\"select * from ts_test\")) {\n        resultSetMetaData = resultSet.getMetaData();\n        // Assert that TIMESTAMP_TZ type matches java.sql.TIMESTAMP when\n        // enableReturnTimestampWithTimeZone is false.\n        assertEquals(resultSetMetaData.getColumnType(1), Types.TIMESTAMP);\n      }\n    }\n  }\n\n  /**\n   * Test getClob(int), and getClob(String) handle SQL nulls and don't throw a NullPointerException\n   * (SNOW-749517)\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetEmptyOrNullClob(String queryResultFormat) throws SQLException {\n    Clob clob = connection.createClob();\n    clob.setString(1, \"hello world\");\n    Clob emptyClob = connection.createClob();\n    emptyClob.setString(1, \"\");\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\n          \"create or replace table test_get_clob(colA varchar, colNull varchar, colEmpty text)\");\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"insert into test_get_clob values(?, ?, ?)\")) {\n        preparedStatement.setClob(1, clob);\n        preparedStatement.setString(2, null);\n        preparedStatement.setClob(3, emptyClob);\n        preparedStatement.execute();\n      }\n      try (ResultSet resultSet = statement.executeQuery(\"select * from test_get_clob\")) {\n        assertTrue(resultSet.next());\n        assertEquals(\"hello world\", resultSet.getClob(1).toString());\n        assertEquals(\"hello world\", resultSet.getClob(\"COLA\").toString());\n        assertNull(resultSet.getClob(2));\n        assertNull(resultSet.getClob(\"COLNULL\"));\n        assertEquals(\"\", resultSet.getClob(3).toString());\n        assertEquals(\"\", resultSet.getClob(\"COLEMPTY\").toString());\n      }\n    }\n  }\n\n  /**\n   * Since now getClob(x) can return a null, theoretically someone may work with a null Clob and try\n   * to use the setClob(int, Clob) method which will result in a NullPointerException. (SNOW-749517)\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testSetNullClob(String queryResultFormat) throws SQLException {\n    Clob clob = null;\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"create or replace table test_set_clob(colNull varchar)\");\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"insert into test_set_clob values(?)\")) {\n        preparedStatement.setClob(1, clob);\n        preparedStatement.execute();\n      }\n\n      try (ResultSet resultSet = statement.executeQuery(\"select * from test_set_clob\")) {\n        assertTrue(resultSet.next());\n        assertNull(resultSet.getClob(1));\n        assertNull(resultSet.getClob(\"COLNULL\"));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testCallStatementType(String queryResultFormat) throws SQLException {\n    Properties props = new Properties();\n    props.put(\"USE_STATEMENT_TYPE_CALL_FOR_STORED_PROC_CALLS\", \"true\");\n    try (Connection connection = getConnection(props);\n        Statement statement = connection.createStatement()) {\n      setQueryResultFormat(statement, queryResultFormat);\n      try {\n        String sp =\n            \"CREATE OR REPLACE PROCEDURE \\\"SP_ZSDLEADTIME_ARCHIVE_DAILY\\\"()\\n\"\n                + \"RETURNS VARCHAR\\n\"\n                + \"LANGUAGE SQL\\n\"\n                + \"EXECUTE AS CALLER\\n\"\n                + \"AS \\n\"\n                + \"'\\n\"\n                + \"declare\\n\"\n                + \"result varchar;\\n\"\n                + \" \\n\"\n                + \"    begin\\n\"\n                + \"        BEGIN TRANSACTION;\\n\"\n                + \"      \\n\"\n                + \"        --Delete records older than 1 year\\n\"\n                + \"        DELETE FROM MYTABLE1 WHERE ID < 5;\\n\"\n                + \"       \\n\"\n                + \"        --Insert new records\\n\"\n                + \"        INSERT INTO MYTABLE1\\n\"\n                + \"            (ID,\\n\"\n                + \"            NAME\\n\"\n                + \"            )\\n\"\n                + \"            SELECT   \\n\"\n                + \"            SEQ,FIRST_NAME\\n\"\n                + \"            FROM MYCSVTABLE;\\n\"\n                + \"        \\n\"\n                + \"COMMIT;\\n\"\n                + \"result := ''SUCCESS'';\\n\"\n                + \"return result;\\n\"\n                + \"exception\\n\"\n                + \"    when other then\\n\"\n                + \"        begin\\n\"\n                + \"        ROLLBACK;\\n\"\n                + \"            --Insert record about error\\n\"\n                + \"            let line := ''sp-sql-msg: '' || SQLERRM || '' code : '' || SQLCODE;\\n\"\n                + \"\\n\"\n                + \"            let sp_name := ''SP_ZSDLEADTIME_ARCHIVE_DAILY'';\\n\"\n                + \"            INSERT into MYTABLE1 values (1000, :line);\\n\"\n                + \"        raise;\\n\"\n                + \"    end;\\n\"\n                + \"end;\\n\"\n                + \"';\";\n        statement.execute(\"create or replace table MYCSVTABLE (SEQ int, FIRST_NAME string)\");\n        statement.execute(\"create or replace table MYTABLE1 (ID int, NAME string)\");\n        statement.execute(sp);\n\n        try (CallableStatement cs = connection.prepareCall(\"CALL SP_ZSDLEADTIME_ARCHIVE_DAILY()\")) {\n          cs.execute();\n          ResultSetMetaData resultSetMetaData = cs.getMetaData();\n          assertEquals(\"SP_ZSDLEADTIME_ARCHIVE_DAILY\", resultSetMetaData.getColumnName(1));\n          assertEquals(\"VARCHAR\", resultSetMetaData.getColumnTypeName(1));\n          assertEquals(0, resultSetMetaData.getScale(1));\n        }\n      } finally {\n        statement.execute(\"drop procedure if exists SP_ZSDLEADTIME_ARCHIVE_DAILY()\");\n        statement.execute(\"drop table if exists MYTABLE1\");\n        statement.execute(\"drop table if exists MYCSVTABLE\");\n      }\n    }\n  }\n\n  /**\n   * Test that new query error message function for checking async query error messages is not\n   * implemented for synchronous queries *\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testNewFeaturesNotSupportedExeceptions(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat);\n        ResultSet rs = statement.executeQuery(\"select 1\")) {\n      expectSnowflakeLoggedFeatureNotSupportedException(() -> rs.getArray(1));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () -> rs.unwrap(SnowflakeBaseResultSet.class).getList(1, String.class));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () -> rs.unwrap(SnowflakeBaseResultSet.class).getArray(1, String.class));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () -> rs.unwrap(SnowflakeBaseResultSet.class).getMap(1, String.class));\n\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () -> rs.unwrap(SnowflakeBaseResultSet.class).getUnicodeStream(1));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () -> rs.unwrap(SnowflakeBaseResultSet.class).getUnicodeStream(\"column1\"));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () ->\n              rs.unwrap(SnowflakeBaseResultSet.class)\n                  .updateAsciiStream(\"column1\", new FakeInputStream(), 5L));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () ->\n              rs.unwrap(SnowflakeBaseResultSet.class)\n                  .updateBinaryStream(\"column1\", new FakeInputStream(), 5L));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () ->\n              rs.unwrap(SnowflakeBaseResultSet.class)\n                  .updateCharacterStream(\"column1\", new FakeReader(), 5L));\n\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () ->\n              rs.unwrap(SnowflakeBaseResultSet.class)\n                  .updateAsciiStream(1, new FakeInputStream(), 5L));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () ->\n              rs.unwrap(SnowflakeBaseResultSet.class)\n                  .updateBinaryStream(1, new FakeInputStream(), 5L));\n      expectSnowflakeLoggedFeatureNotSupportedException(\n          () ->\n              rs.unwrap(SnowflakeBaseResultSet.class)\n                  .updateCharacterStream(1, new FakeReader(), 5L));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testInvalidUnWrap(String queryResultFormat) throws SQLException {\n    try (ResultSet rs = createStatement(queryResultFormat).executeQuery(\"select 1\")) {\n      SQLException ex = assertThrows(SQLException.class, () -> rs.unwrap(SnowflakeUtil.class));\n      assertEquals(\n          ex.getMessage(),\n          \"net.snowflake.client.internal.jdbc.SnowflakeResultSetV1 not unwrappable from net.snowflake.client.internal.jdbc.SnowflakeUtil\");\n    }\n  }\n\n  @Test\n  public void testGetObjectJsonResult() throws SQLException {\n    try (Statement statement = createStatement(\"json\")) {\n      try {\n        statement.execute(\"create or replace table testObj (colA double, colB boolean)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into testObj values(?, ?)\")) {\n          preparedStatement.setDouble(1, 22.2);\n          preparedStatement.setBoolean(2, true);\n          preparedStatement.executeQuery();\n        }\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testObj\")) {\n          assertTrue(resultSet.next());\n          assertEquals(22.2, resultSet.getObject(1));\n          assertEquals(true, resultSet.getObject(2));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testObj\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testMetadataIsCaseSensitive(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n\n      String sampleCreateTableWithAllColTypes =\n          \"CREATE or replace TABLE case_sensitive (\"\n              + \"  boolean_col BOOLEAN,\"\n              + \"  date_col DATE,\"\n              + \"  time_col TIME,\"\n              + \"  timestamp_col TIMESTAMP,\"\n              + \"  timestamp_ltz_col TIMESTAMP_LTZ,\"\n              + \"  timestamp_ntz_col TIMESTAMP_NTZ,\"\n              + \"  number_col NUMBER,\"\n              + \"  float_col FLOAT,\"\n              + \"  double_col DOUBLE,\"\n              + \"  binary_col BINARY,\"\n              + \"  geography_col GEOGRAPHY,\"\n              + \"  variant_col VARIANT,\"\n              + \"  object_col1 OBJECT,\"\n              + \"  array_col1 ARRAY,\"\n              + \"  text_col1 TEXT,\"\n              + \"  varchar_col VARCHAR(16777216),\"\n              + \"  char_col CHAR(16777216)\"\n              + \");\";\n\n      statement.execute(sampleCreateTableWithAllColTypes);\n      try (ResultSet rs = statement.executeQuery(\"select * from case_sensitive\")) {\n        ResultSetMetaData metaData = rs.getMetaData();\n\n        assertFalse(metaData.isCaseSensitive(1)); // BOOLEAN\n        assertFalse(metaData.isCaseSensitive(2)); // DATE\n        assertFalse(metaData.isCaseSensitive(3)); // TIME\n        assertFalse(metaData.isCaseSensitive(4)); // TIMESTAMP\n        assertFalse(metaData.isCaseSensitive(5)); // TIMESTAMP_LTZ\n        assertFalse(metaData.isCaseSensitive(6)); // TIMESTAMP_NTZ\n        assertFalse(metaData.isCaseSensitive(7)); // NUMBER\n        assertFalse(metaData.isCaseSensitive(8)); // FLOAT\n        assertFalse(metaData.isCaseSensitive(9)); // DOUBLE\n        assertFalse(metaData.isCaseSensitive(10)); // BINARY\n\n        assertTrue(metaData.isCaseSensitive(11)); // GEOGRAPHY\n        assertTrue(metaData.isCaseSensitive(12)); // VARIANT\n        assertTrue(metaData.isCaseSensitive(13)); // OBJECT\n        assertTrue(metaData.isCaseSensitive(14)); // ARRAY\n        assertTrue(metaData.isCaseSensitive(15)); // TEXT\n        assertTrue(metaData.isCaseSensitive(16)); // VARCHAR\n        assertTrue(metaData.isCaseSensitive(17)); // CHAR\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testAutoIncrementResult(String queryResultFormat) throws SQLException {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"ENABLE_FIX_759900\", true);\n    try (Connection connection = init(paramProperties, queryResultFormat);\n        Statement statement = connection.createStatement()) {\n\n      statement.execute(\n          \"create or replace table auto_inc(id int autoincrement, name varchar(10), another_col int autoincrement)\");\n      statement.execute(\"insert into auto_inc(name) values('test1')\");\n\n      try (ResultSet resultSet = statement.executeQuery(\"select * from auto_inc\")) {\n        assertTrue(resultSet.next());\n\n        ResultSetMetaData metaData = resultSet.getMetaData();\n        assertTrue(metaData.isAutoIncrement(1));\n        assertFalse(metaData.isAutoIncrement(2));\n        assertTrue(metaData.isAutoIncrement(3));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGranularTimeFunctionsInSessionTimezone(String queryResultFormat)\n      throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        statement.execute(\"create or replace table testGranularTime(t time)\");\n        statement.execute(\"insert into testGranularTime values ('10:10:10')\");\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testGranularTime\")) {\n          assertTrue(resultSet.next());\n          assertEquals(Time.valueOf(\"10:10:10\"), resultSet.getTime(1));\n          assertEquals(10, resultSet.getTime(1).getHours());\n          assertEquals(10, resultSet.getTime(1).getMinutes());\n          assertEquals(10, resultSet.getTime(1).getSeconds());\n        }\n      } finally {\n        statement.execute(\"drop table if exists testGranularTime\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testGranularTimeFunctionsInUTC(String queryResultFormat) throws SQLException {\n    TimeZone origTz = TimeZone.getDefault();\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        TimeZone.setDefault(TimeZone.getTimeZone(\"America/Los_Angeles\"));\n        statement.execute(\"alter session set JDBC_USE_SESSION_TIMEZONE=false\");\n        statement.execute(\"create or replace table testGranularTime(t time)\");\n        statement.execute(\"insert into testGranularTime values ('10:10:10')\");\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testGranularTime\")) {\n          assertTrue(resultSet.next());\n          assertEquals(Time.valueOf(\"02:10:10\"), resultSet.getTime(1));\n          assertEquals(02, resultSet.getTime(1).getHours());\n          assertEquals(10, resultSet.getTime(1).getMinutes());\n          assertEquals(10, resultSet.getTime(1).getSeconds());\n        }\n      } finally {\n        TimeZone.setDefault(origTz);\n        statement.execute(\"drop table if exists testGranularTime\");\n      }\n    }\n  }\n\n  /** Added in > 3.14.5 */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testLargeStringRetrieval(String queryResultFormat) throws SQLException {\n    String originalMaxJsonStringLength =\n        System.getProperty(ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM);\n    System.clearProperty(ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM);\n    String tableName = \"maxJsonStringLength_table_\" + SnowflakeUtil.randomAlphaNumeric(10);\n    int colLength = 16777216;\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      try {\n        setQueryResultFormat(statement, queryResultFormat);\n        SFBaseSession session = con.unwrap(SnowflakeConnectionImpl.class).getSFBaseSession();\n        Integer maxVarcharSize =\n            (Integer) session.getOtherParameter(\"VARCHAR_AND_BINARY_MAX_SIZE_IN_RESULT\");\n        if (maxVarcharSize != null) {\n          colLength = maxVarcharSize;\n        }\n        statement.execute(\n            \"create or replace table \" + tableName + \" (c1 string(\" + colLength + \"))\");\n        statement.execute(\n            \"insert into \" + tableName + \" select randstr(\" + colLength + \", random())\");\n        try (ResultSet rs = statement.executeQuery(\"select * from \" + tableName)) {\n          assertTrue(rs.next());\n          assertEquals(colLength, rs.getString(1).length());\n          assertFalse(rs.next());\n        }\n      } finally {\n        statement.execute(\"drop table if exists \" + tableName);\n      }\n    } finally {\n      if (originalMaxJsonStringLength != null) {\n        System.setProperty(\n            ObjectMapperFactory.MAX_JSON_STRING_LENGTH_JVM, originalMaxJsonStringLength);\n      }\n    }\n  }\n\n  private static void assertAllColumnsAreLongButBigIntIsBigDecimal(ResultSet rs)\n      throws SQLException {\n    while (rs.next()) {\n      assertEquals(Long.class, rs.getObject(1).getClass());\n      assertEquals(BigDecimal.class, rs.getObject(2).getClass());\n      assertEquals(Long.class, rs.getObject(3).getClass());\n      assertEquals(Long.class, rs.getObject(4).getClass());\n    }\n  }\n\n  private static void assertAllColumnsAreBigDecimal(ResultSet rs) throws SQLException {\n    while (rs.next()) {\n      assertEquals(BigDecimal.class, rs.getObject(1).getClass());\n      assertEquals(BigDecimal.class, rs.getObject(2).getClass());\n      assertEquals(BigDecimal.class, rs.getObject(3).getClass());\n      assertEquals(BigDecimal.class, rs.getObject(4).getClass());\n    }\n  }\n\n  // Test setting new connection property JDBC_ARROW_TREAT_DECIMAL_AS_INT=false. Connection property\n  // introduced after version 3.15.0.\n  @Test\n  public void testGetObjectForArrowResultFormatJDBCArrowDecimalAsIntFalse() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"JDBC_ARROW_TREAT_DECIMAL_AS_INT\", false);\n    try (Connection con = getConnection(properties);\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = 'ARROW'\");\n      stmt.execute(createTableSql);\n      stmt.execute(insertStmt);\n\n      // Test with JDBC_ARROW_TREAT_DECIMAL_AS_INT=false and JDBC_TREAT_DECIMAL_AS_INT=true\n      try (ResultSet rs = stmt.executeQuery(selectQuery)) {\n        assertAllColumnsAreLongButBigIntIsBigDecimal(rs);\n      }\n\n      // Test with JDBC_ARROW_TREAT_DECIMAL_AS_INT=false and JDBC_TREAT_DECIMAL_AS_INT=false\n      stmt.execute(setJdbcTreatDecimalAsIntFalse);\n      try (ResultSet rs = stmt.executeQuery(selectQuery)) {\n        assertAllColumnsAreBigDecimal(rs);\n      }\n    }\n  }\n\n  // Test default setting of new connection property JDBC_ARROW_TREAT_DECIMAL_AS_INT=true.\n  // Connection property introduced after version 3.15.0.\n  @Test\n  public void testGetObjectForArrowResultFormatJDBCArrowDecimalAsIntTrue() throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = 'ARROW'\");\n      stmt.execute(createTableSql);\n      stmt.execute(insertStmt);\n\n      // Test with JDBC_ARROW_TREAT_DECIMAL_AS_INT=true and JDBC_TREAT_DECIMAL_AS_INT=true\n      try (ResultSet rs = stmt.executeQuery(selectQuery)) {\n        assertAllColumnsAreLongButBigIntIsBigDecimal(rs);\n      }\n\n      // Test with JDBC_ARROW_TREAT_DECIMAL_AS_INT=true and JDBC_TREAT_DECIMAL_AS_INT=false\n      stmt.execute(setJdbcTreatDecimalAsIntFalse);\n      try (ResultSet rs = stmt.executeQuery(selectQuery)) {\n        assertAllColumnsAreLongButBigIntIsBigDecimal(rs);\n      }\n    }\n  }\n\n  // Test getObject for numeric types when JDBC_TREAT_DECIMAL_AS_INT is set and using JSON result\n  // format.\n  @Test\n  public void testGetObjectForJSONResultFormatUsingJDBCDecimalAsInt() throws SQLException {\n    try (Connection con = BaseJDBCTest.getConnection();\n        Statement stmt = con.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = 'JSON'\");\n      stmt.execute(createTableSql);\n      stmt.execute(insertStmt);\n\n      // Test with JDBC_TREAT_DECIMAL_AS_INT=true (default value)\n      try (ResultSet rs = stmt.executeQuery(selectQuery)) {\n        assertAllColumnsAreLongButBigIntIsBigDecimal(rs);\n      }\n\n      // Test with JDBC_TREAT_DECIMAL_AS_INT=false\n      stmt.execute(setJdbcTreatDecimalAsIntFalse);\n      try (ResultSet rs = stmt.executeQuery(selectQuery)) {\n        assertAllColumnsAreBigDecimal(rs);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetObjectWithType(String queryResultFormat) throws SQLException {\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\n          \" CREATE OR REPLACE TABLE test_all_types (\"\n              + \"                  string VARCHAR, \"\n              + \"                  b TINYINT, \"\n              + \"                  s SMALLINT, \"\n              + \"                  i INTEGER, \"\n              + \"                  l BIGINT, \"\n              + \"                  f FLOAT, \"\n              + \"                  d DOUBLE, \"\n              + \"                  bd DOUBLE, \"\n              + \"                  bool BOOLEAN, \"\n              + \"                  timestampLtz TIMESTAMP_LTZ, \"\n              + \"                  timestampNtz TIMESTAMP_NTZ, \"\n              + \"                  timestampTz TIMESTAMP_TZ, \"\n              + \"                  date DATE,\"\n              + \"                  time TIME \"\n              + \"                  )\");\n      statement.execute(\n          \"insert into test_all_types values('aString',1,2,3,4,1.1,2.2,3.3, false, \"\n              + \"'2021-12-22 09:43:44','2021-12-22 09:43:44','2021-12-22 09:43:44', \"\n              + \"'2023-12-24','12:34:56')\");\n\n      assertResultValueAndType(statement, \"aString\", \"string\", String.class);\n      assertResultValueAndType(statement, new Byte(\"1\"), \"b\", Byte.class);\n      assertResultValueAndType(statement, Short.valueOf(\"2\"), \"s\", Short.class);\n      assertResultValueAndType(statement, Integer.valueOf(\"2\"), \"s\", Integer.class);\n      assertResultValueAndType(statement, Integer.valueOf(\"3\"), \"i\", Integer.class);\n      assertResultValueAndType(statement, Long.valueOf(\"4\"), \"l\", Long.class);\n      assertResultValueAndType(statement, BigDecimal.valueOf(4), \"l\", BigDecimal.class);\n      assertResultValueAndType(statement, Float.valueOf(\"1.1\"), \"f\", Float.class);\n      assertResultValueAndType(statement, Double.valueOf(\"1.1\"), \"f\", Double.class);\n      assertResultValueAndType(statement, Double.valueOf(\"2.2\"), \"d\", Double.class);\n      assertResultValueAndType(statement, BigDecimal.valueOf(3.3), \"bd\", BigDecimal.class);\n      assertResultValueAndType(statement, \"FALSE\", \"bool\", String.class);\n      assertResultValueAndType(statement, Boolean.FALSE, \"bool\", Boolean.class);\n      assertResultValueAndType(statement, 0L, \"bool\", Long.class);\n      assertResultValueAsString(\n          statement,\n          new SnowflakeTimestampWithTimezone(\n              Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)), TimeZone.getDefault()),\n          \"timestampLtz\",\n          Timestamp.class);\n      assertResultValueAsString(\n          statement,\n          new SnowflakeTimestampWithTimezone(\n              Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)), TimeZone.getDefault()),\n          \"timestampNtz\",\n          Timestamp.class);\n      assertResultValueAsString(\n          statement,\n          new SnowflakeTimestampWithTimezone(\n              Timestamp.valueOf(LocalDateTime.of(2021, 12, 22, 9, 43, 44)), TimeZone.getDefault()),\n          \"timestampTz\",\n          Timestamp.class);\n      assertResultValueAndType(\n          statement, Date.valueOf(LocalDate.of(2023, 12, 24)), \"date\", Date.class);\n      assertResultValueAndType(\n          statement, Time.valueOf(LocalTime.of(12, 34, 56)), \"time\", Time.class);\n    }\n  }\n\n  private void assertResultValueAndType(\n      Statement statement, Object expected, String columnName, Class<?> type) throws SQLException {\n    try (ResultSet resultSetString =\n        statement.executeQuery(String.format(\"select %s from test_all_types\", columnName))) {\n      assertTrue(resultSetString.next());\n      assertEquals(expected, resultSetString.getObject(1, type));\n    }\n  }\n\n  private void assertResultValueAsString(\n      Statement statement, Object expected, String columnName, Class type) throws SQLException {\n    try (ResultSet resultSetString =\n        statement.executeQuery(String.format(\"select %s from test_all_types\", columnName))) {\n      assertTrue(resultSetString.next());\n      assertEquals(expected.toString(), resultSetString.getObject(1, type).toString());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetMultiTimeZoneIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.TimeZone;\nimport java.util.stream.Stream;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/** Test ResultSet */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetMultiTimeZoneIT extends BaseJDBCTest {\n  static TimeZone ogTz;\n\n  static class DataProvider implements ArgumentsProvider {\n    @Override\n    public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {\n      List<String> timezones =\n          new ArrayList<String>() {\n            {\n              add(\"UTC\");\n              add(\"Asia/Singapore\");\n              add(\"CET\");\n            }\n          };\n      List<String> queryFormats =\n          new ArrayList<String>() {\n            {\n              add(\"json\");\n              add(\"arrow\");\n            }\n          };\n\n      List<Arguments> args = new ArrayList<>();\n      for (String timeZone : timezones) {\n        for (String queryFormat : queryFormats) {\n          args.add(Arguments.argumentSet(timeZone + \" \" + queryFormat, timeZone, queryFormat));\n        }\n      }\n\n      return args.stream();\n    }\n  }\n\n  @BeforeAll\n  public static void setDefaultTimezone() {\n    ogTz = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n  }\n\n  private static void setTimezone(String timeZone) {\n    System.setProperty(\"user.timezone\", timeZone);\n  }\n\n  @AfterAll\n  public static void clearTimezone() {\n    TimeZone.setDefault(ogTz);\n    System.clearProperty(\"user.timezone\");\n  }\n\n  public Connection init(String queryResultFormat) throws SQLException {\n    Connection connection = BaseJDBCTest.getConnection();\n\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n      statement.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return connection;\n  }\n\n  public Connection init() throws SQLException {\n    Connection connection = BaseJDBCTest.getConnection();\n\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n    }\n    return connection;\n  }\n\n  private final String uniqueTableName =\n      \"orders_jdbc_multitimezone_\" + SnowflakeUtil.randomAlphaNumeric(10);\n\n  @BeforeEach\n  public void setUp() throws SQLException {\n    try (Connection con = init();\n        Statement statement = con.createStatement()) {\n\n      // TEST_RS\n      statement.execute(\"create or replace table test_rs (colA string)\");\n      statement.execute(\"insert into test_rs values('rowOne')\");\n      statement.execute(\"insert into test_rs values('rowTwo')\");\n      statement.execute(\"insert into test_rs values('rowThree')\");\n\n      // ORDERS_JDBC with unique name to prevent race conditions\n      statement.execute(\n          \"create or replace table \"\n              + uniqueTableName\n              + \"(C1 STRING NOT NULL COMMENT 'JDBC', \"\n              + \"C2 STRING, C3 STRING, C4 STRING, C5 STRING, C6 STRING, \"\n              + \"C7 STRING, C8 STRING, C9 STRING) \"\n              + \"stage_file_format = (field_delimiter='|' \"\n              + \"error_on_column_count_mismatch=false)\");\n      // put files\n      assertTrue(\n          statement.execute(\n              \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @%\" + uniqueTableName),\n          \"Failed to put a file\");\n      assertTrue(\n          statement.execute(\n              \"PUT file://\"\n                  + getFullPathFileInResource(TEST_DATA_FILE_2)\n                  + \" @%\"\n                  + uniqueTableName),\n          \"Failed to put a file\");\n\n      int numRows = statement.executeUpdate(\"copy into \" + uniqueTableName);\n\n      assertEquals(73, numRows, \"Unexpected number of rows copied: \" + numRows);\n    }\n  }\n\n  @AfterEach\n  public void tearDown() throws SQLException {\n    System.clearProperty(\"user.timezone\");\n    try (Connection con = init();\n        Statement statement = con.createStatement()) {\n      statement.execute(\"drop table if exists \" + uniqueTableName);\n      statement.execute(\"drop table if exists test_rs\");\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testGetDateAndTime(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table dateTime(colA Date, colB Timestamp, colC Time)\");\n\n        java.util.Date today = new java.util.Date();\n        Date date = buildDate(2016, 3, 20);\n        Timestamp ts = new Timestamp(today.getTime());\n        Time tm = new Time(12345678); // 03:25:45.678\n        final String insertTime = \"insert into datetime values(?, ?, ?)\";\n        try (PreparedStatement prepStatement = connection.prepareStatement(insertTime)) {\n          prepStatement.setDate(1, date);\n          prepStatement.setTimestamp(2, ts);\n          prepStatement.setTime(3, tm);\n\n          prepStatement.execute();\n\n          ResultSet resultSet = statement.executeQuery(\"select * from datetime\");\n          assertTrue(resultSet.next());\n          assertEquals(date, resultSet.getDate(1));\n          assertEquals(date, resultSet.getDate(\"COLA\"));\n          assertEquals(ts, resultSet.getTimestamp(2));\n          assertEquals(ts, resultSet.getTimestamp(\"COLB\"));\n          assertEquals(tm, resultSet.getTime(3));\n          assertEquals(tm, resultSet.getTime(\"COLC\"));\n        }\n        statement.execute(\n            \"create or replace table datetime(colA timestamp_ltz, colB timestamp_ntz, colC timestamp_tz)\");\n        statement.execute(\n            \"insert into dateTime values ('2019-01-01 17:17:17', '2019-01-01 17:17:17', '2019-01-01 \"\n                + \"17:17:17')\");\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\n                \"insert into datetime values(?, '2019-01-01 17:17:17', '2019-01-01 17:17:17')\")) {\n          Timestamp dateTime = new Timestamp(date.getTime());\n          prepStatement.setTimestamp(1, dateTime);\n          prepStatement.execute();\n          try (ResultSet resultSet = statement.executeQuery(\"select * from datetime\")) {\n            assertTrue(resultSet.next());\n            SimpleDateFormat formatter = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n            formatter.setTimeZone(TimeZone.getDefault());\n            String d = formatter.format(resultSet.getDate(\"COLA\"));\n            assertEquals(\"2019-01-02 01:17:17\", d);\n            assertTrue(resultSet.next());\n            assertEquals(date, resultSet.getDate(1));\n            assertEquals(date, resultSet.getDate(\"COLA\"));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists datetime\");\n      }\n    }\n  }\n\n  // SNOW-25029: The driver should reduce Time milliseconds mod 24h.\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testTimeRange(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    final String insertTime = \"insert into timeTest values (?), (?), (?), (?)\";\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table timeTest (c1 time)\");\n\n        long ms1 = -2202968667333L; // 1900-03-11 09:15:33.667\n        long ms2 = -1; // 1969-12-31 23:59:99.999\n        long ms3 = 86400 * 1000; // 1970-01-02 00:00:00\n        long ms4 = 1451680250123L; // 2016-01-01 12:30:50.123\n\n        Time tm1 = new Time(ms1);\n        Time tm2 = new Time(ms2);\n        Time tm3 = new Time(ms3);\n        Time tm4 = new Time(ms4);\n\n        try (PreparedStatement prepStatement = connection.prepareStatement(insertTime)) {\n          prepStatement.setTime(1, tm1);\n          prepStatement.setTime(2, tm2);\n          prepStatement.setTime(3, tm3);\n          prepStatement.setTime(4, tm4);\n\n          prepStatement.execute();\n        }\n\n        // Note that the resulting Time objects are NOT equal because they have\n        // their milliseconds in the range 0 to 86,399,999, i.e. inside Jan 1, 1970.\n        // PreparedStatement accepts Time objects outside this range, but it reduces\n        // modulo 24 hours to discard the date information before sending to GS.\n\n        final long M = 86400 * 1000;\n        try (ResultSet resultSet = statement.executeQuery(\"select * from timeTest\")) {\n          assertTrue(resultSet.next());\n          assertNotEquals(tm1, resultSet.getTime(1));\n          assertEquals(new Time((ms1 % M + M) % M), resultSet.getTime(1));\n          assertTrue(resultSet.next());\n          assertNotEquals(tm2, resultSet.getTime(1));\n          assertEquals(new Time((ms2 % M + M) % M), resultSet.getTime(1));\n          assertTrue(resultSet.next());\n          assertNotEquals(tm3, resultSet.getTime(1));\n          assertEquals(new Time((ms3 % M + M) % M), resultSet.getTime(1));\n          assertTrue(resultSet.next());\n          assertNotEquals(tm4, resultSet.getTime(1));\n          assertEquals(new Time((ms4 % M + M) % M), resultSet.getTime(1));\n        }\n      } finally {\n        statement.execute(\"drop table if exists timeTest\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testCurrentTime(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    final String insertTime = \"insert into datetime values (?, ?, ?)\";\n    try (Connection connection = init(queryResultFormat)) {\n\n      assertFalse(connection.createStatement().execute(\"alter session set TIMEZONE='UTC'\"));\n\n      try (Statement statement = connection.createStatement()) {\n        try {\n          statement.execute(\"create or replace table datetime (d date, ts timestamp, tm time)\");\n          try (PreparedStatement prepStatement = connection.prepareStatement(insertTime)) {\n\n            long currentMillis = System.currentTimeMillis();\n            Date currentDate = new Date(currentMillis);\n            Timestamp currentTS = new Timestamp(currentMillis);\n            Time currentTime = new Time(currentMillis);\n\n            prepStatement.setDate(1, currentDate);\n            prepStatement.setTimestamp(2, currentTS);\n            prepStatement.setTime(3, currentTime);\n\n            prepStatement.execute();\n\n            try (ResultSet resultSet =\n                statement.executeQuery(\"select ts::date = d from datetime\")) {\n              assertTrue(resultSet.next());\n              assertTrue(resultSet.getBoolean(1));\n            }\n            try (ResultSet resultSet =\n                statement.executeQuery(\"select ts::time = tm from datetime\")) {\n              assertTrue(resultSet.next());\n              assertTrue(resultSet.getBoolean(1));\n            }\n          }\n        } finally {\n          statement.execute(\"drop table if exists datetime\");\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testBindTimestampTZ(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table testBindTimestampTZ(\" + \"cola int, colb timestamp_tz)\");\n\n        long millSeconds = System.currentTimeMillis();\n        Timestamp ts = new Timestamp(millSeconds);\n        try (PreparedStatement prepStatement =\n            connection.prepareStatement(\"insert into testBindTimestampTZ values (?, ?)\")) {\n          prepStatement.setInt(1, 123);\n          prepStatement.setTimestamp(2, ts, Calendar.getInstance(TimeZone.getTimeZone(\"UTC\")));\n          prepStatement.execute();\n        }\n\n        try (ResultSet resultSet =\n            statement.executeQuery(\"select cola, colb from testBindTimestampTz\")) {\n          assertTrue(resultSet.next());\n          assertThat(\"integer\", resultSet.getInt(1), equalTo(123));\n          assertThat(\"timestamp_tz\", resultSet.getTimestamp(2), equalTo(ts));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testBindTimestampTZ\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testGetOldDate(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table testOldDate(d date)\");\n        statement.execute(\n            \"insert into testOldDate values ('0001-01-01'), \"\n                + \"(to_date('1000-01-01')), ('1300-01-01'), ('1400-02-02'), \"\n                + \"('1500-01-01'), ('1600-02-03')\");\n\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testOldDate order by d\")) {\n          assertTrue(resultSet.next());\n          assertEquals(\"0001-01-01\", resultSet.getString(1));\n          assertEquals(Date.valueOf(\"0001-01-01\"), resultSet.getDate(1));\n          assertTrue(resultSet.next());\n          assertEquals(\"1000-01-01\", resultSet.getString(1));\n          assertEquals(Date.valueOf(\"1000-01-01\"), resultSet.getDate(1));\n          assertTrue(resultSet.next());\n          assertEquals(\"1300-01-01\", resultSet.getString(1));\n          assertEquals(Date.valueOf(\"1300-01-01\"), resultSet.getDate(1));\n          assertTrue(resultSet.next());\n          assertEquals(\"1400-02-02\", resultSet.getString(1));\n          assertEquals(Date.valueOf(\"1400-02-02\"), resultSet.getDate(1));\n          assertTrue(resultSet.next());\n          assertEquals(\"1500-01-01\", resultSet.getString(1));\n          assertEquals(Date.valueOf(\"1500-01-01\"), resultSet.getDate(1));\n          assertTrue(resultSet.next());\n          assertEquals(\"1600-02-03\", resultSet.getString(1));\n          assertEquals(Date.valueOf(\"1600-02-03\"), resultSet.getDate(1));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testOldDate\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testGetStringForDates(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      String expectedDate1 = \"2020-08-01\";\n      String expectedDate2 = \"1920-11-11\";\n      try (ResultSet rs = statement.executeQuery(\"SELECT '\" + expectedDate1 + \"'::DATE as D1\")) {\n        rs.next();\n        assertEquals(expectedDate1, rs.getString(1));\n      }\n      try (ResultSet rs = statement.executeQuery(\"SELECT '\" + expectedDate2 + \"'::DATE as D1\")) {\n        rs.next();\n        assertEquals(expectedDate2, rs.getString(1));\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testDateTimeRelatedTypeConversion(String tz, String queryResultFormat)\n      throws SQLException {\n    setTimezone(tz);\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table testDateTime\"\n                + \"(colDate DATE, colTS timestamp_ltz, colTime TIME, colString string)\");\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into testDateTime values(?, ?, ?, ?)\")) {\n          Timestamp ts = buildTimestamp(2016, 3, 20, 3, 25, 45, 67800000);\n          Date date = buildDate(2016, 3, 20);\n          Time time = new Time(12345678); // 03:25:45.678\n\n          preparedStatement.setDate(1, date);\n          preparedStatement.setTimestamp(2, ts);\n          preparedStatement.setTime(3, time);\n          preparedStatement.setString(4, \"aaa\");\n\n          preparedStatement.execute();\n          try (ResultSet resultSet = statement.executeQuery(\"select * from testDateTime\")) {\n            assertTrue(resultSet.next());\n\n            // ResultSet.getDate()\n            assertEquals(date, resultSet.getDate(\"COLDATE\"));\n\n            SnowflakeSQLException e =\n                assertThrows(SnowflakeSQLException.class, () -> resultSet.getDate(\"COLTIME\"));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), e.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), e.getSQLState());\n\n            // ResultSet.getTimestamp()\n            assertEquals(new Timestamp(date.getTime()), resultSet.getTimestamp(\"COLDATE\"));\n            assertEquals(ts, resultSet.getTimestamp(\"COLTS\"));\n            assertEquals(new Timestamp(time.getTime()), resultSet.getTimestamp(\"COLTIME\"));\n\n            e =\n                assertThrows(\n                    SnowflakeSQLException.class, () -> resultSet.getTimestamp(\"COLSTRING\"));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), e.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), e.getSQLState());\n\n            // ResultSet.getTime()\n            e = assertThrows(SnowflakeSQLException.class, () -> resultSet.getTime(\"COLDATE\"));\n            assertEquals((int) ErrorCode.INVALID_VALUE_CONVERT.getMessageCode(), e.getErrorCode());\n            assertEquals(ErrorCode.INVALID_VALUE_CONVERT.getSqlState(), e.getSQLState());\n\n            assertEquals(time, resultSet.getTime(\"COLTIME\"));\n            assertEquals(new Time(ts.getTime()), resultSet.getTime(\"COLTS\"));\n          }\n        }\n      } finally {\n        statement.execute(\"drop table if exists testDateTime\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testGetOldTimestamp(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table testOldTs(cola timestamp_ntz)\");\n        statement.execute(\n            \"insert into testOldTs values ('1582-06-22 17:00:00'), \" + \"('1000-01-01 17:00:00')\");\n\n        try (ResultSet resultSet = statement.executeQuery(\"select * from testOldTs\")) {\n\n          assertTrue(resultSet.next());\n\n          assertThat(resultSet.getTimestamp(1).toString(), equalTo(\"1582-06-22 17:00:00.0\"));\n          assertThat(resultSet.getString(1), equalTo(\"Fri, 22 Jun 1582 17:00:00 Z\"));\n\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getTimestamp(1).toString(), equalTo(\"1000-01-01 17:00:00.0\"));\n          assertThat(resultSet.getString(1), equalTo(\"Mon, 01 Jan 1000 17:00:00 Z\"));\n        }\n      } finally {\n        statement.execute(\"drop table if exists testOldTs\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testPrepareOldTimestamp(String tz, String queryResultFormat) throws SQLException {\n    setTimezone(tz);\n    TimeZone origTz = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    try (Connection con = init(queryResultFormat);\n        Statement statement = con.createStatement()) {\n      try {\n        statement.execute(\"create or replace table testPrepOldTs(cola timestamp_ntz, colb date)\");\n        statement.execute(\"alter session set client_timestamp_type_mapping=timestamp_ntz\");\n        PreparedStatement ps = con.prepareStatement(\"insert into testPrepOldTs values (?, ?)\");\n\n        ps.setTimestamp(1, Timestamp.valueOf(\"0001-01-01 08:00:00\"));\n        ps.setDate(2, Date.valueOf(\"0001-01-01\"));\n        ps.executeUpdate();\n\n        ResultSet resultSet = statement.executeQuery(\"select * from testPrepOldTs\");\n\n        assertTrue(resultSet.next());\n        assertThat(resultSet.getTimestamp(1).toString(), equalTo(\"0001-01-01 08:00:00.0\"));\n        assertThat(resultSet.getDate(2).toString(), equalTo(\"0001-01-01\"));\n      } finally {\n        statement.execute(\"drop table if exists testPrepOldTs\");\n        TimeZone.setDefault(origTz);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetMultiTimeZoneLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Timestamp;\nimport java.text.SimpleDateFormat;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.TimeZone;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.BooleanProvider;\nimport net.snowflake.client.providers.ProvidersUtil;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport net.snowflake.client.providers.SnowflakeArgumentsProvider;\nimport net.snowflake.client.providers.TimezoneProvider;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * ResultSet multi timezone tests for the latest JDBC driver. This cannot run for the old driver.\n */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetMultiTimeZoneLatestIT extends BaseJDBCWithSharedConnectionIT {\n\n  private static String originalTz;\n\n  private static class DataProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(\n          context, new SimpleResultFormatProvider(), new TimezoneProvider(4));\n    }\n  }\n\n  private static class DataWithFlagProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(context, new DataProvider(), new BooleanProvider());\n    }\n  }\n\n  @BeforeAll\n  public static void saveTimezone() {\n    originalTz = System.getProperty(\"user.timezone\");\n  }\n\n  @AfterAll\n  public static void restoreTimezone() {\n    if (originalTz != null) {\n      System.setProperty(\"user.timezone\", originalTz);\n    } else {\n      System.clearProperty(\"user.timezone\");\n    }\n  }\n\n  private static void setTimezone(String tz) {\n    System.setProperty(\"user.timezone\", tz);\n  }\n\n  public void init(String queryResultFormat, String tz) throws SQLException {\n    setTimezone(tz);\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n      statement.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n  }\n\n  /**\n   * This tests that all time values (regardless of precision) return the same wallclock value when\n   * getTimestamp() is called.\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimesWithGetTimestamp(String queryResultFormat, String tz) throws SQLException {\n    init(queryResultFormat, tz);\n    try (Statement statement = createStatement(queryResultFormat)) {\n      String timeStringValue = \"10:30:50.123456789\";\n      String timestampStringValue = \"1970-01-01 \" + timeStringValue;\n      int length = timestampStringValue.length();\n      statement.execute(\n          \"create or replace table SRC_DATE_TIME (C2_TIME_3 TIME(3), C3_TIME_5 TIME(5), C4_TIME\"\n              + \" TIME(9))\");\n      statement.execute(\n          \"insert into SRC_DATE_TIME values ('\"\n              + timeStringValue\n              + \"','\"\n              + timeStringValue\n              + \"','\"\n              + timeStringValue\n              + \"')\");\n      try (ResultSet rs = statement.executeQuery(\"select * from SRC_DATE_TIME\")) {\n        assertTrue(rs.next());\n        assertEquals(timestampStringValue.substring(0, length - 6), rs.getTimestamp(1).toString());\n        assertEquals(timestampStringValue.substring(0, length - 4), rs.getTimestamp(2).toString());\n        assertEquals(timestampStringValue, rs.getTimestamp(3).toString());\n      }\n    }\n  }\n\n  /**\n   * This test is for SNOW-366563 where the timestamp was returning an incorrect value for daylight\n   * savings time due to the fact that UTC and Europe/London time have the same offset until\n   * daylight savings comes into effect. This tests that the timestamp value is accurate during\n   * daylight savings time.\n   *\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void testTimestampNTZWithDaylightSavings(String queryResultFormat, String tz)\n      throws SQLException {\n    init(queryResultFormat, tz);\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\n          \"alter session set TIMESTAMP_TYPE_MAPPING='TIMESTAMP_NTZ',\" + \"TIMEZONE='Europe/London'\");\n      try (ResultSet rs = statement.executeQuery(\"select TIMESTAMP '2011-09-04 00:00:00'\")) {\n        assertTrue(rs.next());\n        Timestamp expected = Timestamp.valueOf(\"2011-09-04 00:00:00\");\n        assertEquals(expected, rs.getTimestamp(1));\n      }\n    }\n  }\n\n  /**\n   * Test for getDate(int columnIndex, Calendar cal) function to ensure it matches values with\n   * getTimestamp function\n   */\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  @DontRunOnGithubActions\n  public void testDateAndTimestampWithTimezone(String queryResultFormat, String tz)\n      throws SQLException {\n    init(queryResultFormat, tz);\n    Calendar cal = null;\n    SimpleDateFormat sdf = null;\n    // The following line allows for the tests to work locally. This should be removed when the\n    // tests are properly fixed.\n    TimeZone.setDefault(TimeZone.getTimeZone(\"UTC\"));\n    try (Statement statement = createStatement(queryResultFormat)) {\n      statement.execute(\"alter session set JDBC_FORMAT_DATE_WITH_TIMEZONE=true\");\n      try (ResultSet rs =\n          statement.executeQuery(\n              \"SELECT DATE '1970-01-02 00:00:00' as datefield, \"\n                  + \"TIMESTAMP '1970-01-02 00:00:00' as timestampfield\")) {\n        assertTrue(rs.next());\n\n        // Set a timezone for results to be returned in and set a format for date and timestamp\n        // objects\n        cal = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n        sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        sdf.setTimeZone(cal.getTimeZone());\n\n        // Date object and calendar object should return the same timezone offset with calendar\n        Date dateWithZone = rs.getDate(1, cal);\n        Timestamp timestampWithZone = rs.getTimestamp(2, cal);\n        assertEquals(sdf.format(dateWithZone), sdf.format(timestampWithZone));\n\n        // When fetching Date object with getTimestamp versus Timestamp object with getTimestamp,\n        // results should match\n        assertEquals(rs.getTimestamp(1, cal), rs.getTimestamp(2, cal));\n\n        // When fetching Timestamp object with getDate versus Date object with getDate, results\n        // should\n        // match\n        assertEquals(rs.getDate(1, cal), rs.getDate(2, cal));\n\n        // getDate() without Calendar offset called on Date type should return the same date with no\n        // timezone offset\n        assertEquals(\"1970-01-02 00:00:00\", sdf.format(rs.getDate(1)));\n        // getDate() without Calendar offset called on Timestamp type returns date with timezone\n        // offset\n        assertEquals(\"1970-01-02 08:00:00\", sdf.format(rs.getDate(2)));\n\n        // getTimestamp() without Calendar offset called on Timestamp type should return the\n        // timezone\n        // offset\n        assertEquals(\"1970-01-02 08:00:00\", sdf.format(rs.getTimestamp(2)));\n        // getTimestamp() without Calendar offset called on Date type should not return the timezone\n        // offset\n        assertEquals(\"1970-01-02 00:00:00\", sdf.format(rs.getTimestamp(1)));\n      }\n      // test that session parameter functions as expected. When false, getDate() has same behavior\n      // with or without Calendar input\n      statement.execute(\"alter session set JDBC_FORMAT_DATE_WITH_TIMEZONE=false\");\n      try (ResultSet rs =\n          statement.executeQuery(\"SELECT DATE '1945-05-10 00:00:00' as datefield\")) {\n        assertTrue(rs.next());\n        assertEquals(rs.getDate(1, cal), rs.getDate(1));\n        assertEquals(\"1945-05-10 00:00:00\", sdf.format(rs.getDate(1, cal)));\n      }\n    }\n  }\n\n  /**\n   * Helper function to test behavior of parameter JDBC_USE_SESSION_TIMEZONE. When\n   * JDBC_USE_SESSION_TIMEZONE=true, time/date/timestamp values are displayed using the session\n   * timezone, not the JVM timezone. There should be no offset between the inserted value and the\n   * displayed value from ResultSet. For example, a timestamp value inserted as: 2019-01-01\n   * 17:17:17.6 +0500 will get displayed as so: ResultSet.getTimestamp(): 2019-01-01 17:17:17.6;\n   * ResultSet.getTime(): 17:17:17.6; ResultSet.getDate(): 2019-01-01\n   *\n   * <p>When JDBC_USE_SESSION_TIMEZONE=false, the displayed values will be different depending on\n   * the timezone offset between the session and JVM timezones.\n   *\n   * @param useDefaultParamSettings use default settings of other time/date session formatting\n   *     parameters\n   * @throws SQLException\n   */\n  @ParameterizedTest\n  @ArgumentsSource(DataWithFlagProvider.class)\n  public void testUseSessionTimeZoneHelper(\n      String queryResultFormat, String tz, boolean useDefaultParamSettings) throws SQLException {\n    init(queryResultFormat, tz);\n    try (Statement statement = createStatement(queryResultFormat)) {\n      try {\n        // create table with all timestamp types, time, and date\n        statement.execute(\n            \"create or replace table datetimetypes(colA timestamp_ltz, colB timestamp_ntz, colC\"\n                + \" timestamp_tz, colD time, colE date)\");\n        // Enable session parameter JDBC_USE_SESSION_TIMEZONE\n        statement.execute(\"alter session set JDBC_USE_SESSION_TIMEZONE=true\");\n        if (!useDefaultParamSettings) {\n          // these are 2 other session params that also alter the session display behavior\n          statement.execute(\"alter session set JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true\");\n          statement.execute(\"alter session set JDBC_FORMAT_DATE_WITH_TIMEZONE=true\");\n        }\n\n        String expectedTimestamp = \"2019-01-01 17:17:17.6\";\n        String expectedTime = \"17:17:17\";\n        String expectedDate = \"2019-01-01\";\n        String expectedTimestamp2 = \"1943-12-31 01:01:33.0\";\n        String expectedTime2 = \"01:01:33\";\n        String expectedDate2 = \"1943-12-31\";\n        try (PreparedStatement prepSt =\n            connection.prepareStatement(\"insert into datetimetypes values(?, ?, ?, ?, ?)\")) {\n          prepSt.setString(1, expectedTimestamp);\n          prepSt.setString(2, expectedTimestamp);\n          prepSt.setString(3, expectedTimestamp);\n          prepSt.setString(4, expectedTime);\n          prepSt.setString(5, expectedDate);\n          prepSt.execute();\n          prepSt.setString(1, expectedTimestamp2);\n          prepSt.setString(2, expectedTimestamp2);\n          prepSt.setString(3, expectedTimestamp2);\n          prepSt.setString(4, expectedTime2);\n          prepSt.setString(5, expectedDate2);\n          prepSt.execute();\n        }\n        // Results differ depending on whether flag JDBC_USE_SESSION_TIMEZONE=true. If true, the\n        // returned ResultSet value should match the value inserted into the table with no offset\n        // (with\n        // exceptions for getTimestamp() on date and time objects).\n        try (ResultSet rs =\n            statement.executeQuery(\"select * from datetimetypes ORDER BY colE DESC\")) {\n          assertTrue(rs.next());\n          // Assert date has no offset. When flag is false, timestamp_ltz and timestamp_ntz will\n          // show\n          // offset.\n          assertEquals(expectedDate, rs.getDate(\"COLA\").toString());\n          // always true since timezone_ntz doesn't add time offset\n          assertEquals(expectedDate, rs.getDate(\"COLB\").toString());\n          assertEquals(expectedDate, rs.getDate(\"COLC\").toString());\n          // cannot getDate() for Time column (ColD)\n          // always true since Date objects don't have timezone offsets\n          assertEquals(expectedDate, rs.getDate(\"COLE\").toString());\n\n          // Assert timestamp has no offset. When flag is false, timestamp_ltz and timestamp_ntz\n          // will\n          // show\n          // offset.\n          assertEquals(expectedTimestamp, rs.getTimestamp(\"COLA\").toString());\n          // always true since timezone_ntz doesn't add time offset\n          assertEquals(expectedTimestamp, rs.getTimestamp(\"COLB\").toString());\n          assertEquals(expectedTimestamp, rs.getTimestamp(\"COLC\").toString());\n          // Getting timestamp from Time column will default to epoch start date so date portion is\n          // different than input date of the timestamp\n          assertEquals(\"1970-01-01 17:17:17.0\", rs.getTimestamp(\"COLD\").toString());\n          // Getting timestamp from Date column will default to wallclock time of 0 so time portion\n          // is\n          // different than input time of the timestamp\n          assertEquals(\"2019-01-01 00:00:00.0\", rs.getTimestamp(\"COLE\").toString());\n\n          // Assert time has no offset. When flag is false, timestamp_ltz and timestamp_ntz will\n          // show\n          // offset.\n          assertEquals(expectedTime, rs.getTime(\"COLA\").toString());\n          assertEquals(expectedTime, rs.getTime(\"COLB\").toString());\n          assertEquals(expectedTime, rs.getTime(\"COLC\").toString());\n          assertEquals(expectedTime, rs.getTime(\"COLD\").toString());\n          // Cannot getTime() for Date column (colE)\n\n          assertTrue(rs.next());\n          // Assert date has no offset. Offset will never be seen regardless of flag because\n          // 01:01:33\n          // is\n          // too early for any timezone to round it to the next day.\n          assertEquals(expectedDate2, rs.getDate(\"COLA\").toString());\n          assertEquals(expectedDate2, rs.getDate(\"COLB\").toString());\n          assertEquals(expectedDate2, rs.getDate(\"COLC\").toString());\n          // cannot getDate() for Time column (ColD)\n          assertEquals(expectedDate2, rs.getDate(\"COLE\").toString());\n\n          // Assert timestamp has no offset. When flag is false, timestamp_ltz and timestamp_ntz\n          // will\n          // show\n          // offset.\n          assertEquals(expectedTimestamp2, rs.getTimestamp(\"COLA\").toString());\n          assertEquals(expectedTimestamp2, rs.getTimestamp(\"COLB\").toString());\n          assertEquals(expectedTimestamp2, rs.getTimestamp(\"COLC\").toString());\n          // Getting timestamp from Time column will default to epoch start date\n          assertEquals(\"1970-01-01 01:01:33.0\", rs.getTimestamp(\"COLD\").toString());\n          // Getting timestamp from Date column will default to wallclock time of 0\n          assertEquals(\"1943-12-31 00:00:00.0\", rs.getTimestamp(\"COLE\").toString());\n\n          // Assert time has no offset. When flag is false, timestamp_ltz and timestamp_ntz will\n          // show\n          // offset.\n          assertEquals(expectedTime2, rs.getTime(\"COLA\").toString());\n          assertEquals(expectedTime2, rs.getTime(\"COLB\").toString());\n          assertEquals(expectedTime2, rs.getTime(\"COLC\").toString());\n          assertEquals(expectedTime2, rs.getTime(\"COLD\").toString());\n          // Cannot getTime() for Date column (colE)\n        }\n        // Test special case for timestamp_tz (offset added)\n        // create table with of type timestamp_tz\n        statement.execute(\"create or replace table tabletz (colA timestamp_tz)\");\n        try (PreparedStatement prepSt =\n            connection.prepareStatement(\"insert into tabletz values(?), (?)\")) {\n          // insert 2 timestamp values, but add an offset of a few hours on the end of each value\n          prepSt.setString(\n              1, expectedTimestamp + \" +0500\"); // inserted value is 2019-01-01 17:17:17.6 +0500\n          prepSt.setString(\n              2, expectedTimestamp2 + \" -0200\"); // inserted value is 1943-12-31 01:01:33.0 -0200\n          prepSt.execute();\n\n          try (ResultSet rs = statement.executeQuery(\"select * from tabletz\")) {\n            assertTrue(rs.next());\n            // Assert timestamp is displayed with no offset when flag is true. Timestamp should look\n            // identical to inserted value\n            assertEquals(expectedTimestamp, rs.getTimestamp(\"COLA\").toString());\n            // Time value looks identical to the time portion of inserted timestamp_tz value\n            assertEquals(expectedTime, rs.getTime(\"COLA\").toString());\n            // Date value looks identical to the date portion of inserted timestamp_tz value\n            assertEquals(expectedDate, rs.getDate(\"COLA\").toString());\n            assertTrue(rs.next());\n            // Test that the same results occur for 2nd timestamp_tz value\n            assertEquals(expectedTimestamp2, rs.getTimestamp(\"COLA\").toString());\n            assertEquals(expectedTime2, rs.getTime(\"COLA\").toString());\n            assertEquals(expectedDate2, rs.getDate(\"COLA\").toString());\n          }\n        }\n      } finally {\n        // clean up\n        if (!useDefaultParamSettings) {\n          statement.execute(\"alter session unset JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\");\n          statement.execute(\"alter session unset JDBC_FORMAT_DATE_WITH_TIMEZONE\");\n        }\n        statement.execute(\"alter session unset JDBC_USE_SESSION_TIMEZONE\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ResultSetVectorLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.api.resultset.SnowflakeType.EXTRA_TYPES_VECTOR;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * ResultSet integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. It works for drivers with version bigger than 3.15.1. Revisit this tests\n * whenever bumping up the oldest supported driver to examine if the tests still is not applicable.\n * If it is applicable, move tests to ResultSetVectorIT so that both the latest and oldest supported\n * driver run the tests.\n */\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetVectorLatestIT extends ResultSet0IT {\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetIntVectorAsIntArray(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      Integer[] vector = {-1, 5};\n      try (ResultSet resultSet = stmt.executeQuery(\"select \" + vectorToString(vector, \"int\"))) {\n        assertTrue(resultSet.next());\n        Integer[] result =\n            resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Integer.class);\n        assertArrayEquals(vector, result);\n        assertVectorMetadata(resultSet, 1, Types.INTEGER, 1);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetIntVectorAsLongArray(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      Long[] vector = {-1L, 5L};\n      try (ResultSet resultSet = stmt.executeQuery(\"select \" + vectorToString(vector, \"int\"))) {\n        assertTrue(resultSet.next());\n        Long[] result = resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Long.class);\n        assertArrayEquals(vector, result);\n        assertVectorMetadata(resultSet, 1, Types.INTEGER, 1);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetFloatVectorAsFloatArray(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      Float[] vector = {-1.2f, 5.1f, 15.87f};\n      try (ResultSet resultSet = stmt.executeQuery(\"select \" + vectorToString(vector, \"float\"))) {\n        assertTrue(resultSet.next());\n        Float[] result = resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Float.class);\n        assertArrayEquals(vector, result);\n        assertVectorMetadata(resultSet, 1, Types.FLOAT, 1);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetNullAsIntVector(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      try (ResultSet resultSet = stmt.executeQuery(\"select null::vector(int, 2)\")) {\n        assertTrue(resultSet.next());\n        Integer[] result =\n            resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Integer.class);\n        assertNull(result);\n        assertVectorMetadata(resultSet, 1, Types.INTEGER, 1);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetNullAsFloatVector(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      try (ResultSet resultSet = stmt.executeQuery(\"select null::vector(float, 2)\")) {\n        assertTrue(resultSet.next());\n        Integer[] result =\n            resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Integer.class);\n        assertNull(result);\n        assertVectorMetadata(resultSet, 1, Types.FLOAT, 1);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetIntVectorFromTable(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      stmt.execute(\"create or replace table test_vector_int(x vector(int, 2), y int)\");\n      stmt.execute(\"insert into test_vector_int select [3, 7]::vector(int, 2), 15\");\n      try (ResultSet resultSet = stmt.executeQuery(\"select x, y from test_vector_int\")) {\n        assertTrue(resultSet.next());\n        Integer[] result =\n            resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Integer.class);\n        assertArrayEquals(new Integer[] {3, 7}, result);\n        assertVectorMetadata(resultSet, 1, Types.INTEGER, 2);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetFloatVectorFromTable(String queryResultFormat) throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      stmt.execute(\"create or replace table test_vector_float(x vector(float, 2), y float)\");\n      stmt.execute(\"insert into test_vector_float select [-3, 7.1]::vector(float, 2), 20.3\");\n      try (ResultSet resultSet = stmt.executeQuery(\"select x, y from test_vector_float\")) {\n        assertTrue(resultSet.next());\n        Float[] result = resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Float.class);\n        assertArrayEquals(new Float[] {-3f, 7.1f}, result);\n        assertVectorMetadata(resultSet, 1, Types.FLOAT, 2);\n      }\n    }\n  }\n\n  /** Added in > 3.16.1 */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  public void testGetVectorViaGetStringIsEqualToTheGetObject(String queryResultFormat)\n      throws SQLException {\n    try (Statement stmt = createStatement(queryResultFormat)) {\n      enforceQueryResultFormat(stmt, queryResultFormat);\n      Integer[] intVector = {-1, 5};\n      Float[] floatVector = {-1.2f, 5.1f, 15.87f};\n      try (ResultSet resultSet =\n          stmt.executeQuery(\n              \"select \"\n                  + vectorToString(intVector, \"int\")\n                  + \", \"\n                  + vectorToString(floatVector, \"float\")\n                  + \", \"\n                  + nullVectorToString(\"int\")\n                  + \", \"\n                  + nullVectorToString(\"float\"))) {\n\n        assertTrue(resultSet.next());\n        assertGetObjectAndGetStringBeTheSame(resultSet, \"[-1,5]\", 1);\n        String floatArrayRepresentation =\n            \"json\".equalsIgnoreCase(queryResultFormat)\n                // in json we have slightly different format that we accept in the result\n                ? \"[-1.200000,5.100000,15.870000]\"\n                : \"[-1.2,5.1,15.87]\";\n        assertGetObjectAndGetStringBeTheSame(resultSet, floatArrayRepresentation, 2);\n        assertGetObjectAndGetStringAreNull(resultSet, 3);\n        assertGetObjectAndGetStringAreNull(resultSet, 4);\n      }\n    }\n  }\n\n  private static void assertGetObjectAndGetStringBeTheSame(\n      ResultSet resultSet, String intArrayRepresentation, int columnIndex) throws SQLException {\n    assertEquals(intArrayRepresentation, resultSet.getString(columnIndex));\n    assertEquals(intArrayRepresentation, resultSet.getObject(columnIndex));\n  }\n\n  private static void assertGetObjectAndGetStringAreNull(ResultSet resultSet, int columnIndex)\n      throws SQLException {\n    assertNull(resultSet.getString(columnIndex));\n    assertNull(resultSet.getObject(columnIndex));\n  }\n\n  private <T extends Number> String vectorToString(T[] vector, String vectorType) {\n    return Arrays.toString(vector) + \"::vector(\" + vectorType + \", \" + vector.length + \")\";\n  }\n\n  private <T extends Number> String nullVectorToString(String vectorType) {\n    return \"null::vector(\" + vectorType + \", 2)\";\n  }\n\n  private void enforceQueryResultFormat(Statement stmt, String queryResultFormat)\n      throws SQLException {\n    String sql =\n        String.format(\n            \"alter session set jdbc_query_result_format = '%s'\", queryResultFormat.toUpperCase());\n    stmt.execute(sql);\n  }\n\n  private void assertVectorMetadata(\n      ResultSet resultSet, int vectorColumnIndex, int expectedVectorFieldType, int allColumns)\n      throws SQLException {\n    ResultSetMetaData metadata = resultSet.getMetaData();\n    assertEquals(allColumns, metadata.getColumnCount());\n    assertEquals(EXTRA_TYPES_VECTOR, metadata.getColumnType(vectorColumnIndex));\n    assertEquals(\"VECTOR\", metadata.getColumnTypeName(vectorColumnIndex));\n    SnowflakeResultSetMetaDataV1 sfMetadata = (SnowflakeResultSetMetaDataV1) metadata;\n    assertTrue(sfMetadata.isStructuredTypeColumn(vectorColumnIndex));\n    assertEquals(EXTRA_TYPES_VECTOR, sfMetadata.getInternalColumnType(vectorColumnIndex));\n    List<FieldMetadata> columnFields = sfMetadata.getColumnFields(vectorColumnIndex);\n    assertEquals(1, columnFields.size());\n    assertEquals(expectedVectorFieldType, columnFields.get(0).getType());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SSOConnectionTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.any;\nimport static org.mockito.Mockito.anyInt;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.nullable;\nimport static org.mockito.Mockito.when;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.StringWriter;\nimport java.net.ServerSocket;\nimport java.net.Socket;\nimport java.net.URI;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Properties;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpResponseWithHeaders;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFLoginInput;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.core.SessionUtilExternalBrowser;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.http.client.methods.HttpPost;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\n\nclass MockAuthExternalBrowserHandlers\n    implements SessionUtilExternalBrowser.AuthExternalBrowserHandlers {\n\n  @Override\n  public HttpPost build(URI uri) {\n    HttpPost httpPost = mock(HttpPost.class);\n    when(httpPost.getMethod()).thenReturn(\"POST\");\n    return httpPost;\n  }\n\n  @Override\n  public void openBrowser(String ssoUrl) throws SFException {\n    // nop. Don't open browser\n  }\n\n  @Override\n  public void output(String msg) {\n    // nop. No output\n  }\n}\n\nclass FakeSessionUtilExternalBrowser extends SessionUtilExternalBrowser {\n  private static final String MOCK_SAML_TOKEN = \"MOCK_SAML_TOKEN\";\n  private final ServerSocket mockServerSocket;\n\n  FakeSessionUtilExternalBrowser(SFLoginInput loginInput) {\n    super(loginInput, new MockAuthExternalBrowserHandlers());\n    try {\n      this.mockServerSocket = initMockServerSocket();\n    } catch (IOException ex) {\n      throw new RuntimeException(\"Failed to initialize ServerSocket mock\");\n    }\n  }\n\n  /**\n   * Mock ServerSocket and Socket.\n   *\n   * <p>Socket mock will be included in ServerSocket mock.\n   *\n   * @return Server socket\n   * @throws IOException if any IO error occurs\n   */\n  private static ServerSocket initMockServerSocket() throws IOException {\n    // mock client socket\n    final Socket mockSocket = mock(Socket.class);\n    final String str =\n        String.format(\"GET /?token=%s HTTP/1.1\\r\\nUSER-AGENT: snowflake client\", MOCK_SAML_TOKEN);\n    InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));\n    when(mockSocket.getInputStream()).thenReturn(stream);\n    when(mockSocket.getOutputStream()).thenReturn(new NullOutputStream());\n\n    // mock server socket\n    final ServerSocket mockServerSocket = mock(ServerSocket.class);\n    when(mockServerSocket.getLocalPort()).thenReturn(12345);\n    when(mockServerSocket.accept()).thenReturn(mockSocket);\n    return mockServerSocket;\n  }\n\n  static class NullOutputStream extends OutputStream {\n    @Override\n    public void write(int b) throws IOException {}\n  }\n\n  @Override\n  protected ServerSocket getServerSocket() throws SFException {\n    return mockServerSocket;\n  }\n\n  @Override\n  protected int getLocalPort(ServerSocket ssocket) {\n    return super.getLocalPort(ssocket);\n  }\n}\n\npublic class SSOConnectionTest {\n  private static final String MOCK_PROOF_KEY = \"specialkey\";\n  private static final String MOCK_SSO_URL = \"https://sso.someidp.net/\";\n  private static final String MOCK_MASTER_TOKEN = \"MOCK_MASTER_TOKEN\";\n  private static final String MOCK_SESSION_TOKEN = \"MOCK_SESSION_TOKEN\";\n  private static final String MOCK_ID_TOKEN = \"MOCK_ID_TOKEN\";\n  private static final String MOCK_NEW_SESSION_TOKEN = \"MOCK_NEW_SESSION_TOKEN\";\n  private static final String MOCK_NEW_MASTER_TOKEN = \"MOCK_NEW_MASTER_TOKEN\";\n  private static final String ID_TOKEN_AUTHENTICATOR = \"ID_TOKEN\";\n  private static ObjectMapper mapper = new ObjectMapper();\n\n  class HttpUtilResponseDataSSODTO {\n    public String proofKey;\n    public String ssoUrl;\n\n    public HttpUtilResponseDataSSODTO(String proofKey, String ssoUrl) {\n      this.proofKey = proofKey;\n      this.ssoUrl = ssoUrl;\n    }\n  }\n\n  class HttpUtilResponseDataParaDTO {\n    public String name;\n    public String value;\n\n    HttpUtilResponseDataParaDTO(String name, String value) {\n      this.name = name;\n      this.value = value;\n    }\n  }\n\n  class HttpUtilResponseDataAuthDTO {\n    public String token;\n    public String masterToken;\n    public String idToken;\n    public ArrayList<HttpUtilResponseDataParaDTO> parameters =\n        new ArrayList<HttpUtilResponseDataParaDTO>();\n\n    HttpUtilResponseDataAuthDTO(String token, String masterToken, String idToken) {\n      this.token = token;\n      this.masterToken = masterToken;\n      this.idToken = idToken;\n\n      parameters.add(new HttpUtilResponseDataParaDTO(\"AUTOCOMMIT\", \"true\"));\n    }\n  }\n\n  class HttpUtilResponseDTO {\n    public boolean success;\n    public String message;\n    public Object data;\n\n    HttpUtilResponseDTO(boolean success, String message, Object data) {\n      this.success = success;\n      this.message = message;\n      this.data = data;\n    }\n  }\n\n  private void initMock(\n      MockedStatic<HttpUtil> mockedHttpUtil,\n      MockedStatic<SessionUtilExternalBrowser> mockedSessionUtilExternalBrowser)\n      throws Throwable {\n    initMockHttpUtil(mockedHttpUtil);\n    SFLoginInput loginInput = initMockLoginInput();\n    initMockSessionUtilExternalBrowser(mockedSessionUtilExternalBrowser, loginInput);\n  }\n\n  private JsonNode parseRequest(HttpPost post) throws IOException {\n    StringWriter writer = null;\n    String theString;\n    try {\n      writer = new StringWriter();\n      try (InputStream ins = post.getEntity().getContent()) {\n        IOUtils.copy(ins, writer, \"UTF-8\");\n      }\n      theString = writer.toString();\n    } finally {\n      IOUtils.closeQuietly(writer);\n    }\n\n    JsonNode jsonNode = mapper.readTree(theString);\n    return jsonNode;\n  }\n\n  private void initMockHttpUtil(MockedStatic<HttpUtil> mockedHttpUtil) throws IOException {\n    // connect to SSO for the first connection\n    String retInitialSSO =\n        mapper.writeValueAsString(\n            new HttpUtilResponseDTO(\n                true, null, new HttpUtilResponseDataSSODTO(MOCK_PROOF_KEY, MOCK_SSO_URL)));\n\n    // connect to Snowflake for the first connection\n    String retInitialAuthentication =\n        mapper.writeValueAsString(\n            new HttpUtilResponseDTO(\n                true,\n                null,\n                new HttpUtilResponseDataAuthDTO(\n                    MOCK_SESSION_TOKEN, MOCK_MASTER_TOKEN, MOCK_ID_TOKEN)));\n    // connect too Snowflake with the cached idToken\n    String retSecondAuthentication =\n        mapper.writeValueAsString(\n            new HttpUtilResponseDTO(\n                true,\n                null,\n                new HttpUtilResponseDataAuthDTO(\n                    MOCK_NEW_SESSION_TOKEN, MOCK_NEW_MASTER_TOKEN, \"\")));\n\n    mockedHttpUtil\n        .when(\n            () ->\n                HttpUtil.executeGeneralRequestWithContext(\n                    any(HttpPost.class),\n                    anyInt(),\n                    anyInt(),\n                    anyInt(),\n                    anyInt(),\n                    anyInt(),\n                    nullable(HttpClientSettingsKey.class),\n                    nullable(SFBaseSession.class)))\n        .thenAnswer(\n            new Answer<HttpResponseWithHeaders>() {\n              int callCount = 0;\n\n              @Override\n              public HttpResponseWithHeaders answer(InvocationOnMock invocation)\n                  throws IOException {\n                String resp = \"\";\n                final Object[] args = invocation.getArguments();\n                JsonNode jsonNode;\n                if (callCount == 0) {\n                  resp = retInitialSSO;\n                } else if (callCount == 1) {\n                  jsonNode = parseRequest((HttpPost) args[0]);\n                  assertTrue(\n                      jsonNode\n                          .path(\"data\")\n                          .path(\"SESSION_PARAMETERS\")\n                          .path(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\")\n                          .asBoolean());\n                  assertThat(\n                      \"authenticator\",\n                      jsonNode.path(\"data\").path(\"AUTHENTICATOR\").asText(),\n                      equalTo(\"EXTERNALBROWSER\"));\n                  resp = retInitialAuthentication;\n                } else if (callCount == 2) {\n                  jsonNode = parseRequest((HttpPost) args[0]);\n                  assertTrue(\n                      jsonNode\n                          .path(\"data\")\n                          .path(\"SESSION_PARAMETERS\")\n                          .path(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\")\n                          .asBoolean());\n                  assertThat(\n                      \"authenticator\",\n                      jsonNode.path(\"data\").path(\"AUTHENTICATOR\").asText(),\n                      equalTo(ID_TOKEN_AUTHENTICATOR));\n                  assertThat(\n                      \"idToken\",\n                      jsonNode.path(\"data\").path(\"TOKEN\").asText(),\n                      equalTo(MOCK_ID_TOKEN));\n                  resp = retSecondAuthentication;\n                }\n\n                callCount++;\n                return new HttpResponseWithHeaders(resp, new HashMap<>());\n              }\n            });\n  }\n\n  private void initMockSessionUtilExternalBrowser(\n      MockedStatic<SessionUtilExternalBrowser> mockedSessionUtilExternalBrowser,\n      SFLoginInput loginInput) {\n    SessionUtilExternalBrowser fakeExternalBrowser = new FakeSessionUtilExternalBrowser(loginInput);\n    mockedSessionUtilExternalBrowser\n        .when(() -> SessionUtilExternalBrowser.createInstance(Mockito.any(SFLoginInput.class)))\n        .thenReturn(fakeExternalBrowser);\n  }\n\n  private SFLoginInput initMockLoginInput() {\n    SFLoginInput loginInput = mock(SFLoginInput.class);\n    when(loginInput.getServerUrl()).thenReturn(\"https://testaccount.snowflakecomputing.com/\");\n    when(loginInput.getAuthenticator()).thenReturn(\"EXTERNALBROWSER\");\n    when(loginInput.getAccountName()).thenReturn(\"testaccount\");\n    when(loginInput.getUserName()).thenReturn(\"testuser\");\n    when(loginInput.getDisableConsoleLogin()).thenReturn(true);\n    return loginInput;\n  }\n\n  @Test\n  public void testIdTokenInSSO() throws Throwable {\n    try (MockedStatic<HttpUtil> mockedHttpUtil = mockStatic(HttpUtil.class);\n        MockedStatic<SessionUtilExternalBrowser> mockedSessionUtilExternalBrowser =\n            mockStatic(SessionUtilExternalBrowser.class)) {\n\n      initMock(mockedHttpUtil, mockedSessionUtilExternalBrowser);\n      SessionUtil.deleteIdTokenCache(\"testaccount.snowflakecomputing.com\", \"testuser\");\n\n      Properties properties = new Properties();\n      properties.put(\"user\", \"testuser\");\n      properties.put(\"password\", \"testpassword\");\n      properties.put(\"account\", \"testaccount\");\n      properties.put(\"insecureMode\", true);\n      properties.put(\"authenticator\", \"EXTERNALBROWSER\");\n      properties.put(\"CLIENT_STORE_TEMPORARY_CREDENTIAL\", true);\n\n      // connect url\n      String url = \"jdbc:snowflake://testaccount.snowflakecomputing.com\";\n\n      // initial connection getting id token and storing in the cache file.\n      Connection con = DriverManager.getConnection(url, properties);\n      SnowflakeConnectionImpl sfcon = (SnowflakeConnectionImpl) con;\n      assertThat(\"token\", sfcon.getSfSession().getSessionToken(), equalTo(MOCK_SESSION_TOKEN));\n      assertThat(\"idToken\", sfcon.getSfSession().getIdToken(), equalTo(MOCK_ID_TOKEN));\n\n      // second connection reads the cache and use the id token to get the\n      // session token.\n      Connection conSecond = DriverManager.getConnection(url, properties);\n      SnowflakeConnectionImpl sfconSecond = (SnowflakeConnectionImpl) conSecond;\n      assertThat(\n          \"token\", sfconSecond.getSfSession().getSessionToken(), equalTo(MOCK_NEW_SESSION_TOKEN));\n      // we won't get a new id_token here\n      assertThat(\"idToken\", sfcon.getSfSession().getIdToken(), equalTo(MOCK_ID_TOKEN));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/ServiceNameTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.util.HashMap;\nimport java.util.Properties;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpResponseWithHeaders;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.jdbc.telemetry.ExecTimeTelemetryData;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.mockito.Mockito;\n\n/** Unit test for SERVICE_NAME parameter. */\npublic class ServiceNameTest {\n  static final String SERVICE_NAME_KEY = \"SERVICE_NAME\";\n  static final String INITIAL_SERVICE_NAME = \"initialServiceName\";\n  static final String NEW_SERVICE_NAME = \"newServiceName\";\n  static final String AUTOCOMMIT_KEY = \"AUTOCOMMIT\";\n  static final String AUTOCOMMIT_VAL = \"true\";\n\n  private String responseLogin() {\n    return \"{\\n\"\n        + \"  \\\"data\\\" : {\\n\"\n        + \"  \\\"masterToken\\\" : \\\"masterToken\\\",\\n\"\n        + \"  \\\"token\\\" : \\\"sessionToken\\\",\\n\"\n        + \"  \\\"parameters\\\" : [ {\\n\"\n        + \"    \\\"name\\\" : \\\"\"\n        + SERVICE_NAME_KEY\n        + \"\\\",\\n\"\n        + \"    \\\"value\\\" : \\\"\"\n        + INITIAL_SERVICE_NAME\n        + \"\\\"\\n\"\n        + \"  }, {\\n\"\n        + \"    \\\"name\\\" : \\\"\"\n        + AUTOCOMMIT_KEY\n        + \"\\\",\\n\"\n        + \"    \\\"value\\\" : \\\"\"\n        + AUTOCOMMIT_VAL\n        + \"\\\"\\n\"\n        + \" }],\\n\"\n        + \"  \\\"sessionInfo\\\" : {\\n\"\n        + \"    \\\"databaseName\\\" : \\\"TESTDB\\\",\\n\"\n        + \"    \\\"schemaName\\\" : \\\"TESTSCHEMA\\\",\\n\"\n        + \"    \\\"warehouseName\\\" : \\\"TESTWH\\\",\\n\"\n        + \"    \\\"roleName\\\" : \\\"TESTROLE\\\"\\n\"\n        + \"  },\\n\"\n        + \"  \\\"responseData\\\" : null\\n\"\n        + \"  },\\n\"\n        + \"  \\\"message\\\" : null,\\n\"\n        + \"  \\\"code\\\" : null,\\n\"\n        + \"  \\\"success\\\" : true\\n\"\n        + \"}\\n\";\n  }\n\n  private String responseQuery() {\n    return \"{\\n\"\n        + \"  \\\"data\\\" : {\\n\"\n        + \"    \\\"parameters\\\" : [{\\\"name\\\":\\\"\"\n        + SERVICE_NAME_KEY\n        + \"\\\",\\\"value\\\":\\\"\"\n        + NEW_SERVICE_NAME\n        + \"\\\"}],\\n\"\n        + \"    \\\"rowtype\\\" : [{\\\"name\\\":\\\"COUNT(*)\\\",\\\"database\\\":\\\"\\\",\"\n        + \"\\\"schema\\\":\\\"\\\",\\\"table\\\":\\\"\\\",\\\"byteLength\\\":null,\\\"length\\\":null,\"\n        + \"\\\"type\\\":\\\"fixed\\\",\\\"scale\\\":0,\\\"nullable\\\":false,\\\"precision\\\":18} ],\\n\"\n        + \"    \\\"rowset\\\" : [[\\\"123456\\\"] ],\\n\"\n        + \"    \\\"total\\\" : 1,\\n\"\n        + \"    \\\"returned\\\" : 1,\\n\"\n        + \"    \\\"queryId\\\" : \\\"12345-12345-12345\\\",\\n\"\n        + \"    \\\"databaseProvider\\\" : null,\\n\"\n        + \"    \\\"finalDatabaseName\\\" : \\\"TESTDB\\\",\\n\"\n        + \"    \\\"finalSchemaName\\\" : \\\"TESTSCHEMA\\\",\\n\"\n        + \"    \\\"finalWarehouseName\\\" : \\\"TESTWH\\\",\\n\"\n        + \"    \\\"finalRoleName\\\" : \\\"TESTROLE\\\",\\n\"\n        + \"    \\\"numberOfBinds\\\" : 0,\\n\"\n        + \"    \\\"arrayBindSupported\\\" : false,\\n\"\n        + \"    \\\"statementTypeId\\\" : 4096,\\n\"\n        + \"    \\\"version\\\" : 1,\\n\"\n        + \"    \\\"sendResultTime\\\" : 1538693700000\\n\"\n        + \"  },\\n\"\n        + \"  \\\"message\\\" : null,\\n\"\n        + \"  \\\"code\\\" : null,\\n\"\n        + \"  \\\"success\\\" : true\\n\"\n        + \"}\\n\";\n  }\n\n  @Test\n  public void testAddServiceNameToRequestHeader() throws Throwable {\n    try (MockedStatic<HttpUtil> mockedHttpUtil = Mockito.mockStatic(HttpUtil.class)) {\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeGeneralRequestWithContext(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.any(HttpClientSettingsKey.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(new HttpResponseWithHeaders(responseLogin(), new HashMap<>()));\n      mockedHttpUtil\n          .when(\n              () ->\n                  HttpUtil.executeRequest(\n                      Mockito.any(HttpRequestBase.class),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.anyInt(),\n                      Mockito.any(AtomicBoolean.class),\n                      Mockito.anyBoolean(),\n                      Mockito.anyBoolean(),\n                      Mockito.any(HttpClientSettingsKey.class),\n                      Mockito.any(ExecTimeTelemetryData.class),\n                      Mockito.nullable(SFBaseSession.class)))\n          .thenReturn(responseQuery());\n\n      Properties props = new Properties();\n      props.setProperty(SFSessionProperty.ACCOUNT.getPropertyKey(), \"fakeaccount\");\n      props.setProperty(SFSessionProperty.USER.getPropertyKey(), \"fakeuser\");\n      props.setProperty(SFSessionProperty.PASSWORD.getPropertyKey(), \"fakepassword\");\n      props.setProperty(SFSessionProperty.INSECURE_MODE.getPropertyKey(), Boolean.TRUE.toString());\n      try (SnowflakeConnectionImpl con =\n          new SnowflakeConnectionImpl(\n              \"jdbc:snowflake://http://fakeaccount.snowflakecomputing.com\", props)) {\n        assertThat(con.getSfSession().getServiceName(), is(INITIAL_SERVICE_NAME));\n\n        try (SnowflakeStatementImpl stmt = (SnowflakeStatementImpl) con.createStatement()) {\n          stmt.execute(\"SELECT 1\");\n          assertThat(\n              stmt.getConnection()\n                  .unwrap(SnowflakeConnectionImpl.class)\n                  .getSfSession()\n                  .getServiceName(),\n              is(NEW_SERVICE_NAME));\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SessionContextWiremockLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class SessionContextWiremockLatestIT extends BaseWiremockTest {\n\n  private static final String SESSION_CONTEXT_MAPPING =\n      \"/wiremock/mappings/connection/session_context_switches.json\";\n\n  @Test\n  public void testSessionContextReflectsSwitches() throws Exception {\n    importMappingFromResources(SESSION_CONTEXT_MAPPING);\n\n    Properties props = getWiremockProps();\n    String connectStr = String.format(\"jdbc:snowflake://%s:%s\", WIREMOCK_HOST, wiremockHttpPort);\n\n    try (Connection con = DriverManager.getConnection(connectStr, props);\n        Statement statement = con.createStatement()) {\n      SnowflakeConnection sfCon = con.unwrap(SnowflakeConnection.class);\n\n      assertEquals(\"TEST_DB\", sfCon.getDatabase());\n      assertEquals(\"TEST_SCHEMA\", con.getSchema());\n      assertEquals(\"ANALYST\", sfCon.getRole());\n      assertEquals(\"TEST_WH\", sfCon.getWarehouse());\n\n      con.setCatalog(\"SECOND_DB\");\n      assertEquals(\"SECOND_DB\", sfCon.getDatabase());\n      assertEquals(\"SECOND_DB\", con.getCatalog());\n\n      con.setSchema(\"SECOND_SCHEMA\");\n      assertEquals(\"SECOND_SCHEMA\", con.getSchema());\n\n      statement.execute(\"use role PUBLIC\");\n      assertEquals(\"PUBLIC\", sfCon.getRole());\n\n      statement.execute(\"use role ANALYST\");\n      assertEquals(\"ANALYST\", sfCon.getRole());\n\n      statement.execute(\"use database TEST_DB\");\n      assertEquals(\"TEST_DB\", sfCon.getDatabase());\n\n      statement.execute(\"use schema TEST_SCHEMA\");\n      assertEquals(\"TEST_SCHEMA\", con.getSchema());\n    }\n  }\n\n  private static Properties getWiremockProps() {\n    Properties props = new Properties();\n    props.put(\"account\", \"testaccount\");\n    props.put(\"user\", \"testuser\");\n    props.put(\"password\", \"testpassword\");\n    props.put(\"warehouse\", \"testwh\");\n    props.put(\"database\", \"testdb\");\n    props.put(\"schema\", \"testschema\");\n    props.put(\"ssl\", \"off\");\n    props.put(\"insecureMode\", \"true\");\n    return props;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SessionVariablesIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic final class SessionVariablesIT extends AbstractDriverIT {\n  @Test\n  public void testSettingSessionVariablesInConnectionProperties() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"$var1\", \"some example 1\");\n    properties.put(\"$var2\", \"2\");\n    properties.put(\"var3\", \"some example 3\");\n\n    try (Connection con = getConnection(properties);\n        Statement statement = con.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"show variables\")) {\n      Map<String, String> variablesInSession = new HashMap<>();\n      while (resultSet.next()) {\n        variablesInSession.put(resultSet.getString(\"name\"), resultSet.getString(\"value\"));\n      }\n\n      assertEquals(\"some example 1\", variablesInSession.get(\"VAR1\"));\n      assertEquals(\"2\", variablesInSession.get(\"VAR2\"));\n      assertNull(variablesInSession.get(\"VAR3\"));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeAzureClientHandleExceptionLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static com.azure.storage.common.implementation.Constants.HeaderConstants.ERROR_CODE_HEADER_NAME;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.azure.core.http.HttpHeaders;\nimport com.azure.core.http.HttpResponse;\nimport com.azure.storage.blob.models.BlobStorageException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.SocketTimeoutException;\nimport java.security.InvalidKeyException;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeAzureClient;\nimport org.bouncycastle.util.Exceptions;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.Mockito;\n\n/** Test for SnowflakeAzureClient handle exception function */\n@Tag(TestTags.OTHERS)\npublic class SnowflakeAzureClientHandleExceptionLatestIT extends AbstractDriverIT {\n  @TempDir private File tmpFolder;\n  private Connection connection;\n  private SFStatement sfStatement;\n  private SFSession sfSession;\n  private String command;\n  private SnowflakeAzureClient spyingClient;\n  private int overMaxRetry;\n  private int maxRetry;\n\n  @BeforeEach\n  public void setup() throws SQLException {\n    connection = getConnection(\"azureaccount\");\n    sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    Statement statement = connection.createStatement();\n    sfStatement = statement.unwrap(SnowflakeStatementImpl.class).getSfStatement();\n    statement.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n    command = \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testPutGet_stage\";\n    SnowflakeFileTransferAgent agent =\n        new SnowflakeFileTransferAgent(command, sfSession, sfStatement);\n    SnowflakeAzureClient client =\n        SnowflakeAzureClient.createSnowflakeAzureClient(\n            agent.getStageInfo(), agent.getEncryptionMaterial().get(0), sfSession);\n    maxRetry = client.getMaxRetries();\n    overMaxRetry = maxRetry + 1;\n    spyingClient = Mockito.spy(client);\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void error403RenewExpired() throws SQLException, InterruptedException {\n    // Unauthenticated, renew is called.\n    HttpResponse response = mock(HttpResponse.class);\n    when(response.getStatusCode()).thenReturn(403);\n    BlobStorageException storageException =\n        new BlobStorageException(\"Unauthenticated\", response, new Exception());\n    spyingClient.handleStorageException(storageException, 0, \"upload\", sfSession, command, null);\n    Mockito.verify(spyingClient, Mockito.times(2)).renew(Mockito.anyMap());\n\n    // Unauthenticated, backoff with interrupt, renew is called\n    Exception[] exceptionContainer = new Exception[1];\n    Thread thread =\n        new Thread(\n            () -> {\n              try {\n                spyingClient.handleStorageException(\n                    storageException, maxRetry, \"upload\", sfSession, command, null);\n              } catch (SnowflakeSQLException e) {\n                exceptionContainer[0] = e;\n              }\n            });\n    thread.start();\n    thread.interrupt();\n    thread.join();\n    assertNull(exceptionContainer[0], \"Exception must not have been thrown in here\");\n    Mockito.verify(spyingClient, Mockito.times(4)).renew(Mockito.anyMap());\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void error403OverMaxRetryThrow() {\n    HttpResponse response = mock(HttpResponse.class);\n    when(response.getStatusCode()).thenReturn(403);\n    when(response.getHeaders()).thenReturn(new HttpHeaders().add(ERROR_CODE_HEADER_NAME, \"403\"));\n    BlobStorageException storageException =\n        new BlobStorageException(\"Unauthenticated\", response, new Exception());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                storageException, overMaxRetry, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void error403NullSession() {\n    HttpResponse response = mock(HttpResponse.class);\n    when(response.getStatusCode()).thenReturn(403);\n    when(response.getHeaders()).thenReturn(new HttpHeaders().add(ERROR_CODE_HEADER_NAME, \"403\"));\n    BlobStorageException storageException =\n        new BlobStorageException(\"Unauthenticated\", response, new Exception());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                storageException, 0, \"upload\", null, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorInvalidKey() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new Exception(new InvalidKeyException()), 0, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorInterruptedException() throws SQLException {\n    // Can still retry, no error thrown\n    spyingClient.handleStorageException(\n        new InterruptedException(), 0, \"upload\", sfSession, command, null);\n\n    Mockito.verify(spyingClient, Mockito.never()).renew(Mockito.anyMap());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new InterruptedException(), 26, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorSocketTimeoutException() throws SnowflakeSQLException {\n    // Can still retry, no error thrown\n    spyingClient.handleStorageException(\n        new SocketTimeoutException(), 0, \"upload\", sfSession, command, null);\n\n    Mockito.verify(spyingClient, Mockito.never()).renew(Mockito.anyMap());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new SocketTimeoutException(), 26, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorUnknownException() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new Exception(), 0, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorNoSpaceLeftOnDevice() throws IOException {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    String getCommand =\n        \"get @testPutGet_stage/\" + TEST_DATA_FILE + \" 'file://\" + destFolderCanonicalPath + \"'\";\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                Exceptions.ioException(\n                    \"java.util.concurrent.ExecutionException: java.io.IOException: No space left on device\",\n                    new IOException(Constants.NO_SPACE_LEFT_ON_DEVICE_ERR)),\n                0,\n                \"download\",\n                null,\n                getCommand,\n                null));\n  }\n\n  @AfterEach\n  public void cleanUp() throws SQLException {\n    sfStatement.close();\n    connection.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeBasicDataSourceTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\n\nimport java.sql.SQLException;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.api.datasource.SnowflakeDataSource;\nimport net.snowflake.client.api.datasource.SnowflakeDataSourceFactory;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.http.HttpHeadersCustomizer;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport org.junit.jupiter.api.Test;\n\n/** Data source unit test */\npublic class SnowflakeBasicDataSourceTest {\n  /** snow-37186 */\n  @Test\n  public void testSetLoginTimeout() throws SQLException {\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n\n    ds.setLoginTimeout(10);\n    assertThat(ds.getLoginTimeout(), is(10));\n  }\n\n  @Test\n  public void testDataSourceSetters() {\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n\n    ds.setTracing(\"all\");\n    ds.setApplication(\"application_name\");\n    ds.setAccount(\"testaccount\");\n    ds.setAuthenticator(\"snowflake\");\n    ds.setArrowTreatDecimalAsInt(true);\n    ds.setAllowUnderscoresInHost(true);\n    ds.setClientConfigFile(\"/some/path/file.json\");\n    ds.setDisableGcsDefaultCredentials(false);\n    ds.setDisableSamlURLCheck(false);\n    ds.setDisableSocksProxy(false);\n    ds.setEnablePatternSearch(true);\n    ds.setDatabaseName(\"DB_NAME\");\n    ds.setEnablePutGet(false);\n    ds.setMaxHttpRetries(5);\n    ds.setNetworkTimeout(10);\n    ds.setOcspFailOpen(false);\n    ds.setProxyHost(\"proxyHost.com\");\n    ds.setProxyPort(8080);\n    ds.setProxyProtocol(\"http\");\n    ds.setProxyUser(\"proxyUser\");\n    ds.setProxyPassword(\"proxyPassword\");\n    ds.setPutGetMaxRetries(3);\n    ds.setStringsQuotedForColumnDef(true);\n    ds.setEnableDiagnostics(true);\n    ds.setDiagnosticsAllowlistFile(\"/some/path/allowlist.json\");\n\n    Properties props = ds.getProperties();\n    assertEquals(\"testaccount\", props.get(\"account\"));\n    assertEquals(\"snowflake\", props.get(\"authenticator\"));\n    assertEquals(\"all\", props.get(\"tracing\"));\n    assertEquals(\"application_name\", props.get(SFSessionProperty.APPLICATION.getPropertyKey()));\n    assertEquals(\"snowflake\", props.get(SFSessionProperty.AUTHENTICATOR.getPropertyKey()));\n    assertEquals(\n        \"true\", props.get(SFSessionProperty.JDBC_ARROW_TREAT_DECIMAL_AS_INT.getPropertyKey()));\n    assertEquals(\"true\", props.get(SFSessionProperty.ALLOW_UNDERSCORES_IN_HOST.getPropertyKey()));\n    assertEquals(\n        \"/some/path/file.json\", props.get(SFSessionProperty.CLIENT_CONFIG_FILE.getPropertyKey()));\n    assertEquals(\n        \"false\", props.get(SFSessionProperty.DISABLE_GCS_DEFAULT_CREDENTIALS.getPropertyKey()));\n    assertEquals(\"false\", props.get(SFSessionProperty.DISABLE_SAML_URL_CHECK.getPropertyKey()));\n    assertEquals(\"false\", props.get(SFSessionProperty.DISABLE_SOCKS_PROXY.getPropertyKey()));\n    assertEquals(\"true\", props.get(SFSessionProperty.ENABLE_PATTERN_SEARCH.getPropertyKey()));\n    assertEquals(\"DB_NAME\", props.get(SFSessionProperty.DATABASE.getPropertyKey()));\n    assertEquals(\"false\", props.get(SFSessionProperty.ENABLE_PUT_GET.getPropertyKey()));\n    assertEquals(\"5\", props.get(SFSessionProperty.MAX_HTTP_RETRIES.getPropertyKey()));\n    assertEquals(\"10\", props.get(SFSessionProperty.NETWORK_TIMEOUT.getPropertyKey()));\n    assertEquals(\"false\", props.get(SFSessionProperty.OCSP_FAIL_OPEN.getPropertyKey()));\n    assertEquals(\"proxyHost.com\", props.get(SFSessionProperty.PROXY_HOST.getPropertyKey()));\n    assertEquals(\"8080\", props.get(SFSessionProperty.PROXY_PORT.getPropertyKey()));\n    assertEquals(\"http\", props.get(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey()));\n    assertEquals(\"proxyUser\", props.get(SFSessionProperty.PROXY_USER.getPropertyKey()));\n    assertEquals(\"proxyPassword\", props.get(SFSessionProperty.PROXY_PASSWORD.getPropertyKey()));\n    assertEquals(\"3\", props.get(SFSessionProperty.PUT_GET_MAX_RETRIES.getPropertyKey()));\n    assertEquals(\"true\", props.get(SFSessionProperty.STRINGS_QUOTED.getPropertyKey()));\n    assertEquals(\"true\", props.get(SFSessionProperty.ENABLE_DIAGNOSTICS.getPropertyKey()));\n    assertEquals(\n        \"/some/path/allowlist.json\",\n        props.get(SFSessionProperty.DIAGNOSTICS_ALLOWLIST_FILE.getPropertyKey()));\n\n    ds.setToken(\"a_token\");\n    assertEquals(\"a_token\", props.get(SFSessionProperty.TOKEN.getPropertyKey()));\n\n    ds.setPasscodeInPassword(true);\n    assertEquals(\"true\", props.get(SFSessionProperty.PASSCODE_IN_PASSWORD.getPropertyKey()));\n    assertEquals(\n        \"USERNAME_PASSWORD_MFA\", props.get(SFSessionProperty.AUTHENTICATOR.getPropertyKey()));\n\n    ds.setPrivateKeyFile(\"key.p8\", \"pwd\");\n    assertEquals(\"key.p8\", props.get(SFSessionProperty.PRIVATE_KEY_FILE.getPropertyKey()));\n    assertEquals(\"pwd\", props.get(SFSessionProperty.PRIVATE_KEY_PWD.getPropertyKey()));\n    assertEquals(\"SNOWFLAKE_JWT\", props.get(SFSessionProperty.AUTHENTICATOR.getPropertyKey()));\n\n    ds.setPasscodeInPassword(false);\n    ds.setPasscode(\"a_passcode\");\n    assertEquals(\"false\", props.get(SFSessionProperty.PASSCODE_IN_PASSWORD.getPropertyKey()));\n    assertEquals(\n        \"USERNAME_PASSWORD_MFA\", props.get(SFSessionProperty.AUTHENTICATOR.getPropertyKey()));\n    assertEquals(\"a_passcode\", props.get(SFSessionProperty.PASSCODE.getPropertyKey()));\n\n    ds.setPrivateKeyBase64(\"fake_key\", \"pwd\");\n    assertEquals(\"fake_key\", props.get(SFSessionProperty.PRIVATE_KEY_BASE64.getPropertyKey()));\n    assertEquals(\"pwd\", props.get(SFSessionProperty.PRIVATE_KEY_PWD.getPropertyKey()));\n    assertEquals(\"SNOWFLAKE_JWT\", props.get(SFSessionProperty.AUTHENTICATOR.getPropertyKey()));\n  }\n\n  @Test\n  public void testDataSourceWithoutUsernameOrPasswordThrowsExplicitException() {\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n\n    ds.setAccount(\"testaccount\");\n    ds.setAuthenticator(\"snowflake\");\n    Exception e = assertThrows(SnowflakeSQLException.class, ds::getConnection);\n    assertEquals(\n        \"Cannot create connection because username is missing in DataSource properties.\",\n        e.getMessage());\n\n    ds.setUser(\"testuser\");\n    e = assertThrows(SnowflakeSQLException.class, ds::getConnection);\n    assertEquals(\n        \"Cannot create connection because password is missing in DataSource properties.\",\n        e.getMessage());\n  }\n\n  @Test\n  public void testSetsHttpHeadersCustomizers() {\n    List<HttpHeadersCustomizer> customizers =\n        Collections.singletonList(mock(HttpHeadersCustomizer.class));\n    SnowflakeDataSource ds = SnowflakeDataSourceFactory.createDataSource();\n\n    ds.setHttpHeadersCustomizers(customizers);\n\n    Properties properties = ds.getProperties();\n    assertEquals(\n        customizers, properties.get(HttpHeadersCustomizer.HTTP_HEADER_CUSTOMIZERS_PROPERTY_KEY));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeChunkDownloaderLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.List;\nimport java.util.Properties;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\n@Tag(TestTags.CORE)\npublic class SnowflakeChunkDownloaderLatestIT extends BaseJDBCTest {\n  private static String originalProxyHost;\n  private static String originalProxyPort;\n  private static String originalNonProxyHosts;\n\n  @BeforeAll\n  public static void setUp() throws Exception {\n    originalProxyHost = System.getProperty(\"https.proxyHost\");\n    originalProxyPort = System.getProperty(\"https.proxyPort\");\n    originalNonProxyHosts = System.getProperty(\"https.nonProxyHosts\");\n  }\n\n  private static void restoreProperty(String key, String value) {\n    if (value != null) {\n      System.setProperty(key, value);\n    } else {\n      System.clearProperty(key);\n    }\n  }\n\n  @AfterAll\n  public static void tearDown() throws Exception {\n    restoreProperty(\"https.proxyHost\", originalProxyHost);\n    restoreProperty(\"https.proxyPort\", originalProxyPort);\n    restoreProperty(\"https.nonProxyHosts\", originalNonProxyHosts);\n  }\n  /**\n   * Tests that the chunk downloader uses the maxHttpRetries and doesn't enter and infinite loop of\n   * retries.\n   *\n   * @throws SQLException\n   * @throws InterruptedException\n   */\n  @Test\n  public void testChunkDownloaderRetry() throws SQLException, InterruptedException {\n    // set proxy to invalid host and bypass the snowflakecomputing.com domain\n    // this will cause connection issues to the internal stage on fetching\n    System.setProperty(\"https.proxyHost\", \"127.0.0.1\");\n    System.setProperty(\"https.proxyPort\", \"8080\");\n    System.setProperty(\"http.nonProxyHosts\", \"*snowflakecomputing.com\");\n\n    // set max retries\n    Properties properties = new Properties();\n    properties.put(\"maxHttpRetries\", 2);\n\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      // execute a query that will require chunk downloading\n      try (ResultSet resultSet =\n          statement.executeQuery(\n              \"select seq8(), randstr(1000, random()) from table(generator(rowcount => 10000))\")) {\n        List<SnowflakeResultSetSerializable> resultSetSerializables =\n            ((SnowflakeResultSet) resultSet).getResultSetSerializables(100 * 1024 * 1024);\n        SnowflakeResultSetSerializable resultSetSerializable = resultSetSerializables.get(0);\n        SnowflakeChunkDownloader downloader =\n            new SnowflakeChunkDownloader((SnowflakeResultSetSerializableV1) resultSetSerializable);\n        SnowflakeChunkDownloader snowflakeChunkDownloaderSpy = Mockito.spy(downloader);\n        SnowflakeSQLException exception =\n            assertThrows(\n                SnowflakeSQLException.class, snowflakeChunkDownloaderSpy::getNextChunkToConsume);\n        Mockito.verify(snowflakeChunkDownloaderSpy, Mockito.times(2)).getResultStreamProvider();\n        assertTrue(\n            exception.getMessage().contains(\"Max retry reached for the download of chunk#0\"));\n        assertTrue(exception.getMessage().contains(\"retry: 2\"));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeClobTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.SQLException;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeClobTest extends BaseJDBCTest {\n\n  @Test\n  public void testReadCharacterStream() throws SQLException, IOException {\n    SnowflakeClob clob = new SnowflakeClob(\"hello world\");\n    char[] chars = new char[100];\n    Reader reader = clob.getCharacterStream(1, clob.length());\n    int charRead;\n    charRead = reader.read(chars, 0, chars.length);\n    assertEquals(charRead, 11);\n    assertEquals(\"hello world\", clob.toString());\n  }\n\n  @Test\n  public void testReadWriteAsciiStream() throws SQLException, IOException {\n    SnowflakeClob clob = new SnowflakeClob(\"hello world\");\n    clob.setAsciiStream(1);\n    char[] chars = new char[100];\n    InputStream input = clob.getAsciiStream();\n    int charRead;\n    Reader in = new InputStreamReader(input, StandardCharsets.UTF_8);\n    charRead = in.read(chars, 0, chars.length);\n    assertEquals(charRead, 11);\n  }\n\n  @Test\n  public void testFreeBuffer() throws SQLException, IOException {\n    SnowflakeClob clob = new SnowflakeClob(\"hello world\");\n    clob.setCharacterStream(1).close();\n    assertEquals(11, clob.length());\n    clob.free();\n    assertEquals(0, clob.length());\n  }\n\n  @Test\n  public void testGetSubString() throws SQLException {\n    SnowflakeClob clob = new SnowflakeClob();\n    clob.setString(1, \"hello world\", 0, 11);\n    assertEquals(\"world\", clob.getSubString(6, 5));\n    assertEquals(0, clob.position(\"hello\", 1));\n    assertEquals(0, clob.position(new SnowflakeClob(\"hello world\"), 1));\n  }\n\n  @Test\n  public void testInvalidPositionExceptions() {\n    SnowflakeClob clob = new SnowflakeClob();\n\n    assertThrows(SQLException.class, () -> clob.setString(0, \"this should throw an exception\"));\n\n    assertThrows(\n        SQLException.class, () -> clob.setString(0, \"this should throw an exception\", 0, 5));\n\n    assertThrows(SQLException.class, () -> clob.getSubString(0, 1));\n\n    assertThrows(SQLException.class, () -> clob.position(\"this should throw an exception\", 0));\n\n    assertThrows(\n        SQLException.class,\n        () -> clob.position(new SnowflakeClob(\"this should throw an exception\"), 0));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeConnectionImplTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.DefaultSFConnectionHandler.mergeProperties;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.util.Map;\nimport java.util.Properties;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeConnectionImplTest {\n\n  @Test\n  public void testMergeProperties() {\n    SnowflakeConnectString conStr;\n    Map<String, Object> result;\n\n    // testcase 1\n\n    Properties prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"snowman\");\n    conStr = SnowflakeConnectString.parse(\"jdbc:snowflake://testaccount.localhost:8080\", prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(3));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"https://testaccount.localhost:8080/\"));\n\n    // testcase 2\n    prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"snowman\");\n    conStr = SnowflakeConnectString.parse(\"jdbc:snowflake://testaccount.localhost:8080/?\", prop);\n\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(3));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"https://testaccount.localhost:8080/\"));\n\n    // testcase 3\n    prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"snowman\");\n    conStr =\n        SnowflakeConnectString.parse(\"jdbc:snowflake://testaccount.localhost:8080/?aaaa\", prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(3));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"https://testaccount.localhost:8080/\"));\n\n    // testcase 4\n    prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"snowman\");\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://testaccount.localhost:8080/?prop1=value1\", prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"https://testaccount.localhost:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value1\"));\n\n    // testcase 5\n    prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"snowman\");\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://testaccount.localhost:8080/?prop1=value1&ssl=off\", prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"http://testaccount.localhost:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value1\"));\n\n    // testcase 6\n    prop = new Properties();\n    prop.put(\"account\", \"s3testaccount\");\n    prop.put(\"user\", \"snowman\");\n    prop.put(\"ssl\", Boolean.FALSE.toString());\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://testaccount.localhost:8080/?prop1=value1\", prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"http://testaccount.localhost:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value1\"));\n\n    // testcase 7\n    prop = new Properties();\n    prop.put(\"user\", \"snowman\");\n    prop.put(\"ssl\", Boolean.FALSE.toString());\n    prop.put(\"prop1\", \"value2\");\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://testaccount.localhost:8080/?prop1=value1\", prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(result.get(\"SERVERURL\"), is(\"http://testaccount.localhost:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value2\"));\n\n    // testcase 8 (Global URL with no account specified)\n    prop = new Properties();\n    prop.put(\"user\", \"snowman\");\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://testaccount-1234567890qwertyupalsjhfg\"\n                + \".global.snowflakecomputing.com:8080/?prop1=value\",\n            prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(\n        result.get(\"SERVERURL\"),\n        is(\"https://testaccount-1234567890qwertyupalsjhfg.global.snowflakecomputing.com:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value\"));\n\n    // testcase 9 (Global URL with account specified)\n    prop = new Properties();\n    prop.put(\"user\", \"snowman\");\n    prop.put(\"account\", \"s3testaccount\");\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://testaccount-1234567890qwertyupalsjhfg\"\n                + \".global.snowflakecomputing.com:8080/?prop1=value\",\n            prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"s3testaccount\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(\n        result.get(\"SERVERURL\"),\n        is(\"https://testaccount-1234567890qwertyupalsjhfg.global.snowflakecomputing.com:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value\"));\n\n    // testcase 10 (Global URL with dashed account in URL)\n    prop = new Properties();\n    prop.put(\"user\", \"snowman\");\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://test-account-1234567890qwertyupalsjhfg\"\n                + \".global.snowflakecomputing.com:8080/?prop1=value\",\n            prop);\n    result = mergeProperties(conStr);\n\n    assertThat(result.size(), is(4));\n    assertThat(result.get(\"ACCOUNT\"), is(\"test-account\"));\n    assertThat(result.get(\"USER\"), is(\"snowman\"));\n    assertThat(\n        result.get(\"SERVERURL\"),\n        is(\"https://test-account-1234567890qwertyupalsjhfg.global.snowflakecomputing.com:8080/\"));\n    assertThat(result.get(\"PROP1\"), is(\"value\"));\n\n    // test case when http is already embedded in URL\n    prop = new Properties();\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://http://testaccount.localhost:8080/?prop1=value1\", prop);\n    result = mergeProperties(conStr);\n    assertThat(result.get(\"SERVERURL\"), is(\"http://testaccount.localhost:8080/\"));\n\n    prop = new Properties();\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://https://testaccount.localhost:8080/?prop1=value1\", prop);\n    result = mergeProperties(conStr);\n    assertThat(result.get(\"SERVERURL\"), is(\"https://testaccount.localhost:8080/\"));\n\n    // test case for escaped characters\n    prop = new Properties();\n    conStr =\n        SnowflakeConnectString.parse(\n            \"jdbc:snowflake://http://testaccount\"\n                + \".localhost:8080/?prop1=value1%7Cvalue2&prop2=carrot%5E\",\n            prop);\n    result = mergeProperties(conStr);\n    assertThat(result.get(\"PROP1\"), is(\"value1|value2\"));\n    assertThat(result.get(\"PROP2\"), is(\"carrot^\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeDriverConnectionStressTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.AbstractDriverIT;\n\npublic class SnowflakeDriverConnectionStressTest {\n  private static final String QUERY = \"select current_user()\";\n\n  public static void main(String[] args) throws InterruptedException, ExecutionException {\n    final int run_millis = 2 * 60 * 1000;\n    final int dop = 6;\n    final int queries_per_connection = 50;\n    doTest(run_millis, dop, queries_per_connection);\n  }\n\n  private static void doTest(int run_millis, int dop, final int num_queries)\n      throws InterruptedException, ExecutionException {\n    // make sure we can do basic single threaded\n    doConnectWithQuery(1);\n\n    final ExecutorService executorService = Executors.newFixedThreadPool(dop);\n\n    final AtomicBoolean running = new AtomicBoolean(true);\n\n    Runnable r =\n        new Runnable() {\n          @Override\n          public void run() {\n            while (running.get()) {\n              doConnectWithQuery(num_queries);\n            }\n          }\n        };\n\n    List<Future<?>> futures = new ArrayList<>(dop);\n\n    for (int i = 0; i < dop; i++) {\n      final Future<?> f = executorService.submit(r);\n      futures.add(f);\n    }\n\n    Thread.sleep(run_millis);\n\n    running.set(false);\n\n    for (Future<?> future : futures) {\n      // doing this to get any errors\n      future.get();\n    }\n\n    executorService.shutdownNow();\n    executorService.awaitTermination(1, TimeUnit.SECONDS);\n  }\n\n  private static void doConnectWithQuery(int num_queries) {\n    try {\n      final long before = System.currentTimeMillis();\n      connectAndQuery(num_queries);\n      final long diff = System.currentTimeMillis() - before;\n      say(\"connect_with_queries_millis=\" + diff);\n    } catch (SQLException e) {\n      throw new IllegalStateException(e);\n    }\n  }\n\n  private static void connectAndQuery(int num_queries) throws SQLException {\n\n    try (Connection connection = AbstractDriverIT.getConnection();\n        Statement statement = connection.createStatement()) {\n\n      for (int i = 0; i < num_queries; i++) {\n\n        try (ResultSet resultSet = statement.executeQuery(QUERY)) {\n          while (resultSet.next()) {\n            final String user = resultSet.getString(1);\n            assertNotNull(user);\n          }\n        }\n      }\n    }\n  }\n\n  protected static void say(String arg) {\n    System.out.println(\n        System.currentTimeMillis() + \":\" + Thread.currentThread().getId() + \" \" + arg);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeDriverIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.FileWriter;\nimport java.math.BigDecimal;\nimport java.nio.channels.FileChannel;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.Date;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.TimeZone;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.annotations.DontRunOnTestaccount;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\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;\n\n/** General integration tests */\n@Tag(TestTags.OTHERS)\npublic class SnowflakeDriverIT extends BaseJDBCTest {\n  private static final int MAX_CONCURRENT_QUERIES_PER_USER = 50;\n  private static final String getCurrenTransactionStmt = \"SELECT CURRENT_TRANSACTION()\";\n  private static Logger logger = Logger.getLogger(SnowflakeDriverIT.class.getName());\n  private static final String OAUTH_SCOPE_FORMAT = \"session:role:%s\";\n\n  private static final String ORDERS_JDBC_TABLE =\n      \"orders_jdbc_snowflakedriver_\" + SnowflakeUtil.randomAlphaNumeric(10);\n  private static String ORDERS_JDBC = ORDERS_JDBC_TABLE.toUpperCase();\n\n  @TempDir private File tmpFolder;\n  private ObjectMapper mapper = new ObjectMapper();\n\n  @TempDir public File tmpFolder2;\n\n  public String testStageName =\n      String.format(\"test_stage_%s\", UUID.randomUUID().toString()).replaceAll(\"-\", \"_\");\n\n  @BeforeAll\n  public static void setUp() throws Throwable {\n    try (Connection connection = getConnection()) {\n      try (Statement statement = connection.createStatement()) {\n\n        statement.execute(\n            \"create or replace table \"\n                + ORDERS_JDBC_TABLE\n                + \"(C1 STRING NOT NULL COMMENT 'JDBC', \"\n                + \"C2 STRING, C3 STRING, C4 STRING, C5 STRING, C6 STRING, \"\n                + \"C7 STRING, C8 STRING, C9 STRING) \"\n                + \"stage_file_format = (field_delimiter='|' \"\n                + \"error_on_column_count_mismatch=false)\");\n\n        statement.execute(\n            \"create or replace table clustered_jdbc \" + \"(c1 number, c2 number) cluster by (c1)\");\n\n        // put files\n        assertTrue(\n            statement.execute(\n                \"PUT file://\"\n                    + getFullPathFileInResource(TEST_DATA_FILE)\n                    + \" @%\"\n                    + ORDERS_JDBC_TABLE),\n            \"Failed to put a file\");\n        assertTrue(\n            statement.execute(\n                \"PUT file://\"\n                    + getFullPathFileInResource(TEST_DATA_FILE_2)\n                    + \" @%\"\n                    + ORDERS_JDBC_TABLE),\n            \"Failed to put a file\");\n\n        int numRows = statement.executeUpdate(\"copy into \" + ORDERS_JDBC_TABLE);\n\n        assertEquals(73, numRows, \"Unexpected number of rows copied: \" + numRows);\n      }\n    }\n  }\n\n  @AfterAll\n  public static void tearDown() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"drop table if exists clustered_jdbc\");\n      statement.execute(\"drop table if exists \" + ORDERS_JDBC_TABLE);\n    }\n  }\n\n  public static Connection getConnection(int injectSocketTimeout) throws SQLException {\n    Connection connection = AbstractDriverIT.getConnection(injectSocketTimeout);\n\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='America/Los_Angeles',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n    }\n    return connection;\n  }\n\n  public static Connection getConnection() throws SQLException {\n    return getConnection(AbstractDriverIT.DONT_INJECT_SOCKET_TIMEOUT);\n  }\n\n  /** Test connection to database using Snowflake Oauth instead of username/pw * */\n  @Test\n  @DontRunOnGithubActions\n  public void testOauthConnection() throws SQLException {\n    Map<String, String> params = getConnectionParameters();\n    String role = params.get(\"role\");\n    String token = null;\n\n    try (Connection con = getConnection(\"s3testaccount\");\n        Statement statement = con.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n      statement.execute(\n          \"create or replace security integration jdbc_oauth_integration\\n\"\n              + \"  type=oauth\\n\"\n              + \"  oauth_client=CUSTOM\\n\"\n              + \"  oauth_client_type=CONFIDENTIAL\\n\"\n              + \"  oauth_redirect_uri='https://localhost.com/oauth'\\n\"\n              + \"  oauth_issue_refresh_tokens=true\\n\"\n              + \"  enabled=true oauth_refresh_token_validity=86400;\");\n\n      String scope = String.format(OAUTH_SCOPE_FORMAT, role);\n      try (ResultSet rs =\n          statement.executeQuery(\n              \"select system$it('create_oauth_access_token', 'JDBC_OAUTH_INTEGRATION', '\"\n                  + scope\n                  + \"')\")) {\n        assertTrue(rs.next());\n        token = rs.getString(1);\n      }\n    }\n    Properties props = new Properties();\n    props.put(\"authenticator\", \"OAUTH\");\n    props.put(\"token\", token);\n    props.put(\"role\", role);\n    try (Connection con = getConnection(\"s3testaccount\", props);\n        Statement statement = con.createStatement()) {\n      statement.execute(\"select 1\");\n    }\n  }\n\n  @Disabled\n  @Test\n  public void testConnections() throws Throwable {\n    ExecutorService executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_QUERIES_PER_USER);\n\n    List<Future<?>> futures = new ArrayList<>();\n\n    // create 30 threads, each open a connection and submit a query that\n    // runs for 10 seconds\n    for (int idx = 0; idx < MAX_CONCURRENT_QUERIES_PER_USER; idx++) {\n      logger.info(\"open a new connection and submit query \" + idx);\n\n      final int queryIdx = idx;\n\n      futures.add(\n          executorService.submit(\n              () -> {\n                try (Connection connection = getConnection();\n                    Statement statement = connection.createStatement();\n                    ResultSet resultSet = statement.executeQuery(\"SELECT system$sleep(10) % 1\")) {\n                  ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n                  // assert column count\n                  assertEquals(1, resultSetMetaData.getColumnCount());\n\n                  // assert we get 1 row\n                  for (int i = 0; i < 1; i++) {\n                    assertTrue(resultSet.next());\n\n                    // assert each column is not null except the last one\n                    for (int j = 1; j < 2; j++) {\n                      assertEquals(0, resultSet.getInt(j));\n                    }\n                  }\n\n                  logger.info(\"Query \" + queryIdx + \" passed \");\n                }\n                return true;\n              }));\n    }\n\n    executorService.shutdown();\n\n    for (int idx = 0; idx < MAX_CONCURRENT_QUERIES_PER_USER; idx++) {\n      futures.get(idx).get();\n    }\n  }\n\n  /** Test show columns */\n  @Test\n  public void testShowColumns() throws Throwable {\n    Properties paramProperties = new Properties();\n    try (Connection connection = getConnection(paramProperties);\n        Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"show columns in clustered_jdbc\")) {\n      assertEquals(2, countRows(resultSet), \"number of columns\");\n    }\n  }\n\n  private int countRows(ResultSet rset) throws Throwable {\n    int cnt = 0;\n    while (rset.next()) {\n      cnt++;\n    }\n    return cnt;\n  }\n\n  @Test\n  public void testRowsPerResultset() throws Throwable {\n    try (Connection connection = getConnection()) {\n      connection.createStatement().execute(\"alter session set rows_per_resultset=2048\");\n\n      try (Statement statement = connection.createStatement();\n          ResultSet resultSet = statement.executeQuery(\"SELECT * FROM \" + ORDERS_JDBC_TABLE)) {\n        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n        int numColumns = resultSetMetaData.getColumnCount();\n        assertEquals(9, numColumns);\n        assertEquals(73, countRows(resultSet), \"number of columns\");\n      }\n    }\n  }\n\n  @Test\n  public void testDDLs() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE TABLE testDDLs(version number, name string)\");\n      } finally {\n        statement.execute(\"DROP TABLE testDDLs\");\n      }\n    }\n  }\n\n  private long getCurrentTransaction(Connection connection) throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(getCurrenTransactionStmt);\n      try (ResultSet rs = statement.getResultSet()) {\n        if (rs.next()) {\n          String txnId = rs.getString(1);\n          return txnId != null ? Long.valueOf(txnId) : 0L;\n        }\n      }\n    }\n\n    throw new SQLException(getCurrenTransactionStmt + \" didn't return a result.\");\n  }\n\n  /** Tests autocommit */\n  @Test\n  public void testAutocommit() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // 1. test commit\n        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);\n        assertEquals(Connection.TRANSACTION_READ_COMMITTED, connection.getTransactionIsolation());\n        connection.setAutoCommit(false); // disable autocommit\n        assertFalse(connection.getAutoCommit());\n\n        assertEquals(0, getCurrentTransaction(connection));\n\n        // create a table, this should not start a transaction\n        statement.executeUpdate(\"CREATE OR REPLACE TABLE AUTOCOMMIT_API_TEST (i int)\");\n        assertEquals(0, getCurrentTransaction(connection));\n\n        // insert into it this should start a transaction.\n        statement.executeUpdate(\"INSERT INTO AUTOCOMMIT_API_TEST VALUES (1)\");\n        assertNotEquals(0, getCurrentTransaction(connection));\n\n        // commit it using the api\n        connection.commit();\n        assertFalse(connection.getAutoCommit());\n        assertEquals(0, getCurrentTransaction(connection));\n        try (ResultSet resultSet =\n            statement.executeQuery(\"SELECT COUNT(*) FROM AUTOCOMMIT_API_TEST WHERE i = 1\")) {\n          assertTrue(resultSet.next());\n          assertEquals(1, resultSet.getInt(1));\n        }\n        // 2. test rollback ==\n        // delete from the table, should start a transaction.\n        statement.executeUpdate(\"DELETE FROM AUTOCOMMIT_API_TEST\");\n        assertNotEquals(0, getCurrentTransaction(connection));\n\n        // roll it back using the api\n        connection.rollback();\n        assertFalse(connection.getAutoCommit());\n        assertEquals(0, getCurrentTransaction(connection));\n        try (ResultSet resultSet =\n            statement.executeQuery(\"SELECT COUNT(*) FROM AUTOCOMMIT_API_TEST WHERE i = 1\")) {\n          assertTrue(resultSet.next());\n          assertEquals(1, resultSet.getInt(1));\n        }\n      } finally {\n        statement.execute(\"DROP TABLE AUTOCOMMIT_API_TEST\");\n      }\n    }\n  }\n\n  /**\n   * Assert utility function for constraints. It asserts that result contains the specified number\n   * of rows, and for each row the primary key table name and foreign key table name matches the\n   * expected input.\n   */\n  private void assertConstraintResults(\n      ResultSet resultSet, int numRows, int numCols, String pkTableName, String fkTableName)\n      throws Throwable {\n    ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n    // assert column count\n    assertEquals(numCols, resultSetMetaData.getColumnCount());\n\n    // primary key for testConstraintsP1 should contain two rows\n    for (int i = 0; i < numRows; i++) {\n      assertTrue(resultSet.next(), \"get constraint result row count\");\n\n      if (pkTableName != null) {\n        assertTrue(\n            pkTableName.equalsIgnoreCase(resultSet.getString(3)),\n            \"get constraint result primary table name\");\n      }\n\n      if (fkTableName != null) {\n        assertTrue(\n            fkTableName.equalsIgnoreCase(resultSet.getString(7)),\n            \"get constraint result foreign table name\");\n      }\n    }\n  }\n\n  @Test\n  public void testBoolean() throws Throwable {\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n\n        DatabaseMetaData metadata = connection.getMetaData();\n\n        // Create a table with boolean columns\n        statement.execute(\"create or replace table testBooleanT1(c1 boolean)\");\n\n        // Insert values into the table\n        statement.execute(\"insert into testBooleanT1 values(true), (false), (null)\");\n\n        // Get values from the table\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select c1 from testBooleanT1\")) {\n\n          // I. Test ResultSetMetaData interface\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n            // Verify the column type is Boolean\n            assertEquals(Types.BOOLEAN, resultSetMetaData.getColumnType(1));\n\n            // II. Test DatabaseMetadata interface\n            try (ResultSet columnMetaDataResultSet =\n                metadata.getColumns(\n                    null, // catalog\n                    null, // schema\n                    \"TESTBOOLEANT1\", // table\n                    null // column\n                    )) {\n              resultSetMetaData = columnMetaDataResultSet.getMetaData();\n              // assert column count\n              assertEquals(24, resultSetMetaData.getColumnCount());\n\n              assertTrue(columnMetaDataResultSet.next());\n              assertEquals(Types.BOOLEAN, columnMetaDataResultSet.getInt(5));\n            }\n          }\n        }\n      } finally {\n        statement.execute(\"drop table testBooleanT1\");\n      }\n    }\n  }\n\n  @Test\n  public void testConstraints() throws Throwable {\n    ResultSet manualResultSet = null;\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n\n        DatabaseMetaData metadata = connection.getMetaData();\n\n        // Create primary key tables\n        statement.execute(\n            \"CREATE OR REPLACE TABLE testConstraintsP1(c1 number unique, c2 \"\n                + \"number, constraint cons0 primary key (c1, c2))\");\n\n        statement.execute(\n            \"CREATE OR REPLACE TABLE testConstraintsP2(c1 number \"\n                + \"constraint cons1 primary key, c2 number)\");\n\n        // Create foreign key tables\n        statement.execute(\n            \"CREATE OR REPLACE TABLE testConstraintsF1(c1 number, c2 number, \"\n                + \"constraint cons3 foreign key (c1, c2) references \"\n                + \"testConstraintsP1(c1, c2))\");\n\n        statement.execute(\n            \"CREATE OR REPLACE TABLE testConstraintsF2(c1 number, c2 number, \"\n                + \"constraint cons4 foreign key (c1, c2) references \"\n                + \"testConstraintsP1(c1, c2), constraint cons5 \"\n                + \"foreign key (c2) references testConstraintsP2(c1))\");\n\n        // show primary keys\n        try (ResultSet resultSet = metadata.getPrimaryKeys(null, null, \"TESTCONSTRAINTSP1\")) {\n\n          // primary key for testConstraintsP1 should contain two rows\n          assertConstraintResults(resultSet, 2, 6, \"testConstraintsP1\", null);\n        }\n\n        ResultSet resultSet1 = metadata.getPrimaryKeys(null, null, \"TESTCONSTRAINTSP2\");\n\n        // primary key for testConstraintsP2 contains 1 row\n        assertConstraintResults(resultSet1, 1, 6, \"testConstraintsP2\", null);\n        resultSet1.close();\n        assertFalse(resultSet1.next());\n\n        // Show imported keys\n        try (ResultSet resultSet = metadata.getImportedKeys(null, null, \"TESTCONSTRAINTSF1\")) {\n          assertConstraintResults(resultSet, 2, 14, null, \"testConstraintsF1\");\n        }\n\n        manualResultSet = metadata.getImportedKeys(null, null, \"TESTCONSTRAINTSF2\");\n\n        assertConstraintResults(manualResultSet, 3, 14, null, \"testConstraintsF2\");\n        manualResultSet.close();\n        assertFalse(manualResultSet.next());\n\n        // show exported keys\n        try (ResultSet resultSet = metadata.getExportedKeys(null, null, \"TESTCONSTRAINTSP1\")) {\n          assertConstraintResults(resultSet, 4, 14, \"testConstraintsP1\", null);\n        }\n\n        manualResultSet = metadata.getExportedKeys(null, null, \"TESTCONSTRAINTSP2\");\n\n        assertConstraintResults(manualResultSet, 1, 14, \"testConstraintsP2\", null);\n        manualResultSet.close();\n        assertFalse(manualResultSet.next());\n\n        // show cross references\n        try (ResultSet resultSet =\n            metadata.getCrossReference(\n                null, null, \"TESTCONSTRAINTSP1\", null, null, \"TESTCONSTRAINTSF1\")) {\n          assertConstraintResults(resultSet, 2, 14, \"testConstraintsP1\", \"testConstraintsF1\");\n        }\n\n        try (ResultSet resultSet =\n            metadata.getCrossReference(\n                null, null, \"TESTCONSTRAINTSP2\", null, null, \"TESTCONSTRAINTSF2\")) {\n          assertConstraintResults(resultSet, 1, 14, \"testConstraintsP2\", \"testConstraintsF2\");\n        }\n\n        try (ResultSet resultSet =\n            metadata.getCrossReference(\n                null, null, \"TESTCONSTRAINTSP1\", null, null, \"TESTCONSTRAINTSF2\")) {\n          assertConstraintResults(resultSet, 2, 14, \"testConstraintsP1\", \"testConstraintsF2\");\n        }\n\n        manualResultSet =\n            metadata.getCrossReference(\n                null, null, \"TESTCONSTRAINTSP2\", null, null, \"TESTCONSTRAINTSF1\");\n\n        assertFalse(\n            manualResultSet.next(),\n            \"cross reference from testConstraintsP2 to \" + \"testConstraintsF2 should be empty\");\n        manualResultSet.close();\n        assertFalse(manualResultSet.next());\n      } finally {\n        statement.execute(\"DROP TABLE TESTCONSTRAINTSF1\");\n        statement.execute(\"DROP TABLE TESTCONSTRAINTSF2\");\n        statement.execute(\"DROP TABLE TESTCONSTRAINTSP1\");\n        statement.execute(\"DROP TABLE TESTCONSTRAINTSP2\");\n      }\n    }\n  }\n\n  @Test\n  public void testQueryWithMaxRows() throws Throwable {\n    final int maxRows = 30;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.setMaxRows(maxRows);\n      try (ResultSet resultSet = statement.executeQuery(\"SELECT * FROM \" + ORDERS_JDBC_TABLE)) {\n\n        // assert column count\n        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n        assertEquals(9, resultSetMetaData.getColumnCount());\n        assertEquals(maxRows, countRows(resultSet));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testCancelQueryBySystemFunction() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement getSessionIdStmt = connection.createStatement()) {\n      getSessionIdStmt.setMaxRows(30);\n      try (ResultSet resultSet = getSessionIdStmt.executeQuery(\"SELECT current_session()\")) {\n        assertTrue(resultSet.next());\n        final long sessionId = resultSet.getLong(1);\n        Timer timer = new Timer();\n        timer.schedule(\n            new TimerTask() {\n              @Override\n              public void run() {\n                try {\n                  PreparedStatement cancelAll;\n                  cancelAll = connection.prepareStatement(\"call system$cancel_all_queries(?)\");\n\n                  // bind integer\n                  cancelAll.setLong(1, sessionId);\n                  cancelAll.executeQuery();\n                } catch (SQLException ex) {\n                  logger.log(Level.SEVERE, \"Cancel failed with exception {}\", ex);\n                }\n              }\n            },\n            5000);\n      }\n      // execute a query for 120s\n      try (Statement statement = connection.createStatement()) {\n        statement.setMaxRows(30);\n        SQLException ex =\n            assertThrows(\n                SQLException.class,\n                () ->\n                    statement\n                        .executeQuery(\"SELECT count(*) FROM TABLE(generator(timeLimit => 120))\")\n                        .close());\n        assertEquals(SqlState.QUERY_CANCELED, ex.getSQLState(), \"sqlstate mismatch\");\n      }\n    }\n  }\n\n  @Test\n  public void testDBMetadata() throws Throwable {\n    int cnt = 0;\n    try (Connection connection = getConnection()) {\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\"alter SESSION set CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX=true\");\n      }\n      // get database metadata\n      DatabaseMetaData metaData = connection.getMetaData();\n\n      // the following will issue\n      try (ResultSet databaseSet = metaData.getCatalogs()) {\n        assertTrue(databaseSet.next(), \"databases shouldn't be empty\");\n\n        // \"show schemas in [databaseName]\"\n        ResultSet schemaSet = metaData.getSchemas(connection.getCatalog(), connection.getSchema());\n        assertTrue(schemaSet.next(), \"schemas shouldn't be empty\");\n        assertTrue(\n            connection.getCatalog().equalsIgnoreCase(schemaSet.getString(2)),\n            \"database should be \" + connection.getCatalog());\n        assertTrue(\n            connection.getSchema().equalsIgnoreCase(schemaSet.getString(1)),\n            \"schema should be \" + connection.getSchema());\n        // snow tables in a schema\n        try (ResultSet tableSet =\n            metaData.getTables(\n                connection.getCatalog(), connection.getSchema(), ORDERS_JDBC, null)) { // types\n          assertTrue(\n              tableSet.next(),\n              String.format(\n                  \"table %s should exists in db: %s, schema: %s\",\n                  ORDERS_JDBC, connection.getCatalog(), connection.getSchema()));\n          assertTrue(\n              connection.getCatalog().equalsIgnoreCase(schemaSet.getString(2)),\n              \"database should be \" + connection.getCatalog());\n          assertTrue(\n              connection.getSchema().equalsIgnoreCase(schemaSet.getString(1)),\n              \"schema should be \" + connection.getSchema());\n          assertTrue(\n              ORDERS_JDBC.equalsIgnoreCase(tableSet.getString(3)),\n              \"table should be \" + ORDERS_JDBC);\n        }\n      }\n\n      try (ResultSet tableMetaDataResultSet =\n          metaData.getTables(\n              null, // catalog\n              null, // schema\n              ORDERS_JDBC, // table\n              null)) { // types\n\n        ResultSetMetaData resultSetMetaData = tableMetaDataResultSet.getMetaData();\n\n        assertEquals(10, resultSetMetaData.getColumnCount());\n\n        // assert we get 1 rows\n        cnt = 0;\n        while (tableMetaDataResultSet.next()) {\n          assertTrue(ORDERS_JDBC.equalsIgnoreCase(tableMetaDataResultSet.getString(3)));\n          ++cnt;\n        }\n        assertEquals(1, cnt, \"number of tables\");\n      }\n      // test pattern\n      try (ResultSet tableMetaDataResultSet =\n          metaData.getTables(\n              null, // catalog\n              null, // schema\n              \"%\", // table\n              null)) { // types\n\n        ResultSetMetaData resultSetMetaData = tableMetaDataResultSet.getMetaData();\n\n        // assert column count\n        assertEquals(10, resultSetMetaData.getColumnCount());\n\n        // assert we get our unique orders table\n        boolean found = false;\n        while (tableMetaDataResultSet.next()) {\n          // assert the table name\n          if (ORDERS_JDBC.equalsIgnoreCase(tableMetaDataResultSet.getString(3))) {\n            found = true;\n            break;\n          }\n        }\n        assertTrue(found, \"orders table not found\");\n      }\n\n      // get column metadata\n      try (ResultSet columnMetaDataResultSet = metaData.getColumns(null, null, ORDERS_JDBC, null)) {\n\n        ResultSetMetaData resultSetMetaData = columnMetaDataResultSet.getMetaData();\n\n        // assert column count\n        assertEquals(24, resultSetMetaData.getColumnCount());\n\n        // assert we get 9 rows\n        cnt = 0;\n        while (columnMetaDataResultSet.next()) {\n          // SNOW-16881: assert database name\n          assertTrue(\n              connection.getCatalog().equalsIgnoreCase(columnMetaDataResultSet.getString(1)));\n\n          // assert the table name and column name, data type and type name\n          assertTrue(ORDERS_JDBC.equalsIgnoreCase(columnMetaDataResultSet.getString(3)));\n\n          assertTrue(columnMetaDataResultSet.getString(4).startsWith(\"C\"));\n\n          assertEquals(Types.VARCHAR, columnMetaDataResultSet.getInt(5));\n\n          assertTrue(\"VARCHAR\".equalsIgnoreCase(columnMetaDataResultSet.getString(6)));\n\n          if (cnt == 0) {\n            // assert comment\n            assertEquals(\"JDBC\", columnMetaDataResultSet.getString(12));\n\n            // assert nullable\n            assertEquals(DatabaseMetaData.columnNoNulls, columnMetaDataResultSet.getInt(11));\n\n            // assert is_nullable\n            assertEquals(\"NO\", columnMetaDataResultSet.getString(18));\n          }\n          ++cnt;\n        }\n        assertEquals(9, cnt);\n      }\n\n      // create a table with mix cases\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\"create or replace table \\\"testDBMetadata\\\" (a timestamp_ltz)\");\n        try (ResultSet columnMetaDataResultSet =\n            metaData.getColumns(null, null, \"testDBMetadata\", null)) {\n\n          // assert we get 1 row\n          cnt = 0;\n          while (columnMetaDataResultSet.next()) {\n            // assert the table name and column name, data type and type name\n            assertTrue(\"testDBMetadata\".equalsIgnoreCase(columnMetaDataResultSet.getString(3)));\n\n            assertEquals(Types.TIMESTAMP, columnMetaDataResultSet.getInt(5));\n\n            assertTrue(columnMetaDataResultSet.getString(4).equalsIgnoreCase(\"a\"));\n            cnt++;\n          }\n          assertEquals(1, cnt);\n        }\n      }\n      connection.createStatement().execute(\"DROP TABLE IF EXISTS \\\"testDBMetadata\\\"\");\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(\n      booleans = {\n        /*true, */\n        false\n      })\n  @DontRunOnGithubActions\n  public void testPutWithWildcardGCP(boolean useAwsSDKStrategy) throws Throwable {\n    if (useAwsSDKStrategy) {\n      SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n    }\n\n    Properties _connectionProperties = new Properties();\n    _connectionProperties.put(\"inject_wait_in_put\", 5);\n    _connectionProperties.put(\"ssl\", \"off\");\n    try (Connection connection =\n            getConnection(\n                DONT_INJECT_SOCKET_TIMEOUT, _connectionProperties, false, false, \"gcpaccount\");\n        Statement statement = connection.createStatement()) {\n      String stageName = \"wildcard_stage_\" + SnowflakeUtil.randomAlphaNumeric(10);\n      try {\n        String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n        // replace file name with wildcard character\n        sourceFilePath = sourceFilePath.replace(\"orders_100.csv\", \"orders_10*.csv\");\n\n        File destFolder = new File(tmpFolder, \"dest\");\n        destFolder.mkdirs();\n        String destFolderCanonicalPath = destFolder.getCanonicalPath();\n        String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n        statement.execute(\"alter session set ENABLE_GCP_PUT_EXCEPTION_FOR_OLD_DRIVERS=false\");\n        statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n        assertTrue(\n            statement.execute(\"PUT file://\" + sourceFilePath + \" @\" + stageName),\n            \"Failed to put a file\");\n\n        findFile(statement, \"ls @\" + stageName + \"/\");\n\n        assertTrue(\n            statement.execute(\n                \"GET @\" + stageName + \" 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n            \"Failed to get files\");\n\n        File downloaded;\n        // download the files we just uploaded to stage\n        for (int i = 0; i < fileNames.length; i++) {\n          // Make sure that the downloaded file exists, it should be gzip compressed\n          downloaded = new File(destFolderCanonicalPathWithSeparator + fileNames[i] + \".gz\");\n          assertTrue(downloaded.exists());\n\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + fileNames[i] + \".gz\");\n          p.waitFor();\n\n          String individualFilePath = sourceFilePath.replace(\"orders_10*.csv\", fileNames[i]);\n\n          File original = new File(individualFilePath);\n          File unzipped = new File(destFolderCanonicalPathWithSeparator + fileNames[i]);\n          assertEquals(original.length(), unzipped.length());\n          assertTrue(FileUtils.contentEquals(original, unzipped));\n        }\n      } finally {\n        statement.execute(\"DROP STAGE IF EXISTS \" + stageName);\n      }\n    } finally {\n      SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n    }\n  }\n\n  /**\n   * helper function for creating large file in Java. Copies info from 1 file to another\n   *\n   * @param file1 file with info to be copied\n   * @param file2 file to be copied into\n   * @throws Exception\n   */\n  private void copyContentFrom(File file1, File file2) throws Exception {\n    FileInputStream inputStream = new FileInputStream(file1);\n    FileOutputStream outputStream = new FileOutputStream(file2);\n    try (FileChannel fIn = inputStream.getChannel();\n        FileChannel fOut = outputStream.getChannel()) {\n      fOut.transferFrom(fIn, 0, fIn.size());\n      fIn.position(0);\n      fOut.transferFrom(fIn, fIn.size(), fIn.size());\n    }\n  }\n\n  @ParameterizedTest\n  @ValueSource(\n      booleans = {\n        /*true, */\n        false\n      })\n  @DontRunOnGithubActions\n  public void testPutGetLargeFileGCP(boolean useAwsSDKStrategy) throws Throwable {\n    if (useAwsSDKStrategy) {\n      SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n    }\n    try (Connection connection = getConnection(\"gcpaccount\");\n        Statement statement = connection.createStatement()) {\n      try {\n        File destFolder = new File(tmpFolder, \"dest\");\n        destFolder.mkdirs();\n        String destFolderCanonicalPath = destFolder.getCanonicalPath();\n        String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n        File largeTempFile = new File(tmpFolder, \"largeFile.csv\");\n        largeTempFile.createNewFile();\n        try (BufferedWriter bw = new BufferedWriter(new FileWriter(largeTempFile))) {\n          bw.write(\"Creating large test file for GCP PUT/GET test\");\n          bw.write(System.lineSeparator());\n          bw.write(\"Creating large test file for GCP PUT/GET test\");\n          bw.write(System.lineSeparator());\n        }\n        File largeTempFile2 = new File(tmpFolder, \"largeFile2.csv\");\n        largeTempFile2.createNewFile();\n\n        String sourceFilePath = largeTempFile.getCanonicalPath();\n\n        // copy info from 1 file to another and continue doubling file size until we reach ~1.5GB,\n        // which is a large file\n        for (int i = 0; i < 12; i++) {\n          copyContentFrom(largeTempFile, largeTempFile2);\n          copyContentFrom(largeTempFile2, largeTempFile);\n        }\n\n        statement.execute(\"alter session set ENABLE_GCP_PUT_EXCEPTION_FOR_OLD_DRIVERS=false\");\n\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE largefile_stage\");\n        assertTrue(\n            statement.execute(\"PUT file://\" + sourceFilePath + \" @largefile_stage\"),\n            \"Failed to put a file\");\n\n        // check that file exists in stage after PUT\n        findFile(statement, \"ls @largefile_stage/\");\n\n        // create a new table with columns matching CSV file\n        statement.execute(\"create or replace table large_table (colA string)\");\n        // copy rows from file into table\n        statement.execute(\"copy into large_table from @largefile_stage/largeFile.csv.gz\");\n        // copy back from table into different stage\n        statement.execute(\"create or replace stage extra_stage\");\n        statement.execute(\"copy into @extra_stage/bigFile.csv.gz from large_table single=true\");\n\n        // get file from new stage\n        assertTrue(\n            statement.execute(\n                \"GET @extra_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n            \"Failed to get files\");\n\n        // Make sure that the downloaded file exists; it should be gzip compressed\n        File downloaded = new File(destFolderCanonicalPathWithSeparator + \"bigFile.csv.gz\");\n        assertTrue(downloaded.exists());\n\n        // unzip the file\n        Process p =\n            Runtime.getRuntime()\n                .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + \"bigFile.csv.gz\");\n        p.waitFor();\n\n        // compare the original file with the file that's been uploaded, copied into a table, copied\n        // back into a stage,\n        // downloaded, and unzipped\n        File unzipped = new File(destFolderCanonicalPathWithSeparator + \"bigFile.csv\");\n        assertEquals(largeTempFile.length(), unzipped.length());\n        assertTrue(FileUtils.contentEquals(largeTempFile, unzipped));\n      } finally {\n        statement.execute(\"DROP STAGE IF EXISTS largefile_stage\");\n        statement.execute(\"DROP STAGE IF EXISTS extra_stage\");\n        statement.execute(\"DROP TABLE IF EXISTS large_table\");\n      }\n    } finally {\n      SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutOverwrite() throws Throwable {\n    // create 2 files: an original, and one that will overwrite the original\n    File file1 = new File(tmpFolder, \"testfile.csv\");\n    file1.createNewFile();\n    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file1))) {\n      bw.write(\"Writing original file content. This should get overwritten.\");\n    }\n\n    File file2 = new File(tmpFolder2, \"testfile.csv\");\n    file2.createNewFile();\n    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file2))) {\n      bw.write(\"This is all new! This should be the result of the overwriting.\");\n    }\n\n    String sourceFilePathOriginal = file1.getCanonicalPath();\n    String sourceFilePathOverwrite = file2.getCanonicalPath();\n\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n    List<String> accounts =\n        Arrays.asList(\n            null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\" /*, \"gcpaccount_awssdk\"*/);\n    for (int i = 0; i < accounts.size(); i++) {\n      String accountName = accounts.get(i);\n      if (accounts.get(i) != null && accounts.get(i).equals(\"gcpaccount_awssdk\")) {\n        accountName = \"gcpaccount\";\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n      }\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        String stageName = \"overwrite_stage_\" + SnowflakeUtil.randomAlphaNumeric(10);\n        try {\n          statement.execute(\"alter session set ENABLE_GCP_PUT_EXCEPTION_FOR_OLD_DRIVERS=false\");\n\n          // create a stage to put the file in\n          statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n          assertTrue(\n              statement.execute(\"PUT file://\" + sourceFilePathOriginal + \" @\" + stageName),\n              \"Failed to put a file\");\n          // check that file exists in stage after PUT\n          findFile(statement, \"ls @\" + stageName + \"/\");\n\n          // put another file in same stage with same filename with overwrite = true\n          assertTrue(\n              statement.execute(\n                  \"PUT file://\" + sourceFilePathOverwrite + \" @\" + stageName + \" overwrite=true\"),\n              \"Failed to put a file\");\n\n          // check that file exists in stage after PUT\n          findFile(statement, \"ls @\" + stageName + \"/\");\n\n          // get file from new stage\n          assertTrue(\n              statement.execute(\n                  \"GET @\" + stageName + \" 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n              \"Failed to get files\");\n\n          // Make sure that the downloaded file exists; it should be gzip compressed\n          File downloaded = new File(destFolderCanonicalPathWithSeparator + \"testfile.csv.gz\");\n          assertTrue(downloaded.exists());\n\n          // unzip the file\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + \"testfile.csv.gz\");\n          p.waitFor();\n\n          File unzipped = new File(destFolderCanonicalPathWithSeparator + \"testfile.csv\");\n          assertTrue(FileUtils.contentEqualsIgnoreEOL(file2, unzipped, null));\n        } finally {\n          statement.execute(\"DROP TABLE IF EXISTS testLoadToLocalFS\");\n          statement.execute(\"DROP STAGE IF EXISTS \" + stageName);\n        }\n      } finally {\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPut() throws Throwable {\n\n    List<String> accounts =\n        Arrays.asList(\n            null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\" /*, \"gcpaccount_awssdk\"*/);\n    for (int i = 0; i < accounts.size(); i++) {\n      String accountName = accounts.get(i);\n      if (accounts.get(i) != null && accounts.get(i).equals(\"gcpaccount_awssdk\")) {\n        accountName = \"gcpaccount\";\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n      }\n      if (accounts.get(i) == null || !accounts.get(i).startsWith((\"gcp\"))) {\n        continue;\n      }\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        try {\n          // load file test\n          // create a unique data file name by using current timestamp in millis\n          statement.execute(\"alter session set ENABLE_GCP_PUT_EXCEPTION_FOR_OLD_DRIVERS=false\");\n          // test external table load\n          statement.execute(\"CREATE OR REPLACE TABLE testLoadToLocalFS(a number)\");\n\n          // put files\n          assertTrue(\n              statement.execute(\n                  \"PUT file://\"\n                      + getFullPathFileInResource(TEST_DATA_FILE)\n                      + \" @%testLoadToLocalFS/orders parallel=10\"),\n              \"Failed to put a file\");\n\n          try (ResultSet resultSet = statement.getResultSet()) {\n\n            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n            // assert column count\n            assertTrue(resultSetMetaData.getColumnCount() > 0);\n\n            assertTrue(resultSet.next()); // one row\n            assertFalse(resultSet.next());\n          }\n          findFile(\n              statement, \"ls @%testLoadToLocalFS/ pattern='.*orders/\" + TEST_DATA_FILE + \".g.*'\");\n\n          // remove files\n          try (ResultSet resultSet =\n              statement.executeQuery(\n                  \"rm @%testLoadToLocalFS/ pattern='.*orders/\" + TEST_DATA_FILE + \".g.*'\")) {\n\n            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n            // assert column count\n            assertTrue(resultSetMetaData.getColumnCount() >= 1);\n\n            // assert we get 1 row for the file we copied\n            assertTrue(resultSet.next());\n            assertNotNull(resultSet.getString(1));\n            assertFalse(resultSet.next());\n            SQLException ex = assertThrows(SQLException.class, () -> resultSet.getString(1));\n            assertEquals((int) ErrorCode.COLUMN_DOES_NOT_EXIST.getMessageCode(), ex.getErrorCode());\n\n            Thread.sleep(100);\n          }\n          // show files again\n          try (ResultSet resultSet =\n              statement.executeQuery(\"ls @%testLoadToLocalFS/ pattern='.*orders/orders.*'\")) {\n\n            // assert we get 0 row\n            assertFalse(resultSet.next());\n          }\n        } finally {\n          statement.execute(\"DROP TABLE IF EXISTS testLoadToLocalFS\");\n        }\n      } finally {\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n      }\n    }\n  }\n\n  static void findFile(Statement statement, String checkSQL) throws Throwable {\n    boolean fileFound = false;\n\n    // tolerate at most 60 tries for the following loop\n    for (int numSecs = 0; numSecs <= 60; numSecs++) {\n      // show files\n      try (ResultSet resultSet = statement.executeQuery(checkSQL)) {\n\n        if (resultSet.next()) {\n          fileFound = true;\n          break;\n        }\n        // give enough time for s3 eventual consistency for US region\n        Thread.sleep(1000);\n        assertTrue(fileFound, \"Could not find a file\");\n\n        // assert the first column not null\n        assertNotNull(resultSet.getString(1), \"Null result\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testSQLError42S02() throws SQLException {\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      // execute a bad query\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () -> statement.executeQuery(\"SELECT * FROM nonexistence\").close());\n      assertEquals(SqlState.BASE_TABLE_OR_VIEW_NOT_FOUND, ex.getSQLState(), \"sqlstate mismatch\");\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testExplainPlan() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement();\n\n        // test explain plan: sorry not available for general but debugging purpose only\n        ResultSet resultSet =\n            statement.executeQuery(\"EXPLAIN PLAN FOR SELECT c1 FROM \" + ORDERS_JDBC_TABLE)) {\n\n      ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n      assertTrue(resultSetMetaData.getColumnCount() >= 4, \"must return more than 4 columns\");\n      assertTrue(countRows(resultSet) > 3, \"must return more than 3 rows\");\n    }\n  }\n\n  @Test\n  public void testTimestampParsing() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet =\n            statement.executeQuery(\n                \"select to_timestamp('2013-05-08T15:39:20.123-07:00') from \" + ORDERS_JDBC_TABLE)) {\n\n      assertTrue(resultSet.next());\n      assertEquals(\"Wed, 08 May 2013 15:39:20 -0700\", resultSet.getString(1));\n    }\n  }\n\n  @Test\n  public void testDateParsing() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet = statement.executeQuery(\"select to_date('0001-01-01')\")) {\n      assertTrue(resultSet.next());\n      assertEquals(\"0001-01-01\", resultSet.getString(1));\n    }\n  }\n\n  @Test\n  public void testTimeParsing() throws Throwable {\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet resultSet =\n            statement.executeQuery(\"select to_time('15:39:20.123') from \" + ORDERS_JDBC_TABLE)) {\n      assertTrue(resultSet.next());\n      assertEquals(\"15:39:20\", resultSet.getString(1));\n    }\n  }\n\n  @Test\n  public void testClientSideSorting() throws Throwable {\n    ResultSetMetaData resultSetMetaData = null;\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n\n      // turn on sorting mode\n      statement.execute(\"set-sf-property sort on\");\n\n      try (ResultSet resultSet = statement.executeQuery(\"SELECT c3 FROM \" + ORDERS_JDBC_TABLE)) {\n        resultSetMetaData = resultSet.getMetaData();\n\n        // assert column count\n        assertEquals(1, resultSetMetaData.getColumnCount());\n\n        // assert the values for the first 5 rows\n        for (int i = 0; i < 5; i++) {\n          assertTrue(resultSet.next());\n\n          // assert each column is 'F'\n          assertEquals(\"F\", resultSet.getString(1));\n        }\n      }\n      // turn off sorting mode\n      statement.execute(\"set-sf-property sort off\");\n\n      try (ResultSet resultSet =\n          statement.executeQuery(\"SELECT c3 FROM \" + ORDERS_JDBC_TABLE + \" order by c3 desc\")) {\n\n        resultSetMetaData = resultSet.getMetaData();\n\n        // assert column count\n        assertEquals(1, resultSetMetaData.getColumnCount());\n\n        // assert the values for the first 4 rows\n        for (int i = 0; i < 4; i++) {\n          assertTrue(resultSet.next());\n\n          // assert each column is 'P'\n          assertEquals(\"P\", resultSet.getString(1));\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testUpdateCount() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // create test table\n        statement.execute(\"CREATE OR REPLACE TABLE testUpdateCount(version number, name string)\");\n\n        // insert two rows\n        int numRows =\n            statement.executeUpdate(\"INSERT INTO testUpdateCount values (1, 'a'), (2, 'b')\");\n\n        assertEquals(2, numRows, \"Unexpected number of rows inserted: \" + numRows);\n      } finally {\n        statement.execute(\"DROP TABLE if exists testUpdateCount\");\n      }\n    }\n  }\n\n  @Test\n  public void testSnow4245() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // set timestamp format\n        statement.execute(\"alter session set timestamp_input_format = 'YYYY-MM-DD HH24:MI:SS';\");\n\n        // create test table with different time zone flavors\n        String createSQL =\n            \"create or replace table testSnow4245(t timestamp with local time \"\n                + \"zone,ntz timestamp without time zone,tz  timestamp with time zone)\";\n        statement.execute(createSQL);\n\n        // populate\n        int numRows =\n            statement.executeUpdate(\n                \"insert into testSnow4245 values(NULL,NULL,NULL),\"\n                    + \"('2013-06-04 01:00:04','2013-06-04 01:00:04','2013-06-04 01:00:04'),\"\n                    + \"('2013-06-05 23:00:05','2013-06-05 23:00:05','2013-06-05 23:00:05')\");\n        assertEquals(3, numRows, \"Unexpected number of rows inserted: \" + numRows);\n\n        // query the data\n        try (ResultSet resultSet =\n            statement.executeQuery(\n                \"SELECT * FROM testSnow4245 order by 1 \"\n                    + \"nulls first, 2 nulls first, 3 nulls first\")) {\n\n          int i = 0;\n          // assert we get 3 rows\n\n          while (resultSet.next()) {\n            // assert each column is not null except the first row\n\n            if (i == 0) {\n              for (int j = 1; j < 4; j++) {\n                assertNull(resultSet.getString(j), resultSet.getString(j));\n              }\n            } else {\n              for (int j = 1; j < 4; j++) {\n                assertNotNull(resultSet.getString(j), resultSet.getString(j));\n              }\n            }\n            i = i + 1;\n          }\n        }\n      } finally {\n        statement.execute(\"drop table testSnow4245\");\n      }\n    }\n  }\n\n  /** SNOW-4394 - Four bytes UTF-8 characters are not returned correctly. */\n  @Test\n  public void testSnow4394() throws Throwable {\n    String tableName =\n        String.format(\"snow4394_%s\", UUID.randomUUID().toString()).replaceAll(\"-\", \"_\");\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      // create test table\n      try {\n        statement.execute(String.format(\"CREATE OR REPLACE TABLE %s(str string)\", tableName));\n\n        String data = \"What is \\ud83d\\ude12?\";\n        // insert two rows\n        int numRows =\n            statement.executeUpdate(\n                String.format(\"INSERT INTO %s(str) values('%s')\", tableName, data));\n        assertEquals(1, numRows, \"Unexpected number of rows inserted: \" + numRows);\n\n        try (ResultSet rset =\n            statement.executeQuery(String.format(\"SELECT str FROM %s\", tableName))) {\n          String ret = null;\n          while (rset.next()) {\n            ret = rset.getString(1);\n          }\n          assertEquals(data, ret, \"Unexpected string value: \" + ret);\n        }\n      } finally {\n        statement.execute(String.format(\"DROP TABLE if exists %s\", tableName));\n      }\n    }\n  }\n\n  private void addBindBatch(PreparedStatement preparedStatement, java.sql.Date sqlDate)\n      throws SQLException {\n    preparedStatement.setDouble(1, 1.2);\n    preparedStatement.setString(2, \"hello\");\n    preparedStatement.setDate(3, sqlDate);\n    preparedStatement.setDate(4, sqlDate);\n    preparedStatement.setString(5, \"h\");\n    preparedStatement.setDate(6, sqlDate);\n    preparedStatement.setString(7, \"h\");\n    preparedStatement.setString(8, \"h\");\n    preparedStatement.setString(9, \"h\");\n    preparedStatement.setString(10, \"h\");\n    preparedStatement.setString(11, \"h\");\n    preparedStatement.setDate(12, sqlDate);\n    preparedStatement.setString(13, \"h\");\n    preparedStatement.setDouble(14, 1.2);\n    preparedStatement.setString(15, \"h\");\n    preparedStatement.setString(16, \"h\");\n    preparedStatement.setString(17, \"h\");\n    preparedStatement.setString(18, \"h\");\n    preparedStatement.setString(19, \"h\");\n    preparedStatement.setDate(20, sqlDate);\n    preparedStatement.setString(21, \"h\");\n    preparedStatement.addBatch();\n  }\n\n  @Test\n  public void testBind() throws Throwable {\n    ResultSetMetaData resultSetMetaData = null;\n    Timestamp ts = null;\n    Time tm = null;\n    java.sql.Date sqlDate = null;\n    int[] updateCounts;\n    try (Connection connection = getConnection()) {\n      try (PreparedStatement preparedStatement = connection.prepareStatement(\"SELECT ?, ?\")) {\n\n        // bind integer\n        preparedStatement.setInt(1, 1);\n        preparedStatement.setString(2, \"hello\");\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.BIGINT, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n\n          assertEquals(1, resultSet.getInt(1), \"integer\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n        }\n        // bind float\n        preparedStatement.setDouble(1, 1.2);\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.DOUBLE, resultSetMetaData.getColumnType(1));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(1.2, resultSet.getDouble(1), 0, \"double\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n        }\n        // bind string\n        preparedStatement.setString(1, \"hello\");\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(1));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(\"hello\", resultSet.getString(1), \"string1\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string2\");\n        }\n        // bind date\n        sqlDate = java.sql.Date.valueOf(\"2014-08-26\");\n        preparedStatement.setDate(1, sqlDate);\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.DATE, resultSetMetaData.getColumnType(1));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(\"2014-08-26\", resultSet.getString(1), \"string\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n        }\n        // bind timestamp\n        ts = buildTimestamp(2014, 7, 26, 3, 52, 0, 0);\n        preparedStatement.setTimestamp(1, ts);\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.TIMESTAMP, resultSetMetaData.getColumnType(1));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(\n              \"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(1), \"Incorrect timestamp\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n        }\n        // bind time\n        tm = new Time(12345678); // 03:25:45.678\n        preparedStatement.setTime(1, tm);\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.TIME, resultSetMetaData.getColumnType(1));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(\"03:25:45\", resultSet.getString(1), \"Incorrect time\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n        }\n      }\n      // bind in where clause\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\n              \"SELECT * FROM \" + ORDERS_JDBC_TABLE + \" WHERE to_number(c1) = ?\")) {\n\n        preparedStatement.setInt(1, 100);\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(9, resultSetMetaData.getColumnCount());\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(\"100\", resultSet.getString(1), \"c1\");\n          assertEquals(\"147004\", resultSet.getString(2), \"c2\");\n        }\n      }\n\n      // bind in insert statement\n      // create a test table\n      try (Statement regularStatement = connection.createStatement()) {\n        regularStatement.executeUpdate(\n            \"create or replace table testBind(a int, b string, c double, d date, \"\n                + \"e timestamp, f time, g date)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"insert into testBind(a, b, c, d, e, f) values(?, ?, ?, ?, ?, ?)\")) {\n\n          preparedStatement.setInt(1, 1);\n          preparedStatement.setString(2, \"hello\");\n          preparedStatement.setDouble(3, 1.2);\n          preparedStatement.setDate(4, sqlDate);\n          preparedStatement.setTimestamp(5, ts);\n          preparedStatement.setTime(6, tm);\n          int rowCount = preparedStatement.executeUpdate();\n\n          // update count should be 1\n          assertEquals(1, rowCount, \"update count\");\n\n          // test the inserted rows\n          try (ResultSet resultSet = regularStatement.executeQuery(\"select * from testBind\")) {\n\n            // assert we get 1 rows\n            assertTrue(resultSet.next());\n            assertEquals(1, resultSet.getInt(1), \"int\");\n            assertEquals(\"hello\", resultSet.getString(2), \"string\");\n            assertEquals(1.2, resultSet.getDouble(3), 0, \"double\");\n            assertEquals(\"2014-08-26\", resultSet.getString(4), \"date\");\n            assertEquals(\"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(5), \"timestamp\");\n            assertEquals(\"03:25:45\", resultSet.getString(6), \"time\");\n            assertNull(resultSet.getString(7), \"date\");\n          }\n        }\n        // bind in update statement\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"update testBind set b=? where a=?\")) {\n          preparedStatement.setString(1, \"world\");\n          preparedStatement.setInt(2, 1);\n          preparedStatement.execute();\n        }\n\n        // test the updated rows\n        try (ResultSet resultSet = regularStatement.executeQuery(\"select * from testBind\")) {\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(1, resultSet.getInt(1), \"int\");\n          assertEquals(\"world\", resultSet.getString(2), \"string\");\n          assertEquals(1.2, resultSet.getDouble(3), 0, \"double\");\n          assertEquals(\"2014-08-26\", resultSet.getString(4), \"date\");\n          assertEquals(\"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(5), \"timestamp\");\n          assertEquals(\"03:25:45\", resultSet.getString(6), \"time\");\n          assertNull(resultSet.getString(7), \"date\");\n        }\n        // array bind for insert\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"insert into testBind (a, b, c, d, e, f, g) \"\n                    + \"values(?, ?, ?, ?, ?, ?, current_date())\")) {\n\n          preparedStatement.setInt(1, 2);\n          preparedStatement.setString(2, \"hello\");\n          preparedStatement.setDouble(3, 1.2);\n          preparedStatement.setDate(4, sqlDate);\n          preparedStatement.setTimestamp(5, ts);\n          preparedStatement.setTime(6, tm);\n          preparedStatement.addBatch();\n\n          preparedStatement.setInt(1, 3);\n          preparedStatement.setString(2, \"hello\");\n          preparedStatement.setDouble(3, 1.2);\n          preparedStatement.setDate(4, sqlDate);\n          preparedStatement.setTimestamp(5, ts);\n          preparedStatement.setTime(6, tm);\n          preparedStatement.addBatch();\n\n          updateCounts = preparedStatement.executeBatch();\n\n          // GS optimizes this into one insert execution, but we expand the\n          // return count into an array\n          assertEquals(2, updateCounts.length, \"Number of update counts\");\n\n          // update count should be 1 for each\n          assertEquals(1, updateCounts[0], \"update count\");\n          assertEquals(1, updateCounts[1], \"update count\");\n        }\n        // test the inserted rows\n        try (ResultSet resultSet =\n            regularStatement.executeQuery(\"select * from testBind where a = 2\")) {\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(2, resultSet.getInt(1), \"int\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n          assertEquals(1.2, resultSet.getDouble(3), 0, \"double\");\n          assertEquals(\"2014-08-26\", resultSet.getString(4), \"date\");\n          assertEquals(\"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(5), \"timestamp\");\n          assertEquals(\"03:25:45\", resultSet.getString(6), \"time\");\n        }\n\n        try (ResultSet resultSet =\n            regularStatement.executeQuery(\"select * from testBind where a = 3\")) {\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n          assertEquals(3, resultSet.getInt(1), \"int\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n          assertEquals(1.2, resultSet.getDouble(3), 0, \"double\");\n          assertEquals(\"2014-08-26\", resultSet.getString(4), \"date\");\n          assertEquals(\"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(5), \"timestamp\");\n          assertEquals(\"03:25:45\", resultSet.getString(6), \"time\");\n        }\n\n        // describe mode\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select * from testBind WHERE to_number(a) = ?\")) {\n\n          resultSetMetaData = preparedStatement.getMetaData();\n          assertEquals(7, resultSetMetaData.getColumnCount());\n          assertEquals(Types.BIGINT, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n          assertEquals(Types.DOUBLE, resultSetMetaData.getColumnType(3));\n          assertEquals(Types.DATE, resultSetMetaData.getColumnType(4));\n          assertEquals(Types.TIMESTAMP, resultSetMetaData.getColumnType(5));\n          assertEquals(Types.TIME, resultSetMetaData.getColumnType(6));\n          assertEquals(Types.DATE, resultSetMetaData.getColumnType(7));\n        }\n\n        try (PreparedStatement preparedStatement = connection.prepareStatement(\"select ?, ?\")) {\n          resultSetMetaData = preparedStatement.getMetaData();\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n        }\n\n        try (PreparedStatement preparedStatement = connection.prepareStatement(\"select ?, ?\")) {\n\n          preparedStatement.setInt(1, 1);\n          preparedStatement.setString(2, \"hello\");\n          ResultSet result = preparedStatement.executeQuery();\n\n          resultSetMetaData = result.getMetaData();\n          assertEquals(2, resultSetMetaData.getColumnCount());\n          assertEquals(Types.BIGINT, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n        }\n\n        // test null binding\n        try (PreparedStatement preparedStatement = connection.prepareStatement(\"select ?\")) {\n\n          preparedStatement.setNull(1, Types.VARCHAR);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            // assert column count\n            assertEquals(1, resultSetMetaData.getColumnCount());\n            assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(1));\n\n            // assert we get 1 rows\n            assertTrue(resultSet.next());\n            assertNull(resultSet.getObject(1));\n          }\n          preparedStatement.setNull(1, Types.INTEGER);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            // assert column count\n            assertEquals(1, resultSetMetaData.getColumnCount());\n\n            // assert we get 1 rows\n            assertTrue(resultSet.next());\n            assertNull(resultSet.getObject(1));\n          }\n        }\n      }\n\n      // bind in insert statement\n      // create a test table\n      try (Statement regularStatement = connection.createStatement()) {\n        regularStatement.executeUpdate(\n            \"create or replace table testBind1(c1 double, c2 string, c3 date, \"\n                + \"c4 date, c5 string, c6 date, c7 string, c8 string, \"\n                + \"c9 string, c10 string, c11 string, c12 date, c13 string, \"\n                + \"c14 float, c15 string, c16 string, c17 string, c18 string,\"\n                + \"c19 string, c20 date, c21 string)\");\n\n        // array bind for insert\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"insert into testBind1 (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, \"\n                    + \"c12, c13, c14, c15, c16, c17, c18, c19, c20, c21) values \"\n                    + \"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?,\"\n                    + \" ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\")) {\n\n          for (int idx = 0; idx < 16; idx++) {\n            addBindBatch(preparedStatement, sqlDate);\n          }\n\n          updateCounts = preparedStatement.executeBatch();\n\n          // GS optimizes this into one insert execution\n          assertEquals(16, updateCounts.length, \"Number of update counts\");\n\n          for (int idx = 0; idx < 16; idx++) {\n            assertEquals(1, updateCounts[idx], \"update count\");\n          }\n        }\n      }\n      connection.createStatement().execute(\"DROP TABLE testBind\");\n    }\n  }\n\n  @Test\n  public void testTableBind() throws Throwable {\n    ResultSetMetaData resultSetMetaData = null;\n\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        // select * from table(?)\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * from table(?)\")) {\n          resultSetMetaData = preparedStatement.getMetaData();\n          // we do not have any metadata, without a specified table\n          assertEquals(0, resultSetMetaData.getColumnCount());\n\n          preparedStatement.setString(1, ORDERS_JDBC);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n            assertEquals(9, resultSetMetaData.getColumnCount());\n            // assert we have 73 rows\n            for (int i = 0; i < 73; i++) {\n              assertTrue(resultSet.next());\n            }\n            assertFalse(resultSet.next());\n          }\n        }\n\n        // select * from table(?) where c1 = 1\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * from table(?) where c1 = 1\")) {\n          preparedStatement.setString(1, ORDERS_JDBC);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            assertEquals(9, resultSetMetaData.getColumnCount());\n            assertTrue(resultSet.next());\n            assertFalse(resultSet.next());\n          }\n        }\n\n        // select * from table(?) where c1 = 2 order by c3\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * from table(?) order by c3\")) {\n          preparedStatement.setString(1, ORDERS_JDBC);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            assertEquals(9, resultSetMetaData.getColumnCount());\n            // assert we have 73 rows\n            for (int i = 0; i < 73; i++) {\n              assertTrue(resultSet.next());\n            }\n            assertFalse(resultSet.next());\n          }\n        }\n\n        regularStatement.execute(\"create or replace table testTableBind(c integer, d string)\");\n        // insert into table\n        regularStatement.executeUpdate(\"insert into testTableBind (c, d) values (1, 'one')\");\n        // select c1, c from table(?), testTableBind\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * from table(?), testTableBind\")) {\n          preparedStatement.setString(1, ORDERS_JDBC);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            assertEquals(11, resultSetMetaData.getColumnCount());\n            // assert we have 73 rows\n            for (int i = 0; i < 73; i++) {\n              assertTrue(resultSet.next());\n            }\n            assertFalse(resultSet.next());\n          }\n        }\n\n        // select * from table(?), table(?)\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * from table(?), table(?)\")) {\n          preparedStatement.setString(1, ORDERS_JDBC);\n          preparedStatement.setString(2, \"testTableBind\");\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            assertEquals(11, resultSetMetaData.getColumnCount());\n            // assert we have 73 rows\n            for (int i = 0; i < 73; i++) {\n              assertTrue(resultSet.next());\n            }\n            assertFalse(resultSet.next());\n          }\n        }\n\n        // select tab1.c1, tab2.c from table(?) as a, table(?) as b\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT a.c1, b.c from table(?) as a, table(?) as b\")) {\n          preparedStatement.setString(1, ORDERS_JDBC);\n          preparedStatement.setString(2, \"testTableBind\");\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            assertEquals(2, resultSetMetaData.getColumnCount());\n            // assert we have 73 rows\n            for (int i = 0; i < 73; i++) {\n              assertTrue(resultSet.next());\n            }\n            assertFalse(resultSet.next());\n          }\n        }\n      } finally {\n        regularStatement.execute(\"DROP TABLE testTableBind\");\n      }\n    }\n  }\n\n  @Test\n  public void testBindInWithClause() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        // create a test table\n        regularStatement.execute(\n            \"create or replace table testBind2(a int, b string, c double, \"\n                + \"d date, e timestamp, f time, g date)\");\n\n        // bind in where clause\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"WITH V AS (SELECT * FROM testBind2 WHERE a = ?) \" + \"SELECT count(*) FROM V\")) {\n\n          preparedStatement.setInt(1, 100);\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n            // assert column count\n            assertEquals(1, resultSetMetaData.getColumnCount());\n\n            // assert we get 1 rows\n            assertTrue(resultSet.next());\n          }\n        }\n      } finally {\n        regularStatement.execute(\"DROP TABLE testBind2\");\n      }\n    }\n  }\n\n  @Test\n  public void testBindTimestampNTZ() throws Throwable {\n\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        // create a test table\n        regularStatement.executeUpdate(\n            \"create or replace table testBindTimestampNTZ(a timestamp_ntz)\");\n\n        regularStatement.execute(\"alter session set client_timestamp_type_mapping='timestamp_ntz'\");\n\n        // bind in where clause\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into testBindTimestampNTZ values(?)\")) {\n\n          Timestamp ts = buildTimestamp(2014, 7, 26, 3, 52, 0, 0);\n          preparedStatement.setTimestamp(1, ts);\n\n          int updateCount = preparedStatement.executeUpdate();\n\n          // update count should be 1\n          assertEquals(1, updateCount, \"update count\");\n\n          // test the inserted rows\n          try (ResultSet resultSet =\n              regularStatement.executeQuery(\"select * from testBindTimestampNTZ\")) {\n\n            // assert we get 1 rows\n            assertTrue(resultSet.next());\n            assertEquals(\"Tue, 26 Aug 2014 03:52:00 Z\", resultSet.getString(1), \"timestamp\");\n\n            regularStatement.executeUpdate(\"truncate table testBindTimestampNTZ\");\n\n            preparedStatement.setTimestamp(\n                1, ts, Calendar.getInstance(TimeZone.getTimeZone(\"America/Los_Angeles\")));\n\n            updateCount = preparedStatement.executeUpdate();\n\n            // update count should be 1\n            assertEquals(1, updateCount, \"update count\");\n          }\n          // test the inserted rows\n          try (ResultSet resultSet =\n              regularStatement.executeQuery(\"select * from testBindTimestampNTZ\")) {\n\n            // assert we get 1 rows\n            assertTrue(resultSet.next());\n          }\n        }\n      } finally {\n        regularStatement.execute(\"DROP TABLE testBindTimestampNTZ\");\n      }\n    }\n  }\n\n  @Test\n  public void testNullBind() throws Throwable {\n\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\"create or replace table testNullBind(a double)\");\n\n        // array bind with nulls\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"insert into testNullBind (a) values(?)\")) {\n          preparedStatement.setDouble(1, 1.2);\n          preparedStatement.addBatch();\n\n          preparedStatement.setObject(1, null);\n          preparedStatement.addBatch();\n\n          int[] updateCounts = preparedStatement.executeBatch();\n\n          // GS optimizes this into one insert execution\n          assertEquals(2, updateCounts.length, \"Number of update counts\");\n\n          // update count should be 1\n          assertEquals(1, updateCounts[0], \"update count\");\n          assertEquals(1, updateCounts[1], \"update count\");\n\n          preparedStatement.clearBatch();\n\n          preparedStatement.setObject(1, null);\n          preparedStatement.addBatch();\n\n          preparedStatement.setDouble(1, 1.2);\n          preparedStatement.addBatch();\n\n          updateCounts = preparedStatement.executeBatch();\n\n          // GS optimizes this into one insert execution\n          assertEquals(2, updateCounts.length, \"Number of update counts\");\n\n          // update count should be 1\n          assertEquals(1, updateCounts[0], \"update count\");\n          assertEquals(1, updateCounts[1], \"update count\");\n\n          preparedStatement.clearBatch();\n\n          preparedStatement.setObject(1, null);\n          preparedStatement.addBatch();\n\n          updateCounts = preparedStatement.executeBatch();\n\n          // GS optimizes this into one insert execution\n          assertEquals(1, updateCounts.length, \"Number of update counts\");\n\n          // update count should be 1\n          assertEquals(1, updateCounts[0], \"update count\");\n\n          preparedStatement.clearBatch();\n\n          preparedStatement.setString(1, \"hello\");\n          preparedStatement.addBatch();\n          preparedStatement.setDouble(1, 1.2);\n          SnowflakeSQLException ex =\n              assertThrows(SnowflakeSQLException.class, preparedStatement::addBatch);\n          assertEquals(\n              (int) ErrorCode.ARRAY_BIND_MIXED_TYPES_NOT_SUPPORTED.getMessageCode(),\n              ex.getErrorCode());\n        }\n      } finally {\n        regularStatement.execute(\"DROP TABLE testNullBind\");\n      }\n    }\n  }\n\n  @Test\n  public void testSnow12603() throws Throwable {\n    ResultSetMetaData resultSetMetaData = null;\n    try (Connection connection = getConnection()) {\n      try (PreparedStatement preparedStatement =\n          connection.prepareStatement(\"SELECT ?, ?, ?, ?, ?, ?\")) {\n\n        java.sql.Date sqlDate = java.sql.Date.valueOf(\"2014-08-26\");\n\n        Timestamp ts = buildTimestamp(2014, 7, 26, 3, 52, 0, 0);\n\n        preparedStatement.setObject(1, 1);\n        preparedStatement.setObject(2, \"hello\");\n        preparedStatement.setObject(3, new BigDecimal(\"1.3\"));\n        preparedStatement.setObject(4, Float.valueOf(\"1.3\"));\n        preparedStatement.setObject(5, sqlDate);\n        preparedStatement.setObject(6, ts);\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(6, resultSetMetaData.getColumnCount());\n          assertEquals(Types.BIGINT, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n          assertEquals(Types.DECIMAL, resultSetMetaData.getColumnType(3));\n          assertEquals(Types.DOUBLE, resultSetMetaData.getColumnType(4));\n          assertEquals(Types.DATE, resultSetMetaData.getColumnType(5));\n          assertEquals(Types.TIMESTAMP, resultSetMetaData.getColumnType(6));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n\n          assertEquals(1, resultSet.getInt(1), \"integer\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n          assertEquals(new BigDecimal(\"1.3\"), resultSet.getBigDecimal(3), \"decimal\");\n          assertEquals(1.3, resultSet.getDouble(4), 0, \"double\");\n          assertEquals(\"2014-08-26\", resultSet.getString(5), \"date\");\n          assertEquals(\"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(6), \"timestamp\");\n\n          preparedStatement.setObject(1, 1, Types.INTEGER);\n          preparedStatement.setObject(2, \"hello\", Types.VARCHAR);\n          preparedStatement.setObject(3, new BigDecimal(\"1.3\"), Types.DECIMAL);\n          preparedStatement.setObject(4, Float.valueOf(\"1.3\"), Types.DOUBLE);\n          preparedStatement.setObject(5, sqlDate, Types.DATE);\n          preparedStatement.setObject(6, ts, Types.TIMESTAMP);\n        }\n        try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n          resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertEquals(6, resultSetMetaData.getColumnCount());\n          assertEquals(Types.BIGINT, resultSetMetaData.getColumnType(1));\n          assertEquals(Types.VARCHAR, resultSetMetaData.getColumnType(2));\n          assertEquals(Types.DECIMAL, resultSetMetaData.getColumnType(3));\n          assertEquals(Types.DOUBLE, resultSetMetaData.getColumnType(4));\n          assertEquals(Types.DATE, resultSetMetaData.getColumnType(5));\n          assertEquals(Types.TIMESTAMP, resultSetMetaData.getColumnType(6));\n\n          // assert we get 1 rows\n          assertTrue(resultSet.next());\n\n          assertEquals(1, resultSet.getInt(1), \"integer\");\n          assertEquals(\"hello\", resultSet.getString(2), \"string\");\n          assertEquals(new BigDecimal(\"1.3\"), resultSet.getBigDecimal(3), \"decimal\");\n          assertEquals(1.3, resultSet.getDouble(4), 0, \"double\");\n          assertEquals(\"2014-08-26\", resultSet.getString(5), \"date\");\n          assertEquals(\"Mon, 25 Aug 2014 20:52:00 -0700\", resultSet.getString(6), \"timestamp\");\n        }\n      }\n    }\n  }\n\n  /** SNOW-6290: timestamp value is shifted by local timezone */\n  @Test\n  public void testSnow6290() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // create test table\n        statement.execute(\"CREATE OR REPLACE TABLE testSnow6290(ts timestamp)\");\n\n        PreparedStatement preparedStatement =\n            connection.prepareStatement(\"INSERT INTO testSnow6290(ts) values(?)\");\n\n        Timestamp ts = new Timestamp(System.currentTimeMillis());\n\n        preparedStatement.setTimestamp(1, ts);\n        preparedStatement.executeUpdate();\n\n        ResultSet res = statement.executeQuery(\"select ts from testSnow6290\");\n\n        assertTrue(res.next(), \"expect a row\");\n\n        Timestamp tsFromDB = res.getTimestamp(1);\n\n        assertEquals(ts.getTime(), tsFromDB.getTime(), \"timestamp mismatch\");\n      } finally {\n        statement.execute(\"DROP TABLE if exists testSnow6290\");\n      }\n    }\n  }\n\n  /** SNOW-6986: null sql shouldn't be allowed */\n  @Test\n  public void testInvalidSQL() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n\n      // execute DDLs\n      SnowflakeSQLException ex =\n          assertThrows(SnowflakeSQLException.class, () -> statement.executeQuery(null));\n      assertEquals((int) ErrorCode.INVALID_SQL.getMessageCode(), ex.getErrorCode());\n    }\n  }\n\n  @Test\n  public void testGetObject() throws Throwable {\n    ResultSetMetaData resultSetMetaData;\n\n    try (Connection connection = getConnection();\n        PreparedStatement preparedStatement = connection.prepareStatement(\"SELECT ?\")) {\n      // bind integer\n      preparedStatement.setInt(1, 1);\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n        resultSetMetaData = resultSet.getMetaData();\n\n        assertEquals(\n            Long.class.getName(),\n            resultSetMetaData.getColumnClassName(1),\n            \"column class name=BigDecimal\");\n\n        // assert we get 1 rows\n        assertTrue(resultSet.next());\n\n        assertTrue(resultSet.getObject(1) instanceof Long, \"integer\");\n      }\n      preparedStatement.setString(1, \"hello\");\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n        resultSetMetaData = resultSet.getMetaData();\n\n        assertEquals(\n            String.class.getName(),\n            resultSetMetaData.getColumnClassName(1),\n            \"column class name=String\");\n\n        // assert we get 1 rows\n        assertTrue(resultSet.next());\n\n        assertTrue(resultSet.getObject(1) instanceof String, \"string\");\n      }\n\n      preparedStatement.setDouble(1, 1.2);\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n        resultSetMetaData = resultSet.getMetaData();\n\n        assertEquals(\n            Double.class.getName(),\n            resultSetMetaData.getColumnClassName(1),\n            \"column class name=Double\");\n\n        // assert we get 1 rows\n        assertTrue(resultSet.next());\n\n        assertTrue(resultSet.getObject(1) instanceof Double, \"double\");\n      }\n\n      preparedStatement.setTimestamp(1, new Timestamp(0));\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n\n        resultSetMetaData = resultSet.getMetaData();\n\n        assertEquals(\n            Timestamp.class.getName(),\n            resultSetMetaData.getColumnClassName(1),\n            \"column class name=Timestamp\");\n\n        // assert we get 1 rows\n        assertTrue(resultSet.next());\n\n        assertTrue(resultSet.getObject(1) instanceof Timestamp, \"timestamp\");\n      }\n\n      preparedStatement.setDate(1, new java.sql.Date(0));\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n        resultSetMetaData = resultSet.getMetaData();\n\n        assertEquals(\n            Date.class.getName(),\n            resultSetMetaData.getColumnClassName(1),\n            \"column class name=Date\");\n\n        // assert we get 1 rows\n        assertTrue(resultSet.next());\n\n        assertTrue(resultSet.getObject(1) instanceof Date, \"date\");\n      }\n    }\n  }\n\n  @Test\n  public void testGetDoubleForNull() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement stmt = connection.createStatement();\n        ResultSet resultSet = stmt.executeQuery(\"select cast(null as int) as null_int\")) {\n      assertTrue(resultSet.next());\n      assertEquals(0, resultSet.getDouble(1), 0.0001, \"0 for null\");\n    }\n  }\n\n  // SNOW-27438\n  @Test\n  public void testGetDoubleForNaN() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement stmt = connection.createStatement();\n        ResultSet resultSet = stmt.executeQuery(\"select 'nan'::float\")) {\n      assertTrue(resultSet.next());\n      assertThat(\"NaN for NaN\", resultSet.getDouble(1), equalTo(Double.NaN));\n    }\n  }\n\n  @Test\n  public void testPutViaExecuteQuery() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // load file test\n        // create a unique data file name by using current timestamp in millis\n        // test external table load\n        statement.execute(\"CREATE OR REPLACE TABLE testPutViaExecuteQuery(a number)\");\n\n        // put files\n        try (ResultSet resultSet =\n            statement.executeQuery(\n                \"PUT file://\"\n                    + getFullPathFileInResource(TEST_DATA_FILE)\n                    + \" @%testPutViaExecuteQuery/orders parallel=10\")) {\n\n          ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n\n          // assert column count\n          assertTrue(resultSetMetaData.getColumnCount() > 0);\n          // assert we get 1 rows\n          for (int i = 0; i < 1; i++) {\n            assertTrue(resultSet.next());\n          }\n        }\n      } finally {\n        statement.execute(\"DROP TABLE IF EXISTS testPutViaExecuteQuery\");\n      }\n    }\n  }\n\n  @Disabled(\"takes 7 min. enable this for long running tests\")\n  @Test\n  public void testSnow16332() throws Throwable {\n    // use v1 query request API and inject 200ms socket timeout for first\n    // http request to simulate network failure\n    try (Connection conn = getConnection();\n        Statement stmt = conn.createStatement()) {\n      try {\n        // create a table\n        stmt.execute(\"CREATE OR REPLACE TABLE SNOW16332 (i int)\");\n\n        // make sure QC is JIT optimized. Change the GS JVM args to include\n        // -Xcomp or -XX:CompileThreshold = < a number smaller than the\n        // stmtCounter\n\n        int stmtCounter = 2000;\n        while (stmtCounter > 0) {\n          // insert into it this should start a transaction.\n          stmt.executeUpdate(\"INSERT INTO SNOW16332 VALUES (\" + stmtCounter + \")\");\n          --stmtCounter;\n        }\n\n        try (Connection connWithNwError = getConnection(500)) { // inject socket timeout in ms\n          try (Statement stmtWithNwError = connWithNwError.createStatement()) {\n\n            // execute dml\n            stmtWithNwError.executeUpdate(\n                \"INSERT INTO SNOW16332 \"\n                    + \"SELECT seq8() \"\n                    + \"FROM table(generator(timeLimit => 1))\");\n\n            // and execute another dml\n            stmtWithNwError.executeUpdate(\n                \"INSERT INTO SNOW16332 \"\n                    + \"SELECT seq8() \"\n                    + \"FROM table(generator(timeLimit => 1))\");\n          }\n        }\n      } finally {\n        stmt.executeQuery(\"DROP TABLE SNOW16332\");\n      }\n    }\n  }\n\n  @Test\n  public void testV1Query() throws Throwable {\n    ResultSetMetaData resultSetMetaData = null;\n    // use v1 query request API and inject 200ms socket timeout for first\n    // http request to simulate network failure\n    try (Connection connection = getConnection(200); // inject socket timeout = 200m\n        Statement statement = connection.createStatement()) {\n\n      // execute query\n      try (ResultSet resultSet =\n          statement.executeQuery(\"SELECT count(*) FROM table(generator(rowCount => 100000000))\")) {\n        resultSetMetaData = resultSet.getMetaData();\n\n        // assert column count\n        assertEquals(1, resultSetMetaData.getColumnCount());\n\n        // assert we get 1 row\n        for (int i = 0; i < 1; i++) {\n          assertTrue(resultSet.next());\n          assertTrue(resultSet.getInt(1) > 0);\n        }\n      }\n\n      // Test parsing for timestamp with timezone value that has new encoding\n      // where timezone index follows timestamp value\n      try (ResultSet resultSet =\n          statement.executeQuery(\"SELECT 'Fri, 23 Oct 2015 12:35:38 -0700'::timestamp_tz\")) {\n        resultSetMetaData = resultSet.getMetaData();\n\n        // assert column count\n        assertEquals(1, resultSetMetaData.getColumnCount());\n\n        // assert we get 1 row\n        for (int i = 0; i < 1; i++) {\n          assertTrue(resultSet.next());\n          assertEquals(\"Fri, 23 Oct 2015 12:35:38 -0700\", resultSet.getString(1));\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testCancelQuery() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      // schedule a cancel in 5 seconds\n      Timer timer = new Timer();\n      timer.schedule(\n          new TimerTask() {\n            @Override\n            public void run() {\n              try {\n                statement.cancel();\n              } catch (SQLException ex) {\n                logger.log(Level.SEVERE, \"Cancel failed with exception {}\", ex);\n              }\n            }\n          },\n          5000);\n\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  statement\n                      .executeQuery(\"SELECT count(*) FROM TABLE(generator(timeLimit => 120))\")\n                      .close());\n      // assert the sqlstate is what we expect (QUERY CANCELLED)\n      assertEquals(SqlState.QUERY_CANCELED, ex.getSQLState(), \"sqlstate mismatch\");\n    }\n  }\n\n  /** SNOW-14774: timestamp_ntz value should use client time zone to adjust the epoch time. */\n  @Test\n  public void testSnow14774() throws Throwable {\n    Calendar calendar = null;\n    Timestamp tsInUTC = null;\n    Timestamp tsInLA = null;\n    SimpleDateFormat sdf = null;\n    String tsStrInLA = null;\n    String tsStrInUTC = null;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      // 30 minutes past daylight saving change (from 2am to 3am)\n      try (ResultSet res = statement.executeQuery(\"select '2015-03-08 03:30:00'::timestamp_ntz\")) {\n\n        assertTrue(res.next());\n\n        // get timestamp in UTC\n        calendar = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n        tsInUTC = res.getTimestamp(1, calendar);\n\n        sdf = new SimpleDateFormat(\"yyyy.MM.dd HH:mm:ss\");\n        sdf.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n        tsStrInUTC = sdf.format(tsInUTC);\n\n        // get timestamp in LA timezone\n        calendar.setTimeZone(TimeZone.getTimeZone(\"America/Los_Angeles\"));\n        tsInLA = res.getTimestamp(1, calendar);\n\n        sdf.setTimeZone(TimeZone.getTimeZone(\"America/Los_Angeles\"));\n        tsStrInLA = sdf.format(tsInLA);\n\n        // the timestamp in LA and in UTC should be the same\n        assertEquals(tsStrInUTC, tsStrInLA, \"timestamp values not equal\");\n      }\n      // 30 minutes before daylight saving change\n      try (ResultSet res = statement.executeQuery(\"select '2015-03-08 01:30:00'::timestamp_ntz\")) {\n\n        assertTrue(res.next());\n\n        // get timestamp in UTC\n        calendar.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n        tsInUTC = res.getTimestamp(1, calendar);\n\n        sdf.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n        tsStrInUTC = sdf.format(tsInUTC);\n\n        // get timestamp in LA timezone\n        calendar.setTimeZone(TimeZone.getTimeZone(\"America/Los_Angeles\"));\n        tsInLA = res.getTimestamp(1, calendar);\n\n        sdf.setTimeZone(TimeZone.getTimeZone(\"America/Los_Angeles\"));\n        tsStrInLA = sdf.format(tsInLA);\n\n        // the timestamp in LA and in UTC should be the same\n        assertEquals(tsStrInUTC, tsStrInLA, \"timestamp values not equal\");\n      }\n    }\n  }\n\n  /** SNOW-19172: getMoreResults should return false after executeQuery */\n  @Test\n  public void testSnow19172() throws SQLException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.executeQuery(\"select 1\");\n\n      assertTrue(!statement.getMoreResults());\n    }\n  }\n\n  @Test\n  public void testSnow19819() throws Throwable {\n    try (Connection connection = getConnection()) {\n      try (Statement regularStatement = connection.createStatement()) {\n        try {\n          regularStatement.execute(\n              \"create or replace table testSnow19819(\\n\"\n                  + \"s string,\\n\"\n                  + \"v variant,\\n\"\n                  + \"t timestamp_ltz)\\n\");\n\n          try (PreparedStatement preparedStatement =\n              connection.prepareStatement(\n                  \"insert into testSnow19819 (s, v, t)\\n\"\n                      + \"select ?, parse_json(?), to_timestamp(?)\")) {\n\n            preparedStatement.setString(1, \"foo\");\n            preparedStatement.setString(2, \"{ }\");\n            preparedStatement.setString(3, \"2016-05-12 12:15:00\");\n            preparedStatement.addBatch();\n\n            preparedStatement.setString(1, \"foo2\");\n            preparedStatement.setString(2, \"{ \\\"a\\\": 1 }\");\n            preparedStatement.setString(3, \"2016-05-12 12:16:00\");\n            preparedStatement.addBatch();\n\n            preparedStatement.executeBatch();\n\n            try (ResultSet resultSet =\n                connection\n                    .createStatement()\n                    .executeQuery(\"SELECT s, v, t FROM testSnow19819 ORDER BY 1\")) {\n              assertThat(\"next result\", resultSet.next());\n              assertThat(\"String\", resultSet.getString(1), equalTo(\"foo\"));\n              assertThat(\"Variant\", resultSet.getString(2), equalTo(\"{}\"));\n              assertThat(\"next result\", resultSet.next());\n              assertThat(\"String\", resultSet.getString(1), equalTo(\"foo2\"));\n              assertThat(\"Variant\", resultSet.getString(2), equalTo(\"{\\n  \\\"a\\\": 1\\n}\"));\n              assertThat(\"no more result\", !resultSet.next());\n            }\n          }\n        } finally {\n          regularStatement.execute(\"DROP TABLE testSnow19819\");\n        }\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnTestaccount\n  public void testClientInfo() throws Throwable {\n    System.setProperty(\n        \"snowflake.client.info\",\n        \"{\\\"spark.version\\\":\\\"3.0.0\\\", \\\"spark.snowflakedb.version\\\":\\\"2.8.5\\\", \\\"spark.app.name\\\":\\\"SnowflakeSourceSuite\\\", \\\"scala.version\\\":\\\"2.12.11\\\", \\\"java.version\\\":\\\"1.8.0_221\\\", \\\"snowflakedb.jdbc.version\\\":\\\"3.13.2\\\"}\");\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement();\n        ResultSet res = statement.executeQuery(\"select current_session_client_info()\")) {\n\n      assertTrue(res.next(), \"result expected\");\n\n      String clientInfoJSONStr = res.getString(1);\n\n      JsonNode clientInfoJSON = mapper.readTree(clientInfoJSONStr);\n\n      // assert that spark version and spark app are found\n      assertEquals(\"3.0.0\", clientInfoJSON.get(\"spark.version\").asText(), \"spark version mismatch\");\n      assertEquals(\n          \"2.8.5\",\n          clientInfoJSON.get(\"spark.snowflakedb.version\").asText(),\n          \"snowflakedb version mismatch\");\n      assertEquals(\n          \"SnowflakeSourceSuite\",\n          clientInfoJSON.get(\"spark.app.name\").asText(),\n          \"spark app mismatch\");\n\n      closeSQLObjects(res, statement, connection);\n    }\n    System.clearProperty(\"snowflake.client.info\");\n  }\n\n  @Test\n  public void testLargeResultSet() throws Throwable {\n    try (Connection connection = getConnection();\n        // create statement\n        Statement statement = connection.createStatement()) {\n      String sql =\n          \"SELECT random()||random(), randstr(1000, random()) FROM table(generator(rowcount =>\"\n              + \" 10000))\";\n      try (ResultSet result = statement.executeQuery(sql)) {\n        int cnt = 0;\n        while (result.next()) {\n          ++cnt;\n        }\n        assertEquals(10000, cnt);\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testSnow26503() throws Throwable {\n    ResultSetMetaData resultSetMetaData;\n    String queryId = null;\n    try (Connection connection = getConnection();\n        // create a test table\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\n            \"create or replace table testBind2(a int) as select * from values(1),(2),(8),(10)\");\n\n        // test binds in BETWEEN predicate\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * FROM testBind2 WHERE a between ? and ?\")) {\n          preparedStatement.setInt(1, 3);\n          preparedStatement.setInt(2, 9);\n          // test that the query succeeds; used to fail with incident\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            resultSetMetaData = resultSet.getMetaData();\n\n            // assert column count\n            assertEquals(1, resultSetMetaData.getColumnCount());\n\n            // assert we get 1 row\n            assertTrue(resultSet.next());\n          }\n        }\n        try (PreparedStatement preparedStatement =\n                connection.prepareStatement(\"SELECT last_query_id()\");\n            ResultSet resultSet = preparedStatement.executeQuery()) {\n          assertTrue(resultSet.next());\n          queryId = resultSet.getString(1);\n        }\n\n        // check that the bind values can be retrieved using system$get_bind_values\n        try (Connection snowflakeConnection = getSnowflakeAdminConnection()) {\n          try (Statement regularStatementSF = snowflakeConnection.createStatement()) {\n            regularStatementSF.execute(\"create or replace warehouse wh26503 warehouse_size=xsmall\");\n\n            try (PreparedStatement preparedStatement =\n                snowflakeConnection.prepareStatement(\n                    \"select bv:\\\"1\\\":\\\"value\\\"::string, bv:\\\"2\\\":\\\"value\\\"::string from (select\"\n                        + \" parse_json(system$get_bind_values(?)) bv)\")) {\n              preparedStatement.setString(1, queryId);\n              try (ResultSet resultSet = preparedStatement.executeQuery()) {\n                assertTrue(resultSet.next());\n\n                // check that the bind values are correct\n                assertEquals(3, resultSet.getInt(1));\n                assertEquals(9, resultSet.getInt(2));\n              }\n            }\n          }\n          snowflakeConnection.createStatement().execute(\"DROP warehouse wh26503\");\n        }\n      } finally {\n        regularStatement.execute(\"DROP TABLE testBind2\");\n      }\n    }\n  }\n\n  /**\n   * Test binding variable when creating view or udf, which currently is not supported in Snowflake.\n   * Exception will be thrown and returned back to user\n   */\n  @Test\n  public void testSnow28530() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\"create or replace table t(a number, b number)\");\n\n        /////////////////////////////////////////\n        // bind variables in a view definition\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"create or replace view v as select * from t where a=?\")) {\n          preparedStatement.setInt(1, 1);\n          SnowflakeSQLException e =\n              assertThrows(\n                  SnowflakeSQLException.class,\n                  preparedStatement::execute,\n                  \"Bind variable in view definition did not cause a user error\");\n          assertEquals(ERROR_CODE_BIND_VARIABLE_NOT_ALLOWED_IN_VIEW_OR_UDF_DEF, e.getErrorCode());\n        }\n\n        /////////////////////////////////////////////\n        // bind variables in a scalar UDF definition\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"create or replace function f(n number) returns number as \" + \"'n + ?'\")) {\n          SnowflakeSQLException e =\n              assertThrows(\n                  SnowflakeSQLException.class,\n                  preparedStatement::execute,\n                  \"Bind variable in scalar UDF definition did not cause a user error\");\n          assertEquals(ERROR_CODE_BIND_VARIABLE_NOT_ALLOWED_IN_VIEW_OR_UDF_DEF, e.getErrorCode());\n        }\n\n        ///////////////////////////////////////////\n        // bind variables in a table UDF definition\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"create or replace function tf(n number) returns table(b number) as\"\n                    + \" 'select b from t where a=?'\")) {\n          SnowflakeSQLException e =\n              assertThrows(\n                  SnowflakeSQLException.class,\n                  preparedStatement::execute,\n                  \"Bind variable in table UDF definition did not cause a user error\");\n          assertEquals(ERROR_CODE_BIND_VARIABLE_NOT_ALLOWED_IN_VIEW_OR_UDF_DEF, e.getErrorCode());\n        }\n      } finally {\n        regularStatement.execute(\"drop table t\");\n      }\n    }\n  }\n\n  /**\n   * SNOW-31104 improves the type inference for string constants that need to be coerced to numbers.\n   * Verify that the same improvements work when the constant is a bind ref.\n   */\n  @Test\n  public void testSnow31104() throws Throwable {\n\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      // Repeat a couple of test cases from snow-31104.sql\n      // We don't need to repeat all of them; we just need to verify\n      // that string bind refs and null bind refs are treated the same as\n      // string and null constants.\n      try {\n        regularStatement.execute(\"create or replace table t(n number)\");\n\n        regularStatement.executeUpdate(\n            \"insert into t values (1), (90000000000000000000000000000000000000)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select n, n > ? from t order by 1\")) {\n          preparedStatement.setString(1, \"1\");\n\n          // this should not produce a user error\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertTrue(resultSet.next());\n            assertFalse(resultSet.getBoolean(2));\n            assertTrue(resultSet.next());\n            assertTrue(resultSet.getBoolean(2));\n          }\n        }\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"select n, '1' in (?, '256', n, 10) from t order by 1\")) {\n          preparedStatement.setString(1, null);\n\n          try (ResultSet resultSet = preparedStatement.executeQuery()) {\n            assertTrue(resultSet.next());\n            assertTrue(resultSet.getBoolean(2));\n            assertTrue(resultSet.next());\n            assertNull(resultSet.getObject(2));\n          }\n        }\n      } finally {\n        regularStatement.execute(\"drop table t\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGet() throws Throwable {\n\n    List<String> accounts =\n        Arrays.asList(\n            null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\" /*, \"gcpaccount_awssdk\"*/);\n    for (int i = 0; i < accounts.size(); i++) {\n      String accountName = accounts.get(i);\n      if (accounts.get(i) != null && accounts.get(i).equals(\"gcpaccount_awssdk\")) {\n        accountName = \"gcpaccount\";\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n      }\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        String stageName = \"testGetPut_stage_\" + SnowflakeUtil.randomAlphaNumeric(10);\n        try {\n          String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n\n          File destFolder = new File(tmpFolder, \"dest\");\n          destFolder.mkdirs();\n          String destFolderCanonicalPath = destFolder.getCanonicalPath();\n          String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n          statement.execute(\"alter session set ENABLE_GCP_PUT_EXCEPTION_FOR_OLD_DRIVERS=false\");\n          statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n\n          assertTrue(\n              statement.execute(\"PUT file://\" + sourceFilePath + \" @\" + stageName),\n              \"Failed to put a file\");\n\n          findFile(statement, \"ls @\" + stageName + \"/\");\n\n          // download the file we just uploaded to stage\n          assertTrue(\n              statement.execute(\n                  \"GET @\" + stageName + \" 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n              \"Failed to get a file\");\n\n          // Make sure that the downloaded file exists, it should be gzip compressed\n          File downloaded = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE + \".gz\");\n          assertTrue(downloaded.exists());\n\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + TEST_DATA_FILE + \".gz\");\n          p.waitFor();\n\n          File original = new File(sourceFilePath);\n          File unzipped = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE);\n          assertEquals(original.length(), unzipped.length());\n        } finally {\n          statement.execute(\"DROP STAGE IF EXISTS \" + stageName);\n        }\n      } finally {\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n      }\n    }\n  }\n\n  // This test is intended to be run locally, without CloudVM.\n  // It works on your current CSP.\n  // It doesn't work on GHA, because it needs to change the account parameter.\n  @Test\n  @DontRunOnGithubActions\n  public void testPutCopyIntoWith256BitEncryption() throws SQLException {\n    try (Connection connection = getConnection()) {\n      testPutCopyIntoWith256BitEncryption(connection);\n    }\n  }\n\n  private static void testPutCopyIntoWith256BitEncryption(Connection connection)\n      throws SQLException {\n    String randomSuffix = UUID.randomUUID().toString().replace(\"-\", \"\");\n    String tableName = \"TEST_TABLE_256_\" + randomSuffix;\n    String stageName = \"TEST_STAGE_256_\" + randomSuffix;\n    String originalKeySize = null;\n\n    try (Statement statement = connection.createStatement()) {\n\n      try (ResultSet rs =\n          statement.executeQuery(\"SHOW PARAMETERS LIKE 'CLIENT_ENCRYPTION_KEY_SIZE' IN ACCOUNT\")) {\n        if (rs.next()) {\n          originalKeySize = rs.getString(\"value\");\n        }\n      }\n\n      if (!\"256\".equals(originalKeySize)) {\n        statement.execute(\"ALTER ACCOUNT SET CLIENT_ENCRYPTION_KEY_SIZE = 256\");\n      }\n\n      try {\n        statement.execute(\n            \"CREATE OR REPLACE TABLE \"\n                + tableName\n                + \" (id INTEGER, name STRING) \"\n                + \"stage_file_format = (type = 'CSV' field_delimiter = ',' skip_header = 0)\");\n\n        statement.execute(\"CREATE OR REPLACE STAGE \" + stageName);\n\n        String testFileName = \"test_encryption_256.csv\";\n        String csvFilePath = getFullPathFileInResource(testFileName);\n        String putCommand = \"PUT file://\" + csvFilePath + \" @\" + stageName + \" AUTO_COMPRESS=FALSE\";\n        assertTrue(statement.execute(putCommand), \"PUT command should return a result set\");\n\n        int rowsCopied =\n            statement.executeUpdate(\n                \"COPY INTO \" + tableName + \" FROM @\" + stageName + \"/\" + testFileName);\n        assertEquals(3, rowsCopied, \"Should have copied 3 rows\");\n\n        try (ResultSet rs = statement.executeQuery(\"SELECT * FROM \" + tableName + \" ORDER BY id\")) {\n          assertTrue(rs.next());\n          assertEquals(1, rs.getInt(\"ID\"));\n          assertEquals(\"hello\", rs.getString(\"NAME\"));\n\n          assertTrue(rs.next());\n          assertEquals(2, rs.getInt(\"ID\"));\n          assertEquals(\"world\", rs.getString(\"NAME\"));\n\n          assertTrue(rs.next());\n          assertEquals(3, rs.getInt(\"ID\"));\n          assertEquals(\"test\", rs.getString(\"NAME\"));\n        }\n      } finally {\n        if (originalKeySize != null && !\"256\".equals(originalKeySize)) {\n          statement.execute(\"ALTER ACCOUNT SET CLIENT_ENCRYPTION_KEY_SIZE = \" + originalKeySize);\n        }\n        statement.execute(\"DROP TABLE IF EXISTS \" + tableName);\n        statement.execute(\"DROP STAGE IF EXISTS \" + stageName);\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutCopyIntoWith256BitEncryptionOnAllAccounts() throws Throwable {\n\n    List<String> accounts =\n        Arrays.asList(\n            null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\" /*, \"gcpaccount_awssdk\"*/);\n    for (int i = 0; i < accounts.size(); i++) {\n      String accountName = accounts.get(i);\n      if (accounts.get(i) != null && accounts.get(i).equals(\"gcpaccount_awssdk\")) {\n        accountName = \"gcpaccount\";\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n      }\n      try (Connection connection = getConnection(accountName)) {\n        testPutCopyIntoWith256BitEncryption(connection);\n      } finally {\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n      }\n    }\n  }\n\n  /**\n   * Tests unencrypted named stages, which don't use client-side encryption to enable data lake\n   * scenarios. Unencrypted named stages are specified with encryption=(TYPE='SNOWFLAKE_SSE').\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGetToUnencryptedStage() throws Throwable {\n\n    List<String> accounts =\n        Arrays.asList(\n            null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\" /*, \"gcpaccount_awssdk\"*/);\n    for (int i = 0; i < accounts.size(); i++) {\n      String accountName = accounts.get(i);\n      if (accounts.get(i) != null && accounts.get(i).equals(\"gcpaccount_awssdk\")) {\n        accountName = \"gcpaccount\";\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"true\");\n      }\n      String stageName = \"testPutGet_unencstage_\" + SnowflakeUtil.randomAlphaNumeric(10);\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        try {\n          String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE);\n\n          File destFolder = new File(tmpFolder, \"dest\");\n          destFolder.mkdirs();\n          String destFolderCanonicalPath = destFolder.getCanonicalPath();\n          String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n          statement.execute(\"alter session set ENABLE_GCP_PUT_EXCEPTION_FOR_OLD_DRIVERS=false\");\n          statement.execute(\n              \"CREATE OR REPLACE STAGE \" + stageName + \" encryption=(TYPE='SNOWFLAKE_SSE')\");\n\n          assertTrue(\n              statement.execute(\"PUT file://\" + sourceFilePath + \" @\" + stageName),\n              \"Failed to put a file\");\n\n          findFile(statement, \"ls @\" + stageName + \"/\");\n\n          // download the file we just uploaded to stage\n          assertTrue(\n              statement.execute(\n                  \"GET @\" + stageName + \" 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n              \"Failed to get a file\");\n\n          // Make sure that the downloaded file exists, it should be gzip compressed\n          File downloaded = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE + \".gz\");\n          assertTrue(downloaded.exists());\n\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + TEST_DATA_FILE + \".gz\");\n          p.waitFor();\n\n          File original = new File(sourceFilePath);\n          File unzipped = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE);\n          assertEquals(original.length(), unzipped.length());\n        } finally {\n          statement.execute(\"DROP STAGE IF EXISTS \" + stageName);\n        }\n      } finally {\n        SnowflakeUtil.systemSetEnv(\"SNOWFLAKE_GCS_FORCE_VIRTUAL_STYLE_DOMAINS\", \"false\");\n      }\n    }\n  }\n\n  /** Prepare statement will fail if the connection is already closed. */\n  @Test\n  public void testNotClosedSession() throws SQLException {\n    Connection connection = getConnection();\n    connection.close();\n    assertThrows(SnowflakeSQLException.class, () -> connection.prepareStatement(\"select 1\"));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testToTimestampNullBind() throws Throwable {\n    try (Connection connection = getConnection();\n        PreparedStatement preparedStatement =\n            connection.prepareStatement(\n                \"select 3 where to_timestamp_ltz(?, 3) = '1970-01-01 00:00:12.345\"\n                    + \" +000'::timestamp_ltz\")) {\n      // First test, normal usage.\n      preparedStatement.setInt(1, 12345);\n\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();\n        // Assert column count.\n        assertEquals(1, resultSetMetaData.getColumnCount());\n        // Assert this returned a 3.\n        assertTrue(resultSet.next());\n        assertEquals(3, resultSet.getInt(1));\n        assertFalse(resultSet.next());\n\n        // Second test, input is null.\n        preparedStatement.setNull(1, Types.INTEGER);\n      }\n      try (ResultSet resultSet = preparedStatement.executeQuery()) {\n        // Assert no rows returned.\n        assertFalse(resultSet.next());\n      }\n    }\n    // NOTE: Don't add new tests here. Instead, add it to other appropriate test class or create a\n    // new\n    // one. This class is too large to have more tests.\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeDriverLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeDriverIT.findFile;\nimport static net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1.mapper;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.google.cloud.storage.StorageException;\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.channels.FileChannel;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.DatabaseMetaData;\nimport java.sql.Driver;\nimport java.sql.DriverManager;\nimport java.sql.DriverPropertyInfo;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Types;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.UUID;\nimport java.util.zip.GZIPInputStream;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.annotations.DontRunOnTestaccount;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeGCSClient;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeStorageClient;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StageInfo;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageClientFactory;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StorageObjectMetadata;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n/**\n * General JDBC tests for the latest JDBC driver. This doesn't work for the oldest supported driver.\n * Revisit this tests whenever bumping up the oldest supported driver to examine if the tests still\n * is not applicable. If it is applicable, move tests to SnowflakeDriverIT so that both the latest\n * and oldest supported driver run the tests.\n */\n@Tag(TestTags.OTHERS)\npublic class SnowflakeDriverLatestIT extends BaseJDBCTest {\n  @TempDir private File tmpFolder;\n  @TempDir private File tmpFolder2;\n\n  public String testStageName =\n      String.format(\"test_stage_%s\", UUID.randomUUID().toString()).replaceAll(\"-\", \"_\");\n\n  // Check whether the two file's content are logically identical\n  private boolean isFileContentEqual(\n      String fileFullPath1, boolean compressedFile1, String fileFullPath2, boolean compressedFile2)\n      throws Throwable {\n    InputStream inputStream1 = new FileInputStream(fileFullPath1);\n    InputStream inputStream2 = new FileInputStream(fileFullPath2);\n\n    try {\n      if (compressedFile1) {\n        inputStream1 = new GZIPInputStream(inputStream1);\n      }\n      if (compressedFile2) {\n        inputStream2 = new GZIPInputStream(inputStream2);\n      }\n      return Arrays.equals(IOUtils.toByteArray(inputStream1), IOUtils.toByteArray(inputStream2));\n    } finally {\n      inputStream1.close();\n      inputStream2.close();\n    }\n  }\n\n  @Test\n  @DontRunOnTestaccount\n  public void testClientInfoConnectionProperty() throws Throwable {\n    String clientInfoJSONStr = null;\n    JsonNode clientInfoJSON = null;\n    Properties props = new Properties();\n    props.put(\n        \"snowflakeClientInfo\",\n        \"{\\\"spark.version\\\":\\\"3.0.0\\\", \\\"spark.snowflakedb.version\\\":\\\"2.8.5\\\",\"\n            + \" \\\"spark.app.name\\\":\\\"SnowflakeSourceSuite\\\", \\\"scala.version\\\":\\\"2.12.11\\\",\"\n            + \" \\\"java.version\\\":\\\"1.8.0_221\\\", \\\"snowflakedb.jdbc.version\\\":\\\"3.13.2\\\"}\");\n    try (Connection connection = getConnection(DONT_INJECT_SOCKET_TIMEOUT, props, false, false);\n        Statement statement = connection.createStatement();\n        ResultSet res = statement.executeQuery(\"select current_session_client_info()\")) {\n      assertTrue(res.next());\n      clientInfoJSONStr = res.getString(1);\n      clientInfoJSON = mapper.readTree(clientInfoJSONStr);\n      // assert that spart version and spark app are found\n      assertEquals(\"3.0.0\", clientInfoJSON.get(\"spark.version\").asText(), \"spark version mismatch\");\n      assertEquals(\n          \"SnowflakeSourceSuite\",\n          clientInfoJSON.get(\"spark.app.name\").asText(),\n          \"spark app mismatch\");\n    }\n\n    // Test that when session property is set, connection parameter overrides it\n    System.setProperty(\n        \"snowflake.client.info\",\n        \"{\\\"spark.version\\\":\\\"fake\\\", \\\"spark.snowflakedb.version\\\":\\\"fake\\\",\"\n            + \" \\\"spark.app.name\\\":\\\"fake\\\", \\\"scala.version\\\":\\\"fake\\\",\"\n            + \" \\\"java.version\\\":\\\"fake\\\", \\\"snowflakedb.jdbc.version\\\":\\\"fake\\\"}\");\n    try (Connection connection = getConnection(DONT_INJECT_SOCKET_TIMEOUT, props, false, false);\n        Statement statement = connection.createStatement();\n        ResultSet res = statement.executeQuery(\"select current_session_client_info()\")) {\n      assertTrue(res.next());\n      clientInfoJSONStr = res.getString(1);\n      clientInfoJSON = mapper.readTree(clientInfoJSONStr);\n      // assert that spart version and spark app are found\n      assertEquals(\"3.0.0\", clientInfoJSON.get(\"spark.version\").asText(), \"spark version mismatch\");\n      assertEquals(\n          \"SnowflakeSourceSuite\",\n          clientInfoJSON.get(\"spark.app.name\").asText(),\n          \"spark app mismatch\");\n    }\n    System.clearProperty(\"snowflake.client.info\");\n  }\n\n  @Test\n  public void testGetSessionID() throws Throwable {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement();\n        ResultSet rset = statement.executeQuery(\"select current_session()\")) {\n      String sessionID = con.unwrap(SnowflakeConnection.class).getSessionID();\n      assertTrue(rset.next());\n      assertEquals(sessionID, rset.getString(1));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutThreshold() throws SQLException {\n    try (Connection connection = getConnection()) {\n      // assert that threshold equals default 200 from server side\n      SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      try (Statement statement = connection.createStatement()) {\n        SFStatement sfStatement = statement.unwrap(SnowflakeStatementImpl.class).getSfStatement();\n        statement.execute(\"CREATE OR REPLACE STAGE PUTTHRESHOLDSTAGE\");\n        String command =\n            \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @PUTTHRESHOLDSTAGE\";\n        SnowflakeFileTransferAgent agent =\n            new SnowflakeFileTransferAgent(command, sfSession, sfStatement);\n        assertEquals(200 * 1024 * 1024, agent.getBigFileThreshold());\n        // assert that setting threshold via put statement directly sets the big file threshold\n        // appropriately\n        String commandWithPut = command + \" threshold=314572800\";\n        agent = new SnowflakeFileTransferAgent(commandWithPut, sfSession, sfStatement);\n        assertEquals(314572800, agent.getBigFileThreshold());\n        // assert that after put statement, threshold goes back to previous session threshold\n        agent = new SnowflakeFileTransferAgent(command, sfSession, sfStatement);\n        assertEquals(200 * 1024 * 1024, agent.getBigFileThreshold());\n        // Attempt to set threshold to an invalid value such as a negative number\n        String commandWithInvalidThreshold = command + \" threshold=-1\";\n        SQLException e =\n            assertThrows(\n                SQLException.class,\n                () ->\n                    new SnowflakeFileTransferAgent(\n                        commandWithInvalidThreshold, sfSession, sfStatement));\n        assertEquals(SqlState.INVALID_PARAMETER_VALUE, e.getSQLState());\n      }\n    } catch (SQLException ex) {\n      throw ex;\n    }\n  }\n\n  /** Test API for Spark connector for FileTransferMetadata */\n  @Test\n  @Disabled\n  public void testGCPFileTransferMetadataWithOneFile() throws Throwable {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n\n    try (Connection connection = getConnection(\"gcpaccount\");\n        Statement statement = connection.createStatement()) {\n      try {\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        // Test put file with internal compression\n        String putCommand1 = \"put file:///dummy/path/file1.gz @\" + testStageName;\n        SnowflakeFileTransferAgent sfAgent1 =\n            new SnowflakeFileTransferAgent(putCommand1, sfSession, new SFStatement(sfSession));\n        List<SnowflakeFileTransferMetadata> metadatas1 = sfAgent1.getFileTransferMetadatas();\n\n        String srcPath1 = getFullPathFileInResource(TEST_DATA_FILE);\n        for (SnowflakeFileTransferMetadata oneMetadata : metadatas1) {\n          InputStream inputStream = new FileInputStream(srcPath1);\n\n          assertTrue(oneMetadata.isForOneFile());\n          SnowflakeFileTransferAgent.uploadWithoutConnection(\n              SnowflakeFileTransferConfig.Builder.newInstance()\n                  .setSnowflakeFileTransferMetadata(oneMetadata)\n                  .setUploadStream(inputStream)\n                  .setRequireCompress(true)\n                  .setNetworkTimeoutInMilli(0)\n                  .setOcspMode(OCSPMode.FAIL_OPEN)\n                  .build());\n        }\n\n        // Test Put file with external compression\n        String putCommand2 = \"put file:///dummy/path/file2.gz @\" + testStageName;\n        SnowflakeFileTransferAgent sfAgent2 =\n            new SnowflakeFileTransferAgent(putCommand2, sfSession, new SFStatement(sfSession));\n        List<SnowflakeFileTransferMetadata> metadatas2 = sfAgent2.getFileTransferMetadatas();\n\n        String srcPath2 = getFullPathFileInResource(TEST_DATA_FILE_2);\n        for (SnowflakeFileTransferMetadata oneMetadata : metadatas2) {\n          String gzfilePath = destFolderCanonicalPath + \"/tmp_compress.gz\";\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\"cp -fr \" + srcPath2 + \" \" + destFolderCanonicalPath + \"/tmp_compress\");\n          p.waitFor();\n          p = Runtime.getRuntime().exec(\"gzip \" + destFolderCanonicalPath + \"/tmp_compress\");\n          p.waitFor();\n\n          InputStream gzInputStream = new FileInputStream(gzfilePath);\n          assertTrue(oneMetadata.isForOneFile());\n          SnowflakeFileTransferAgent.uploadWithoutConnection(\n              SnowflakeFileTransferConfig.Builder.newInstance()\n                  .setSnowflakeFileTransferMetadata(oneMetadata)\n                  .setUploadStream(gzInputStream)\n                  .setRequireCompress(false)\n                  .setNetworkTimeoutInMilli(0)\n                  .setOcspMode(OCSPMode.FAIL_OPEN)\n                  .build());\n        }\n\n        // Download two files and verify their content.\n        assertTrue(\n            statement.execute(\n                \"GET @\" + testStageName + \" 'file://\" + destFolderCanonicalPath + \"/' parallel=8\"),\n            \"Failed to get files\");\n\n        // Make sure that the downloaded files are EQUAL,\n        // they should be gzip compressed\n        assertTrue(\n            isFileContentEqual(srcPath1, false, destFolderCanonicalPath + \"/file1.gz\", true));\n        assertTrue(\n            isFileContentEqual(srcPath2, false, destFolderCanonicalPath + \"/file2.gz\", true));\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + testStageName);\n      }\n    }\n  }\n\n  /** Test API for Kafka connector for FileTransferMetadata */\n  @Test\n  @DontRunOnGithubActions\n  public void testAzureS3FileTransferMetadataWithOneFile() throws Throwable {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n\n    List<String> supportedAccounts = Arrays.asList(\"s3testaccount\", \"azureaccount\");\n    for (String accountName : supportedAccounts) {\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        try {\n          // create a stage to put the file in\n          statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n          SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n          // Test put file with internal compression\n          String putCommand1 = \"put file:///dummy/path/file1.gz @\" + testStageName;\n          SnowflakeFileTransferAgent sfAgent1 =\n              new SnowflakeFileTransferAgent(putCommand1, sfSession, new SFStatement(sfSession));\n          List<SnowflakeFileTransferMetadata> metadatas1 = sfAgent1.getFileTransferMetadatas();\n\n          String srcPath1 = getFullPathFileInResource(TEST_DATA_FILE);\n          for (SnowflakeFileTransferMetadata oneMetadata : metadatas1) {\n            InputStream inputStream = new FileInputStream(srcPath1);\n\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(inputStream)\n                    .setRequireCompress(true)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .setSFSession(sfSession)\n                    .setCommand(putCommand1)\n                    .build());\n          }\n\n          // Test Put file with external compression\n          String putCommand2 = \"put file:///dummy/path/file2.gz @\" + testStageName;\n          SnowflakeFileTransferAgent sfAgent2 =\n              new SnowflakeFileTransferAgent(putCommand2, sfSession, new SFStatement(sfSession));\n          List<SnowflakeFileTransferMetadata> metadatas2 = sfAgent2.getFileTransferMetadatas();\n\n          String srcPath2 = getFullPathFileInResource(TEST_DATA_FILE_2);\n          for (SnowflakeFileTransferMetadata oneMetadata : metadatas2) {\n            String gzfilePath = destFolderCanonicalPath + \"/tmp_compress.gz\";\n            Process p =\n                Runtime.getRuntime()\n                    .exec(\"cp -fr \" + srcPath2 + \" \" + destFolderCanonicalPath + \"/tmp_compress\");\n            p.waitFor();\n            p = Runtime.getRuntime().exec(\"gzip \" + destFolderCanonicalPath + \"/tmp_compress\");\n            p.waitFor();\n\n            InputStream gzInputStream = new FileInputStream(gzfilePath);\n\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(gzInputStream)\n                    .setRequireCompress(false)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .setSFSession(sfSession)\n                    .setCommand(putCommand2)\n                    .build());\n          }\n\n          // Download two files and verify their content.\n          assertTrue(\n              statement.execute(\n                  \"GET @\"\n                      + testStageName\n                      + \" 'file://\"\n                      + destFolderCanonicalPath\n                      + \"/' parallel=8\"),\n              \"Failed to get files\");\n\n          // Make sure that the downloaded files are EQUAL,\n          // they should be gzip compressed\n          assertTrue(\n              isFileContentEqual(srcPath1, false, destFolderCanonicalPath + \"/file1.gz\", true));\n          assertTrue(\n              isFileContentEqual(srcPath2, false, destFolderCanonicalPath + \"/file2.gz\", true));\n        } finally {\n          statement.execute(\"DROP STAGE if exists \" + testStageName);\n        }\n      }\n    }\n  }\n\n  /** Negative test for FileTransferMetadata. It is only supported for PUT. */\n  @Test\n  @DontRunOnGithubActions\n  public void testGCPFileTransferMetadataNegativeOnlySupportPut() throws Throwable {\n    try (Connection connection = getConnection(\"gcpaccount\");\n        Statement statement = connection.createStatement()) {\n      try {\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n        // Put one file to the stage\n        String srcPath = getFullPathFileInResource(TEST_DATA_FILE);\n        statement.execute(\"put file://\" + srcPath + \" @\" + testStageName);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        File destFolder = new File(tmpFolder, \"dest\");\n        destFolder.mkdirs();\n        String destFolderCanonicalPath = destFolder.getCanonicalPath();\n\n        String getCommand = \"get @\" + testStageName + \" file://\" + destFolderCanonicalPath;\n\n        // The GET can be executed in normal way.\n        statement.execute(getCommand);\n\n        // Start negative test for GET.\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(getCommand, sfSession, new SFStatement(sfSession));\n\n        assertThrows(Exception.class, sfAgent::getFileTransferMetadatas);\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + testStageName);\n      }\n    }\n  }\n\n  @Test\n  public void testGetPropertyInfo() throws SQLException {\n    // Test with blank URL and no properties. ServerURL is needed.\n    String url = \"\";\n    Properties props = new Properties();\n    Driver driver = DriverManager.getDriver(\"jdbc:snowflake://snowflake.reg.local:8082\");\n    DriverPropertyInfo[] info = driver.getPropertyInfo(url, props);\n    assertEquals(1, info.length);\n    assertEquals(\"serverURL\", info[0].name);\n    assertEquals(\n        \"server URL in form of <protocol>://<host or domain>:<port number>/<path of resource>\",\n        info[0].description);\n\n    // Test with null URL and no properties. ServerURL is needed.\n    url = null;\n    props = new Properties();\n    driver = DriverManager.getDriver(\"jdbc:snowflake://snowflake.reg.local:8082\");\n    info = driver.getPropertyInfo(url, props);\n    assertEquals(1, info.length);\n    assertEquals(\"serverURL\", info[0].name);\n    assertEquals(\n        \"server URL in form of <protocol>://<host or domain>:<port number>/<path of resource>\",\n        info[0].description);\n\n    // Test with URL that requires username and password.\n    url = \"jdbc:snowflake://snowflake.reg.local:8082\";\n    info = driver.getPropertyInfo(url, props);\n    assertEquals(2, info.length);\n    assertEquals(\"user\", info[0].name);\n    assertEquals(\"username for account\", info[0].description);\n    assertEquals(\"password\", info[1].name);\n    assertEquals(\"password for account\", info[1].description);\n\n    // Add username and try again; get password requirement back\n    props.put(\"user\", \"snowman\");\n    props.put(\"password\", \"test\");\n    info = driver.getPropertyInfo(url, props);\n    assertEquals(0, info.length);\n\n    props.put(\"useProxy\", \"true\");\n    info = driver.getPropertyInfo(url, props);\n    assertEquals(2, info.length);\n    assertEquals(\"proxyHost\", info[0].name);\n    assertEquals(\"proxy host name\", info[0].description);\n    assertEquals(\"proxyPort\", info[1].name);\n    assertEquals(\"proxy port; should be an integer\", info[1].description);\n\n    props.put(\"proxyHost\", \"dummyHost\");\n    props.put(\"proxyPort\", \"dummyPort\");\n    info = driver.getPropertyInfo(url, props);\n    assertEquals(0, info.length);\n\n    // invalid URL still throws SQLException\n    String invalidUrl = \"snowflake.reg.local:8082\";\n    Properties fileProps = new Properties();\n    Driver finalDriver = driver;\n    SQLException e =\n        assertThrows(SQLException.class, () -> finalDriver.getPropertyInfo(invalidUrl, fileProps));\n    assertEquals((int) ErrorCode.INVALID_CONNECT_STRING.getMessageCode(), e.getErrorCode());\n  }\n\n  /**\n   * Tests put with overwrite set to false. Require SNOW-206907 to be merged to pass tests for gcp\n   * account\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPutOverwriteFalseNoDigest() throws Throwable {\n\n    // create 2 files: an original, and one that will overwrite the original\n    File file1 = new File(tmpFolder, \"testfile.csv\");\n    file1.createNewFile();\n    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file1))) {\n      bw.write(\"Writing original file content. This should get overwritten.\");\n    }\n\n    File file2 = new File(tmpFolder2, \"testfile.csv\");\n    file2.createNewFile();\n    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file2))) {\n      bw.write(\"This is all new! This should be the result of the overwriting.\");\n    }\n    String sourceFilePathOriginal = file1.getCanonicalPath();\n    String sourceFilePathOverwrite = file2.getCanonicalPath();\n\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n\n    List<String> accounts = Arrays.asList(null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\");\n    for (int i = 0; i < accounts.size(); i++) {\n      try (Connection connection = getConnection(accounts.get(i), paramProperties);\n          Statement statement = connection.createStatement()) {\n        try {\n          // create a stage to put the file in\n          statement.execute(\"CREATE OR REPLACE STAGE testing_stage\");\n          assertTrue(\n              statement.execute(\"PUT file://\" + sourceFilePathOriginal + \" @testing_stage\"),\n              \"Failed to put a file\");\n          // check that file exists in stage after PUT\n          findFile(statement, \"ls @testing_stage/\");\n\n          // put another file in same stage with same filename with overwrite = true\n          assertTrue(\n              statement.execute(\n                  \"PUT file://\" + sourceFilePathOverwrite + \" @testing_stage overwrite=false\"),\n              \"Failed to put a file\");\n\n          // check that file exists in stage after PUT\n          findFile(statement, \"ls @testing_stage/\");\n\n          // get file from new stage\n          assertTrue(\n              statement.execute(\n                  \"GET @testing_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n              \"Failed to get files\");\n\n          // Make sure that the downloaded file exists; it should be gzip compressed\n          File downloaded = new File(destFolderCanonicalPathWithSeparator + \"testfile.csv.gz\");\n          assertTrue(downloaded.exists());\n\n          // unzip the file\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + \"testfile.csv.gz\");\n          p.waitFor();\n\n          // 2nd file should never be uploaded\n          File unzipped = new File(destFolderCanonicalPathWithSeparator + \"testfile.csv\");\n          assertTrue(FileUtils.contentEqualsIgnoreEOL(file1, unzipped, null));\n        } finally {\n          statement.execute(\"DROP TABLE IF EXISTS testLoadToLocalFS\");\n        }\n      }\n    }\n  }\n\n  /**\n   * Tests PUT disable test\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPutDisable() throws Throwable {\n\n    // create a file\n    File file = new File(tmpFolder, \"testfile99.csv\");\n    file.createNewFile();\n    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {\n      bw.write(\"This content won't be uploaded as PUT is disabled.\");\n    }\n\n    String sourceFilePathOriginal = file.getCanonicalPath();\n\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"enablePutGet\", false);\n\n    List<String> accounts = Arrays.asList(null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\");\n    for (int i = 0; i < accounts.size(); i++) {\n      try (Connection connection = getConnection(accounts.get(i), paramProperties);\n          Statement statement = connection.createStatement()) {\n        Exception ex =\n            assertThrows(\n                Exception.class,\n                () ->\n                    statement.execute(\n                        \"PUT file://\" + sourceFilePathOriginal + \" @testPutGet_disable_stage\"));\n        assertTrue(ex.getMessage().equalsIgnoreCase(\"File transfers have been disabled.\"));\n      }\n    }\n  }\n\n  /**\n   * Tests GET disable test\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testGetDisable() throws Throwable {\n\n    // create a folder\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"enablePutGet\", false);\n\n    List<String> accounts = Arrays.asList(null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\");\n    for (int i = 0; i < accounts.size(); i++) {\n      try (Connection connection = getConnection(accounts.get(i), paramProperties);\n          Statement statement = connection.createStatement()) {\n\n        Exception ex =\n            assertThrows(\n                Exception.class,\n                () ->\n                    statement.execute(\n                        \"GET @testPutGet_disable_stage 'file://\"\n                            + destFolderCanonicalPath\n                            + \"' parallel=8\"));\n\n        assertTrue(ex.getMessage().equalsIgnoreCase(\"File transfers have been disabled.\"));\n      }\n    }\n  }\n\n  /**\n   * Test NULL in LIMIT and OFFSET with Snow-76376 enabled this should be handled as without LIMIT\n   * and OFFSET\n   */\n  @Test\n  public void testSnow76376() throws Throwable {\n    try (Connection connection = getConnection();\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\n            \"create or replace table t(a int) as select * from values\" + \"(1),(2),(8),(10)\");\n\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT * FROM t \" + \"ORDER BY a LIMIT \" + \"? OFFSET ?\")) {\n\n          ////////////////////////////\n          // both NULL\n          preparedStatement.setNull(1, 4); // int\n          preparedStatement.setNull(2, 4); // int\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute preparedStatement with OFFSET and LIMIT set to NULL\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(1, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(2, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(8, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(10, resultSet.getInt(1));\n          }\n\n          ////////////////////////////\n          // both empty string\n          preparedStatement.setString(1, \"\");\n          preparedStatement.setString(2, \"\");\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute preparedStatement with OFFSET and LIMIT set to empty string\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(1, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(2, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(8, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(10, resultSet.getInt(1));\n          }\n\n          ////////////////////////////\n          // only LIMIT NULL\n          preparedStatement.setNull(1, 4); // int\n          preparedStatement.setInt(2, 2);\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute preparedStatement with LIMIT set to NULL\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(8, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(10, resultSet.getInt(1));\n          }\n\n          ////////////////////////////\n          // only LIMIT empty string\n          preparedStatement.setString(1, \"\");\n          preparedStatement.setInt(2, 2);\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute preparedStatement with LIMIT set to empty string\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(8, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(10, resultSet.getInt(1));\n          }\n\n          ////////////////////////////\n          // only OFFSET NULL\n          preparedStatement.setInt(1, 3); // int\n          preparedStatement.setNull(2, 4);\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute preparedStatement with OFFSET set to NULL\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(1, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(2, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(8, resultSet.getInt(1));\n          }\n\n          ////////////////////////////\n          // only OFFSET empty string\n          preparedStatement.setInt(1, 3); // int\n          preparedStatement.setNull(2, 4);\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute preparedStatement with OFFSET set to empty string\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            assertTrue(resultSet.next());\n            assertEquals(1, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(2, resultSet.getInt(1));\n            assertTrue(resultSet.next());\n            assertEquals(8, resultSet.getInt(1));\n          }\n        }\n        ////////////////////////////\n        // OFFSET and LIMIT NULL for constant select query\n        try (PreparedStatement preparedStatement =\n            connection.prepareStatement(\"SELECT 1 FROM t \" + \"ORDER BY a LIMIT \" + \"? OFFSET ?\")) {\n          preparedStatement.setNull(1, 4); // int\n          preparedStatement.setNull(2, 4); // int\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute constant preparedStatement with OFFSET and LIMIT set to NULL\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            for (int i = 0; i < 4; i++) {\n              assertTrue(resultSet.next());\n              assertEquals(1, resultSet.getInt(1));\n            }\n          }\n\n          ////////////////////////////\n          // OFFSET and LIMIT empty string for constant select query\n          preparedStatement.setString(1, \"\"); // int\n          preparedStatement.setString(2, \"\"); // int\n\n          assertTrue(\n              preparedStatement.execute(),\n              \"Could not execute constant preparedStatement with OFFSET and LIMIT set to empty string\");\n          try (ResultSet resultSet = preparedStatement.getResultSet()) {\n            for (int i = 0; i < 4; i++) {\n              assertTrue(resultSet.next());\n              assertEquals(1, resultSet.getInt(1));\n            }\n          }\n        }\n      } finally {\n        regularStatement.execute(\"drop table t\");\n      }\n    }\n  }\n\n  /**\n   * Tests that result columns of type GEOGRAPHY appear as VARCHAR / VARIANT / BINARY to the client,\n   * depending on the value of GEOGRAPHY_OUTPUT_FORMAT\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testGeoOutputTypes() throws Throwable {\n\n    Properties paramProperties = new Properties();\n\n    paramProperties.put(\"ENABLE_USER_DEFINED_TYPE_EXPANSION\", true);\n    paramProperties.put(\"ENABLE_GEOGRAPHY_TYPE\", true);\n\n    try (Connection connection = getConnection(paramProperties);\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\"create or replace table t_geo(geo geography);\");\n\n        regularStatement.execute(\n            \"insert into t_geo values ('POINT(0 0)'), ('LINESTRING(1 1, 2 2)')\");\n\n        testGeoOutputTypeSingle(\n            regularStatement, \"geoJson\", \"GEOGRAPHY\", \"java.lang.String\", Types.VARCHAR);\n\n        testGeoOutputTypeSingle(\n            regularStatement, \"wkt\", \"GEOGRAPHY\", \"java.lang.String\", Types.VARCHAR);\n\n        testGeoOutputTypeSingle(regularStatement, \"wkb\", \"GEOGRAPHY\", \"[B\", Types.BINARY);\n      } finally {\n        regularStatement.execute(\"drop table t_geo\");\n      }\n    }\n  }\n\n  private void testGeoOutputTypeSingle(\n      Statement regularStatement,\n      String outputFormat,\n      String expectedColumnTypeName,\n      String expectedColumnClassName,\n      int expectedColumnType)\n      throws Throwable {\n\n    regularStatement.execute(\"alter session set GEOGRAPHY_OUTPUT_FORMAT='\" + outputFormat + \"'\");\n\n    try (ResultSet resultSet = regularStatement.executeQuery(\"select * from t_geo\")) {\n      ResultSetMetaData metadata = resultSet.getMetaData();\n\n      assertEquals(1, metadata.getColumnCount());\n\n      // GeoJSON: SQL type OBJECT, Java type String\n      assertEquals(expectedColumnTypeName, metadata.getColumnTypeName(1));\n      assertEquals(expectedColumnClassName, metadata.getColumnClassName(1));\n      assertEquals(expectedColumnType, metadata.getColumnType(1));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGeoMetadata() throws Throwable {\n    Properties paramProperties = new Properties();\n\n    paramProperties.put(\"ENABLE_FIX_182763\", true);\n\n    try (Connection connection = getConnection(paramProperties);\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\"create or replace table t_geo(geo geography);\");\n\n        testGeoMetadataSingle(connection, regularStatement, \"geoJson\", Types.VARCHAR);\n\n        testGeoMetadataSingle(connection, regularStatement, \"geoJson\", Types.VARCHAR);\n\n        testGeoMetadataSingle(connection, regularStatement, \"wkt\", Types.VARCHAR);\n\n        testGeoMetadataSingle(connection, regularStatement, \"wkt\", Types.VARCHAR);\n\n        testGeoMetadataSingle(connection, regularStatement, \"wkb\", Types.BINARY);\n\n        testGeoMetadataSingle(connection, regularStatement, \"wkb\", Types.BINARY);\n      } finally {\n        regularStatement.execute(\"drop table t_geo\");\n      }\n    }\n  }\n\n  private void testGeoMetadataSingle(\n      Connection connection,\n      Statement regularStatement,\n      String outputFormat,\n      int expectedColumnType)\n      throws Throwable {\n\n    regularStatement.execute(\"alter session set GEOGRAPHY_OUTPUT_FORMAT='\" + outputFormat + \"'\");\n\n    DatabaseMetaData md = connection.getMetaData();\n    try (ResultSet resultSet = md.getColumns(null, null, \"T_GEO\", null)) {\n      ResultSetMetaData metadata = resultSet.getMetaData();\n\n      assertEquals(24, metadata.getColumnCount());\n\n      assertTrue(resultSet.next());\n\n      assertEquals(expectedColumnType, resultSet.getInt(5));\n      assertEquals(\"GEOGRAPHY\", resultSet.getString(6));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGeometryOutputTypes() throws Throwable {\n    Properties paramProperties = new Properties();\n\n    paramProperties.put(\"ENABLE_USER_DEFINED_TYPE_EXPANSION\", true);\n    paramProperties.put(\"ENABLE_GEOMETRY_TYPE\", true);\n\n    try (Connection connection = getConnection(paramProperties);\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\"create or replace table t_geo2(geo geometry);\");\n\n        regularStatement.execute(\n            \"insert into t_geo2 values ('POINT(0 0)'), ('LINESTRING(1 1, 2 2)')\");\n\n        testGeometryOutputTypeSingle(\n            regularStatement, \"geoJson\", \"GEOMETRY\", \"java.lang.String\", Types.VARCHAR);\n\n        testGeometryOutputTypeSingle(\n            regularStatement, \"wkt\", \"GEOMETRY\", \"java.lang.String\", Types.VARCHAR);\n      } finally {\n        regularStatement.execute(\"drop table t_geo2\");\n      }\n    }\n  }\n\n  private void testGeometryOutputTypeSingle(\n      Statement regularStatement,\n      String outputFormat,\n      String expectedColumnTypeName,\n      String expectedColumnClassName,\n      int expectedColumnType)\n      throws Throwable {\n\n    regularStatement.execute(\"alter session set GEOGRAPHY_OUTPUT_FORMAT='\" + outputFormat + \"'\");\n\n    try (ResultSet resultSet = regularStatement.executeQuery(\"select * from t_geo2\")) {\n\n      ResultSetMetaData metadata = resultSet.getMetaData();\n\n      assertEquals(1, metadata.getColumnCount());\n\n      // GeoJSON: SQL type OBJECT, Java type String\n      assertEquals(expectedColumnTypeName, metadata.getColumnTypeName(1));\n      assertEquals(expectedColumnClassName, metadata.getColumnClassName(1));\n      assertEquals(expectedColumnType, metadata.getColumnType(1));\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testGeometryMetadata() throws Throwable {\n\n    Properties paramProperties = new Properties();\n\n    try (Connection connection = getConnection(paramProperties);\n        Statement regularStatement = connection.createStatement()) {\n      try {\n        regularStatement.execute(\"create or replace table t_geo2(geo geometry);\");\n\n        testGeometryMetadataSingle(connection, regularStatement, \"geoJson\", Types.VARCHAR);\n\n        testGeometryMetadataSingle(connection, regularStatement, \"wkt\", Types.VARCHAR);\n      } finally {\n        regularStatement.execute(\"drop table t_geo2\");\n      }\n    }\n  }\n\n  private void testGeometryMetadataSingle(\n      Connection connection,\n      Statement regularStatement,\n      String outputFormat,\n      int expectedColumnType)\n      throws Throwable {\n\n    regularStatement.execute(\"alter session set GEOGRAPHY_OUTPUT_FORMAT='\" + outputFormat + \"'\");\n\n    DatabaseMetaData md = connection.getMetaData();\n    try (ResultSet resultSet = md.getColumns(null, null, \"T_GEO2\", null)) {\n      ResultSetMetaData metadata = resultSet.getMetaData();\n\n      assertEquals(24, metadata.getColumnCount());\n\n      assertTrue(resultSet.next());\n\n      assertEquals(expectedColumnType, resultSet.getInt(5));\n      assertEquals(\"GEOMETRY\", resultSet.getString(6));\n    }\n  }\n\n  /**\n   * Tests that upload and download small file to gcs stage. Require SNOW-206907 to be merged (still\n   * pass when not merge, but will use presigned url instead of GoogleCredential)\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGetGcsDownscopedCredential() throws Throwable {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      putAndGetFile(statement);\n    }\n  }\n\n  /** Added in > 3.15.0 */\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGetGcsDownscopedCredentialWithDisabledDefaultCredentials() throws Throwable {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    paramProperties.put(SFSessionProperty.DISABLE_GCS_DEFAULT_CREDENTIALS.getPropertyKey(), true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      putAndGetFile(statement);\n    }\n  }\n\n  private void putAndGetFile(Statement statement) throws Throwable {\n    String sourceFilePath = getFullPathFileInResource(TEST_DATA_FILE_2);\n\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n    try {\n      statement.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n\n      assertTrue(\n          statement.execute(\"PUT file://\" + sourceFilePath + \" @testPutGet_stage\"),\n          \"Failed to put a file\");\n\n      findFile(statement, \"ls @testPutGet_stage/\");\n\n      // download the file we just uploaded to stage\n      assertTrue(\n          statement.execute(\n              \"GET @testPutGet_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n          \"Failed to get a file\");\n\n      // Make sure that the downloaded file exists, it should be gzip compressed\n      File downloaded = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2 + \".gz\");\n      assertTrue(downloaded.exists());\n\n      Process p =\n          Runtime.getRuntime()\n              .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2 + \".gz\");\n      p.waitFor();\n\n      File original = new File(sourceFilePath);\n      File unzipped = new File(destFolderCanonicalPathWithSeparator + TEST_DATA_FILE_2);\n      System.out.println(\n          \"Original file: \" + original.getAbsolutePath() + \", size: \" + original.length());\n      System.out.println(\n          \"Unzipped file: \" + unzipped.getAbsolutePath() + \", size: \" + unzipped.length());\n      assertEquals(original.length(), unzipped.length());\n    } finally {\n      statement.execute(\"DROP STAGE IF EXISTS testGetPut_stage\");\n    }\n  }\n\n  /**\n   * Tests that upload and download big file to gcs stage. Require SNOW-206907 to be merged (still\n   * pass when not merge, but will use presigned url instead of GoogleCredential)\n   *\n   * @throws Throwable\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGetLargeFileGCSDownscopedCredential() throws Throwable {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        File destFolder = new File(tmpFolder, \"dest\");\n        destFolder.mkdirs();\n        String destFolderCanonicalPath = destFolder.getCanonicalPath();\n        String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n        File largeTempFile = new File(tmpFolder, \"largeFile.csv\");\n        largeTempFile.createNewFile();\n        try (BufferedWriter bw = new BufferedWriter(new FileWriter(largeTempFile))) {\n          bw.write(\"Creating large test file for GCP PUT/GET test\");\n          bw.write(System.lineSeparator());\n          bw.write(\"Creating large test file for GCP PUT/GET test\");\n          bw.write(System.lineSeparator());\n        }\n        File largeTempFile2 = new File(tmpFolder, \"largeFile2.csv\");\n        largeTempFile2.createNewFile();\n\n        String sourceFilePath = largeTempFile.getCanonicalPath();\n\n        // copy info from 1 file to another and continue doubling file size until we reach ~1.5GB,\n        // which is a large file\n        for (int i = 0; i < 12; i++) {\n          copyContentFrom(largeTempFile, largeTempFile2);\n          copyContentFrom(largeTempFile2, largeTempFile);\n        }\n\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE largefile_stage\");\n        assertTrue(\n            statement.execute(\"PUT file://\" + sourceFilePath + \" @largefile_stage\"),\n            \"Failed to put a file\");\n\n        // check that file exists in stage after PUT\n        findFile(statement, \"ls @largefile_stage/\");\n\n        // create a new table with columns matching CSV file\n        statement.execute(\"create or replace table large_table (colA string)\");\n        // copy rows from file into table\n        statement.execute(\"copy into large_table from @largefile_stage/largeFile.csv.gz\");\n        // copy back from table into different stage\n        statement.execute(\"create or replace stage extra_stage\");\n        statement.execute(\"copy into @extra_stage/bigFile.csv.gz from large_table single=true\");\n\n        // get file from new stage\n        assertTrue(\n            statement.execute(\n                \"GET @extra_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n            \"Failed to get files\");\n\n        // Make sure that the downloaded file exists; it should be gzip compressed\n        File downloaded = new File(destFolderCanonicalPathWithSeparator + \"bigFile.csv.gz\");\n        assertTrue(downloaded.exists());\n\n        // unzip the file\n        Process p =\n            Runtime.getRuntime()\n                .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + \"bigFile.csv.gz\");\n        p.waitFor();\n\n        // compare the original file with the file that's been uploaded, copied into a table, copied\n        // back into a stage,\n        // downloaded, and unzipped\n        File unzipped = new File(destFolderCanonicalPathWithSeparator + \"bigFile.csv\");\n        assertEquals(largeTempFile.length(), unzipped.length());\n        assertTrue(FileUtils.contentEquals(largeTempFile, unzipped));\n      } finally {\n        statement.execute(\"DROP STAGE IF EXISTS largefile_stage\");\n        statement.execute(\"DROP STAGE IF EXISTS extra_stage\");\n        statement.execute(\"DROP TABLE IF EXISTS large_table\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGetLargeFileAzure() throws Throwable {\n    Properties paramProperties = new Properties();\n    try (Connection connection = getConnection(\"azureaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        File destFolder = new File(tmpFolder, \"dest\");\n        destFolder.mkdirs();\n        String destFolderCanonicalPath = destFolder.getCanonicalPath();\n        String destFolderCanonicalPathWithSeparator = destFolderCanonicalPath + File.separator;\n\n        File largeTempFile = new File(tmpFolder, \"largeFile.csv\");\n        largeTempFile.createNewFile();\n        try (BufferedWriter bw = new BufferedWriter(new FileWriter(largeTempFile))) {\n          bw.write(\"Creating large test file for Azure PUT/GET test\");\n          bw.write(System.lineSeparator());\n          bw.write(\"Creating large test file for Azure PUT/GET test\");\n          bw.write(System.lineSeparator());\n        }\n        File largeTempFile2 = new File(tmpFolder, \"largeFile2.csv\");\n        largeTempFile2.createNewFile();\n\n        String sourceFilePath = largeTempFile.getCanonicalPath();\n\n        // copy info from 1 file to another and continue doubling file size until we reach ~1.5GB,\n        // which is a large file\n        for (int i = 0; i < 12; i++) {\n          copyContentFrom(largeTempFile, largeTempFile2);\n          copyContentFrom(largeTempFile2, largeTempFile);\n        }\n\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE largefile_stage\");\n        assertTrue(\n            statement.execute(\"PUT file://\" + sourceFilePath + \" @largefile_stage\"),\n            \"Failed to put a file\");\n\n        // check that file exists in stage after PUT\n        findFile(statement, \"ls @largefile_stage/\");\n\n        // create a new table with columns matching CSV file\n        statement.execute(\"create or replace table large_table (colA string)\");\n        // copy rows from file into table\n        statement.execute(\"copy into large_table from @largefile_stage/largeFile.csv.gz\");\n        // copy back from table into different stage\n        statement.execute(\"create or replace stage extra_stage\");\n        statement.execute(\"copy into @extra_stage/bigFile.csv.gz from large_table single=true\");\n\n        // get file from new stage\n        assertTrue(\n            statement.execute(\n                \"GET @extra_stage 'file://\" + destFolderCanonicalPath + \"' parallel=8\"),\n            \"Failed to get files\");\n\n        // Make sure that the downloaded file exists; it should be gzip compressed\n        File downloaded = new File(destFolderCanonicalPathWithSeparator + \"bigFile.csv.gz\");\n        assertTrue(downloaded.exists());\n\n        // unzip the file\n        Process p =\n            Runtime.getRuntime()\n                .exec(\"gzip -d \" + destFolderCanonicalPathWithSeparator + \"bigFile.csv.gz\");\n        p.waitFor();\n\n        // compare the original file with the file that's been uploaded, copied into a table, copied\n        // back into a stage,\n        // downloaded, and unzipped\n        File unzipped = new File(destFolderCanonicalPathWithSeparator + \"bigFile.csv\");\n        assertEquals(largeTempFile.length(), unzipped.length());\n        assertTrue(FileUtils.contentEquals(largeTempFile, unzipped));\n      } finally {\n        statement.execute(\"DROP STAGE IF EXISTS largefile_stage\");\n        statement.execute(\"DROP STAGE IF EXISTS extra_stage\");\n        statement.execute(\"DROP TABLE IF EXISTS large_table\");\n      }\n    }\n  }\n\n  /**\n   * helper function for creating large file in Java. Copies info from 1 file to another\n   *\n   * @param file1 file with info to be copied\n   * @param file2 file to be copied into\n   * @throws Exception\n   */\n  private void copyContentFrom(File file1, File file2) throws Exception {\n    FileInputStream inputStream = new FileInputStream(file1);\n    FileOutputStream outputStream = new FileOutputStream(file2);\n    try (FileChannel fIn = inputStream.getChannel();\n        FileChannel fOut = outputStream.getChannel()) {\n      fOut.transferFrom(fIn, 0, fIn.size());\n      fIn.position(0);\n      fOut.transferFrom(fIn, fIn.size(), fIn.size());\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutS3RegionalUrl() throws Throwable {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n\n    List<String> supportedAccounts = Arrays.asList(\"s3testaccount\", \"azureaccount\");\n    for (String accountName : supportedAccounts) {\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        try {\n          // create a stage to put the file in\n          statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n          SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n          // Test put file with internal compression\n          String putCommand1 = \"put file:///dummy/path/file1.gz @\" + testStageName;\n          SnowflakeFileTransferAgent sfAgent1 =\n              new SnowflakeFileTransferAgent(putCommand1, sfSession, new SFStatement(sfSession));\n          List<SnowflakeFileTransferMetadata> metadatas1 = sfAgent1.getFileTransferMetadatas();\n\n          String srcPath1 = getFullPathFileInResource(TEST_DATA_FILE);\n\n          for (SnowflakeFileTransferMetadata oneMetadata : metadatas1) {\n            InputStream inputStream = new FileInputStream(srcPath1);\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(inputStream)\n                    .setRequireCompress(true)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .setSFSession(sfSession)\n                    .setCommand(putCommand1)\n                    .setUseS3RegionalUrl(false)\n                    .build());\n          }\n\n          for (SnowflakeFileTransferMetadata oneMetadata : metadatas1) {\n            InputStream inputStream = new FileInputStream(srcPath1);\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(inputStream)\n                    .setRequireCompress(true)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .setSFSession(sfSession)\n                    .setCommand(putCommand1)\n                    .setUseS3RegionalUrl(true)\n                    .build());\n          }\n\n          // Test Put file with external compression\n          String putCommand2 = \"put file:///dummy/path/file2.gz @\" + testStageName;\n          SnowflakeFileTransferAgent sfAgent2 =\n              new SnowflakeFileTransferAgent(putCommand2, sfSession, new SFStatement(sfSession));\n          List<SnowflakeFileTransferMetadata> metadatas2 = sfAgent2.getFileTransferMetadatas();\n\n          String srcPath2 = getFullPathFileInResource(TEST_DATA_FILE_2);\n          for (SnowflakeFileTransferMetadata oneMetadata : metadatas2) {\n            String gzfilePath = destFolderCanonicalPath + \"/tmp_compress.gz\";\n            Process p =\n                Runtime.getRuntime()\n                    .exec(\"cp -fr \" + srcPath2 + \" \" + destFolderCanonicalPath + \"/tmp_compress\");\n            p.waitFor();\n            p = Runtime.getRuntime().exec(\"gzip \" + destFolderCanonicalPath + \"/tmp_compress\");\n            p.waitFor();\n\n            InputStream gzInputStream = new FileInputStream(gzfilePath);\n\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(gzInputStream)\n                    .setRequireCompress(false)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .setSFSession(sfSession)\n                    .setCommand(putCommand2)\n                    .build());\n          }\n\n          // Download two files and verify their content.\n          assertTrue(\n              statement.execute(\n                  \"GET @\"\n                      + testStageName\n                      + \" 'file://\"\n                      + destFolderCanonicalPath\n                      + \"/' parallel=8\"),\n              \"Failed to get files\");\n\n          // Make sure that the downloaded files are EQUAL,\n          // they should be gzip compressed\n          assertTrue(\n              isFileContentEqual(srcPath1, false, destFolderCanonicalPath + \"/file1.gz\", true));\n          assertTrue(\n              isFileContentEqual(srcPath2, false, destFolderCanonicalPath + \"/file2.gz\", true));\n        } finally {\n          statement.execute(\"DROP STAGE if exists \" + testStageName);\n        }\n      }\n    }\n  }\n\n  /**\n   * Test the streaming ingest client name and client key should be part of the file metadata in S3\n   * and Azure\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testAzureS3UploadStreamingIngestFileMetadata() throws Throwable {\n    String clientName = \"clientName\";\n    String clientKey = \"clientKey\";\n    List<String> supportedAccounts = Arrays.asList(\"s3testaccount\", \"azureaccount\");\n    for (String accountName : supportedAccounts) {\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        try {\n          // create a stage to put the file in\n          statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n          SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n          // Test put file with internal compression\n          String putCommand = \"put file:///dummy/path/file1.gz @\" + testStageName;\n          SnowflakeFileTransferAgent sfAgent =\n              new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n          List<SnowflakeFileTransferMetadata> metadata = sfAgent.getFileTransferMetadatas();\n\n          String srcPath1 = getFullPathFileInResource(TEST_DATA_FILE);\n          for (SnowflakeFileTransferMetadata oneMetadata : metadata) {\n            InputStream inputStream = new FileInputStream(srcPath1);\n\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(inputStream)\n                    .setRequireCompress(true)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .setSFSession(sfSession)\n                    .setCommand(putCommand)\n                    .setStreamingIngestClientName(clientName)\n                    .setStreamingIngestClientKey(clientKey)\n                    .build());\n\n            SnowflakeStorageClient client =\n                StorageClientFactory.getFactory()\n                    .createClient(\n                        ((SnowflakeFileTransferMetadataV1) oneMetadata).getStageInfo(),\n                        1,\n                        null,\n                        /* session= */ null);\n\n            String location =\n                ((SnowflakeFileTransferMetadataV1) oneMetadata).getStageInfo().getLocation();\n            int idx = location.indexOf('/');\n            String remoteStageLocation = location.substring(0, idx);\n            String path = location.substring(idx + 1) + \"file1.gz\";\n            StorageObjectMetadata meta = client.getObjectMetadata(remoteStageLocation, path);\n\n            // Verify that we are able to fetch the metadata\n            assertEquals(clientName, client.getStreamingIngestClientName(meta));\n            assertEquals(clientKey, client.getStreamingIngestClientKey(meta));\n          }\n        } finally {\n          statement.execute(\"DROP STAGE if exists \" + testStageName);\n        }\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testNoSpaceLeftOnDeviceException() throws SQLException {\n    List<String> supportedAccounts = Arrays.asList(\"gcpaccount\", \"s3testaccount\", \"azureaccount\");\n    for (String accountName : supportedAccounts) {\n      try (Connection connection = getConnection(accountName)) {\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        try (Statement statement = connection.createStatement()) {\n          try {\n            SFStatement sfStatement =\n                statement.unwrap(SnowflakeStatementImpl.class).getSfStatement();\n            statement.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n            statement.execute(\n                \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testPutGet_stage\");\n            String command = \"get @testPutGet_stage/\" + TEST_DATA_FILE + \" 'file:///tmp'\";\n            SnowflakeFileTransferAgent sfAgent =\n                new SnowflakeFileTransferAgent(command, sfSession, sfStatement);\n            StageInfo info = sfAgent.getStageInfo();\n            SnowflakeStorageClient client =\n                StorageClientFactory.getFactory().createClient(info, 1, null, /* session= */ null);\n\n            assertThrows(\n                SnowflakeSQLException.class,\n                () ->\n                    client.handleStorageException(\n                        new StorageException(\n                            client.getMaxRetries(),\n                            Constants.NO_SPACE_LEFT_ON_DEVICE_ERR,\n                            new IOException(Constants.NO_SPACE_LEFT_ON_DEVICE_ERR)),\n                        client.getMaxRetries(),\n                        \"download\",\n                        null,\n                        command,\n                        null));\n          } finally {\n            statement.execute(\"DROP STAGE if exists testPutGet_stage\");\n          }\n        }\n      }\n    }\n  }\n\n  @Test\n  @Disabled // TODO: ignored until SNOW-1616480 is resolved\n  public void testUploadWithGCSPresignedUrlWithoutConnection() throws Throwable {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    // set parameter for presignedUrl upload instead of downscoped token\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", false);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        // Test put file with internal compression\n        String putCommand = \"put file:///dummy/path/file1.gz @\" + testStageName;\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n        List<SnowflakeFileTransferMetadata> metadata = sfAgent.getFileTransferMetadatas();\n\n        String srcPath = getFullPathFileInResource(TEST_DATA_FILE);\n        for (SnowflakeFileTransferMetadata oneMetadata : metadata) {\n          InputStream inputStream = new FileInputStream(srcPath);\n\n          assertTrue(oneMetadata.isForOneFile());\n          SnowflakeFileTransferAgent.uploadWithoutConnection(\n              SnowflakeFileTransferConfig.Builder.newInstance()\n                  .setSnowflakeFileTransferMetadata(oneMetadata)\n                  .setUploadStream(inputStream)\n                  .setRequireCompress(true)\n                  .setNetworkTimeoutInMilli(0)\n                  .setOcspMode(OCSPMode.FAIL_OPEN)\n                  .build());\n        }\n\n        assertTrue(\n            statement.execute(\n                \"GET @\" + testStageName + \" 'file://\" + destFolderCanonicalPath + \"/' parallel=8\"),\n            \"Failed to get files\");\n        assertTrue(\n            isFileContentEqual(\n                srcPath,\n                false,\n                java.nio.file.Paths.get(destFolderCanonicalPath, \"file1.gz\").toString(),\n                true));\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + testStageName);\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testUploadWithGCSDownscopedCredentialWithoutConnection() throws Throwable {\n    uploadWithGCSDownscopedCredentialWithoutConnection();\n  }\n\n  /** Added in > 3.15.0 */\n  @Test\n  @DontRunOnGithubActions\n  public void\n      testUploadWithGCSDownscopedCredentialAndDisabledGcsDefaultCredentialsWithoutConnection()\n          throws Throwable {\n    System.setProperty(SnowflakeGCSClient.DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME, \"true\");\n    try {\n      uploadWithGCSDownscopedCredentialWithoutConnection();\n    } finally {\n      System.clearProperty(SnowflakeGCSClient.DISABLE_GCS_DEFAULT_CREDENTIALS_PROPERTY_NAME);\n    }\n  }\n\n  private void uploadWithGCSDownscopedCredentialWithoutConnection() throws Throwable {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement(); ) {\n      try {\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n        SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n\n        // Test put file with internal compression\n        String putCommand = \"put file:///dummy/path/file1.gz @\" + testStageName;\n        SnowflakeFileTransferAgent sfAgent =\n            new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n        List<SnowflakeFileTransferMetadata> metadataList = sfAgent.getFileTransferMetadatas();\n        assertEquals(1, metadataList.size());\n        SnowflakeFileTransferMetadata oneMetadata = metadataList.get(0);\n        assertFalse(oneMetadata.isForOneFile());\n\n        // Upload multiple file with the same SnowflakeFileTransferMetadata\n        String[] fileNames = {TEST_DATA_FILE, TEST_DATA_FILE_2};\n        for (String fileName : fileNames) {\n          String srcPath = getFullPathFileInResource(fileName);\n          try (InputStream inputStream = new FileInputStream(srcPath)) {\n            // upload file 1\n            String targetFileName = fileName + \".gz\";\n            SnowflakeFileTransferAgent.uploadWithoutConnection(\n                SnowflakeFileTransferConfig.Builder.newInstance()\n                    .setSnowflakeFileTransferMetadata(oneMetadata)\n                    .setUploadStream(inputStream)\n                    .setDestFileName(targetFileName)\n                    .setRequireCompress(true)\n                    .setNetworkTimeoutInMilli(0)\n                    .setOcspMode(OCSPMode.FAIL_OPEN)\n                    .build());\n            assertTrue(\n                statement.execute(\n                    \"GET @\" + testStageName + \" 'file://\" + destFolderCanonicalPath + \"/'\"),\n                \"Failed to get files with down-scoped token\");\n            assertTrue(\n                isFileContentEqual(\n                    srcPath,\n                    false,\n                    java.nio.file.Paths.get(destFolderCanonicalPath, targetFileName).toString(),\n                    true));\n          }\n        }\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + testStageName);\n      }\n    }\n  }\n\n  /**\n   * This tests that when the HTAP optimization parameter ENABLE_SNOW_654741_FOR_TESTING is set to\n   * true and no session parameters or db/schema/wh are returned for select/dml statements, the\n   * parameters and metadata are still accessible after creating a resultset object.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testHTAPOptimizations() throws SQLException {\n    try {\n      // Set the HTAP test parameter to true\n      try (Connection con = getSnowflakeAdminConnection();\n          Statement statement = con.createStatement()) {\n        statement.execute(\n            \"alter account \"\n                + TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\")\n                + \" set ENABLE_SNOW_654741_FOR_TESTING=true\");\n      }\n      // Create a normal connection and assert that database, schema, and warehouse have expected\n      // values\n      try (Connection con = getConnection()) {\n        SFSession session = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        assertTrue(\n            TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SCHEMA\").equalsIgnoreCase(con.getSchema()));\n        assertTrue(\n            TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_DATABASE\").equalsIgnoreCase(con.getCatalog()));\n        assertTrue(\n            TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_WAREHOUSE\")\n                .equalsIgnoreCase(session.getWarehouse()));\n        try (Statement statement = con.createStatement()) {\n          // Set TIMESTAMP_OUTPUT_FORMAT (which is a session parameter) to check its value later\n          try {\n            statement.execute(\n                \"alter session set TIMESTAMP_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FFTZH'\");\n            statement.execute(\"create or replace table testtable1 (cola string, colb int)\");\n            statement.execute(\n                \"insert into testtable1 values ('row1', 1), ('row2', 2), ('row3', 3)\");\n            try (ResultSet rs = statement.executeQuery(\"select * from testtable1\")) {\n              assertEquals(3, getSizeOfResultSet(rs));\n              // Assert database, schema, and warehouse have the same values as before even though\n              // the select statement will return no parameters or metadata\n              assertTrue(\n                  TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_SCHEMA\").equalsIgnoreCase(con.getSchema()));\n              assertTrue(\n                  TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_DATABASE\")\n                      .equalsIgnoreCase(con.getCatalog()));\n              assertTrue(\n                  TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_WAREHOUSE\")\n                      .equalsIgnoreCase(session.getWarehouse()));\n              // Assert session parameter TIMESTAMP_OUTPUT_FORMAT has the same value as before the\n              // select statement\n              assertEquals(\n                  \"YYYY-MM-DD HH24:MI:SS.FFTZH\",\n                  session.getCommonParameters().get(\"TIMESTAMP_OUTPUT_FORMAT\"));\n            }\n          } finally {\n            statement.execute(\"alter session unset TIMESTAMP_OUTPUT_FORMAT\");\n            statement.execute(\"drop table if exists testtable1\");\n          }\n        }\n      }\n    } finally {\n      try (Connection con = getSnowflakeAdminConnection();\n          Statement statement = con.createStatement()) {\n        statement.execute(\n            \"alter account \"\n                + TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\")\n                + \" unset ENABLE_SNOW_654741_FOR_TESTING\");\n      }\n    }\n  }\n\n  /**\n   * This tests that statement parameters are still used correctly when the HTAP optimization of\n   * removing all parameters is enabled.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testHTAPStatementParameterCaching() throws SQLException {\n    // Set the HTAP test parameter to true\n    try (Connection con = getSnowflakeAdminConnection()) {\n      Statement statement = con.createStatement();\n      statement.execute(\n          \"alter account \"\n              + TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\")\n              + \" set ENABLE_SNOW_654741_FOR_TESTING=true\");\n    }\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      // Set up a test table with time, date, and timestamp values\n      try {\n        statement.execute(\"create or replace table timetable (t1 time, t2 timestamp, t3 date)\");\n        statement.execute(\n            \"insert into timetable values ('13:53:11', '2023-08-17 13:53:33', '2023-08-17')\");\n        // Set statement- level parameters that will affect the output (set output format params)\n        statement\n            .unwrap(SnowflakeStatement.class)\n            .setParameter(\"TIME_OUTPUT_FORMAT\", \"HH12:MI:SS.FF AM\");\n        statement\n            .unwrap(SnowflakeStatement.class)\n            .setParameter(\"DATE_OUTPUT_FORMAT\", \"DD-MON-YYYY\");\n        statement\n            .unwrap(SnowflakeStatement.class)\n            .setParameter(\"TIMESTAMP_OUTPUT_FORMAT\", \"YYYY-MM-DD\\\"T\\\"HH24:MI:SS\");\n        try (ResultSet resultSet = statement.executeQuery(\"select * from timetable\")) {\n          assertTrue(resultSet.next());\n          // Assert that the values match the format of the specified statement parameter output\n          // format\n          // values\n          assertEquals(\"01:53:11.000000000 PM\", resultSet.getString(1));\n          assertEquals(\"2023-08-17T13:53:33\", resultSet.getString(2));\n          assertEquals(\"17-Aug-2023\", resultSet.getString(3));\n        }\n\n        // Set a different statement parameter value for DATE_OUTPUT_FORMAT\n        statement.unwrap(SnowflakeStatement.class).setParameter(\"DATE_OUTPUT_FORMAT\", \"MM/DD/YYYY\");\n        try (ResultSet resultSet = statement.executeQuery(\"select * from timetable\")) {\n          assertTrue(resultSet.next());\n          // Verify it matches the new statement parameter specified output format\n          assertEquals(\"08/17/2023\", resultSet.getString(3));\n        }\n      } finally {\n        statement.execute(\"drop table if exists timetable\");\n      }\n    }\n    // cleanup\n    try (Connection con2 = getSnowflakeAdminConnection();\n        Statement statement = con2.createStatement()) {\n      statement.execute(\n          \"alter account \"\n              + TestUtil.systemGetEnv(\"SNOWFLAKE_TEST_ACCOUNT\")\n              + \" unset ENABLE_SNOW_654741_FOR_TESTING\");\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testS3PutInGS() throws Throwable {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    Properties paramProperties = new Properties();\n    try (Connection connection = getConnection(\"s3testaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n      try {\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE \" + testStageName);\n\n        // put file using GS system commmand, this is internal GS behavior\n        final String fileName = \"testFile.json\";\n        final String content = \"testName: testS3PutInGs\";\n        String putSystemCall =\n            String.format(\n                \"call system$it('PUT_FILE_TO_STAGE', '%s', '%s', '%s', '%s')\",\n                testStageName, fileName, content, \"false\");\n        statement.execute(putSystemCall);\n\n        // get file using jdbc\n        String getCall =\n            String.format(\"GET @%s 'file://%s/'\", testStageName, destFolderCanonicalPath);\n        statement.execute(getCall);\n\n        InputStream downloadedFileStream =\n            new FileInputStream(\n                java.nio.file.Paths.get(destFolderCanonicalPath, fileName).toString());\n        String downloadedFile = IOUtils.toString(downloadedFileStream, StandardCharsets.UTF_8);\n        assertTrue(\n            content.equals(downloadedFile), \"downloaded content does not equal uploaded content\");\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + testStageName);\n      }\n    }\n  }\n\n  /** Added in > 3.17.0 */\n  @Test\n  public void shouldLoadDriverWithDisabledTelemetryOob() throws ClassNotFoundException {\n    Class.forName(\"net.snowflake.client.api.driver.SnowflakeDriver\");\n\n    assertFalse(TelemetryService.getInstance().isEnabled());\n    assertFalse(TelemetryService.getInstance().isHTAPEnabled());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeDriverTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Properties;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport org.junit.jupiter.api.Test;\n\n/** Driver unit test */\npublic class SnowflakeDriverTest {\n  class TestCase {\n    String url;\n    String scheme;\n    String host;\n    int port;\n    String account;\n    Map<String, String> parameters;\n\n    TestCase(\n        String url,\n        String scheme,\n        String host,\n        int port,\n        String account,\n        Map<String, String> parameters) {\n      this.url = url;\n      this.scheme = scheme;\n      this.host = host;\n      this.port = port;\n      this.account = account;\n      this.parameters = parameters;\n    }\n\n    void match(String url, SnowflakeConnectString sc) {\n      String scheme = sc.getScheme();\n      String host = sc.getHost();\n      int port = sc.getPort();\n      Map<String, Object> parameters = sc.getParameters();\n\n      assertEquals(this.scheme, scheme, \"URL scheme: \" + url);\n      assertEquals(this.host, host, \"URL scheme: \" + url);\n      assertEquals(this.port, port, \"URL scheme: \" + url);\n      assertEquals(this.parameters.size(), parameters.size(), \"URL scheme: \" + url);\n      assertEquals(this.account, account, \"URL scheme. \" + url);\n\n      for (Map.Entry<String, String> entry : this.parameters.entrySet()) {\n        String k = entry.getKey().toUpperCase(Locale.US);\n        Object v = parameters.get(k);\n        assertEquals(entry.getValue(), v, \"URL scheme: \" + url + \", key: \" + k);\n      }\n    }\n  }\n\n  private Map<String, String> EMPTY_PARAMETERS = Collections.emptyMap();\n\n  @Test\n  public void testAcceptUrls() throws Exception {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n\n    Map<String, String> expectedParameters;\n\n    List<TestCase> testCases = new ArrayList<>();\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"prop1\", \"value1\");\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://http://testaccount.localhost?prop1=value1\",\n            \"http\",\n            \"testaccount.localhost\",\n            443,\n            \"testaccount\",\n            expectedParameters));\n\n    // value including dash\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"CLIENT_SESSION_KEEP_ALIVE\", Boolean.TRUE.toString());\n    expectedParameters.put(\"db\", \"TEST_DB\");\n    expectedParameters.put(\"proxyHost\", \"your-host.com\");\n    expectedParameters.put(\"proxyPort\", \"1234\");\n    expectedParameters.put(\"schema\", \"PUBLIC\");\n    expectedParameters.put(\"useProxy\", Boolean.TRUE.toString());\n    expectedParameters.put(\"warehouse\", \"TEST_WH\");\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.snowflakecomputing.com:443\"\n                + \"?CLIENT_SESSION_KEEP_ALIVE=true&db=TEST_DB&proxyHost=your-host.com&proxyPort=1234\"\n                + \"&schema=PUBLIC&useProxy=true&warehouse=TEST_WH\",\n            \"https\",\n            \"testaccount.snowflakecomputing.com\",\n            443,\n            \"testaccount\",\n            expectedParameters));\n\n    // value including dot\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"proxyHost\", \"72.1.32.55\");\n    expectedParameters.put(\"proxyPort\", \"portU\");\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.snowflakecomputing.com\"\n                + \"?proxyHost=72.1.32.55&proxyPort=port%55\",\n            \"https\", \"testaccount.snowflakecomputing.com\", 443, \"testaccount\", expectedParameters));\n\n    // value in escaped value\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"proxyHost\", \"=/\");\n    expectedParameters.put(\"proxyPort\", \"777\");\n    expectedParameters.put(\"ssl\", \"off\");\n    expectedParameters.put(\"account\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.com:8080?proxyHost=%3d%2f&proxyPort=777&ssl=off\",\n            \"http\", \"testaccount.com\", 8080, null, expectedParameters));\n\n    // value including non ascii characters\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"proxyHost\", \"cheese\");\n    expectedParameters.put(\"proxyPort\", \"!@\");\n    expectedParameters.put(\"account\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.com:8080?proxyHost=cheese&proxyPort=!@\",\n            \"https\",\n            \"testaccount.com\",\n            8080,\n            null,\n            expectedParameters));\n\n    // value including non ascii characters\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"proxyHost\", \"cheese\");\n    expectedParameters.put(\"proxyPort\", \"cake\");\n    expectedParameters.put(\"account\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://https://testaccount.com:8080?proxyHost=cheese&proxyPort=cake\",\n            \"https\",\n            \"testaccount.com\",\n            8080,\n            null,\n            expectedParameters));\n\n    // host including underscore\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"snowflake\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://snowflake.reg-RT-Replication1-7387_2.local:8082\",\n            \"https\",\n            \"snowflake.reg-RT-Replication1-7387_2.local\",\n            8082,\n            \"snowflake\",\n            expectedParameters));\n\n    // host including underscore with parameters\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"db\", \"testdb\");\n    expectedParameters.put(\"schema\", \"testschema\");\n    expectedParameters.put(\"ACCOUNT\", \"snowflake\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://snowflake.reg-RT-Replication1-7387_2.local:8082\"\n                + \"?db=testdb&schema=testschema\",\n            \"https\",\n            \"snowflake.reg-RT-Replication1-7387_2.local\",\n            8082,\n            \"snowflake\",\n            expectedParameters));\n\n    // host including underscore with parameters after a path slash\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"db\", \"testdb\");\n    expectedParameters.put(\"schema\", \"testschema\");\n    expectedParameters.put(\"ACCOUNT\", \"snowflake\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://snowflake.reg-RT-Replication1-7387_2.local:8082/\"\n                + \"?db=testdb&schema=testschema\",\n            \"https\",\n            \"snowflake.reg-RT-Replication1-7387_2.local\",\n            8082,\n            \"snowflake\",\n            expectedParameters));\n\n    // host including underscore with parameters after a path slash\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    expectedParameters.put(\"authenticator\", \"https://snowflakecomputing.okta.com\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.snowflakecomputing.com/\"\n                + \"?authenticator=https://snowflakecomputing.okta.com\",\n            \"https\",\n            \"testaccount.snowflakecomputing.com\",\n            443,\n            \"testaccount\",\n            expectedParameters));\n\n    // localhost without port\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.com:\",\n            \"https\",\n            \"testaccount.com\",\n            443,\n            null,\n            expectedParameters));\n\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\n        \"authenticator\",\n        \"https://snowflakecomputing.okta.com/app/template_saml_2_0/ky7gy61iAOAMLLSOZSVX/sso/saml\");\n    expectedParameters.put(\"account\", \"testaccount\");\n    expectedParameters.put(\"user\", \"qa\");\n    expectedParameters.put(\"ssl\", \"off\");\n    expectedParameters.put(\"db\", \"testdb\");\n    expectedParameters.put(\"schema\", \"testschema\");\n    expectedParameters.put(\"networkTimeout\", \"3600\");\n    expectedParameters.put(\"retryQuery\", \"on\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://snowflake.reg.local:8082/\"\n                + \"?account=testaccount\"\n                + \"&authenticator=https://snowflakecomputing.okta.com/app/template_saml_2_0/ky7gy61iAOAMLLSOZSVX/sso/saml\"\n                + \"&user=qa&ssl=off\"\n                + \"&schema=testschema&db=testdb&networkTimeout=3600&retryQuery=on\",\n            \"http\",\n            \"snowflake.reg.local\",\n            8082,\n            \"testaccount\",\n            expectedParameters));\n\n    // invalid key value pair including multiple equal signs\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    expectedParameters.put(\"proxyPort\", \"781\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.com?proxyHost=puppy==dog&proxyPort=781\",\n            \"https\",\n            \"testaccount.com\",\n            443,\n            \"testaccount\",\n            expectedParameters));\n\n    // invalid key value including multiple amp\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"testaccount\");\n    expectedParameters.put(\"proxyPort\", \"5\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://testaccount.com?proxyHost=&&&proxyPort=5\",\n            \"https\",\n            \"testaccount.com\",\n            443,\n            \"testaccount\",\n            expectedParameters));\n\n    // NOT global url\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"globalaccount-12345\");\n    expectedParameters.put(\"proxyPort\", \"45.12.34.5\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://globalaccount-12345.com?proxyHost=&&&proxyPort=45.12.34.5\",\n            \"https\",\n            \"globalaccount-12345.com\",\n            443,\n            \"globalaccount-12345\",\n            expectedParameters));\n\n    // global url\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"globalaccount\");\n    expectedParameters.put(\"proxyPort\", \"45-12-34-5\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://globalaccount-12345.global.snowflakecomputing.com\"\n                + \"?proxyHost=&&&proxyPort=45-12-34-5\",\n            \"https\",\n            \"globalaccount-12345.global.snowflakecomputing.com\",\n            443,\n            \"globalaccount\",\n            expectedParameters));\n\n    // rt-language1\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"snowflake\");\n    expectedParameters.put(\"user\", \"admin\");\n    expectedParameters.put(\"networkTimeout\", \"3600\");\n    expectedParameters.put(\"retryQuery\", \"on\");\n    expectedParameters.put(\"ssl\", \"off\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://snowflake.reg-RT-PC-Language1-10850.local:8082/\"\n                + \"?account=snowflake&user=admin&ssl=off&networkTimeout=3600&retryQuery=on\",\n            \"http\",\n            \"snowflake.reg-RT-PC-Language1-10850.local\",\n            8082,\n            \"snowflake\",\n            expectedParameters));\n\n    // rt-language1 with account parameter\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"testaccount100\");\n    expectedParameters.put(\"user\", \"admin\");\n    expectedParameters.put(\"networkTimeout\", \"3600\");\n    expectedParameters.put(\"retryQuery\", \"on\");\n    expectedParameters.put(\"ssl\", \"off\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://snowflake.reg-RT-PC-Language1-10850.local:8082/\"\n                + \"?account=testaccount100&user=admin&ssl=off&networkTimeout=3600&retryQuery=on\",\n            \"http\",\n            \"snowflake.reg-RT-PC-Language1-10850.local\",\n            8082,\n            \"snowflake\",\n            expectedParameters));\n\n    // first_t_single\n    expectedParameters = new HashMap<>();\n    expectedParameters.put(\"ACCOUNT\", \"cutoff_ds_consumer\");\n    expectedParameters.put(\"user\", \"snowman\");\n    expectedParameters.put(\"tracing\", \"off\");\n    expectedParameters.put(\"retryQuery\", \"on\");\n    expectedParameters.put(\"ssl\", \"off\");\n    expectedParameters.put(\"password\", \"test\");\n    testCases.add(\n        new TestCase(\n            \"jdbc:snowflake://10.180.189.160:7510/?account=cutoff_ds_consumer&user=snowman&password=test&tracing=off&ssl=off&retryQuery=on\",\n            \"http\",\n            \"10.180.189.160\",\n            7510,\n            \"cutoff_ds_consumer\",\n            expectedParameters));\n\n    for (TestCase t : testCases) {\n      assertTrue(snowflakeDriver.acceptsURL(t.url), \"URL is not valid: \" + t.url);\n      t.match(t.url, SnowflakeConnectString.parse(t.url, SnowflakeDriver.EMPTY_PROPERTIES));\n    }\n\n    // autoconfig with connections.toml\n    assertTrue(snowflakeDriver.acceptsURL(\"jdbc:snowflake:auto\"));\n\n    // negative tests\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflake://\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflake://:\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflake://:8080\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflake://localhost:xyz\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflak://localhost:8080\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflake://localhost:8080/a=b\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:snowflake://testaccount.com?proxyHost=%%\"));\n    assertFalse(\n        snowflakeDriver.acceptsURL(\"jdbc:snowflake://testaccount.com?proxyHost=%b&proxyPort=\"));\n    assertFalse(snowflakeDriver.acceptsURL(\"jdbc:mysql://localhost:3306/dbname\"));\n  }\n\n  @Test\n  public void testInvalidNullConnect() {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n    SQLException ex =\n        assertThrows(SQLException.class, () -> snowflakeDriver.connect(null, null).close());\n    assertEquals(\"Unable to connect to url of 'null'.\", ex.getMessage());\n  }\n\n  @Test\n  public void testGetVersion() {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n\n    int majorVersion = snowflakeDriver.getMajorVersion();\n    int minorVersion = snowflakeDriver.getMinorVersion();\n\n    assertThat(majorVersion, greaterThanOrEqualTo(3));\n    assertThat(minorVersion, greaterThanOrEqualTo(0));\n  }\n\n  @Test\n  public void testJDBCCompliant() {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n    assertFalse(snowflakeDriver.jdbcCompliant());\n  }\n\n  @Test\n  public void testGetParentLogger() {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n    assertNull(snowflakeDriver.getParentLogger());\n  }\n\n  @Test\n  public void testParseConnectStringException() {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n    Properties info = new Properties();\n    String jdbcConnectString =\n        \"jdbc:snowflake://abc-test.us-east-1.snowflakecomputing.com/?private_key_file=C:\\\\temp\\\\rsa_key.p8&private_key_pwd=test_password&user=test_user\";\n    Exception ex =\n        assertThrows(\n            Exception.class, () -> snowflakeDriver.connect(jdbcConnectString, info).close());\n    assertEquals(\"Connection string is invalid. Unable to parse.\", ex.getMessage());\n  }\n\n  @Test\n  public void testReturnsNullForOtherJdbcConnectString() throws SQLException {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n    Properties info = new Properties();\n    String jdbcConnectString = \"jdbc:mysql://host:port/database\";\n    assertNull(snowflakeDriver.connect(jdbcConnectString, info));\n  }\n\n  @Test\n  public void testConnectWithMissingAccountIdentifier() {\n    SnowflakeDriver snowflakeDriver = SnowflakeDriver.INSTANCE;\n    SnowflakeSQLException ex =\n        assertThrows(\n            SnowflakeSQLException.class,\n            () ->\n                snowflakeDriver.getPropertyInfo(\n                    \"jdbc:snowflake://localhost:443/?&ssl=on\", new Properties()));\n    assertEquals(\n        \"Invalid Connect String: jdbc:snowflake://localhost:443/?&ssl=on.\", ex.getMessage());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeFileTransferAgentExtractSafeDestFileNameTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\n/**\n * Unit tests for {@link SnowflakeFileTransferAgent#extractSafeDestFileName(String, String)}.\n *\n * <p>Covers the SNOW-3437355 regression: server-supplied stage object keys must not be allowed to\n * escape the user's download directory via Windows backslash path separators or other traversal\n * forms.\n */\npublic class SnowflakeFileTransferAgentExtractSafeDestFileNameTest {\n\n  private static final String QUERY_ID = \"test-query-id\";\n\n  @Test\n  public void simpleNameIsReturnedAsIs() throws SnowflakeSQLException {\n    assertEquals(\n        \"report.csv\", SnowflakeFileTransferAgent.extractSafeDestFileName(\"report.csv\", QUERY_ID));\n  }\n\n  @Test\n  public void forwardSlashPathReturnsBasename() throws SnowflakeSQLException {\n    assertEquals(\n        \"report.csv\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir/sub/report.csv\", QUERY_ID));\n  }\n\n  @Test\n  public void backslashOnlyPathReturnsBasename() throws SnowflakeSQLException {\n    assertEquals(\n        \"evil.dll\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage\\\\dir\\\\evil.dll\", QUERY_ID));\n  }\n\n  @Test\n  public void mixedSeparatorsReturnTrailingBasename() throws SnowflakeSQLException {\n    assertEquals(\n        \"real.csv\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir\\\\sub/real.csv\", QUERY_ID));\n    assertEquals(\n        \"real.csv\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage\\\\dir/sub\\\\real.csv\", QUERY_ID));\n  }\n\n  @Test\n  public void windowsTraversalAttackIsNeutralized() throws SnowflakeSQLException {\n    assertEquals(\n        \"evil.dll\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\n            \"stage/dir/..\\\\..\\\\..\\\\Windows\\\\System32\\\\evil.dll\", QUERY_ID));\n  }\n\n  @Test\n  public void singleBackslashTraversalIsNeutralized() throws SnowflakeSQLException {\n    assertEquals(\n        \"evil\", SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir/..\\\\evil\", QUERY_ID));\n  }\n\n  @Test\n  public void trailingDotDotIsRejected() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir/..\", QUERY_ID));\n  }\n\n  @Test\n  public void backslashTrailingDotDotIsRejected() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage\\\\dir\\\\..\", QUERY_ID));\n  }\n\n  @ParameterizedTest\n  @ValueSource(strings = {\".\", \"..\", \"\"})\n  public void dotAndEmptyTokensAreRejected(String input) {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(input, QUERY_ID));\n  }\n\n  @Test\n  public void trailingForwardSlashYieldsEmptyAndIsRejected() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir/\", QUERY_ID));\n  }\n\n  @Test\n  public void trailingBackslashYieldsEmptyAndIsRejected() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage\\\\dir\\\\\", QUERY_ID));\n  }\n\n  @Test\n  public void nulByteIsRejected() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            SnowflakeFileTransferAgent.extractSafeDestFileName(\n                \"stage/dir/file\\u0000hidden\", QUERY_ID));\n  }\n\n  @Test\n  public void nullSourceFileIsRejected() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(null, QUERY_ID));\n  }\n\n  @Test\n  public void leadingDotsInOtherwiseValidNamesAreAllowed() throws SnowflakeSQLException {\n    assertEquals(\n        \".hidden\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir/.hidden\", QUERY_ID));\n    assertEquals(\n        \"..something\",\n        SnowflakeFileTransferAgent.extractSafeDestFileName(\"stage/dir/..something\", QUERY_ID));\n  }\n\n  @Test\n  public void noSeparatorAtAllReturnsWholeString() throws SnowflakeSQLException {\n    assertEquals(\n        \"lonely.csv\", SnowflakeFileTransferAgent.extractSafeDestFileName(\"lonely.csv\", QUERY_ID));\n  }\n\n  @ParameterizedTest\n  @ValueSource(\n      strings = {\n        \"C:foo\",\n        \":foo\",\n        \"foo:\",\n        \"passwd:hidden\",\n        \"stage/dir/C:foo\",\n        \"stage\\\\dir\\\\name:stream\"\n      })\n  public void colonInBasenameIsRejected(String input) {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> SnowflakeFileTransferAgent.extractSafeDestFileName(input, QUERY_ID));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeFileTransferConfigTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.ByteArrayInputStream;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeFileTransferConfigTest {\n\n  private static final SnowflakeFileTransferMetadataV1 SNOWFLAKE_FILE_TRANSFER_METADATA =\n      new SnowflakeFileTransferMetadataV1(\n          \"\", \"\", \"\", \"\", 0L, SFBaseFileTransferAgent.CommandType.UPLOAD, null);\n  private static final ByteArrayInputStream UPLOAD_STREAM = new ByteArrayInputStream(new byte[0]);\n\n  @Test\n  public void shouldBuildDefaultConfig() {\n    SnowflakeFileTransferConfig config = createObligatoryConfigPartInBuilder().build();\n\n    assertObligatoryParameters(config);\n    assertFalse(config.isSilentException());\n  }\n\n  private static void assertObligatoryParameters(SnowflakeFileTransferConfig config) {\n    assertNotNull(config);\n    assertEquals(SNOWFLAKE_FILE_TRANSFER_METADATA, config.getSnowflakeFileTransferMetadata());\n    assertEquals(UPLOAD_STREAM, config.getUploadStream());\n    assertEquals(OCSPMode.DISABLE_OCSP_CHECKS, config.getOcspMode());\n  }\n\n  @Test\n  public void shouldBuildConfig() {\n    SnowflakeFileTransferConfig config =\n        createObligatoryConfigPartInBuilder().setSilentException(true).build();\n\n    assertObligatoryParameters(config);\n    assertTrue(config.isSilentException());\n  }\n\n  private static SnowflakeFileTransferConfig.Builder createObligatoryConfigPartInBuilder() {\n    return SnowflakeFileTransferConfig.Builder.newInstance()\n        .setSnowflakeFileTransferMetadata(SNOWFLAKE_FILE_TRANSFER_METADATA)\n        .setUploadStream(UPLOAD_STREAM)\n        .setOcspMode(OCSPMode.DISABLE_OCSP_CHECKS);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeGcsClientHandleExceptionLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport com.google.cloud.storage.StorageException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.SocketTimeoutException;\nimport java.security.InvalidKeyException;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeGCSClient;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.Mockito;\n\n/** Test for SnowflakeGcsClient handle exception function, only work with latest driver */\n@Tag(TestTags.OTHERS)\npublic class SnowflakeGcsClientHandleExceptionLatestIT extends AbstractDriverIT {\n  @TempDir private File tmpFolder;\n  private Connection connection;\n  private SFStatement sfStatement;\n  private SFSession sfSession;\n  private String command;\n  private SnowflakeGCSClient spyingClient;\n  private int overMaxRetry;\n  private int maxRetry;\n\n  @BeforeEach\n  public void setup() throws SQLException {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n    connection = getConnection(\"gcpaccount\", paramProperties);\n    sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    Statement statement = connection.createStatement();\n    sfStatement = statement.unwrap(SnowflakeStatementImpl.class).getSfStatement();\n    statement.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n    command = \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testPutGet_stage\";\n    SnowflakeFileTransferAgent agent =\n        new SnowflakeFileTransferAgent(command, sfSession, sfStatement);\n    SnowflakeGCSClient client =\n        SnowflakeGCSClient.createSnowflakeGCSClient(\n            agent.getStageInfo(), agent.getEncryptionMaterial().get(0), sfSession);\n    maxRetry = client.getMaxRetries();\n    overMaxRetry = maxRetry + 1;\n    spyingClient = Mockito.spy(client);\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void error401RenewExpired() throws SQLException, InterruptedException {\n    // Unauthenticated, renew is called.\n    spyingClient.handleStorageException(\n        new StorageException(401, \"Unauthenticated\"), 0, \"upload\", sfSession, command, null);\n    Mockito.verify(spyingClient, Mockito.times(1)).renew(Mockito.anyMap());\n\n    // Unauthenticated, command null, not renew, renew called remaining 1\n    spyingClient.handleStorageException(\n        new StorageException(401, \"Unauthenticated\"), 0, \"upload\", sfSession, null, null);\n    Mockito.verify(spyingClient, Mockito.times(1)).renew(Mockito.anyMap());\n\n    // Unauthenticated, backoff with interrupt, renew is called\n    Exception[] exceptionContainer = new Exception[1];\n    Thread thread =\n        new Thread(\n            new Runnable() {\n              @Override\n              public void run() {\n                try {\n                  spyingClient.handleStorageException(\n                      new StorageException(401, \"Unauthenticated\"),\n                      maxRetry,\n                      \"upload\",\n                      sfSession,\n                      command,\n                      null);\n                } catch (SnowflakeSQLException e) {\n                  exceptionContainer[0] = e;\n                }\n              }\n            });\n    thread.start();\n    thread.interrupt();\n    thread.join();\n    assertNull(exceptionContainer[0], \"Exception must not have been thrown in here\");\n    Mockito.verify(spyingClient, Mockito.times(2)).renew(Mockito.anyMap());\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void error401OverMaxRetryThrow() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new StorageException(401, \"Unauthenticated\"),\n                overMaxRetry,\n                \"upload\",\n                sfSession,\n                command,\n                null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorInvalidKey() {\n    // Unauthenticated, renew is called.\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new Exception(new InvalidKeyException()), 0, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorInterruptedException() throws SQLException {\n    // Can still retry, no error thrown\n    spyingClient.handleStorageException(\n        new InterruptedException(), 0, \"upload\", sfSession, command, null);\n\n    Mockito.verify(spyingClient, Mockito.never()).renew(Mockito.anyMap());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new InterruptedException(), 26, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorSocketTimeoutException() throws SnowflakeSQLException {\n    // Can still retry, no error thrown\n    spyingClient.handleStorageException(\n        new SocketTimeoutException(), 0, \"upload\", sfSession, command, null);\n\n    Mockito.verify(spyingClient, Mockito.never()).renew(Mockito.anyMap());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new SocketTimeoutException(), 26, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorUnknownException() {\n    // Unauthenticated, renew is called.\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new Exception(), 0, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorWithNullSession() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new StorageException(401, \"Unauthenticated\"), 0, \"upload\", null, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorNoSpaceLeftOnDevice() throws IOException {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    String getCommand =\n        \"get @testPutGet_stage/\" + TEST_DATA_FILE + \" 'file://\" + destFolderCanonicalPath + \"'\";\n    assertThrows(\n        SQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new StorageException(\n                    maxRetry,\n                    Constants.NO_SPACE_LEFT_ON_DEVICE_ERR,\n                    new IOException(Constants.NO_SPACE_LEFT_ON_DEVICE_ERR)),\n                0,\n                \"download\",\n                null,\n                getCommand,\n                null));\n  }\n\n  @AfterEach\n  public void cleanUp() throws SQLException {\n    sfStatement.close();\n    connection.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeResultSetSerializableIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.greaterThan;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.ResultSetMetaData;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Properties;\nimport javax.annotation.Nullable;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\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.ArgumentsSource;\n\n/** SnowflakeResultSetSerializable tests */\n@Tag(TestTags.RESULT_SET)\npublic class SnowflakeResultSetSerializableIT extends BaseJDBCTest {\n  @TempDir private File tmpFolder;\n\n  private static boolean developPrint = false;\n\n  // sfFullURL is used to support private link URL.\n  // This test case is not for private link env, so just use a valid URL for testing purpose.\n  private String sfFullURL = \"https://sfctest0.snowflakecomputing.com\";\n\n  public Connection init(String queryResultFormat) throws SQLException {\n    return init(null, queryResultFormat);\n  }\n\n  public Connection init(@Nullable Properties properties, String queryResultFormat)\n      throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection(properties);\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n\n      // Set up theses parameters as smaller values in order to generate\n      // multiple file chunks with small data volumes.\n      stmt.execute(\"alter session set result_first_chunk_max_size = 512\");\n      stmt.execute(\"alter session set result_min_chunk_size = 512\");\n      stmt.execute(\"alter session set arrow_result_rb_flush_size = 512\");\n      stmt.execute(\"alter session set result_chunk_size_multiplier = 1.2\");\n    }\n    return conn;\n  }\n\n  /**\n   * Generate CSV string for ResultSet for correctness comparison getString() is used for each cell.\n   *\n   * @param rs The result set to be accessed.\n   * @return The CSV string for the Result Set.\n   * @throws Throwable If any error happens\n   */\n  private String generateCSVResult(ResultSet rs) throws Throwable {\n    StringBuilder builder = new StringBuilder(1024 * 1024);\n    builder.append(\"==== result start ===\\n\");\n\n    ResultSetMetaData metadata = rs.getMetaData();\n    int colCount = metadata.getColumnCount();\n\n    while (rs.next()) {\n      for (int i = 1; i <= colCount; i++) {\n        rs.getObject(i);\n        if (rs.wasNull()) {\n          builder.append(\"\\\"\").append(\"null\").append(\"\\\",\");\n        } else {\n          builder.append(\"\\\"\").append(rs.getString(i)).append(\"\\\",\");\n        }\n      }\n      builder.append(\"\\n\");\n    }\n\n    builder.append(\"==== result end   ===\\n\");\n\n    return builder.toString();\n  }\n\n  /**\n   * Split the result set to SnowflakeResultSetSerializable objects based on the INPUT max size and\n   * serialize the objects into files. One object is serialized into one separate file.\n   *\n   * @param rs The result set to be accessed.\n   * @param maxSizeInBytes The expected data size in one serializable object.\n   * @param fileNameAppendix The generated file's appendix to avoid duplicated file names\n   * @return a list of file name.\n   * @throws Throwable If any error happens.\n   */\n  private List<String> serializeResultSet(\n      SnowflakeResultSet rs, long maxSizeInBytes, String fileNameAppendix) throws Throwable {\n    List<String> result = new ArrayList<>();\n\n    List<SnowflakeResultSetSerializable> resultSetChunks =\n        rs.getResultSetSerializables(maxSizeInBytes);\n\n    for (int i = 0; i < resultSetChunks.size(); i++) {\n      SnowflakeResultSetSerializable entry = resultSetChunks.get(i);\n\n      // Write object to file\n      String tmpFileName = tmpFolder.getPath() + \"_result_\" + i + \".\" + fileNameAppendix;\n      try (FileOutputStream fo = new FileOutputStream(tmpFileName);\n          ObjectOutputStream so = new ObjectOutputStream(fo)) {\n        so.writeObject(entry);\n        so.flush();\n      }\n      result.add(tmpFileName);\n    }\n\n    if (developPrint) {\n      System.out.println(\"\\nSplit ResultSet as \" + result.size() + \" parts.\");\n      for (String filename : result) {\n        System.out.println(filename);\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Give a file list, deserialize SnowflakeResultSetSerializableV1 object from each file, access\n   * the content with ResultSet and generate CSV string for them for correctness comparison.\n   *\n   * @param files The file names where the serializable objects are serialized.\n   * @return The CSV string wrapped in these SnowflakeResultSetSerializableV1\n   * @throws Throwable If any error happens.\n   */\n  private String deserializeResultSet(List<String> files) throws Throwable {\n    return deserializeResultSetWithProperties(files, null);\n  }\n\n  private String deserializeResultSetWithProperties(List<String> files, Properties props)\n      throws Throwable {\n    StringBuilder builder = new StringBuilder(1024 * 1024);\n    builder.append(\"==== result start ===\\n\");\n\n    for (String filename : files) {\n      // Read Object from file\n      try (FileInputStream fi = new FileInputStream(filename);\n          ObjectInputStream si = new ObjectInputStream(fi)) {\n        SnowflakeResultSetSerializableV1 resultSetChunk =\n            (SnowflakeResultSetSerializableV1) si.readObject();\n\n        if (developPrint) {\n          System.out.println(\n              \"\\nFormat: \"\n                  + resultSetChunk.getQueryResultFormat()\n                  + \" UncompChunksize: \"\n                  + resultSetChunk.getUncompressedDataSizeInBytes()\n                  + \" firstChunkContent: \"\n                  + (resultSetChunk.getFirstChunkStringData() == null ? \" null \" : \" not null \"));\n          for (SnowflakeResultSetSerializableV1.ChunkFileMetadata chunkFileMetadata :\n              resultSetChunk.chunkFileMetadatas) {\n            System.out.println(\n                \"RowCount=\"\n                    + chunkFileMetadata.getRowCount()\n                    + \", cpsize=\"\n                    + chunkFileMetadata.getCompressedByteSize()\n                    + \", uncpsize=\"\n                    + chunkFileMetadata.getUncompressedByteSize()\n                    + \", URL= \"\n                    + chunkFileMetadata.getFileURL());\n          }\n        }\n\n        // Read data from object\n        try (ResultSet rs =\n            resultSetChunk.getResultSet(\n                SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                    .setProxyProperties(props)\n                    .setSfFullURL(sfFullURL)\n                    .build())) {\n\n          // print result set meta data\n          ResultSetMetaData metadata = rs.getMetaData();\n          int colCount = metadata.getColumnCount();\n          if (developPrint) {\n            for (int j = 1; j <= colCount; j++) {\n              System.out.print(\" table: \" + metadata.getTableName(j));\n              System.out.print(\" schema: \" + metadata.getSchemaName(j));\n              System.out.print(\" type: \" + metadata.getColumnTypeName(j));\n              System.out.print(\" name: \" + metadata.getColumnName(j));\n              System.out.print(\" precision: \" + metadata.getPrecision(j));\n              System.out.println(\" scale:\" + metadata.getScale(j));\n            }\n          }\n\n          // Print and count data\n          while (rs.next()) {\n            for (int i = 1; i <= colCount; i++) {\n              rs.getObject(i);\n              if (rs.wasNull()) {\n                builder.append(\"\\\"\").append(\"null\").append(\"\\\",\");\n              } else {\n                builder.append(\"\\\"\").append(rs.getString(i)).append(\"\\\",\");\n              }\n            }\n            builder.append(\"\\n\");\n          }\n        }\n      }\n    }\n\n    builder.append(\"==== result end   ===\\n\");\n\n    return builder.toString();\n  }\n\n  /**\n   * The is the test harness function for basic table.\n   *\n   * @param rowCount inserted row count\n   * @param maxSizeInBytes expected data size in one SnowflakeResultSetSerializableV1 object.\n   * @param whereClause where clause when executing query.\n   * @throws Throwable If any error happens.\n   */\n  private void testBasicTableHarness(\n      int rowCount,\n      long maxSizeInBytes,\n      String whereClause,\n      boolean needSetupTable,\n      boolean async,\n      String queryResultFormat)\n      throws Throwable {\n    List<String> fileNameList = null;\n    String originalResultCSVString = null;\n    try (Connection connection = init(queryResultFormat)) {\n      Statement statement = connection.createStatement();\n\n      if (developPrint) {\n        System.out.println(\n            \"testBasicTableHarness: rowCount=\"\n                + rowCount\n                + \", maxSizeInBytes=\"\n                + maxSizeInBytes\n                + \", whereClause=\"\n                + whereClause\n                + \", needSetupTable=\"\n                + needSetupTable\n                + \", async=\"\n                + async);\n      }\n\n      if (needSetupTable) {\n        statement.execute(\n            \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n        if (rowCount > 0) {\n          statement.execute(\n              \"insert into table_basic select \"\n                  + \"seq4(), 'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n                  + \" from table(generator(rowcount=>\"\n                  + rowCount\n                  + \"))\");\n        }\n      }\n\n      String sqlSelect = \"select * from table_basic \" + whereClause;\n      try (ResultSet rs =\n          async\n              ? statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(sqlSelect)\n              : statement.executeQuery(sqlSelect)) {\n\n        fileNameList = serializeResultSet((SnowflakeResultSet) rs, maxSizeInBytes, \"txt\");\n\n        originalResultCSVString = generateCSVResult(rs);\n      }\n    }\n\n    String chunkResultString = deserializeResultSet(fileNameList);\n    assertEquals(chunkResultString, originalResultCSVString);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBasicTableWithEmptyResult(String queryResultFormat) throws Throwable {\n    // Use complex WHERE clause in order to test both ARROW and JSON.\n    // It looks GS only generates JSON format result.\n    testBasicTableHarness(10, 1024, \"where int_c * int_c = 2\", true, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(10, 1024, \"where int_c * int_c = 2\", true, true, queryResultFormat);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBasicTableWithOnlyFirstChunk(String queryResultFormat) throws Throwable {\n    // Result only includes first data chunk, test maxSize is small.\n    testBasicTableHarness(1, 1, \"\", true, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(1, 1, \"\", true, true, queryResultFormat);\n    // Result only includes first data chunk, test maxSize is big.\n    testBasicTableHarness(1, 1024 * 1024, \"\", false, false, queryResultFormat);\n    // Test async mode\n    testBasicTableHarness(1, 1024 * 1024, \"\", false, true, queryResultFormat);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBasicTableWithOneFileChunk(String queryResultFormat) throws Throwable {\n    // Result only includes first data chunk, test maxSize is small.\n    testBasicTableHarness(300, 1, \"\", true, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(300, 1, \"\", true, true, queryResultFormat);\n    // Result only includes first data chunk, test maxSize is big.\n    testBasicTableHarness(300, 1024 * 1024, \"\", false, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(300, 1024 * 1024, \"\", false, true, queryResultFormat);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBasicTableWithSomeFileChunks(String queryResultFormat) throws Throwable {\n    // Result only includes first data chunk, test maxSize is small.\n    testBasicTableHarness(10000, 1, \"\", true, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(10000, 1, \"\", true, true, queryResultFormat);\n    // Result only includes first data chunk, test maxSize is median.\n    testBasicTableHarness(10000, 300 * 1024, \"\", false, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(10000, 300 * 1024, \"\", false, true, queryResultFormat);\n    // Result only includes first data chunk, test maxSize is big.\n    testBasicTableHarness(10000, 1024 * 1024, \"\", false, false, queryResultFormat);\n    // Test Async mode\n    testBasicTableHarness(10000, 1024 * 1024, \"\", false, true, queryResultFormat);\n  }\n\n  /**\n   * Test harness function to test timestamp_*, date, time types.\n   *\n   * @param rowCount inserted row count\n   * @param maxSizeInBytes expected data size in one SnowflakeResultSetSerializableV1 object.\n   * @param whereClause where clause when executing query.\n   * @param format_date configuration value for DATE_OUTPUT_FORMAT\n   * @param format_time configuration value for TIME_OUTPUT_FORMAT\n   * @param format_ntz configuration value for TIMESTAMP_NTZ_OUTPUT_FORMAT\n   * @param format_ltz configuration value for TIMESTAMP_LTZ_OUTPUT_FORMAT\n   * @param format_tz configuration value for TIMESTAMP_TZ_OUTPUT_FORMAT\n   * @param timezone configuration value for TIMEZONE\n   * @throws Throwable If any error happens.\n   */\n  private void testTimestampHarness(\n      int rowCount,\n      long maxSizeInBytes,\n      String whereClause,\n      String format_date,\n      String format_time,\n      String format_ntz,\n      String format_ltz,\n      String format_tz,\n      String timezone,\n      String queryResultFormat)\n      throws Throwable {\n    List<String> fileNameList = null;\n    String originalResultCSVString = null;\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"alter session set DATE_OUTPUT_FORMAT = '\" + format_date + \"'\");\n      statement.execute(\"alter session set TIME_OUTPUT_FORMAT = '\" + format_time + \"'\");\n      statement.execute(\"alter session set TIMESTAMP_NTZ_OUTPUT_FORMAT = '\" + format_ntz + \"'\");\n      statement.execute(\"alter session set TIMESTAMP_LTZ_OUTPUT_FORMAT = '\" + format_ltz + \"'\");\n      statement.execute(\"alter session set TIMESTAMP_TZ_OUTPUT_FORMAT = '\" + format_tz + \"'\");\n      statement.execute(\"alter session set TIMEZONE = '\" + timezone + \"'\");\n\n      statement.execute(\n          \"Create or replace table all_timestamps (\"\n              + \"int_c int, date_c date, \"\n              + \"time_c time, time_c0 time(0), time_c3 time(3), time_c6 time(6), \"\n              + \"ts_ltz_c timestamp_ltz, ts_ltz_c0 timestamp_ltz(0), \"\n              + \"ts_ltz_c3 timestamp_ltz(3), ts_ltz_c6 timestamp_ltz(6), \"\n              + \"ts_ntz_c timestamp_ntz, ts_ntz_c0 timestamp_ntz(0), \"\n              + \"ts_ntz_c3 timestamp_ntz(3), ts_ntz_c6 timestamp_ntz(6) \"\n              + \", ts_tz_c timestamp_tz, ts_tz_c0 timestamp_tz(0), \"\n              + \"ts_tz_c3 timestamp_tz(3), ts_tz_c6 timestamp_tz(6) \"\n              + \")\");\n\n      if (rowCount > 0) {\n        statement.execute(\n            \"insert into all_timestamps \"\n                + \"select seq4(), '2015-10-25' , \"\n                + \"'23:59:59.123456789', '23:59:59', '23:59:59.123', '23:59:59.123456', \"\n                + \"   '2014-01-11 06:12:13.123456789', '2014-01-11 06:12:13',\"\n                + \"   '2014-01-11 06:12:13.123', '2014-01-11 06:12:13.123456',\"\n                + \"   '2014-01-11 06:12:13.123456789', '2014-01-11 06:12:13',\"\n                + \"   '2014-01-11 06:12:13.123', '2014-01-11 06:12:13.123456',\"\n                + \"   '2014-01-11 06:12:13.123456789', '2014-01-11 06:12:13',\"\n                + \"   '2014-01-11 06:12:13.123', '2014-01-11 06:12:13.123456'\"\n                + \" from table(generator(rowcount=>\"\n                + rowCount\n                + \"))\");\n      }\n\n      String sqlSelect = \"select * from all_timestamps \" + whereClause;\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n\n        fileNameList = serializeResultSet((SnowflakeResultSet) rs, maxSizeInBytes, \"txt\");\n\n        originalResultCSVString = generateCSVResult(rs);\n      }\n    }\n\n    String chunkResultString = deserializeResultSet(fileNameList);\n    assertEquals(chunkResultString, originalResultCSVString);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testTimestamp(String queryResultFormat) throws Throwable {\n    String[] dateFormats = {\"YYYY-MM-DD\", \"DD-MON-YYYY\", \"MM/DD/YYYY\"};\n    String[] timeFormats = {\"HH24:MI:SS.FFTZH:TZM\", \"HH24:MI:SS.FF\", \"HH24:MI:SS\"};\n    String[] timestampFormats = {\n      \"YYYY-MM-DD HH24:MI:SS.FF3\",\n      \"TZHTZM YYYY-MM-DD HH24:MI:SS.FF3\",\n      \"DY, DD MON YYYY HH24:MI:SS.FF TZHTZM\"\n    };\n    String[] timezones = {\"America/Los_Angeles\", \"Europe/London\", \"GMT\"};\n\n    for (int i = 0; i < dateFormats.length; i++) {\n      testTimestampHarness(\n          10,\n          1,\n          \"\",\n          dateFormats[i],\n          timeFormats[i],\n          timestampFormats[i],\n          timestampFormats[i],\n          timestampFormats[i],\n          timezones[i],\n          queryResultFormat);\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testBasicTableWithSerializeObjectsAfterReadResultSet(String queryResultFormat)\n      throws Throwable {\n    List<String> fileNameList = null;\n    String originalResultCSVString = null;\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace schema testschema\");\n\n      statement.execute(\n          \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n      int rowCount = 5000;\n      statement.execute(\n          \"insert into table_basic select \"\n              + \"seq4(), 'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n              + \" from table(generator(rowcount=>\"\n              + rowCount\n              + \"))\");\n\n      String sqlSelect = \"select * from table_basic \";\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n\n        originalResultCSVString = generateCSVResult(rs);\n\n        // In previous test, the serializable objects are serialized before\n        // reading the ResultSet. This test covers the case that serializes the\n        // object after reading the result set.\n        fileNameList = serializeResultSet((SnowflakeResultSet) rs, 1 * 1024 * 1024, \"txt\");\n      }\n    }\n\n    String chunkResultString = deserializeResultSet(fileNameList);\n    assertEquals(chunkResultString, originalResultCSVString);\n  }\n\n  /**\n   * Split the ResultSetSerializable objects based on max size.\n   *\n   * @param files The files where SnowflakeResultSetSerializable objects are serialized in.\n   * @param maxSizeInBytes The max data size wrapped in split serializable object\n   * @return a name file list where the new serializable objects resides.\n   * @throws Throwable if any error occurs.\n   */\n  private synchronized List<String> splitResultSetSerializables(\n      List<String> files, long maxSizeInBytes) throws Throwable {\n    List<String> resultFileList = new ArrayList<>();\n\n    for (String filename : files) {\n      // Read Object from file\n      try (FileInputStream fi = new FileInputStream(filename);\n          ObjectInputStream si = new ObjectInputStream(fi)) {\n        SnowflakeResultSetSerializableV1 resultSetChunk =\n            (SnowflakeResultSetSerializableV1) si.readObject();\n\n        // Get ResultSet from object\n        try (ResultSet rs =\n            resultSetChunk.getResultSet(\n                SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                    .setProxyProperties(new Properties())\n                    .setSfFullURL(sfFullURL)\n                    .build())) {\n\n          String[] filePathParts = filename.split(File.separator);\n          String appendix = filePathParts[filePathParts.length - 1];\n\n          List<String> thisFileList =\n              serializeResultSet((SnowflakeResultSet) rs, maxSizeInBytes, appendix);\n          for (int i = 0; i < thisFileList.size(); i++) {\n            resultFileList.add(thisFileList.get(i));\n          }\n        }\n      }\n    }\n\n    if (developPrint) {\n      System.out.println(\n          \"Split from \" + files.size() + \" files to \" + resultFileList.size() + \" files\");\n    }\n\n    return resultFileList;\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testSplitResultSetSerializable(String queryResultFormat) throws Throwable {\n    List<String> fileNameList = null;\n    String originalResultCSVString = null;\n    int rowCount = 10000;\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n\n      statement.execute(\n          \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n      statement.execute(\n          \"insert into table_basic select \"\n              + \"seq4(), \"\n              + \"'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n              + \" from table(generator(rowcount=>\"\n              + rowCount\n              + \"))\");\n\n      String sqlSelect = \"select * from table_basic \";\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n\n        fileNameList = serializeResultSet((SnowflakeResultSet) rs, 100 * 1024 * 1024, \"txt\");\n\n        originalResultCSVString = generateCSVResult(rs);\n      }\n    }\n\n    // Split deserializedResultSet by 300KB, the result should be the same\n    List<String> fileNameSplit300K = splitResultSetSerializables(fileNameList, 300 * 1024);\n    String chunkResultString = deserializeResultSet(fileNameSplit300K);\n    assertEquals(chunkResultString, originalResultCSVString);\n\n    // Split deserializedResultSet by 100KB, the result should be the same\n    List<String> fileNameSplit100K = splitResultSetSerializables(fileNameSplit300K, 100 * 1024);\n    chunkResultString = deserializeResultSet(fileNameSplit100K);\n    assertEquals(chunkResultString, originalResultCSVString);\n\n    // Split deserializedResultSet by smallest, the result should be the same\n    List<String> fileNameSplitSmallest = splitResultSetSerializables(fileNameSplit100K, 1);\n    chunkResultString = deserializeResultSet(fileNameSplitSmallest);\n    assertEquals(chunkResultString, originalResultCSVString);\n  }\n\n  /**\n   * Setup wrong file URL for the result set serializable objects for negative test.\n   *\n   * @param resultSetSerializables a list of result set serializable object.\n   */\n  private void hackToSetupWrongURL(List<SnowflakeResultSetSerializable> resultSetSerializables) {\n    for (int i = 0; i < resultSetSerializables.size(); i++) {\n      SnowflakeResultSetSerializableV1 serializableV1 =\n          (SnowflakeResultSetSerializableV1) resultSetSerializables.get(i);\n      for (SnowflakeResultSetSerializableV1.ChunkFileMetadata chunkFileMetadata :\n          serializableV1.getChunkFileMetadatas()) {\n        chunkFileMetadata.setFileURL(chunkFileMetadata.getFileURL() + \"_hacked_wrong_file\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testCloseUnconsumedResultSet(String queryResultFormat) throws Throwable {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n        int rowCount = 1000;\n        statement.execute(\n            \"insert into table_basic select \"\n                + \"seq4(), \"\n                + \"'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n                + \" from table(generator(rowcount=>\"\n                + rowCount\n                + \"))\");\n\n        int testCount = 2;\n        while (testCount-- > 0) {\n          String sqlSelect = \"select * from table_basic \";\n          try (ResultSet rs = statement.executeQuery(sqlSelect)) {}\n          ;\n        }\n      } finally {\n        statement.execute(\"drop table if exists table_basic\");\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testNegativeWithChunkFileNotExist(String queryResultFormat) throws Throwable {\n    // This test takes about (download worker retry times * networkTimeout) long to finish\n    Properties properties = new Properties();\n    properties.put(\"networkTimeout\", 10000); // 10000 millisec\n    try (Connection connection = init(properties, queryResultFormat)) {\n      try (Statement statement = connection.createStatement()) {\n        statement.execute(\n            \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n        int rowCount = 300;\n        statement.execute(\n            \"insert into table_basic select \"\n                + \"seq4(), \"\n                + \"'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n                + \" from table(generator(rowcount=>\"\n                + rowCount\n                + \"))\");\n\n        String sqlSelect = \"select * from table_basic \";\n        try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n          // Test case 1: Generate one Serializable object\n          List<SnowflakeResultSetSerializable> resultSetSerializables =\n              ((SnowflakeResultSet) rs).getResultSetSerializables(100 * 1024 * 1024);\n\n          hackToSetupWrongURL(resultSetSerializables);\n\n          // Expected to hit credential issue when access the result.\n          assertEquals(resultSetSerializables.size(), 1);\n          SnowflakeResultSetSerializable resultSetSerializable = resultSetSerializables.get(0);\n\n          ResultSet resultSet =\n              resultSetSerializable.getResultSet(\n                  SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                      .setProxyProperties(new Properties())\n                      .setSfFullURL(sfFullURL)\n                      .build());\n\n          SQLException ex =\n              assertThrows(\n                  SQLException.class,\n                  () -> {\n                    while (resultSet.next()) {\n                      resultSet.getString(1);\n                    }\n                  },\n                  \"error should happen when accessing the data because the file URL is corrupted.\");\n          assertEquals((long) ErrorCode.INTERNAL_ERROR.getMessageCode(), ex.getErrorCode());\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testNegativeWithClosedResultSet(String queryResultFormat) throws Throwable {\n    try (Connection connection = init(queryResultFormat)) {\n      Statement statement = connection.createStatement();\n\n      statement.execute(\n          \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n      int rowCount = 300;\n      statement.execute(\n          \"insert into table_basic select \"\n              + \"seq4(), \"\n              + \"'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n              + \" from table(generator(rowcount=>\"\n              + rowCount\n              + \"))\");\n\n      String sqlSelect = \"select * from table_basic \";\n      ResultSet rs = statement.executeQuery(sqlSelect);\n      rs.close();\n\n      // The getResultSetSerializables() can only be called for unclosed\n      // result set.\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () -> ((SnowflakeResultSet) rs).getResultSetSerializables(100 * 1024 * 1024));\n      System.out.println(\"Negative test hits expected error: \" + ex.getMessage());\n    }\n  }\n\n  /**\n   * This test is related to proxy, it can only be tested manually since there is no testing proxy\n   * server setup. Below are the unit test to for the proxy. 1. Setup proxy on your testing box. The\n   * instruction can be found by search \"How to setup Proxy Server for Client tests\" in engineering\n   * pages. OR\n   * https://snowflakecomputing.atlassian.net/wiki/spaces/EN/pages/65438343/How+to+setup+Proxy+Server+for+Client+tests\n   * 2. There are two steps to run the test manually because the proxy info cached in static\n   * variables in HttpUtil. So it needs the JVM to be restarted between the 2 steps. Step 1:\n   * Generate file list for SnowflakeResultSetSerializable objects. Set variable 'generateFiles' as\n   * true. The file list will be printed. For example, Split ResultSet as 4 parts.\n   * /tmp/junit16319222538342218700_result_0.txt /tmp/junit16319222538342218700_result_1.txt\n   * /tmp/junit16319222538342218700_result_2.txt /tmp/junit16319222538342218700_result_3.txt Step 2:\n   * Set variable 'generateFiles' as false. Replace the above printed file names to 'fileNameList'.\n   * Run this test. The step 2 can be run with 'correctProxy' = true or false. Run it with wrong\n   * proxy is to make sure the proxy setting is used.\n   *\n   * @throws Throwable\n   */\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @Disabled\n  @DontRunOnGithubActions\n  public void testCustomProxyWithFiles(String queryResultFormat) throws Throwable {\n    boolean generateFiles = false;\n    boolean correctProxy = false;\n\n    if (generateFiles) {\n      generateTestFiles(queryResultFormat);\n      fail(\"This is generate test file.\");\n    }\n\n    // Setup proxy information\n    Properties props = new Properties();\n    props.put(\"useProxy\", \"true\");\n    props.put(\"proxyHost\", \"localhost\");\n    props.put(\"proxyPort\", \"3128\");\n    props.put(\"proxyUser\", \"testuser1\");\n    if (correctProxy) {\n      props.put(\"proxyPassword\", \"test\");\n    } else {\n      props.put(\"proxyPassword\", \"wrongPasswd\");\n    }\n    props.put(\"nonProxyHosts\", \"*.foo.com\");\n\n    // Setup files to deserialize SnowflakeResultSetSerializable objects.\n    List<String> fileNameList = new ArrayList<>();\n    fileNameList.add(\"/tmp/junit16319222538342218700_result_0.txt\");\n    fileNameList.add(\"/tmp/junit16319222538342218700_result_1.txt\");\n    fileNameList.add(\"/tmp/junit16319222538342218700_result_2.txt\");\n    fileNameList.add(\"/tmp/junit16319222538342218700_result_3.txt\");\n\n    if (correctProxy) {\n      String chunkResultString = deserializeResultSetWithProperties(fileNameList, props);\n      System.out.println(chunkResultString.length());\n    } else {\n      assertThrows(Exception.class, () -> deserializeResultSetWithProperties(fileNameList, props));\n    }\n  }\n\n  private void generateTestFiles(String queryResultFormat) throws Throwable {\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n\n      statement.execute(\n          \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n      int rowCount = 60000;\n      statement.execute(\n          \"insert into table_basic select \"\n              + \"seq4(), \"\n              + \"'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n              + \" from table(generator(rowcount=>\"\n              + rowCount\n              + \"))\");\n\n      String sqlSelect = \"select * from table_basic \";\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        developPrint = true;\n        serializeResultSet((SnowflakeResultSet) rs, 2 * 1024 * 1024, \"txt\");\n        System.exit(-1);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(SimpleResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testRetrieveMetadata(String queryResultFormat) throws Throwable {\n    List<String> fileNameList;\n    int rowCount = 10000;\n    long expectedTotalRowCount = 0;\n    long expectedTotalCompressedSize = 0;\n    long expectedTotalUncompressedSize = 0;\n    try (Connection connection = init(queryResultFormat);\n        Statement statement = connection.createStatement()) {\n\n      statement.execute(\n          \"create or replace table table_basic \" + \" (int_c int, string_c string(128))\");\n\n      statement.execute(\n          \"insert into table_basic select \"\n              + \"seq4(), \"\n              + \"'arrow_1234567890arrow_1234567890arrow_1234567890arrow_1234567890'\"\n              + \" from table(generator(rowcount=>\"\n              + rowCount\n              + \"))\");\n\n      String sqlSelect = \"select * from table_basic \";\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        fileNameList = serializeResultSet((SnowflakeResultSet) rs, 100 * 1024 * 1024, \"txt\");\n\n        // Only one serializable object is generated with 100M data.\n        assertEquals(fileNameList.size(), 1);\n\n        try (FileInputStream fi = new FileInputStream(fileNameList.get(0));\n            ObjectInputStream si = new ObjectInputStream(fi)) {\n          SnowflakeResultSetSerializableV1 wholeResultSetChunk =\n              (SnowflakeResultSetSerializableV1) si.readObject();\n          expectedTotalRowCount = wholeResultSetChunk.getRowCount();\n          expectedTotalCompressedSize = wholeResultSetChunk.getCompressedDataSizeInBytes();\n          expectedTotalUncompressedSize = wholeResultSetChunk.getUncompressedDataSizeInBytes();\n        }\n        if (developPrint) {\n          System.out.println(\n              \"Total statistic: RowCount=\"\n                  + expectedTotalRowCount\n                  + \" CompSize=\"\n                  + expectedTotalCompressedSize\n                  + \" UncompSize=\"\n                  + expectedTotalUncompressedSize);\n        }\n      }\n    }\n    assertEquals(expectedTotalRowCount, rowCount);\n    assertThat(expectedTotalCompressedSize, greaterThan((long) 0));\n    assertThat(expectedTotalUncompressedSize, greaterThan((long) 0));\n\n    // Split deserializedResultSet by 300KB\n    List<String> fileNameSplit300K = splitResultSetSerializables(fileNameList, 300 * 1024);\n    // Verify the metadata is correct.\n    assertTrue(\n        isMetadataConsistent(\n            expectedTotalRowCount,\n            expectedTotalCompressedSize,\n            expectedTotalUncompressedSize,\n            fileNameSplit300K,\n            null));\n\n    // Split deserializedResultSet by 100KB\n    List<String> fileNameSplit100K = splitResultSetSerializables(fileNameSplit300K, 100 * 1024);\n    // Verify the metadata is correct.\n    assertTrue(\n        isMetadataConsistent(\n            expectedTotalRowCount,\n            expectedTotalCompressedSize,\n            expectedTotalUncompressedSize,\n            fileNameSplit100K,\n            null));\n\n    // Split deserializedResultSet by smallest\n    List<String> fileNameSplitSmallest = splitResultSetSerializables(fileNameSplit100K, 1);\n    // Verify the metadata is correct.\n    assertTrue(\n        isMetadataConsistent(\n            expectedTotalRowCount,\n            expectedTotalCompressedSize,\n            expectedTotalUncompressedSize,\n            fileNameSplitSmallest,\n            null));\n  }\n\n  /**\n   * Give a file list, deserialize SnowflakeResultSetSerializableV1 object from each file, verify\n   * the metadata on rowcount and data size to be corrected.\n   *\n   * @param files The file names where the serializable objects are serialized.\n   * @param props additional properties for JDBC.\n   * @return Return true if the metadata is consistent with the content\n   * @throws Throwable If any error happens.\n   */\n  private boolean isMetadataConsistent(\n      long expectedTotalRowCount,\n      long expectedTotalCompressedSize,\n      long expectedTotalUncompressedSize,\n      List<String> files,\n      Properties props)\n      throws Throwable {\n    long actualRowCountFromMetadata = 0;\n    long actualTotalCompressedSize = 0;\n    long actualTotalUncompressedSize = 0;\n    long actualRowCount = 0;\n    long chunkFileCount = 0;\n\n    for (String filename : files) {\n      // Read Object from file\n      try (FileInputStream fi = new FileInputStream(filename);\n          ObjectInputStream si = new ObjectInputStream(fi)) {\n        SnowflakeResultSetSerializableV1 resultSetChunk =\n            (SnowflakeResultSetSerializableV1) si.readObject();\n\n        // Accumulate statistic from metadata\n        actualRowCountFromMetadata += resultSetChunk.getRowCount();\n        actualTotalCompressedSize += resultSetChunk.getCompressedDataSizeInBytes();\n        actualTotalUncompressedSize += resultSetChunk.getUncompressedDataSizeInBytes();\n        chunkFileCount += resultSetChunk.chunkFileCount;\n\n        // Get actual row count from result set.\n        // sfFullURL is used to support private link URL.\n        // This test case is not for private link env, so just use a valid URL for testing purpose.\n        try (ResultSet rs =\n            resultSetChunk.getResultSet(\n                SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance()\n                    .setProxyProperties(props)\n                    .setSfFullURL(sfFullURL)\n                    .build())) {\n\n          // Accumulate the actual row count from result set.\n          while (rs.next()) {\n            actualRowCount++;\n          }\n        }\n      }\n      if (developPrint) {\n        System.out.println(\n            \"isMetadataConsistent: FileCount=\"\n                + files.size()\n                + \" RowCounts=\"\n                + expectedTotalRowCount\n                + \" \"\n                + actualRowCountFromMetadata\n                + \" (\"\n                + actualRowCount\n                + \") CompSize=\"\n                + expectedTotalCompressedSize\n                + \" \"\n                + actualTotalCompressedSize\n                + \" UncompSize=\"\n                + expectedTotalUncompressedSize\n                + \" \"\n                + actualTotalUncompressedSize\n                + \" chunkFileCount=\"\n                + chunkFileCount);\n      }\n    }\n    return actualRowCount == expectedTotalRowCount\n        && actualRowCountFromMetadata == expectedTotalRowCount\n        && actualTotalCompressedSize == expectedTotalCompressedSize\n        && expectedTotalUncompressedSize == actualTotalUncompressedSize;\n  }\n\n  @Test\n  public void testResultSetRetrieveConfig() throws Throwable {\n    SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder builder =\n        SnowflakeResultSetSerializable.ResultSetRetrieveConfig.Builder.newInstance();\n\n    boolean hitExpectedException = false;\n    try {\n      builder.setSfFullURL(\"sfctest0.snowflakecomputing.com\");\n      // The URL is invalid because it doesn't include protocol, it should raise exception\n      builder.build();\n    } catch (Exception ex) {\n      hitExpectedException = true;\n    }\n    assertTrue(hitExpectedException);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeS3ClientHandleExceptionLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport com.google.cloud.storage.StorageException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.SocketTimeoutException;\nimport java.security.InvalidKeyException;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.concurrent.CompletionException;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.Constants;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.cloud.storage.SnowflakeS3Client;\nimport net.snowflake.client.internal.jdbc.cloud.storage.StageInfo;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.Mockito;\nimport software.amazon.awssdk.awscore.exception.AwsErrorDetails;\nimport software.amazon.awssdk.core.exception.SdkClientException;\nimport software.amazon.awssdk.core.exception.SdkServiceException;\nimport software.amazon.awssdk.services.s3.model.S3Exception;\n\n/** Test for SnowflakeS3Client handle exception function */\n@Tag(TestTags.OTHERS)\npublic class SnowflakeS3ClientHandleExceptionLatestIT extends AbstractDriverIT {\n  @TempDir private File tmpFolder;\n  private Connection connection;\n  private SFStatement sfStatement;\n  private SFSession sfSession;\n  private String command;\n  private SnowflakeS3Client spyingClient;\n  private int overMaxRetry;\n  private int maxRetry;\n  private static final String EXPIRED_AWS_TOKEN_ERROR_CODE = \"ExpiredToken\";\n\n  @BeforeEach\n  public void setup() throws SQLException {\n    connection = getConnection(\"s3testaccount\");\n    sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n    Statement statement = connection.createStatement();\n    sfStatement = statement.unwrap(SnowflakeStatementImpl.class).getSfStatement();\n    statement.execute(\"CREATE OR REPLACE STAGE testPutGet_stage\");\n    command = \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @testPutGet_stage\";\n    SnowflakeFileTransferAgent agent =\n        new SnowflakeFileTransferAgent(command, sfSession, sfStatement);\n    StageInfo info = agent.getStageInfo();\n    SnowflakeS3Client.ClientConfiguration clientConfig =\n        new SnowflakeS3Client.ClientConfiguration(1, 1, 10_000, 10_000);\n    SnowflakeS3Client client =\n        new SnowflakeS3Client(\n            info.getCredentials(),\n            clientConfig,\n            agent.getEncryptionMaterial().get(0),\n            info.getProxyProperties(),\n            info.getRegion(),\n            info.getEndPoint(),\n            info.getIsClientSideEncrypted(),\n            sfSession,\n            info.getUseS3RegionalUrl());\n    maxRetry = client.getMaxRetries();\n    overMaxRetry = maxRetry + 1;\n    spyingClient = Mockito.spy(client);\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorRenewExpired() throws SQLException, InterruptedException {\n    CompletionException ex =\n        new CompletionException(\n            S3Exception.builder()\n                .message(\"unauthenticated\")\n                .awsErrorDetails(\n                    AwsErrorDetails.builder().errorCode(EXPIRED_AWS_TOKEN_ERROR_CODE).build())\n                .build());\n    spyingClient.handleStorageException(ex, 0, \"upload\", sfSession, command, null);\n    Mockito.verify(spyingClient, Mockito.times(1)).renew(Mockito.anyMap());\n\n    // Unauthenticated, backoff with interrupt, renew is called\n    Exception[] exceptionContainer = new Exception[1];\n    Thread thread =\n        new Thread(\n            new Runnable() {\n              @Override\n              public void run() {\n                try {\n                  spyingClient.handleStorageException(\n                      ex, maxRetry, \"upload\", sfSession, command, null);\n                } catch (SnowflakeSQLException e) {\n                  exceptionContainer[0] = e;\n                }\n              }\n            });\n    thread.start();\n    thread.interrupt();\n    thread.join();\n    assertNull(exceptionContainer[0], \"Exception must not have been thrown in here\");\n    Mockito.verify(spyingClient, Mockito.times(2)).renew(Mockito.anyMap());\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorNotFound() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                S3Exception.builder().message(\"Not found\").build(),\n                overMaxRetry,\n                \"upload\",\n                sfSession,\n                command,\n                null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorBadRequestTokenExpired() throws SQLException {\n    CompletionException ex =\n        new CompletionException(\n            SdkServiceException.builder().message(\"Bad Request\").statusCode(400).build());\n    spyingClient.handleStorageException(ex, 0, \"download\", sfSession, command, null);\n    // renew token\n    Mockito.verify(spyingClient, Mockito.times(1)).renew(Mockito.anyMap());\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorClientUnknown() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                SdkClientException.create(\"Not found\", new IOException()),\n                overMaxRetry,\n                \"upload\",\n                sfSession,\n                command,\n                null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorInvalidKey() {\n    // Unauthenticated, renew is called.\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new Exception(new InvalidKeyException()), 0, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorInterruptedException() throws SnowflakeSQLException {\n    // Can still retry, no error thrown\n    spyingClient.handleStorageException(\n        new InterruptedException(), 0, \"upload\", sfSession, command, null);\n\n    Mockito.verify(spyingClient, Mockito.never()).renew(Mockito.anyMap());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new InterruptedException(), 26, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorSocketTimeoutException() throws SnowflakeSQLException {\n    // Can still retry, no error thrown\n    spyingClient.handleStorageException(\n        new SocketTimeoutException(), 0, \"upload\", sfSession, command, null);\n\n    Mockito.verify(spyingClient, Mockito.never()).renew(Mockito.anyMap());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new SocketTimeoutException(), 26, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorUnknownException() {\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new Exception(), 0, \"upload\", sfSession, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorRenewExpiredNullSession() {\n    // Unauthenticated, renew is called.\n    CompletionException ex =\n        new CompletionException(\n            S3Exception.builder()\n                .message(\"unauthenticated\")\n                .awsErrorDetails(\n                    AwsErrorDetails.builder().errorCode(EXPIRED_AWS_TOKEN_ERROR_CODE).build())\n                .build());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> spyingClient.handleStorageException(ex, 0, \"upload\", null, command, null));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void errorNoSpaceLeftOnDevice() throws IOException {\n    File destFolder = new File(tmpFolder, \"dest\");\n    destFolder.mkdirs();\n    String destFolderCanonicalPath = destFolder.getCanonicalPath();\n    String getCommand =\n        \"get @testPutGet_stage/\" + TEST_DATA_FILE + \" 'file://\" + destFolderCanonicalPath + \"'\";\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            spyingClient.handleStorageException(\n                new StorageException(\n                    maxRetry,\n                    Constants.NO_SPACE_LEFT_ON_DEVICE_ERR,\n                    new IOException(Constants.NO_SPACE_LEFT_ON_DEVICE_ERR)),\n                0,\n                \"download\",\n                null,\n                getCommand,\n                null));\n  }\n\n  @AfterEach\n  public void cleanUp() throws SQLException {\n    sfStatement.close();\n    connection.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeSerializableTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeChunkDownloader.NoOpChunkDownloader;\nimport static net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1.ChunkFileMetadata;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.util.HashMap;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.core.QueryResultFormat;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFStatementType;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeSerializableTest {\n\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n  private static final String STANDARD_SERIALIZABLE_V1_JSON_STRING =\n      \"{\\n\"\n          + \"  \\\"data\\\": {\\n\"\n          + \"    \\\"parameters\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"CLIENT_PREFETCH_THREADS\\\",\\n\"\n          + \"        \\\"value\\\": 4\\n\"\n          + \"      },\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"TIMESTAMP_OUTPUT_FORMAT\\\",\\n\"\n          + \"        \\\"value\\\": \\\"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\\\"\\n\"\n          + \"      },\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"CLIENT_RESULT_CHUNK_SIZE\\\",\\n\"\n          + \"        \\\"value\\\": 128\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"rowtype\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"1\\\",\\n\"\n          + \"        \\\"database\\\": \\\"some-db\\\",\\n\"\n          + \"        \\\"schema\\\": \\\"some-schema\\\",\\n\"\n          + \"        \\\"table\\\": \\\"some-table\\\",\\n\"\n          + \"        \\\"byteLength\\\": null,\\n\"\n          + \"        \\\"type\\\": \\\"fixed\\\",\\n\"\n          + \"        \\\"length\\\": 256,\\n\"\n          + \"        \\\"scale\\\": 0,\\n\"\n          + \"        \\\"precision\\\": 1,\\n\"\n          + \"        \\\"nullable\\\": false,\\n\"\n          + \"        \\\"collation\\\": null\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"rowset\\\": [\\n\"\n          + \"      [\\n\"\n          + \"        \\\"1\\\"\\n\"\n          + \"      ]\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"qrmk\\\": \\\"ADCDEFGHIJdwadawYhiF81aC0wT0IU+NN8QtobPWCk=\\\",\\n\"\n          + \"    \\\"chunkHeaders\\\": {\\n\"\n          + \"      \\\"x-amz-server-side-encryption-customer-key-md5\\\": \\\"A2dDf2ff7HI8OCdsR3pK82g==\\\"\\n\"\n          + \"    },\\n\"\n          + \"    \\\"chunks\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"url\\\": \\\"https://sfc-ds2-customer-stage.s3.us-west-2.amazonaws.com\\\",\\n\"\n          + \"        \\\"rowCount\\\": 756,\\n\"\n          + \"        \\\"uncompressedSize\\\": 312560,\\n\"\n          + \"        \\\"compressedSize\\\": 26828\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"total\\\": 1,\\n\"\n          + \"    \\\"returned\\\": 1,\\n\"\n          + \"    \\\"queryId\\\": \\\"01b341c1-0000-772f-0000-0004189328ca\\\",\\n\"\n          + \"    \\\"databaseProvider\\\": \\\"some-db-provider\\\",\\n\"\n          + \"    \\\"finalDatabaseName\\\": \\\"some-db\\\",\\n\"\n          + \"    \\\"finalSchemaName\\\": \\\"some-schema\\\",\\n\"\n          + \"    \\\"finalWarehouseName\\\": \\\"some-warehouse\\\",\\n\"\n          + \"    \\\"finalRoleName\\\": \\\"ENG_OPS_RL\\\",\\n\"\n          + \"    \\\"numberOfBinds\\\": 0,\\n\"\n          + \"    \\\"arrayBindSupported\\\": false,\\n\"\n          + \"    \\\"statementTypeId\\\": 4096,\\n\"\n          + \"    \\\"version\\\": 1,\\n\"\n          + \"    \\\"sendResultTime\\\": 1711499620154,\\n\"\n          + \"    \\\"queryResultFormat\\\": \\\"json\\\",\\n\"\n          + \"    \\\"queryContext\\\": {\\n\"\n          + \"      \\\"entries\\\": [\\n\"\n          + \"        {\\n\"\n          + \"          \\\"id\\\": 0,\\n\"\n          + \"          \\\"timestamp\\\": 55456940208204,\\n\"\n          + \"          \\\"priority\\\": 0\\n\"\n          + \"        }\\n\"\n          + \"      ]\\n\"\n          + \"    }\\n\"\n          + \"  },\\n\"\n          + \"  \\\"code\\\": null,\\n\"\n          + \"  \\\"message\\\": null,\\n\"\n          + \"  \\\"success\\\": true\\n\"\n          + \"}\";\n\n  public static final String RICH_RESULTS_SERIALIZABLE_V1_JSON_STRING =\n      \"{\\n\"\n          + \"  \\\"data\\\": {\\n\"\n          + \"    \\\"parameters\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"CLIENT_PREFETCH_THREADS\\\",\\n\"\n          + \"        \\\"value\\\": 4\\n\"\n          + \"      },\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"TIMESTAMP_OUTPUT_FORMAT\\\",\\n\"\n          + \"        \\\"value\\\": \\\"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\\\"\\n\"\n          + \"      },\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"CLIENT_RESULT_CHUNK_SIZE\\\",\\n\"\n          + \"        \\\"value\\\": 128\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"rowtype\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"name\\\": \\\"1\\\",\\n\"\n          + \"        \\\"database\\\": \\\"some-db\\\",\\n\"\n          + \"        \\\"schema\\\": \\\"some-schema\\\",\\n\"\n          + \"        \\\"table\\\": \\\"some-table\\\",\\n\"\n          + \"        \\\"byteLength\\\": null,\\n\"\n          + \"        \\\"type\\\": \\\"fixed\\\",\\n\"\n          + \"        \\\"length\\\": 256,\\n\"\n          + \"        \\\"scale\\\": 0,\\n\"\n          + \"        \\\"precision\\\": 1,\\n\"\n          + \"        \\\"nullable\\\": false,\\n\"\n          + \"        \\\"collation\\\": null\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"rowset\\\": [\\n\"\n          + \"      [\\n\"\n          + \"        \\\"1\\\"\\n\"\n          + \"      ]\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"qrmk\\\": \\\"ADCDEFGHIJdwadawYhiF81aC0wT0IU+NN8QtobPWCk=\\\",\\n\"\n          + \"    \\\"chunkHeaders\\\": {\\n\"\n          + \"      \\\"x-amz-server-side-encryption-customer-key-md5\\\": \\\"A2dDf2ff7HI8OCdsR3pK82g==\\\"\\n\"\n          + \"    },\\n\"\n          + \"    \\\"chunks\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"url\\\": \\\"https://sfc-ds2-customer-stage.s3.us-west-2.amazonaws.com\\\",\\n\"\n          + \"        \\\"rowCount\\\": 756,\\n\"\n          + \"        \\\"uncompressedSize\\\": 312560,\\n\"\n          + \"        \\\"compressedSize\\\": 26828\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"total\\\": 1,\\n\"\n          + \"    \\\"returned\\\": 1,\\n\"\n          + \"    \\\"queryId\\\": \\\"01b341c1-0000-772f-0000-0004189328ca\\\",\\n\"\n          + \"    \\\"databaseProvider\\\": \\\"some-db-provider\\\",\\n\"\n          + \"    \\\"finalDatabaseName\\\": \\\"some-db\\\",\\n\"\n          + \"    \\\"finalSchemaName\\\": \\\"some-schema\\\",\\n\"\n          + \"    \\\"finalWarehouseName\\\": \\\"some-warehouse\\\",\\n\"\n          + \"    \\\"finalRoleName\\\": \\\"ENG_OPS_RL\\\",\\n\"\n          + \"    \\\"numberOfBinds\\\": 0,\\n\"\n          + \"    \\\"arrayBindSupported\\\": false,\\n\"\n          + \"    \\\"statementTypeId\\\": 4096,\\n\"\n          + \"    \\\"version\\\": 1,\\n\"\n          + \"    \\\"sendResultTime\\\": 1711499620154,\\n\"\n          + \"    \\\"queryResultFormat\\\": \\\"json\\\",\\n\"\n          + \"    \\\"queryContext\\\": {\\n\"\n          + \"      \\\"entries\\\": [\\n\"\n          + \"        {\\n\"\n          + \"          \\\"id\\\": 0,\\n\"\n          + \"          \\\"timestamp\\\": 55456940208204,\\n\"\n          + \"          \\\"priority\\\": 0\\n\"\n          + \"        }\\n\"\n          + \"      ]\\n\"\n          + \"    }\\n\"\n          + \"  },\\n\"\n          + \"  \\\"richResult\\\": {\\n\"\n          + \"    \\\"rowtype\\\": [\\n\"\n          + \"        {\\n\"\n          + \"          \\\"name\\\": \\\"LOWER_BOUND\\\",\\n\"\n          + \"          \\\"database\\\": \\\"TEMP\\\",\\n\"\n          + \"          \\\"schema\\\": \\\"PUBLIC\\\",\\n\"\n          + \"          \\\"table\\\": \\\"T_TEST\\\",\\n\"\n          + \"          \\\"precision\\\": null,\\n\"\n          + \"          \\\"byteLength\\\": 16777216,\\n\"\n          + \"          \\\"type\\\": \\\"fixed\\\",\\n\"\n          + \"          \\\"scale\\\": null,\\n\"\n          + \"          \\\"nullable\\\": true,\\n\"\n          + \"          \\\"collation\\\": null,\\n\"\n          + \"          \\\"length\\\": 16777216,\\n\"\n          + \"          \\\"columnIndexing\\\": 1\\n\"\n          + \"      },\\n\"\n          + \"      {\\n\"\n          + \"          \\\"name\\\": \\\"UPPER_BOUND\\\",\\n\"\n          + \"          \\\"database\\\": \\\"TEMP\\\",\\n\"\n          + \"          \\\"schema\\\": \\\"PUBLIC\\\",\\n\"\n          + \"          \\\"table\\\": \\\"T_TEST\\\",\\n\"\n          + \"          \\\"precision\\\": null,\\n\"\n          + \"          \\\"byteLength\\\": 16777216,\\n\"\n          + \"          \\\"type\\\": \\\"fixed\\\",\\n\"\n          + \"          \\\"scale\\\": null,\\n\"\n          + \"          \\\"nullable\\\": true,\\n\"\n          + \"          \\\"collation\\\": null,\\n\"\n          + \"          \\\"length\\\": 16777216,\\n\"\n          + \"          \\\"columnIndexing\\\": 1\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"rowset\\\": [\\n\"\n          + \"      [\\n\"\n          + \"        \\\"value1_lower\\\",\\n\"\n          + \"        \\\"value1_upper\\\"\\n\"\n          + \"      ],\\n\"\n          + \"      [\\n\"\n          + \"        \\\"value2_lower\\\",\\n\"\n          + \"        \\\"value2_upper\\\"\\n\"\n          + \"      ]\\n\"\n          + \"\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"qrmk\\\": \\\"ZXYADCDEFGHIJdwadawYhiF81aC0wT0IU+NN8QtobPWCk=\\\",\\n\"\n          + \"    \\\"chunkHeaders\\\": {\\n\"\n          + \"      \\\"x-amz-server-side-encryption-customer-key-md5\\\": \\\"f342lkkftyf7HI8OCdsR3pK82g==\\\"\\n\"\n          + \"    },\\n\"\n          + \"    \\\"chunks\\\": [\\n\"\n          + \"      {\\n\"\n          + \"        \\\"url\\\": \\\"https://sfc-ds2-customer-stage.s3.us-west-2.amazonaws.com/rich-res\\\",\\n\"\n          + \"        \\\"rowCount\\\": 756,\\n\"\n          + \"        \\\"uncompressedSize\\\": 312560,\\n\"\n          + \"        \\\"compressedSize\\\": 26828\\n\"\n          + \"      }\\n\"\n          + \"    ],\\n\"\n          + \"    \\\"total\\\": 1,\\n\"\n          + \"    \\\"queryId\\\": \\\"01b341c1-0000-772f-0000-0004189328ca\\\",\\n\"\n          + \"    \\\"queryResultFormat\\\": \\\"json\\\"\\n\"\n          + \"  },\\n\"\n          + \"  \\\"code\\\": null,\\n\"\n          + \"  \\\"message\\\": null,\\n\"\n          + \"  \\\"success\\\": true\\n\"\n          + \"}\";\n\n  private static final SFBaseSession MOCK_SESSION =\n      new MockConnectionTest.MockSnowflakeConnectionImpl().getSFSession();\n  private static final SFBaseStatement MOCK_STATEMENT =\n      new MockConnectionTest.MockSnowflakeConnectionImpl().getSFStatement();\n\n  @Test\n  public void shouldProperlyCreateSerializableV1()\n      throws JsonProcessingException, SnowflakeSQLException {\n    JsonNode rootNode = OBJECT_MAPPER.readTree(STANDARD_SERIALIZABLE_V1_JSON_STRING);\n    SnowflakeResultSetSerializableV1 s =\n        SnowflakeResultSetSerializableV1.create(\n            rootNode, MOCK_SESSION, MOCK_STATEMENT, new DefaultResultStreamProvider());\n    assertRegularResultSetSerializable(s, SnowflakeChunkDownloader.class);\n  }\n\n  @Test\n  public void shouldCreateSerializableWithNoOpChunksDownloader()\n      throws JsonProcessingException, SnowflakeSQLException {\n    JsonNode rootNode = OBJECT_MAPPER.readTree(STANDARD_SERIALIZABLE_V1_JSON_STRING);\n    SnowflakeResultSetSerializableV1 s =\n        SnowflakeResultSetSerializableV1.createWithChunksPrefetchDisabled(\n            rootNode, MOCK_SESSION, MOCK_STATEMENT);\n    assertRegularResultSetSerializable(s, NoOpChunkDownloader.class);\n  }\n\n  @Test\n  public void shouldProperlyCreateRichSerializableV1()\n      throws JsonProcessingException, SnowflakeSQLException {\n    JsonNode rootNode = OBJECT_MAPPER.readTree(RICH_RESULTS_SERIALIZABLE_V1_JSON_STRING);\n    SnowflakeRichResultSetSerializableV1 s =\n        SnowflakeRichResultSetSerializableV1.createWithChunksPrefetchDisabled(\n            rootNode, MOCK_SESSION, MOCK_STATEMENT);\n    assertRegularResultSetSerializable(s, NoOpChunkDownloader.class);\n    assertRichResultSetSerializable(s);\n  }\n\n  private void assertRegularResultSetSerializable(\n      SnowflakeResultSetSerializableV1 s, Class<?> expectedChunkDownloaderType) {\n    assertNotNull(s);\n    assertEquals(\"01b341c1-0000-772f-0000-0004189328ca\", s.getQueryId());\n    assertEquals(\"some-db\", s.getFinalDatabaseName());\n    assertEquals(\"some-schema\", s.getFinalSchemaName());\n    assertEquals(\"some-warehouse\", s.getFinalWarehouseName());\n    assertEquals(\"ENG_OPS_RL\", s.getFinalRoleName());\n    assertEquals(0, s.getNumberOfBinds());\n    assertEquals(QueryResultFormat.JSON, s.getQueryResultFormat());\n    assertEquals(SFStatementType.SELECT, s.getStatementType());\n    assertEquals(\n        new HashMap<String, Object>() {\n          {\n            put(\"CLIENT_PREFETCH_THREADS\", 4);\n            put(\"TIMESTAMP_OUTPUT_FORMAT\", \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\");\n            put(\"CLIENT_RESULT_CHUNK_SIZE\", 128);\n          }\n        },\n        s.getParameters());\n    assertEquals(\"ADCDEFGHIJdwadawYhiF81aC0wT0IU+NN8QtobPWCk=\", s.getQrmk());\n    assertFalse(s.isArrayBindSupported());\n    assertEquals(1, s.getResultVersion());\n\n    // column metadata\n    assertEquals(1, s.getColumnCount());\n    assertEquals(1, s.getResultColumnMetadata().size());\n    SnowflakeColumnMetadata column = s.getResultColumnMetadata().get(0);\n    assertEquals(\"1\", column.getName());\n    assertEquals(\"NUMBER\", column.getTypeName());\n    assertEquals(-5, column.getType());\n    assertFalse(column.isNullable());\n    assertEquals(256, column.getLength());\n    assertEquals(0, column.getScale());\n    assertEquals(1, column.getPrecision());\n    assertEquals(SnowflakeType.FIXED, column.getBase());\n    assertEquals(\"some-db\", column.getColumnSrcDatabase());\n    assertEquals(\"some-schema\", column.getColumnSrcSchema());\n    assertEquals(\"some-table\", column.getColumnSrcTable());\n\n    // chunks metadata\n    assertEquals(\"[[\\\"1\\\"]]\", s.getFirstChunkStringData());\n    assertEquals(1, s.getChunkHeadersMap().size());\n    assertEquals(\n        \"A2dDf2ff7HI8OCdsR3pK82g==\",\n        s.getChunkHeadersMap().get(\"x-amz-server-side-encryption-customer-key-md5\"));\n    assertEquals(1, s.getChunkFileCount());\n    assertEquals(1, s.getChunkFileMetadatas().size());\n    ChunkFileMetadata chunkMeta = s.getChunkFileMetadatas().get(0);\n    assertEquals(756, chunkMeta.getRowCount());\n    assertEquals(26828, chunkMeta.getCompressedByteSize());\n    assertEquals(312560, chunkMeta.getUncompressedByteSize());\n    assertEquals(\n        \"https://sfc-ds2-customer-stage.s3.us-west-2.amazonaws.com\", chunkMeta.getFileURL());\n    assertNotNull(s.chunkDownloader);\n    assertTrue(expectedChunkDownloaderType.isInstance(s.chunkDownloader));\n  }\n\n  private void assertRichResultSetSerializable(SnowflakeRichResultSetSerializableV1 s) {\n    // column metadata\n    assertEquals(2, s.getRichResultsColumnCount());\n    assertEquals(2, s.getRichResultsColumnMetadata().size());\n    SnowflakeRichResultSetSerializableV1.SnowflakeRichResultsColumnMetadata lowerBound =\n        s.getRichResultsColumnMetadata().get(0);\n    assertEquals(\"LOWER_BOUND\", lowerBound.getName());\n    assertEquals(\"NUMBER\", lowerBound.getTypeName());\n    assertEquals(-5, lowerBound.getType());\n    assertTrue(lowerBound.isNullable());\n    assertEquals(16777216, lowerBound.getLength());\n    assertEquals(SnowflakeType.FIXED, lowerBound.getBase());\n    assertEquals(\"TEMP\", lowerBound.getColumnSrcDatabase());\n    assertEquals(\"PUBLIC\", lowerBound.getColumnSrcSchema());\n    assertEquals(\"T_TEST\", lowerBound.getColumnSrcTable());\n    assertEquals(1, lowerBound.getColumnIndex());\n\n    SnowflakeRichResultSetSerializableV1.SnowflakeRichResultsColumnMetadata upperBound =\n        s.getRichResultsColumnMetadata().get(1);\n    assertEquals(\"UPPER_BOUND\", upperBound.getName());\n    assertEquals(\"NUMBER\", upperBound.getTypeName());\n    assertEquals(-5, upperBound.getType());\n    assertTrue(upperBound.isNullable());\n    assertEquals(16777216, upperBound.getLength());\n    assertEquals(SnowflakeType.FIXED, upperBound.getBase());\n    assertEquals(\"TEMP\", upperBound.getColumnSrcDatabase());\n    assertEquals(\"PUBLIC\", upperBound.getColumnSrcSchema());\n    assertEquals(\"T_TEST\", upperBound.getColumnSrcTable());\n    assertEquals(1, upperBound.getColumnIndex());\n\n    // chunks metadata\n    assertEquals(\n        \"[[\\\"value1_lower\\\",\\\"value1_upper\\\"],[\\\"value2_lower\\\",\\\"value2_upper\\\"]]\",\n        s.getRichResultsFirstChunkStringData());\n    assertEquals(\"ZXYADCDEFGHIJdwadawYhiF81aC0wT0IU+NN8QtobPWCk=\", s.getRichResultsQrmk());\n    assertEquals(1, s.getRichResultsChunkHeadersMap().size());\n    assertEquals(\n        \"f342lkkftyf7HI8OCdsR3pK82g==\",\n        s.getRichResultsChunkHeadersMap().get(\"x-amz-server-side-encryption-customer-key-md5\"));\n    assertEquals(1, s.getRichResultsChunkFileCount());\n    assertEquals(1, s.getRichResultsChunkFilesMetadata().size());\n    ChunkFileMetadata chunkMeta = s.getRichResultsChunkFilesMetadata().get(0);\n    assertEquals(756, chunkMeta.getRowCount());\n    assertEquals(26828, chunkMeta.getCompressedByteSize());\n    assertEquals(312560, chunkMeta.getUncompressedByteSize());\n    assertEquals(\n        \"https://sfc-ds2-customer-stage.s3.us-west-2.amazonaws.com/rich-res\",\n        chunkMeta.getFileURL());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeTimestampWithTimezoneTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Timestamp;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.time.ZonedDateTime;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.TimeZone;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n/**\n * Tests SnowflakeTimestampWithTimezone to ensure the output is not impacted by Day Light Saving\n * Time. Not this test case is not thread safe, because TimeZone.setDefault is called.\n */\npublic class SnowflakeTimestampWithTimezoneTest extends BaseJDBCTest {\n  private static TimeZone orgTimeZone;\n\n  static class Params implements ArgumentsProvider {\n    public Stream<Arguments> provideArguments(ExtensionContext context) {\n      String[] timeZoneList = {\"PST\", \"America/New_York\", \"UTC\", \"Asia/Singapore\"};\n\n      String[] dateTimeList = {\n        \"2018-03-11 01:10:34.0123456\",\n        \"2018-03-11 02:10:34.0123456\",\n        \"2018-03-11 03:10:34.0123456\",\n        \"2018-11-04 01:10:34.123\",\n        \"2018-11-04 02:10:34.123\",\n        \"2018-11-04 03:10:34.123\",\n        \"2020-03-11 01:10:34.456\",\n        \"2020-03-11 02:10:34.456\",\n        \"2020-03-11 03:10:34.456\",\n        \"2020-11-01 01:10:34.123\",\n        \"2020-11-01 02:10:34.123\",\n        \"2020-11-01 03:10:34.123\"\n      };\n\n      List<Arguments> testCases = new ArrayList<>();\n      for (String timeZone : timeZoneList) {\n        for (String dateTime : dateTimeList) {\n          testCases.add(Arguments.of(timeZone, dateTime, dateTime));\n        }\n      }\n      return testCases.stream();\n    }\n  }\n\n  /** Records the original TimeZone */\n  @BeforeAll\n  public static void keepOriginalTimeZone() {\n    orgTimeZone = TimeZone.getDefault();\n  }\n\n  @AfterAll\n  public static void restoreTimeZone() {\n    TimeZone.setDefault(orgTimeZone);\n  }\n\n  @ParameterizedTest(name = \"{index}: {1} {0}\")\n  @ArgumentsSource(Params.class)\n  public void testTimestampNTZ(String timeZone, String inputTimestamp, String outputTimestamp) {\n    TimeZone.setDefault(TimeZone.getTimeZone(timeZone));\n    LocalDateTime dt = parseTimestampNTZ(inputTimestamp);\n    SnowflakeTimestampWithTimezone stn =\n        new SnowflakeTimestampWithTimezone(\n            dt.toEpochSecond(ZoneOffset.UTC) * 1000, dt.getNano(), TimeZone.getTimeZone(\"UTC\"));\n    assertEquals(outputTimestamp, stn.toString());\n  }\n\n  @Test\n  public void testGetTimezone() {\n    String timezone = \"Australia/Sydney\";\n    // Create a timestamp from a point in time\n    Long datetime = System.currentTimeMillis();\n    Timestamp currentTimestamp = new Timestamp(datetime);\n    SnowflakeTimestampWithTimezone ts =\n        new SnowflakeTimestampWithTimezone(currentTimestamp, TimeZone.getTimeZone(timezone));\n    // verify timezone was set\n    assertEquals(ts.getTimezone().getID(), timezone);\n  }\n\n  @Test\n  public void testToZonedDateTime() {\n    String timezone = \"Australia/Sydney\";\n    String zonedDateTime = \"2022-03-17T10:10:08+11:00[Australia/Sydney]\";\n    // Create a timestamp from a point in time\n    Long datetime = 1647472208000L;\n    Timestamp timestamp = new Timestamp(datetime);\n    SnowflakeTimestampWithTimezone ts =\n        new SnowflakeTimestampWithTimezone(timestamp, TimeZone.getTimeZone(timezone));\n    ZonedDateTime zd = ts.toZonedDateTime();\n    // verify timestamp was converted to zoned datetime\n    assertEquals(zd.toString(), zonedDateTime);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeTypeTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper.convertStringToType;\nimport static net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil.getJavaType;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.math.BigDecimal;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.Time;\nimport java.sql.Types;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeHelper;\nimport net.snowflake.client.internal.jdbc.util.SnowflakeTypeUtil;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeTypeTest {\n\n  @Test\n  public void testSnowflakeType() {\n    assertEquals(\n        getJavaType(SnowflakeType.CHAR, false), SnowflakeTypeHelper.JavaDataType.JAVA_STRING);\n    assertEquals(\n        getJavaType(SnowflakeType.INTEGER, false), SnowflakeTypeHelper.JavaDataType.JAVA_LONG);\n    assertEquals(\n        getJavaType(SnowflakeType.FIXED, false), SnowflakeTypeHelper.JavaDataType.JAVA_BIGDECIMAL);\n    assertEquals(\n        getJavaType(SnowflakeType.TIMESTAMP, false),\n        SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP);\n    assertEquals(\n        getJavaType(SnowflakeType.TIME, false), SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP);\n    assertEquals(\n        getJavaType(SnowflakeType.TIMESTAMP_LTZ, false),\n        SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP);\n    assertEquals(\n        getJavaType(SnowflakeType.TIMESTAMP_NTZ, false),\n        SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP);\n    assertEquals(\n        getJavaType(SnowflakeType.TIMESTAMP_TZ, false),\n        SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP);\n    assertEquals(\n        getJavaType(SnowflakeType.DATE, false), SnowflakeTypeHelper.JavaDataType.JAVA_TIMESTAMP);\n    assertEquals(\n        getJavaType(SnowflakeType.BOOLEAN, false), SnowflakeTypeHelper.JavaDataType.JAVA_BOOLEAN);\n    assertEquals(\n        getJavaType(SnowflakeType.VECTOR, false), SnowflakeTypeHelper.JavaDataType.JAVA_STRING);\n    assertEquals(\n        getJavaType(SnowflakeType.BINARY, false), SnowflakeTypeHelper.JavaDataType.JAVA_BYTES);\n    assertEquals(\n        getJavaType(SnowflakeType.ANY, false), SnowflakeTypeHelper.JavaDataType.JAVA_OBJECT);\n    assertEquals(\n        getJavaType(SnowflakeType.OBJECT, true), SnowflakeTypeHelper.JavaDataType.JAVA_OBJECT);\n    assertEquals(\n        getJavaType(SnowflakeType.OBJECT, false), SnowflakeTypeHelper.JavaDataType.JAVA_STRING);\n    assertEquals(\n        getJavaType(SnowflakeType.GEOMETRY, false), SnowflakeTypeHelper.JavaDataType.JAVA_STRING);\n  }\n\n  @Test\n  public void testConvertStringToType() {\n    assertEquals(convertStringToType(null), Types.NULL);\n    assertEquals(convertStringToType(\"decimal\"), Types.DECIMAL);\n    assertEquals(convertStringToType(\"int\"), Types.INTEGER);\n    assertEquals(convertStringToType(\"integer\"), Types.INTEGER);\n    assertEquals(convertStringToType(\"byteint\"), Types.INTEGER);\n    assertEquals(convertStringToType(\"smallint\"), Types.SMALLINT);\n    assertEquals(convertStringToType(\"bigint\"), Types.BIGINT);\n    assertEquals(convertStringToType(\"double\"), Types.DOUBLE);\n    assertEquals(convertStringToType(\"double precision\"), Types.DOUBLE);\n    assertEquals(convertStringToType(\"real\"), Types.REAL);\n    assertEquals(convertStringToType(\"char\"), Types.CHAR);\n    assertEquals(convertStringToType(\"character\"), Types.CHAR);\n    assertEquals(convertStringToType(\"varbinary\"), Types.VARBINARY);\n    assertEquals(convertStringToType(\"boolean\"), Types.BOOLEAN);\n    assertEquals(convertStringToType(\"date\"), Types.DATE);\n    assertEquals(convertStringToType(\"time\"), Types.TIME);\n    assertEquals(convertStringToType(\"timestamp\"), Types.TIMESTAMP);\n    assertEquals(convertStringToType(\"datetime\"), Types.TIMESTAMP);\n    assertEquals(convertStringToType(\"timestamp_ntz\"), Types.TIMESTAMP);\n    assertEquals(convertStringToType(\"timestamp_ltz\"), Types.TIMESTAMP_WITH_TIMEZONE);\n    assertEquals(convertStringToType(\"timestamp_tz\"), Types.TIMESTAMP_WITH_TIMEZONE);\n    assertEquals(convertStringToType(\"variant\"), Types.OTHER);\n    assertEquals(convertStringToType(\"object\"), Types.JAVA_OBJECT);\n    assertEquals(convertStringToType(\"vector\"), SnowflakeType.EXTRA_TYPES_VECTOR);\n    assertEquals(convertStringToType(\"array\"), Types.ARRAY);\n    assertEquals(convertStringToType(\"uuid\"), Types.OTHER);\n    assertEquals(convertStringToType(\"default\"), Types.OTHER);\n  }\n\n  @Test\n  public void testJavaSQLTypeFind() {\n    assertNull(SnowflakeTypeHelper.JavaSQLType.find(200000));\n  }\n\n  @Test\n  public void testJavaSQLTypeLexicalValue() {\n    assertEquals(SnowflakeTypeUtil.lexicalValue(1.0f, null, null, null, null), \"0x1.0p0\");\n    assertEquals(\n        SnowflakeTypeUtil.lexicalValue(new BigDecimal(100.0), null, null, null, null), \"100\");\n    assertEquals(\n        SnowflakeTypeUtil.lexicalValue(\"random\".getBytes(), null, null, null, null),\n        \"72616E646F6D\");\n  }\n\n  @Test\n  public void testJavaTypeToSFType() throws SnowflakeSQLException {\n    assertEquals(SnowflakeTypeUtil.javaTypeToSFType(0, null), SnowflakeType.ANY);\n    assertThrows(\n        SnowflakeSQLLoggedException.class,\n        () -> {\n          SnowflakeTypeUtil.javaTypeToSFType(2000000, null);\n        });\n  }\n\n  @Test\n  public void testJavaTypeToClassName() throws SQLException {\n    assertEquals(SnowflakeTypeUtil.javaTypeToClassName(Types.DECIMAL), BigDecimal.class.getName());\n    assertEquals(SnowflakeTypeUtil.javaTypeToClassName(Types.TIME), Time.class.getName());\n    assertEquals(SnowflakeTypeUtil.javaTypeToClassName(Types.BOOLEAN), Boolean.class.getName());\n    assertThrows(\n        SQLFeatureNotSupportedException.class,\n        () -> {\n          SnowflakeTypeUtil.javaTypeToClassName(-2000000);\n        });\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/SnowflakeUtilTest.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.createCaseInsensitiveMap;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.createOwnerOnlyPermissionDir;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.extractColumnMetadata;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.getSnowflakeType;\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.hostnameMatchesGlob;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.attribute.PosixFileAttributes;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.nio.file.attribute.PosixFilePermissions;\nimport java.sql.Types;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeMap;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport net.snowflake.client.api.resultset.FieldMetadata;\nimport net.snowflake.client.api.resultset.SnowflakeType;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.resultset.FieldMetadataImpl;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport org.apache.http.Header;\nimport org.apache.http.message.BasicHeader;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n@Tag(TestTags.CORE)\npublic class SnowflakeUtilTest extends BaseJDBCTest {\n\n  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();\n\n  @Test\n  public void testCreateMetadata() throws Throwable {\n    // given\n    ObjectNode rootNode = createRootNode();\n    ArrayNode fields = OBJECT_MAPPER.createArrayNode();\n    JsonNode fieldOne = createFieldNode(\"name1\", null, 256, null, \"text\", false, \"collation\", 256);\n    fields.add(fieldOne);\n    JsonNode fieldTwo = createFieldNode(\"name2\", 5, 128, 2, \"real\", true, \"collation\", 256);\n    fields.add(fieldTwo);\n    rootNode.putIfAbsent(\"fields\", fields);\n    SnowflakeColumnMetadata expectedColumnMetadata =\n        createExpectedMetadata(rootNode, fieldOne, fieldTwo);\n    // when\n    SnowflakeColumnMetadata columnMetadata = extractColumnMetadata(rootNode, false, null);\n    // then\n    assertNotNull(columnMetadata);\n    assertEquals(\n        OBJECT_MAPPER.writeValueAsString(expectedColumnMetadata),\n        OBJECT_MAPPER.writeValueAsString(columnMetadata));\n  }\n\n  @Test\n  public void testCreateFieldsMetadataForObject() throws Throwable {\n    // given\n    ObjectNode rootNode = createRootNode();\n    ArrayNode fields = OBJECT_MAPPER.createArrayNode();\n    fields.add(\n        OBJECT_MAPPER.readTree(\n            \"{\\\"fieldName\\\":\\\"name1\\\", \\\"fieldType\\\": {\\\"type\\\":\\\"text\\\",\\\"precision\\\":null,\\\"length\\\":256,\\\"scale\\\":null,\\\"nullable\\\":false}}\"));\n    fields.add(\n        OBJECT_MAPPER.readTree(\n            \"{\\\"fieldName\\\":\\\"name2\\\", \\\"fieldType\\\": {\\\"type\\\":\\\"real\\\",\\\"precision\\\":5,\\\"length\\\":128,\\\"scale\\\":null,\\\"nullable\\\":true}}\"));\n    rootNode.putIfAbsent(\"fields\", fields);\n\n    // when\n    SnowflakeColumnMetadata columnMetadata = extractColumnMetadata(rootNode, false, null);\n    // then\n    assertNotNull(columnMetadata);\n    assertEquals(\"OBJECT\", columnMetadata.getTypeName());\n\n    FieldMetadata firstField = columnMetadata.getFields().get(0);\n    assertEquals(\"name1\", firstField.getName());\n    assertEquals(SnowflakeType.TEXT, firstField.getBase());\n    assertEquals(256, firstField.getByteLength());\n    assertFalse(firstField.isNullable());\n\n    FieldMetadata secondField = columnMetadata.getFields().get(1);\n    assertEquals(\"name2\", secondField.getName());\n    assertEquals(SnowflakeType.REAL, secondField.getBase());\n    assertEquals(128, secondField.getByteLength());\n    assertEquals(5, secondField.getPrecision());\n    assertTrue(secondField.isNullable());\n  }\n\n  @Test\n  public void shouldConvertCreateCaseInsensitiveMap() {\n    Map<String, String> map = new HashMap<>();\n    map.put(\"key1\", \"value1\");\n\n    map = SnowflakeUtil.createCaseInsensitiveMap(map);\n    assertTrue(map instanceof TreeMap);\n    assertEquals(String.CASE_INSENSITIVE_ORDER, ((TreeMap<String, String>) map).comparator());\n    assertEquals(\"value1\", map.get(\"key1\"));\n    assertEquals(\"value1\", map.get(\"Key1\"));\n    assertEquals(\"value1\", map.get(\"KEy1\"));\n\n    map.put(\"KEY1\", \"changed_value1\");\n    assertEquals(\"changed_value1\", map.get(\"KEY1\"));\n  }\n\n  @Test\n  public void shouldConvertHeadersCreateCaseInsensitiveMap() {\n    Header[] headers =\n        new Header[] {new BasicHeader(\"key1\", \"value1\"), new BasicHeader(\"key2\", \"value2\")};\n\n    Map<String, String> map = createCaseInsensitiveMap(headers);\n    assertTrue(map instanceof TreeMap);\n    assertEquals(String.CASE_INSENSITIVE_ORDER, ((TreeMap<String, String>) map).comparator());\n    assertEquals(\"value1\", map.get(\"key1\"));\n    assertEquals(\"value2\", map.get(\"key2\"));\n    assertEquals(\"value1\", map.get(\"Key1\"));\n    assertEquals(\"value2\", map.get(\"Key2\"));\n  }\n\n  @Test\n  @DontRunOnWindows\n  public void testCreateOwnerOnlyPermissionDir(@TempDir Path tempDir)\n      throws IOException, UnsupportedOperationException {\n    Path tmp = tempDir.resolve(\"folder-permission-testing\");\n    String folderPath = tmp.toString();\n    boolean isDirCreated = createOwnerOnlyPermissionDir(folderPath);\n    assertTrue(isDirCreated);\n    assertTrue(tmp.toFile().isDirectory());\n    PosixFileAttributes attributes = Files.readAttributes(tmp, PosixFileAttributes.class);\n    Set<PosixFilePermission> permissions = attributes.permissions();\n    assertEquals(PosixFilePermissions.toString(permissions), \"rwx------\");\n  }\n\n  @Test\n  @DontRunOnWindows\n  public void testValidateFilePermission(@TempDir Path tempDir)\n      throws IOException, UnsupportedOperationException {\n    Path tmp = tempDir.resolve(\"fileTesting.txt\");\n    File file = tmp.toFile();\n    assertTrue(file.createNewFile());\n    SnowflakeUtil.assureOnlyUserAccessibleFilePermissions(file, true);\n    PosixFileAttributes attributes = Files.readAttributes(tmp, PosixFileAttributes.class);\n    Set<PosixFilePermission> permissions = attributes.permissions();\n    assertEquals(PosixFilePermissions.toString(permissions), \"rw-------\");\n  }\n\n  private static SnowflakeColumnMetadata createExpectedMetadata(\n      JsonNode rootNode, JsonNode fieldOne, JsonNode fieldTwo) throws SnowflakeSQLLoggedException {\n    ColumnTypeInfo columnTypeInfo =\n        getSnowflakeType(rootNode.path(\"type\").asText(), null, null, null, 0, true, false);\n    ColumnTypeInfo columnTypeInfoNodeOne =\n        getSnowflakeType(\n            fieldOne.path(\"type\").asText(), null, null, null, Types.BIGINT, true, false);\n    ColumnTypeInfo columnTypeInfoNodeTwo =\n        getSnowflakeType(\n            fieldTwo.path(\"type\").asText(), null, null, null, Types.DECIMAL, true, false);\n    SnowflakeColumnMetadata expectedColumnMetadata =\n        new SnowflakeColumnMetadata(\n            rootNode.path(\"name\").asText(),\n            columnTypeInfo.getColumnType(),\n            rootNode.path(\"nullable\").asBoolean(),\n            rootNode.path(\"length\").asInt(),\n            rootNode.path(\"precision\").asInt(),\n            rootNode.path(\"scale\").asInt(),\n            columnTypeInfo.getExtColTypeName(),\n            false,\n            columnTypeInfo.getSnowflakeType(),\n            Arrays.asList(\n                new FieldMetadataImpl(\n                    fieldOne.path(\"name\").asText(),\n                    columnTypeInfoNodeOne.getExtColTypeName(),\n                    columnTypeInfoNodeOne.getColumnType(),\n                    fieldOne.path(\"nullable\").asBoolean(),\n                    fieldOne.path(\"length\").asInt(),\n                    fieldOne.path(\"precision\").asInt(),\n                    fieldOne.path(\"scale\").asInt(),\n                    fieldOne.path(\"fixed\").asBoolean(),\n                    columnTypeInfoNodeOne.getSnowflakeType(),\n                    new ArrayList<>()),\n                new FieldMetadataImpl(\n                    fieldTwo.path(\"name\").asText(),\n                    columnTypeInfoNodeTwo.getExtColTypeName(),\n                    columnTypeInfoNodeTwo.getColumnType(),\n                    fieldTwo.path(\"nullable\").asBoolean(),\n                    fieldTwo.path(\"length\").asInt(),\n                    fieldTwo.path(\"precision\").asInt(),\n                    fieldTwo.path(\"scale\").asInt(),\n                    fieldTwo.path(\"fixed\").asBoolean(),\n                    columnTypeInfoNodeTwo.getSnowflakeType(),\n                    new ArrayList<>())),\n            rootNode.path(\"database\").asText(),\n            rootNode.path(\"schema\").asText(),\n            rootNode.path(\"table\").asText(),\n            false,\n            rootNode.path(\"dimension\").asInt());\n    return expectedColumnMetadata;\n  }\n\n  private static ObjectNode createRootNode() {\n    ObjectNode rootNode = createFieldNode(\"STRUCT\", 2, 128, 8, \"object\", false, null, 42);\n    rootNode.put(\"database\", \"databaseName\");\n    rootNode.put(\"schema\", \"schemaName\");\n    rootNode.put(\"table\", \"tableName\");\n    return rootNode;\n  }\n\n  private static ObjectNode createFieldNode(\n      String name,\n      Integer precision,\n      Integer byteLength,\n      Integer scale,\n      String type,\n      boolean nullable,\n      String collation,\n      Integer length) {\n    ObjectNode fieldNode = OBJECT_MAPPER.createObjectNode();\n    fieldNode.put(\"name\", name);\n    fieldNode.put(\"type\", type);\n    fieldNode.put(\"precision\", precision);\n    fieldNode.put(\"byteLength\", byteLength);\n    fieldNode.put(\"scale\", scale);\n    fieldNode.put(\"type\", type);\n    fieldNode.put(\"nullable\", nullable);\n    fieldNode.put(\"collation\", collation);\n    fieldNode.put(\"length\", length);\n    return fieldNode;\n  }\n\n  @Test\n  public void testHostnameMatchesGlob() {\n    // Exact match\n    assertTrue(hostnameMatchesGlob(\"localhost\", \"localhost\"));\n    assertTrue(hostnameMatchesGlob(\"example.com\", \"example.com\"));\n    assertFalse(hostnameMatchesGlob(\"example.com\", \"other.com\"));\n\n    // Case insensitive\n    assertTrue(hostnameMatchesGlob(\"EXAMPLE.COM\", \"example.com\"));\n    assertTrue(hostnameMatchesGlob(\"example.com\", \"EXAMPLE.COM\"));\n\n    // Leading wildcard\n    assertTrue(hostnameMatchesGlob(\"foo.example.com\", \"*.example.com\"));\n    assertTrue(hostnameMatchesGlob(\"bar.baz.example.com\", \"*.example.com\"));\n    assertFalse(hostnameMatchesGlob(\"example.com\", \"*.example.com\"));\n    assertFalse(hostnameMatchesGlob(\"notexample.com\", \"*.example.com\"));\n\n    // Trailing wildcard\n    assertTrue(hostnameMatchesGlob(\"192.168.1.1\", \"192.168.*\"));\n    assertFalse(hostnameMatchesGlob(\"10.0.0.1\", \"192.168.*\"));\n\n    // Middle wildcard\n    assertTrue(hostnameMatchesGlob(\"foo.bar.com\", \"foo.*.com\"));\n    assertFalse(hostnameMatchesGlob(\"bar.baz.com\", \"foo.*.com\"));\n\n    // Wildcard only\n    assertTrue(hostnameMatchesGlob(\"anything.example.com\", \"*\"));\n    assertTrue(hostnameMatchesGlob(\"\", \"*\"));\n\n    // Whitespace trimming\n    assertTrue(hostnameMatchesGlob(\"example.com\", \"  example.com  \"));\n\n    // Null safety\n    assertFalse(hostnameMatchesGlob(null, \"*.example.com\"));\n    assertFalse(hostnameMatchesGlob(\"example.com\", null));\n    assertFalse(hostnameMatchesGlob(null, null));\n\n    // Regex metacharacters are treated as literals, not regex\n    assertFalse(hostnameMatchesGlob(\"aaa\", \"(a+)+\"));\n    assertFalse(hostnameMatchesGlob(\"exampleXcom\", \"example.com\"));\n    assertTrue(hostnameMatchesGlob(\"example.com\", \"example.com\"));\n  }\n\n  @Test\n  public void testHostnameMatchesGlobReDoSPatternDoesNotHang() {\n    // Exact ReDoS payload from the vulnerability report (SNOW-3104251).\n    // With unquoted regex this would cause catastrophic backtracking.\n    String maliciousHost =\n        \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac\";\n    long start = System.currentTimeMillis();\n    assertFalse(hostnameMatchesGlob(maliciousHost, \"(a+)+\"));\n    long elapsed = System.currentTimeMillis() - start;\n    assertTrue(\n        elapsed < 1000, \"Glob matching should complete nearly instantly, took \" + elapsed + \"ms\");\n  }\n\n  @Test\n  public void testFieldMetadataSetterMethods() {\n    FieldMetadataImpl fieldMetadata = new FieldMetadataImpl();\n\n    // Testing setName\n    fieldMetadata.setName(\"testName\");\n    assertEquals(\"testName\", fieldMetadata.getName());\n\n    // Testing setTypeName\n    fieldMetadata.setTypeName(\"VARCHAR\");\n    assertEquals(\"VARCHAR\", fieldMetadata.getTypeName());\n\n    // Testing setType\n    fieldMetadata.setType(42);\n    assertEquals(42, fieldMetadata.getType());\n\n    // Testing setNullable\n    fieldMetadata.setNullable(true);\n    assertTrue(fieldMetadata.isNullable());\n    fieldMetadata.setNullable(false);\n    assertFalse(fieldMetadata.isNullable());\n\n    // Testing setByteLength\n    fieldMetadata.setByteLength(255);\n    assertEquals(255, fieldMetadata.getByteLength());\n\n    // Testing setPrecision\n    fieldMetadata.setPrecision(10);\n    assertEquals(10, fieldMetadata.getPrecision());\n\n    // Testing setScale\n    fieldMetadata.setScale(5);\n    assertEquals(5, fieldMetadata.getScale());\n\n    // Testing setFixed\n    fieldMetadata.setFixed(true);\n    assertTrue(fieldMetadata.isFixed());\n    fieldMetadata.setFixed(false);\n    assertFalse(fieldMetadata.isFixed());\n\n    // Testing setBase\n    SnowflakeType testBase = SnowflakeType.TEXT;\n    fieldMetadata.setBase(testBase);\n    assertEquals(testBase, fieldMetadata.getBase());\n\n    // Testing setFields\n    List<FieldMetadata> fieldList = new ArrayList<>();\n    fieldList.add(new FieldMetadataImpl());\n    fieldMetadata.setFields(fieldList);\n    assertEquals(fieldList, fieldMetadata.getFields());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementAlreadyClosedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class StatementAlreadyClosedIT extends BaseJDBCTest {\n  @Test\n  public void testStatementAlreadyClosed() throws Throwable {\n    try (Connection connection = getConnection()) {\n      Statement statement = connection.createStatement();\n      assertFalse(statement.isClosed());\n      statement.close();\n      assertTrue(statement.isClosed());\n\n      expectStatementAlreadyClosedException(\n          () -> statement.execute(\"select 1\", Statement.NO_GENERATED_KEYS));\n      expectStatementAlreadyClosedException(statement::executeBatch);\n      expectStatementAlreadyClosedException(() -> statement.executeQuery(\"select 1\"));\n      expectStatementAlreadyClosedException(() -> statement.executeUpdate(\"update t set c1=1\"));\n      expectStatementAlreadyClosedException(() -> statement.execute(\"update t set c1=1\"));\n      expectStatementAlreadyClosedException(statement::getConnection);\n      expectStatementAlreadyClosedException(statement::getFetchDirection);\n      expectStatementAlreadyClosedException(statement::getFetchSize);\n      expectStatementAlreadyClosedException(statement::getMoreResults);\n      expectStatementAlreadyClosedException(() -> statement.getMoreResults(0));\n      expectStatementAlreadyClosedException(statement::getQueryTimeout);\n      expectStatementAlreadyClosedException(statement::getResultSet);\n      expectStatementAlreadyClosedException(statement::getResultSetConcurrency);\n      expectStatementAlreadyClosedException(statement::getResultSetHoldability);\n      expectStatementAlreadyClosedException(statement::getResultSetType);\n      expectStatementAlreadyClosedException(statement::getUpdateCount);\n      expectStatementAlreadyClosedException(statement::getWarnings);\n      expectStatementAlreadyClosedException(() -> statement.setEscapeProcessing(true));\n      expectStatementAlreadyClosedException(\n          () -> statement.setFetchDirection(ResultSet.FETCH_FORWARD));\n      expectStatementAlreadyClosedException(() -> statement.setFetchSize(10));\n      expectStatementAlreadyClosedException(() -> statement.setMaxRows(10));\n      expectStatementAlreadyClosedException(statement::isPoolable);\n      expectStatementAlreadyClosedException(() -> statement.setPoolable(false));\n      expectStatementAlreadyClosedException(() -> statement.setQueryTimeout(10));\n      expectStatementAlreadyClosedException(statement::cancel);\n      expectStatementAlreadyClosedException(statement::clearWarnings);\n      expectStatementAlreadyClosedException(() -> statement.addBatch(\"select 2\"));\n      expectStatementAlreadyClosedException(statement::clearBatch);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementArrowIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\n\n@Tag(TestTags.ARROW)\npublic class StatementArrowIT extends StatementIT {\n  public StatementArrowIT() {\n    super();\n    queryResultFormat = \"arrow\";\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementFeatureNotSupportedIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class StatementFeatureNotSupportedIT extends BaseJDBCTest {\n  @Test\n  public void testFeatureNotSupportedException() throws Throwable {\n    try (Connection connection = getConnection()) {\n      try (Statement statement = connection.createStatement()) {\n        expectFeatureNotSupportedException(() -> statement.execute(\"select 1\", new int[] {}));\n        expectFeatureNotSupportedException(() -> statement.execute(\"select 1\", new String[] {}));\n        expectFeatureNotSupportedException(\n            () ->\n                statement.executeUpdate(\n                    \"insert into a values(1)\", Statement.RETURN_GENERATED_KEYS));\n        expectFeatureNotSupportedException(\n            () -> statement.executeUpdate(\"insert into a values(1)\", new int[] {}));\n        expectFeatureNotSupportedException(\n            () -> statement.executeUpdate(\"insert into a values(1)\", new String[] {}));\n        expectFeatureNotSupportedException(\n            () ->\n                statement.executeLargeUpdate(\n                    \"insert into a values(1)\", Statement.RETURN_GENERATED_KEYS));\n        expectFeatureNotSupportedException(\n            () -> statement.executeLargeUpdate(\"insert into a values(1)\", new int[] {}));\n        expectFeatureNotSupportedException(\n            () -> statement.executeLargeUpdate(\"insert into a values(1)\", new String[] {}));\n        expectFeatureNotSupportedException(() -> statement.setCursorName(\"curname\"));\n        expectFeatureNotSupportedException(\n            () -> statement.setFetchDirection(ResultSet.FETCH_REVERSE));\n        expectFeatureNotSupportedException(\n            () -> statement.setFetchDirection(ResultSet.FETCH_UNKNOWN));\n        expectFeatureNotSupportedException(() -> statement.setMaxFieldSize(10));\n        expectFeatureNotSupportedException(statement::closeOnCompletion);\n        expectFeatureNotSupportedException(statement::isCloseOnCompletion);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.sql.BatchUpdateException;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.List;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.SnowflakeResultSet;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.jdbc.telemetry.Telemetry;\nimport net.snowflake.client.internal.jdbc.telemetry.TelemetryClient;\nimport net.snowflake.common.core.SqlState;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n/** Statement tests */\n@Tag(TestTags.STATEMENT)\npublic class StatementIT extends BaseJDBCWithSharedConnectionIT {\n  protected static String queryResultFormat = \"json\";\n\n  public static Connection getConnection() throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection();\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  @TempDir private File tmpFolder;\n\n  @Test\n  public void testFetchDirection() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      assertEquals(ResultSet.FETCH_FORWARD, statement.getFetchDirection());\n      assertThrows(\n          SQLFeatureNotSupportedException.class,\n          () -> statement.setFetchDirection(ResultSet.FETCH_REVERSE));\n    }\n  }\n\n  @Disabled(\"Not working for setFetchSize\")\n  @Test\n  public void testFetchSize() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      assertEquals(50, statement.getFetchSize());\n      statement.setFetchSize(1);\n      ResultSet rs = statement.executeQuery(\"select * from JDBC_STATEMENT\");\n      assertEquals(1, getSizeOfResultSet(rs));\n    }\n  }\n\n  @Test\n  public void testMaxRows() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String sqlSelect = \"select seq4() from table(generator(rowcount=>3))\";\n      assertEquals(0, statement.getMaxRows());\n\n      //    statement.setMaxRows(1);\n      //    assertEquals(1, statement.getMaxRows());\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        int resultSizeCount = getSizeOfResultSet(rs);\n        //    assertEquals(1, resultSizeCount);\n      }\n      statement.setMaxRows(0);\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        //    assertEquals(3, getSizeOfResultSet(rs));\n      }\n      statement.setMaxRows(-1);\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        //    assertEquals(3, getSizeOfResultSet(rs));\n      }\n    }\n  }\n\n  @Test\n  public void testQueryTimeOut() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      assertEquals(0, statement.getQueryTimeout());\n      statement.setQueryTimeout(5);\n      assertEquals(5, statement.getQueryTimeout());\n      SQLException e =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  statement.executeQuery(\n                      \"select count(*) from table(generator(timeLimit => 100))\"));\n      assertTrue(true);\n      assertEquals(SqlState.QUERY_CANCELED, e.getSQLState());\n      assertEquals(\"SQL execution canceled\", e.getMessage());\n    }\n  }\n\n  @Test\n  public void testStatementClose() throws SQLException {\n    try (Statement statement = connection.createStatement(); ) {\n      assertEquals(connection, statement.getConnection());\n      assertTrue(!statement.isClosed());\n      statement.close();\n      assertTrue(statement.isClosed());\n    }\n  }\n\n  @Test\n  public void testExecuteSelect() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      String sqlSelect = \"select seq4() from table(generator(rowcount=>3))\";\n      boolean success = statement.execute(sqlSelect);\n      assertTrue(success);\n      String queryID1 = statement.unwrap(SnowflakeStatement.class).getQueryID();\n      assertNotNull(queryID1);\n\n      try (ResultSet rs = statement.getResultSet()) {\n        assertEquals(3, getSizeOfResultSet(rs));\n        assertEquals(-1, statement.getUpdateCount());\n        assertEquals(-1L, statement.getLargeUpdateCount());\n        String queryID2 = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        assertEquals(queryID2, queryID1);\n      }\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        assertEquals(3, getSizeOfResultSet(rs));\n        String queryID4 = rs.unwrap(SnowflakeResultSet.class).getQueryID();\n        assertNotEquals(queryID4, queryID1);\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteInsert() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"create or replace table test_insert(cola number)\");\n\n        String insertSQL = \"insert into test_insert values(2),(3)\";\n        int updateCount;\n        boolean success;\n        updateCount = statement.executeUpdate(insertSQL);\n        assertEquals(2, updateCount);\n\n        success = statement.execute(insertSQL);\n        assertFalse(success);\n        assertEquals(2, statement.getUpdateCount());\n        assertEquals(2L, statement.getLargeUpdateCount());\n        assertNull(statement.getResultSet());\n\n        try (ResultSet rs = statement.executeQuery(\"select count(*) from test_insert\")) {\n          assertTrue(rs.next());\n          assertEquals(4, rs.getInt(1));\n        }\n\n        assertTrue(statement.execute(\"select 1\"));\n        try (ResultSet rs0 = statement.getResultSet()) {\n          assertTrue(rs0.next());\n          assertEquals(rs0.getInt(1), 1);\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_insert\");\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteUpdateAndDelete() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\n            \"create or replace table test_update(cola number, colb string) \"\n                + \"as select 1, 'str1'\");\n\n        statement.execute(\"insert into test_update values(2, 'str2')\");\n\n        int updateCount;\n        boolean success;\n        updateCount =\n            statement.executeUpdate(\"update test_update set COLB = 'newStr' where COLA = 1\");\n        assertEquals(1, updateCount);\n\n        success = statement.execute(\"update test_update set COLB = 'newStr' where COLA = 2\");\n        assertFalse(success);\n        assertEquals(1, statement.getUpdateCount());\n        assertEquals(1L, statement.getLargeUpdateCount());\n        assertNull(statement.getResultSet());\n\n        updateCount = statement.executeUpdate(\"delete from test_update where colA = 1\");\n        assertEquals(1, updateCount);\n\n        success = statement.execute(\"delete from test_update where colA = 2\");\n        assertFalse(success);\n        assertEquals(1, statement.getUpdateCount());\n        assertEquals(1L, statement.getLargeUpdateCount());\n        assertNull(statement.getResultSet());\n      } finally {\n        statement.execute(\"drop table if exists test_update\");\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteMerge() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String mergeSQL =\n          \"merge into target using source on target.id = source.id \"\n              + \"when matched and source.sb =22 then update set ta = 'newStr' \"\n              + \"when not matched then insert (ta, tb) values (source.sa, source.sb)\";\n      try {\n        statement.execute(\"create or replace table target(id integer, ta string, tb integer)\");\n        statement.execute(\"create or replace table source(id integer, sa string, sb integer)\");\n        statement.execute(\"insert into target values(1, 'str', 1)\");\n        statement.execute(\"insert into target values(2, 'str', 2)\");\n        statement.execute(\"insert into target values(3, 'str', 3)\");\n        statement.execute(\"insert into source values(1, 'str1', 11)\");\n        statement.execute(\"insert into source values(2, 'str2', 22)\");\n        statement.execute(\"insert into source values(3, 'str3', 33)\");\n\n        int updateCount = statement.executeUpdate(mergeSQL);\n\n        assertEquals(1, updateCount);\n      } finally {\n        statement.execute(\"drop table if exists target\");\n        statement.execute(\"drop table if exists source\");\n      }\n    }\n  }\n\n  /**\n   * Test Autogenerated key.\n   *\n   * @throws Throwable if any error occurs\n   */\n  @Test\n  public void testAutogenerateKey() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace table t(c1 int)\");\n      statement.execute(\"insert into t values(1)\", Statement.NO_GENERATED_KEYS);\n\n      assertThrows(\n          SQLFeatureNotSupportedException.class,\n          () -> statement.execute(\"insert into t values(2)\", Statement.RETURN_GENERATED_KEYS));\n\n      // empty result\n      try (ResultSet rset = statement.getGeneratedKeys()) {\n        assertFalse(rset.next());\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteMultiInsert() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String multiInsertionSQL =\n          \" insert all \"\n              + \"into foo \"\n              + \"into foo1 \"\n              + \"into bar (b1, b2, b3) values (s3, s2, s1) \"\n              + \"select s1, s2, s3 from source\";\n\n      try {\n        assertFalse(\n            statement.execute(\"create or replace table foo (f1 integer, f2 integer, f3 integer)\"));\n        assertFalse(\n            statement.execute(\"create or replace table foo1 (f1 integer, f2 integer, f3 integer)\"));\n        assertFalse(\n            statement.execute(\"create or replace table bar (b1 integer, b2 integer, b3 integer)\"));\n        assertFalse(\n            statement.execute(\n                \"create or replace table source(s1 integer, s2 integer, s3 integer)\"));\n        assertFalse(statement.execute(\"insert into source values(1, 2, 3)\"));\n        assertFalse(statement.execute(\"insert into source values(11, 22, 33)\"));\n        assertFalse(statement.execute(\"insert into source values(111, 222, 333)\"));\n\n        int updateCount = statement.executeUpdate(multiInsertionSQL);\n        assertEquals(9, updateCount);\n      } finally {\n        statement.execute(\"drop table if exists foo\");\n        statement.execute(\"drop table if exists foo1\");\n        statement.execute(\"drop table if exists bar\");\n        statement.execute(\"drop table if exists source\");\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteBatch() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      try {\n        connection.setAutoCommit(false);\n        // mixed of ddl/dml in batch\n        statement.addBatch(\"create or replace table test_batch(a string, b integer)\");\n        statement.addBatch(\"insert into test_batch values('str1', 1), ('str2', 2)\");\n        statement.addBatch(\n            \"update test_batch set test_batch.b = src.b + 5 from \"\n                + \"(select 'str1' as a, 2 as b) src where test_batch.a = src.a\");\n\n        int[] rowCounts = statement.executeBatch();\n        connection.commit();\n\n        assertThat(rowCounts.length, is(3));\n        assertThat(rowCounts[0], is(0));\n        assertThat(rowCounts[1], is(2));\n        assertThat(rowCounts[2], is(1));\n\n        List<String> batchQueryIDs = statement.unwrap(SnowflakeStatement.class).getBatchQueryIDs();\n        assertEquals(3, batchQueryIDs.size());\n        assertEquals(statement.unwrap(SnowflakeStatement.class).getQueryID(), batchQueryIDs.get(2));\n\n        try (ResultSet resultSet =\n            statement.executeQuery(\"select * from test_batch order by b asc\")) {\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(\"B\"), is(2));\n          assertTrue(resultSet.next());\n          assertThat(resultSet.getInt(\"B\"), is(7));\n          statement.clearBatch();\n\n          // one of the batch query returns error\n          // it should continue processing\n          statement.addBatch(\"insert into test_batch values('str3', 3)\");\n          statement.addBatch(\"select * from test_batch\");\n          statement.addBatch(\"select * from test_batch_not_exist\");\n          statement.addBatch(\"insert into test_batch values('str4', 4)\");\n          BatchUpdateException e =\n              assertThrows(BatchUpdateException.class, statement::executeBatch);\n          rowCounts = e.getUpdateCounts();\n          assertThat(rowCounts[0], is(1));\n          assertThat(rowCounts[1], is(Statement.SUCCESS_NO_INFO));\n          assertThat(rowCounts[2], is(Statement.EXECUTE_FAILED));\n          assertThat(rowCounts[3], is(1));\n\n          connection.rollback();\n\n          statement.clearBatch();\n\n          statement.addBatch(\n              \"put file://\"\n                  + getFullPathFileInResource(TEST_DATA_FILE)\n                  + \" @%test_batch auto_compress=false\");\n          File tempFolder = new File(tmpFolder, \"test_downloads_folder\");\n          tempFolder.mkdirs();\n          statement.addBatch(\"get @%test_batch file://\" + tempFolder.getCanonicalPath());\n\n          rowCounts = statement.executeBatch();\n          assertThat(rowCounts.length, is(2));\n          assertThat(rowCounts[0], is(Statement.SUCCESS_NO_INFO));\n          assertThat(rowCounts[0], is(Statement.SUCCESS_NO_INFO));\n          statement.clearBatch();\n        }\n      } finally {\n        statement.execute(\"drop table if exists test_batch\");\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteLargeBatch() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      /**\n       * Generate a table with several rows and 1 column named test_large_batch Note: to truly test\n       * that executeLargeBatch works with a number of rows greater than MAX_INT, replace rowcount\n       * => 15 in next code line with rowcount => 2147483648, or some other number larger than\n       * MAX_INT. Test will take about 15 minutes to run.\n       */\n      try {\n        statement.execute(\n            \"create or replace table test_large_batch (a number) as (select * from (select 5 from table\"\n                + \"(generator(rowcount => 15)) v));\");\n        // update values in table so that all rows are updated\n        statement.addBatch(\"update test_large_batch set a = 7 where a = 5;\");\n        long[] rowsUpdated = statement.executeLargeBatch();\n        assertThat(rowsUpdated.length, is(1));\n        long testVal = 15L;\n        assertThat(rowsUpdated[0], is(testVal));\n        statement.clearBatch();\n\n        /**\n         * To test SQLException for integer overflow when using executeBatch() for row updates of\n         * larger than MAX_INT, uncomment the following lines of code. Test will take about 15\n         * minutes to run.\n         *\n         * <p>statement.execute(\"create or replace table test_large_batch (a number) as (select *\n         * from (select 5 from table\" + \"(generator(rowcount => 2147483648)) v));\");\n         * statement.addBatch(\"update test_large_batch set a = 7 where a = 5;\"); try { int[]\n         * rowsUpdated = statement.executeBatch(); fail(); } catch (SnowflakeSQLException e) {\n         * assertEquals((int) ErrorCode.EXECUTE_BATCH_INTEGER_OVERFLOW.getMessageCode(),\n         * e.getErrorCode()); assertEquals(ErrorCode.EXECUTE_BATCH_INTEGER_OVERFLOW.getSqlState(),\n         * e.getSQLState()); } statement.clearBatch();\n         */\n      } finally {\n        statement.execute(\"drop table if exists test_large_batch\");\n      }\n    }\n  }\n\n  /**\n   * Mainly tested which types of statement CAN be executed by executeUpdate\n   *\n   * @throws SQLException if any error occurs\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testExecuteUpdateZeroCount() throws SQLException {\n    try (Connection connection = getConnection()) {\n      String[] testCommands = {\n        \"use role accountadmin\",\n        \"use database testdb\",\n        \"use schema testschema\",\n        \"create or replace table testExecuteUpdate(cola number)\",\n        \"comment on table testExecuteUpdate is 'comments'\",\n        \"alter table testExecuteUpdate rename column cola to colb\",\n        \"create or replace role testRole\",\n        \"create or replace user testuser\",\n        \"grant SELECT on table testExecuteUpdate to role testrole\",\n        \"grant role testrole to user testuser\",\n        \"revoke SELECT on table testExecuteUpdate from role testrole\",\n        \"revoke role testrole from user testuser\",\n        \"truncate table testExecuteUpdate\",\n        \"alter session set autocommit=false\",\n        \"alter session unset autocommit\",\n        \"drop table if exists testExecuteUpdate\",\n        \"set v1 = 10\",\n        \"unset v1\",\n        \"undrop table testExecuteUpdate\"\n      };\n      try {\n        for (String testCommand : testCommands) {\n          try (Statement statement = connection.createStatement()) {\n            int updateCount = statement.executeUpdate(testCommand);\n            assertThat(updateCount, is(0));\n          }\n        }\n      } finally {\n        try (Statement statement = connection.createStatement()) {\n          statement.execute(\"use role accountadmin\");\n          statement.execute(\"drop table if exists testExecuteUpdate\");\n          statement.execute(\"drop role if exists testrole\");\n          statement.execute(\"drop user if exists testuser\");\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testExecuteUpdateFail() throws Exception {\n    try (Statement statement = connection.createStatement()) {\n      String[] testCommands = {\n        \"list @~\",\n        \"ls @~\",\n        \"remove @~/testfile\",\n        \"rm @~/testfile\",\n        \"select 1\",\n        \"show databases\",\n        \"desc database \" + AbstractDriverIT.getConnectionParameters().get(\"database\")\n      };\n\n      for (String testCommand : testCommands) {\n        SQLException e =\n            assertThrows(\n                SQLException.class,\n                () -> statement.executeUpdate(testCommand),\n                \"TestCommand: \" + testCommand + \" is expected to be failed to execute\");\n        assertThat(\n            testCommand,\n            e.getErrorCode(),\n            is(ErrorCode.UNSUPPORTED_STATEMENT_TYPE_IN_EXECUTION_API.getMessageCode()));\n      }\n    }\n  }\n\n  @Test\n  public void testTelemetryBatch() throws SQLException {\n    Telemetry telemetryClient = null;\n    try (Statement statement = connection.createStatement()) {\n\n      String sqlSelect = \"select seq4() from table(generator(rowcount=>3))\";\n      statement.execute(sqlSelect);\n\n      try (ResultSet rs = statement.getResultSet()) {\n        assertEquals(3, getSizeOfResultSet(rs));\n        assertEquals(-1, statement.getUpdateCount());\n        assertEquals(-1L, statement.getLargeUpdateCount());\n      }\n\n      try (ResultSet rs = statement.executeQuery(sqlSelect)) {\n        assertEquals(3, getSizeOfResultSet(rs));\n      }\n\n      telemetryClient =\n          ((SnowflakeStatementImpl) statement).connection.getSfSession().getTelemetryClient();\n\n      // there should be logs ready to be sent\n      assertTrue(((TelemetryClient) telemetryClient).bufferSize() > 0);\n    }\n\n    Telemetry finalTelemetryClient = telemetryClient;\n    Awaitility.await()\n        .atMost(Duration.ofSeconds(10))\n        .until(() -> ((TelemetryClient) finalTelemetryClient).bufferSize(), equalTo(0));\n  }\n\n  @Test\n  public void testMultiStmtNotEnabled() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      String multiStmtQuery =\n          \"create or replace temporary table test_multi (cola int);\\n\"\n              + \"insert into test_multi VALUES (1), (2);\\n\"\n              + \"select cola from test_multi order by cola asc\";\n\n      SnowflakeSQLException ex =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> statement.execute(multiStmtQuery),\n              \"Using a multi-statement query without the parameter set should fail\");\n      assertEquals(SqlState.FEATURE_NOT_SUPPORTED, ex.getSQLState());\n    }\n  }\n\n  @Test\n  public void testCallStoredProcedure() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"create or replace procedure SP()\\n\"\n              + \"returns string not null\\n\"\n              + \"language javascript\\n\"\n              + \"as $$\\n\"\n              + \"  snowflake.execute({sqlText:'select seq4() from table(generator(rowcount=>5))'});\\n\"\n              + \"  return 'done';\\n\"\n              + \"$$\");\n\n      assertTrue(statement.execute(\"call SP()\"));\n      try (ResultSet rs = statement.getResultSet()) {\n        assertNotNull(rs);\n        assertTrue(rs.next());\n        assertEquals(\"done\", rs.getString(1));\n        assertFalse(rs.next());\n        assertFalse(statement.getMoreResults());\n        assertEquals(-1, statement.getUpdateCount());\n        assertEquals(-1L, statement.getLargeUpdateCount());\n      }\n    }\n  }\n\n  @Test\n  public void testCreateStatementWithParameters() throws Throwable {\n    try (Connection connection = getConnection()) {\n      connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);\n      SQLException ex =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  connection.createStatement(\n                      ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE));\n      assertEquals((int) ErrorCode.FEATURE_UNSUPPORTED.getMessageCode(), ex.getErrorCode());\n\n      connection.createStatement(\n          ResultSet.TYPE_FORWARD_ONLY,\n          ResultSet.CONCUR_READ_ONLY,\n          ResultSet.CLOSE_CURSORS_AT_COMMIT);\n\n      ex =\n          assertThrows(\n              SQLException.class,\n              () ->\n                  connection.createStatement(\n                      ResultSet.TYPE_FORWARD_ONLY,\n                      ResultSet.CONCUR_READ_ONLY,\n                      ResultSet.HOLD_CURSORS_OVER_COMMIT));\n      assertEquals((int) ErrorCode.FEATURE_UNSUPPORTED.getMessageCode(), ex.getErrorCode());\n    }\n  }\n\n  @Test\n  public void testUnwrapper() throws Throwable {\n    try (Statement statement = connection.createStatement()) {\n      assertTrue(statement.isWrapperFor(SnowflakeStatementImpl.class));\n      statement.execute(\"select 1\");\n      SnowflakeStatement sfstatement = statement.unwrap(SnowflakeStatement.class);\n      assertNotNull(sfstatement.getQueryID());\n\n      assertThrows(SQLException.class, () -> statement.unwrap(SnowflakeConnectionImpl.class));\n    }\n  }\n\n  @Test\n  public void testQueryIdIsNullOnFreshStatement() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      assertNull(stmt.unwrap(SnowflakeStatement.class).getQueryID());\n    }\n  }\n\n  @Test\n  public void testRepeatedPutDoesNotLeakThreads() throws Exception {\n    int iterations = Integer.getInteger(\"snowflake.test.putThreadLeak.iterations\", 10);\n    int putParallelism = 4;\n    assertTrue(iterations > 0, \"Iterations must be positive\");\n\n    File testFile = new File(tmpFolder, \"thread_leak_test_1mb.csv\");\n    createCsvFile(testFile, 1024 * 1024);\n    String stageName = \"TEMP_THREAD_LEAK_STAGE_\" + System.nanoTime();\n\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"CREATE OR REPLACE TEMPORARY STAGE \" + stageName);\n\n      // Warm up: first PUT initializes Netty event loops and other one-time resources\n      String warmupPut =\n          \"PUT file://\"\n              + testFile.getCanonicalPath()\n              + \" @\"\n              + stageName\n              + \" PARALLEL=\"\n              + putParallelism\n              + \" AUTO_COMPRESS=FALSE\";\n      try (ResultSet rs = statement.executeQuery(warmupPut)) {\n        while (rs.next()) {}\n      }\n      waitForTransferThreadCount(putParallelism);\n\n      long baselineTransferThreads = countTransferManagerThreads();\n\n      // Use a unique file name per iteration to avoid GCS per-object rate limits\n      for (int i = 0; i < iterations; i++) {\n        Path iterFile = tmpFolder.toPath().resolve(\"thread_leak_put_\" + i + \".csv\");\n        Files.createLink(iterFile, testFile.toPath());\n        String putCommand =\n            \"PUT file://\"\n                + iterFile.toFile().getCanonicalPath()\n                + \" @\"\n                + stageName\n                + \" PARALLEL=\"\n                + putParallelism\n                + \" AUTO_COMPRESS=FALSE\";\n        try (ResultSet rs = statement.executeQuery(putCommand)) {\n          while (rs.next()) {}\n        }\n      }\n\n      waitForTransferThreadCount(baselineTransferThreads);\n      long finalTransferThreads = countTransferManagerThreads();\n\n      // Without the fix, each PUT leaks `putParallelism` threads, so after\n      // N iterations we'd see N * putParallelism extra threads (e.g. 10 * 4 = 40).\n      // With the fix, all executor threads are shut down after each PUT,\n      // so the count must not grow at all.\n      long threadGrowth = finalTransferThreads - baselineTransferThreads;\n      assertTrue(\n          threadGrowth <= 0,\n          String.format(\n              \"Transfer manager threads grew by %d after %d PUTs with PARALLEL=%d \"\n                  + \"(baseline=%d, final=%d, would be %d without fix).\"\n                  + \" This indicates a thread pool leak in S3 upload.\",\n              threadGrowth,\n              iterations,\n              putParallelism,\n              baselineTransferThreads,\n              finalTransferThreads,\n              (long) iterations * putParallelism));\n    }\n  }\n\n  @Test\n  public void testRepeatedGetDoesNotLeakThreads() throws Exception {\n    int iterations = Integer.getInteger(\"snowflake.test.getThreadLeak.iterations\", 10);\n    int getParallelism = 4;\n    assertTrue(iterations > 0, \"Iterations must be positive\");\n\n    File testFile = new File(tmpFolder, \"thread_leak_test_1mb.csv\");\n    createCsvFile(testFile, 1024 * 1024);\n    String stageName = \"TEMP_THREAD_LEAK_GET_STAGE_\" + System.nanoTime();\n    File destFolder = new File(tmpFolder, \"downloads\");\n    destFolder.mkdirs();\n\n    try (Statement statement = connection.createStatement()) {\n      statement.execute(\"CREATE OR REPLACE TEMPORARY STAGE \" + stageName);\n\n      String putCommand =\n          \"PUT file://\"\n              + testFile.getCanonicalPath()\n              + \" @\"\n              + stageName\n              + \" AUTO_COMPRESS=FALSE OVERWRITE=TRUE\";\n      try (ResultSet rs = statement.executeQuery(putCommand)) {\n        while (rs.next()) {}\n      }\n\n      String getCommand =\n          \"GET @\"\n              + stageName\n              + \" 'file://\"\n              + destFolder.getCanonicalPath().replace(\"\\\\\", \"/\")\n              + \"' PARALLEL=\"\n              + getParallelism;\n\n      // Warm up: first GET initializes Netty event loops and other one-time resources\n      try (ResultSet rs = statement.executeQuery(getCommand)) {\n        while (rs.next()) {}\n      }\n      waitForTransferThreadCount(getParallelism);\n\n      long baselineTransferThreads = countTransferManagerThreads();\n\n      for (int i = 0; i < iterations; i++) {\n        for (File f : destFolder.listFiles()) {\n          f.delete();\n        }\n        try (ResultSet rs = statement.executeQuery(getCommand)) {\n          while (rs.next()) {}\n        }\n      }\n\n      waitForTransferThreadCount(baselineTransferThreads);\n      long finalTransferThreads = countTransferManagerThreads();\n\n      long threadGrowth = finalTransferThreads - baselineTransferThreads;\n      assertTrue(\n          threadGrowth <= 0,\n          String.format(\n              \"Transfer manager threads grew by %d after %d GETs with PARALLEL=%d \"\n                  + \"(baseline=%d, final=%d, would be %d without fix).\"\n                  + \" This indicates a thread pool leak in S3 download.\",\n              threadGrowth,\n              iterations,\n              getParallelism,\n              baselineTransferThreads,\n              finalTransferThreads,\n              (long) iterations * getParallelism));\n    }\n  }\n\n  private static void waitForTransferThreadCount(long atMost) {\n    Awaitility.await()\n        .atMost(Duration.ofSeconds(7))\n        .pollInterval(Duration.ofSeconds(1))\n        .until(() -> countTransferManagerThreads() <= atMost);\n  }\n\n  private static long countTransferManagerThreads() {\n    return Thread.getAllStackTraces().keySet().stream()\n        .filter(t -> t.getName().startsWith(\"s3-transfer-manager-\"))\n        .count();\n  }\n\n  private static void createCsvFile(File targetFile, long targetBytes) throws Exception {\n    byte[] line =\n        \"1,abcdefghijklmnopqrstuvwxyz,2026-01-01T00:00:00Z\\n\".getBytes(StandardCharsets.US_ASCII);\n    try (FileOutputStream out = new FileOutputStream(targetFile)) {\n      long written = 0;\n      while (written < targetBytes) {\n        int chunk = (int) Math.min(line.length, targetBytes - written);\n        out.write(line, 0, chunk);\n        written += chunk;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementLargeUpdateIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.sql.Connection;\nimport java.sql.Statement;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Large update test. No JSON/ARROW specific test case is required. */\n@Tag(TestTags.STATEMENT)\npublic class StatementLargeUpdateIT extends BaseJDBCTest {\n  @Test\n  public void testLargeUpdate() throws Throwable {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      long expectedUpdateRows = (long) Integer.MAX_VALUE + 10L;\n      try {\n        statement.execute(\"create or replace table test_large_update(c1 boolean)\");\n        long updatedRows =\n            statement.executeLargeUpdate(\n                \"insert into test_large_update select true from table(generator(rowcount=>\"\n                    + expectedUpdateRows\n                    + \"))\");\n        assertEquals(expectedUpdateRows, updatedRows);\n        assertEquals(expectedUpdateRows, statement.getLargeUpdateCount());\n      } finally {\n        statement.execute(\"drop table if exists test_large_update\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static net.snowflake.client.api.exception.ErrorCode.ROW_DOES_NOT_EXIST;\nimport static org.awaitility.Awaitility.await;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.io.File;\nimport java.net.URL;\nimport java.sql.Connection;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.annotations.DontRunOnJenkins;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.api.resultset.QueryStatus;\nimport net.snowflake.client.api.resultset.SnowflakeAsyncResultSet;\nimport net.snowflake.client.api.statement.SnowflakeStatement;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakePreparedStatementImpl;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport net.snowflake.client.internal.core.ParameterBindingDTO;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.bind.BindUploader;\nimport net.snowflake.common.core.SqlState;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n/**\n * Statement integration tests for the latest JDBC driver. This doesn't work for the oldest\n * supported driver. Revisit this tests whenever bumping up the oldest supported driver to examine\n * if the tests still is not applicable. If it is applicable, move tests to StatementIT so that both\n * the latest and oldest supported driver run the tests.\n */\n@Tag(TestTags.STATEMENT)\npublic class StatementLatestIT extends BaseJDBCWithSharedConnectionIT {\n  protected static String queryResultFormat = \"json\";\n\n  public static Connection getConnection() throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection();\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set jdbc_query_result_format = '\" + queryResultFormat + \"'\");\n    }\n    return conn;\n  }\n\n  @TempDir private File tmpFolder;\n\n  @Test\n  public void testExecuteCreateAndDrop() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n\n      boolean success = statement.execute(\"create or replace table test_create(colA integer)\");\n      assertFalse(success);\n      assertEquals(0, statement.getUpdateCount());\n      assertEquals(0, statement.getLargeUpdateCount());\n      assertNull(statement.getResultSet());\n\n      int rowCount = statement.executeUpdate(\"create or replace table test_create_2(colA integer)\");\n      assertEquals(0, rowCount);\n      assertEquals(0, statement.getUpdateCount());\n\n      success = statement.execute(\"drop table if exists TEST_CREATE\");\n      assertFalse(success);\n      assertEquals(0, statement.getUpdateCount());\n      assertEquals(0, statement.getLargeUpdateCount());\n      assertNull(statement.getResultSet());\n\n      rowCount = statement.executeUpdate(\"drop table if exists TEST_CREATE_2\");\n      assertEquals(0, rowCount);\n      assertEquals(0, statement.getUpdateCount());\n      assertEquals(0, statement.getLargeUpdateCount());\n      assertNull(statement.getResultSet());\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testCopyAndUpload() throws Exception {\n    File tempFolder = new File(tmpFolder, \"test_downloads_folder\");\n    tempFolder.mkdirs();\n    List<String> accounts = Arrays.asList(null, \"s3testaccount\", \"azureaccount\", \"gcpaccount\");\n    for (int i = 0; i < accounts.size(); i++) {\n      String fileName = \"test_copy.csv\";\n      URL resource = StatementIT.class.getResource(fileName);\n\n      try (Connection connection = getConnection(accounts.get(i));\n          Statement statement = connection.createStatement()) {\n        try {\n          statement.execute(\"create or replace table test_copy(c1 number, c2 number, c3 string)\");\n          assertEquals(0, statement.getUpdateCount());\n          assertEquals(0, statement.getLargeUpdateCount());\n\n          String path = resource.getFile();\n\n          // put files\n          try (ResultSet rset = statement.executeQuery(\"PUT file://\" + path + \" @%test_copy\")) {\n            SQLException ex = assertThrows(SQLException.class, () -> rset.getString(1));\n            assertThat(\n                \"No row found error\",\n                ex.getErrorCode(),\n                equalTo(ROW_DOES_NOT_EXIST.getMessageCode()));\n\n            int cnt = 0;\n            while (rset.next()) {\n              assertThat(\"uploaded file name\", rset.getString(1), equalTo(fileName));\n              ++cnt;\n            }\n            assertEquals(0, statement.getUpdateCount());\n            assertEquals(0, statement.getLargeUpdateCount());\n            assertThat(\"number of files\", cnt, equalTo(1));\n            int numRows = statement.executeUpdate(\"copy into test_copy\");\n            assertEquals(2, numRows);\n            assertEquals(2, statement.getUpdateCount());\n            assertEquals(2L, statement.getLargeUpdateCount());\n\n            // get files\n            statement.executeQuery(\n                \"get @%test_copy 'file://\" + tempFolder.getCanonicalPath() + \"' parallel=8\");\n\n            // Make sure that the downloaded file exists, it should be gzip compressed\n            File downloaded =\n                new File(tempFolder.getCanonicalPath() + File.separator + fileName + \".gz\");\n            assertTrue(downloaded.exists());\n          }\n          // unzip the new file\n          Process p =\n              Runtime.getRuntime()\n                  .exec(\n                      \"gzip -d \"\n                          + tempFolder.getCanonicalPath()\n                          + File.separator\n                          + fileName\n                          + \".gz\");\n          p.waitFor();\n          File newCopy = new File(tempFolder.getCanonicalPath() + File.separator + fileName);\n          // check that the get worked by uploading new file again to a different table and\n          // comparing it\n          // to original table\n          statement.execute(\"create or replace table test_copy_2(c1 number, c2 number, c3 string)\");\n\n          // put copy of file\n          statement.executeQuery(\"PUT file://\" + newCopy.getPath() + \" @%test_copy_2\");\n          // assert that the result set is empty when you subtract each table from the other\n          try (ResultSet rset =\n              statement.executeQuery(\n                  \"select * from @%test_copy minus select * from @%test_copy_2\")) {\n            assertFalse(rset.next());\n          }\n          try (ResultSet rset =\n              statement.executeQuery(\n                  \"select * from @%test_copy_2 minus select * from @%test_copy\")) {\n            assertFalse(rset.next());\n          }\n        } finally {\n          statement.execute(\"drop table if exists test_copy\");\n          statement.execute(\"drop table if exists test_copy_2\");\n        }\n      }\n    }\n  }\n\n  /**\n   * Tests that resultsets that have been closed are not added to the set of openResultSets.\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testExecuteOpenResultSets() throws SQLException {\n    try (Statement statement = connection.createStatement()) {\n      for (int i = 0; i < 10; i++) {\n        statement.execute(\"select 1\");\n        statement.getResultSet();\n      }\n\n      assertEquals(9, statement.unwrap(SnowflakeStatementImpl.class).getOpenResultSets().size());\n    }\n\n    try (Statement statement = connection.createStatement()) {\n      for (int i = 0; i < 10; i++) {\n        statement.execute(\"select 1\");\n        ResultSet resultSet = statement.getResultSet();\n        resultSet.close();\n      }\n\n      assertEquals(0, statement.unwrap(SnowflakeStatementImpl.class).getOpenResultSets().size());\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPreparedStatementLogging() throws SQLException {\n    try (Connection con = getConnection();\n        Statement stmt = con.createStatement()) {\n      try {\n        SFSession sfSession = con.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n        sfSession.setPreparedStatementLogging(true);\n\n        stmt.execute(\"ALTER SESSION SET CLIENT_STAGE_ARRAY_BINDING_THRESHOLD = 1\");\n        stmt.executeQuery(\n            \"create or replace table mytab(cola int, colb int, colc int, cold int, cole int\"\n                + \", colf int, colg int, colh int)\");\n        PreparedStatement pstatement =\n            con.prepareStatement(\n                \"INSERT INTO mytab(cola, colb, colc, cold, cole, colf, colg, colh) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\");\n\n        for (int i = 1; i <= 1001; i++) {\n          pstatement.setInt(1, i);\n          pstatement.setInt(2, i);\n          pstatement.setInt(3, i);\n          pstatement.setInt(4, i);\n          pstatement.setInt(5, i);\n          pstatement.setInt(6, i);\n          pstatement.setInt(7, i);\n          pstatement.setInt(8, i);\n          pstatement.addBatch();\n        }\n\n        Map<String, ParameterBindingDTO> bindings =\n            pstatement.unwrap(SnowflakePreparedStatementImpl.class).getBatchParameterBindings();\n        assertTrue(bindings.size() > 0);\n        int bindValues = BindUploader.arrayBindValueCount(bindings);\n        assertEquals(8008, bindValues);\n        pstatement.executeBatch();\n      } finally {\n        stmt.execute(\"drop table if exists mytab\");\n      }\n    }\n  }\n\n  @Test // SNOW-647217\n  public void testSchemaWith255CharactersDoesNotCauseException() throws SQLException {\n    String schemaName =\n        TestUtil.GENERATED_SCHEMA_PREFIX\n            + SnowflakeUtil.randomAlphaNumeric(255 - TestUtil.GENERATED_SCHEMA_PREFIX.length());\n    try (Statement stmt = connection.createStatement()) {\n      stmt.execute(\"create schema \" + schemaName);\n      stmt.execute(\"use schema \" + schemaName);\n      stmt.execute(\"drop schema \" + schemaName);\n    }\n  }\n\n  /** Added in > 3.14.4 */\n  @Test\n  public void testQueryIdIsSetOnFailedQueryExecute() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      assertNull(stmt.unwrap(SnowflakeStatement.class).getQueryID());\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> stmt.execute(\"use database not_existing_database\"));\n      String queryID = stmt.unwrap(SnowflakeStatement.class).getQueryID();\n      TestUtil.assertValidQueryId(queryID);\n      assertEquals(queryID, e.getQueryId());\n    }\n  }\n\n  /** Added in > 3.14.4 */\n  @Test\n  public void testQueryIdIsSetOnFailedExecuteUpdate() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      assertNull(stmt.unwrap(SnowflakeStatement.class).getQueryID());\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> stmt.executeUpdate(\"update not_existing_table set a = 1 where id = 42\"));\n      String queryID = stmt.unwrap(SnowflakeStatement.class).getQueryID();\n      TestUtil.assertValidQueryId(queryID);\n      assertEquals(queryID, e.getQueryId());\n    }\n  }\n\n  /** Added in > 3.14.4 */\n  @Test\n  public void testQueryIdIsSetOnFailedExecuteQuery() throws SQLException {\n    try (Statement stmt = connection.createStatement()) {\n      assertNull(stmt.unwrap(SnowflakeStatement.class).getQueryID());\n      SnowflakeSQLException e =\n          assertThrows(\n              SnowflakeSQLException.class,\n              () -> stmt.executeQuery(\"select * from not_existing_table\"));\n      String queryID = stmt.unwrap(SnowflakeStatement.class).getQueryID();\n      TestUtil.assertValidQueryId(queryID);\n      assertEquals(queryID, e.getQueryId());\n    }\n  }\n\n  /**\n   * Test for setting query timeout on async queries. Applicable to versions after 3.21.0.\n   *\n   * @throws SQLException if there is an error when executing\n   */\n  @Test\n  public void testSetQueryTimeoutForAsyncQueryUsingConnectionProperty() throws SQLException {\n    Properties p = new Properties();\n    p.put(\"IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT\", true);\n    try (Connection con = getConnection(p);\n        Statement statement = con.createStatement()) {\n      statement.setQueryTimeout(1);\n\n      String sql = \"select system$wait(100);\";\n\n      try (ResultSet resultSet =\n          statement.unwrap(SnowflakeStatement.class).executeAsyncQuery(sql)) {\n        SnowflakeAsyncResultSet asyncResultSet = resultSet.unwrap(SnowflakeAsyncResultSet.class);\n        await()\n            .atMost(Duration.ofSeconds(10))\n            .untilAsserted(\n                () -> {\n                  QueryStatus.Status actualStatus = asyncResultSet.getStatus().getStatus();\n                  assertThat(\n                      \"Expected query to fail but status was: \" + actualStatus,\n                      actualStatus,\n                      equalTo(QueryStatus.Status.FAILED_WITH_ERROR));\n                });\n\n        assertTrue(\n            asyncResultSet\n                .getStatus()\n                .getErrorMessage()\n                .contains(\n                    \"Statement reached its statement or warehouse timeout of 1 second(s) and was canceled\"));\n      }\n    }\n  }\n\n  /**\n   * Test for setting query timeout on regular queries with the IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT\n   * property set to true should rely on server only. Applicable to versions after 3.21.0. In\n   * version above 3.22.0 the error should be handled only on the server side.\n   *\n   * @throws SQLException if there is an error when executing\n   */\n  @Test\n  @DontRunOnJenkins // uses too many resources on Jenkins making the test flaky\n  public void testSetQueryTimeoutOnStatementWhenImplicitQueryTimeoutIsSet()\n      throws SQLException, InterruptedException, ExecutionException {\n    int threads = 20;\n    ExecutorService executor = Executors.newFixedThreadPool(threads);\n    List<Future<?>> futures = new ArrayList<>();\n    Properties p = new Properties();\n    p.put(\"IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT\", true);\n    try (Connection con = getConnection(p)) {\n\n      String sql = \"select system$wait(100);\";\n\n      for (int i = 0; i < threads; ++i) {\n        futures.add(\n            executor.submit(\n                () -> {\n                  try (Statement statement = con.createStatement()) {\n                    statement.setQueryTimeout(3);\n                    SQLException e =\n                        assertThrows(SQLException.class, () -> statement.executeQuery(sql));\n                    assertEquals(SqlState.QUERY_CANCELED, e.getSQLState());\n                  } catch (SQLException e) {\n                    fail(e.getMessage());\n                  }\n                }));\n      }\n      executor.shutdown();\n      assertTrue(executor.awaitTermination(60, TimeUnit.SECONDS));\n      for (Future<?> future : futures) {\n        assertNull(future.get());\n      }\n    }\n  }\n\n  /**\n   * Test for setting connection level query timeout on regular queries with the\n   * IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT property set to true should rely on server only. Applicable\n   * to versions after 3.22.0.\n   *\n   * @throws SQLException if there is an error when executing\n   */\n  @Test\n  @DontRunOnJenkins // uses too many resources on Jenkins making the test flaky\n  public void testSetQueryTimeoutOnConnectionWhenImplicitQueryTimeoutIsSet()\n      throws SQLException, InterruptedException, ExecutionException {\n    int threads = 20;\n    ExecutorService executor = Executors.newFixedThreadPool(threads);\n    List<Future<?>> futures = new ArrayList<>();\n    Properties p = new Properties();\n    p.put(\"IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT\", true);\n    p.put(\"queryTimeout\", 3);\n    try (Connection con = getConnection(p)) {\n\n      String sql = \"select system$wait(100);\";\n\n      for (int i = 0; i < threads; ++i) {\n        futures.add(\n            executor.submit(\n                () -> {\n                  try (Statement statement = con.createStatement()) {\n                    SQLException e =\n                        assertThrows(SQLException.class, () -> statement.executeQuery(sql));\n                    assertEquals(SqlState.QUERY_CANCELED, e.getSQLState());\n                  } catch (SQLException e) {\n                    fail(e.getMessage());\n                  }\n                }));\n      }\n      executor.shutdown();\n      assertTrue(executor.awaitTermination(60, TimeUnit.SECONDS));\n      for (Future<?> future : futures) {\n        assertNull(future.get());\n      }\n    }\n  }\n\n  /**\n   * Test for setting query timeout on async queries. Applicable to versions after 3.21.0.\n   *\n   * @throws SQLException if there is an error when executing\n   */\n  @Test\n  public void testSetQueryTimeoutForAsyncQuery() throws SQLException {\n    try (Connection con = getConnection();\n        Statement statement = con.createStatement()) {\n      SnowflakeStatement sfStmt = statement.unwrap(SnowflakeStatement.class);\n      sfStmt.setAsyncQueryTimeout(1);\n\n      String sql = \"select system$wait(100);\";\n\n      try (ResultSet resultSet = sfStmt.executeAsyncQuery(sql)) {\n        SnowflakeAsyncResultSet asyncResultSet = resultSet.unwrap(SnowflakeAsyncResultSet.class);\n        await()\n            .atMost(Duration.ofSeconds(10))\n            .untilAsserted(\n                () -> {\n                  QueryStatus.Status actualStatus = asyncResultSet.getStatus().getStatus();\n                  assertThat(\n                      \"Expected query to fail but status was: \" + actualStatus,\n                      actualStatus,\n                      equalTo(QueryStatus.Status.FAILED_WITH_ERROR));\n                });\n\n        assertThat(\n            asyncResultSet.getStatus().getErrorMessage(),\n            containsString(\n                \"Statement reached its statement or warehouse timeout of 1 second(s) and was canceled\"));\n      }\n    }\n  }\n\n  /**\n   * Applicable to versions after 3.22.0.\n   *\n   * @throws SQLException if there is an error when executing\n   */\n  @Test\n  public void testSetAsyncQueryTimeoutOverridesConnectionQueryTimeoutForAsyncQuery()\n      throws SQLException {\n    Properties p = new Properties();\n    p.put(\"IMPLICIT_SERVER_SIDE_QUERY_TIMEOUT\", true);\n    p.put(\"queryTimeout\", 1);\n    try (Connection con = getConnection(p);\n        Statement statement = con.createStatement()) {\n      SnowflakeStatement sfStmt = statement.unwrap(SnowflakeStatement.class);\n      sfStmt.setAsyncQueryTimeout(1);\n\n      String sql = \"select system$wait(100);\";\n\n      try (ResultSet resultSet = sfStmt.executeAsyncQuery(sql)) {\n        SnowflakeAsyncResultSet asyncResultSet = resultSet.unwrap(SnowflakeAsyncResultSet.class);\n        await()\n            .atMost(Duration.ofSeconds(10))\n            .untilAsserted(\n                () -> {\n                  QueryStatus.Status actualStatus = asyncResultSet.getStatus().getStatus();\n                  assertThat(\n                      \"Expected query to fail but status was: \" + actualStatus,\n                      actualStatus,\n                      equalTo(QueryStatus.Status.FAILED_WITH_ERROR));\n                });\n\n        assertThat(\n            asyncResultSet.getStatus().getErrorMessage(),\n            containsString(\n                \"Statement reached its statement or warehouse timeout of 1 second(s) and was canceled\"));\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testCreatingPatReturnsResultSetForExecute() throws SQLException {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n    String testToken = \"TOKEN_\" + SnowflakeUtil.randomAlphaNumeric(5);\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      boolean execute =\n          statement.execute(\n              \"ALTER USER \"\n                  + testUser\n                  + \" ADD PROGRAMMATIC ACCESS TOKEN \"\n                  + testToken\n                  + \" ROLE_RESTRICTION = 'PUBLIC'\");\n      assertTrue(execute);\n      try (ResultSet rs = statement.getResultSet()) {\n        assertTrue(rs.next());\n        assertEquals(testToken, rs.getString(1));\n        assertNotNull(rs.getString(2));\n        assertFalse(rs.getString(2).isEmpty());\n      } finally {\n        statement.execute(\n            \"ALTER USER \" + testUser + \" REMOVE PROGRAMMATIC ACCESS TOKEN \" + testToken);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StatementNoOpLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.statement.SnowflakeStatementImpl;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.STATEMENT)\npublic class StatementNoOpLatestIT {\n  @Test\n  public void testSnowflakeNoOpStatement() throws SQLException {\n    SnowflakeStatementImpl.NoOpSnowflakeStatementImpl statement =\n        new SnowflakeStatementImpl.NoOpSnowflakeStatementImpl();\n    expectSQLException((() -> statement.executeQuery(\"select 1\")));\n    expectSQLException((() -> statement.executeUpdate(\"insert into a values(1)\")));\n    expectSQLException((() -> statement.executeLargeUpdate(\"insert into a values(1)\")));\n    expectSQLException((() -> statement.execute(\"select 1\")));\n    expectSQLException((() -> statement.execute(\"select 1\", Statement.RETURN_GENERATED_KEYS)));\n    expectSQLException((() -> statement.execute(\"select 1\", new int[] {})));\n    expectSQLException((() -> statement.execute(\"select 1\", new String[] {})));\n    expectSQLException((statement::executeBatch));\n    expectSQLException((statement::executeLargeBatch));\n    expectSQLException(\n        (() ->\n            statement.executeUpdate(\"insert into a values(1)\", Statement.RETURN_GENERATED_KEYS)));\n    expectSQLException(\n        (() ->\n            statement.executeLargeUpdate(\n                \"insert into a values(1)\", Statement.RETURN_GENERATED_KEYS)));\n    expectSQLException((() -> statement.executeUpdate(\"insert into a values(1)\", new int[] {})));\n    expectSQLException(\n        (() -> statement.executeLargeUpdate(\"insert into a values(1)\", new int[] {})));\n    expectSQLException((() -> statement.executeUpdate(\"insert into a values(1)\", new String[] {})));\n    expectSQLException(\n        (() -> statement.executeLargeUpdate(\"insert into a values(1)\", new String[] {})));\n    expectSQLException((statement::getFetchDirection));\n    expectSQLException((statement::getFetchSize));\n    expectSQLException((statement::getGeneratedKeys));\n    expectSQLException((statement::getMaxFieldSize));\n    expectSQLException((statement::getMaxRows));\n    expectSQLException((statement::getMoreResults));\n    expectSQLException((() -> statement.getMoreResults(1)));\n    expectSQLException((statement::getQueryTimeout));\n    expectSQLException((statement::getResultSet));\n    expectSQLException((statement::getResultSetConcurrency));\n    expectSQLException((statement::getResultSetHoldability));\n    expectSQLException((statement::getResultSetType));\n    expectSQLException((statement::getUpdateCount));\n    expectSQLException((statement::getLargeUpdateCount));\n    expectSQLException((statement::getWarnings));\n    expectSQLException((statement::isClosed));\n    expectSQLException((statement::isPoolable));\n    expectSQLException((statement::isCloseOnCompletion));\n    expectSQLException((statement::getUpdateCount));\n    expectSQLException((statement::getConnection));\n  }\n\n  @Test\n  public void testGetQueryID() throws SQLException {\n    SnowflakeStatementImpl.NoOpSnowflakeStatementImpl statement =\n        new SnowflakeStatementImpl.NoOpSnowflakeStatementImpl();\n    assertEquals(\"invalid_query_id\", statement.getQueryID());\n    assertEquals(new ArrayList<>(), statement.getBatchQueryIDs());\n  }\n\n  @Test\n  public void testSetNoOp() throws SQLException {\n    SnowflakeStatementImpl.NoOpSnowflakeStatementImpl statement =\n        new SnowflakeStatementImpl.NoOpSnowflakeStatementImpl();\n    expectNoOp(() -> statement.setMaxRows(2));\n    expectNoOp(() -> statement.setCursorName(\"a\"));\n    expectNoOp(() -> statement.setEscapeProcessing(false));\n    expectNoOp(() -> statement.setFetchDirection(2));\n    expectNoOp(() -> statement.setFetchSize(2));\n    expectNoOp(() -> statement.setMaxFieldSize(2));\n    expectNoOp(() -> statement.setPoolable(false));\n    expectNoOp(() -> statement.setParameter(\"ab\", 3));\n    expectNoOp(() -> statement.setQueryTimeout(2));\n    expectNoOp(() -> statement.setMaxRows(2));\n    expectNoOp(() -> statement.setMaxRows(2));\n    expectNoOp(() -> statement.addBatch(\"select 1\"));\n    expectNoOp(() -> statement.close(false));\n    expectNoOp(statement::close);\n    expectNoOp(statement::closeOnCompletion);\n    expectNoOp(statement::cancel);\n    expectNoOp(statement::clearWarnings);\n    expectNoOp(statement::clearBatch);\n  }\n\n  protected void expectSQLException(BaseJDBCTest.MethodRaisesSQLException f) {\n    assertThrows(SQLException.class, f::run);\n  }\n\n  protected void expectNoOp(NoOpMethod f) throws SQLException {\n    f.run();\n  }\n\n  protected interface NoOpMethod {\n    void run() throws SQLException;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StreamIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.InputStream;\nimport java.io.StringWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.connection.UploadStreamConfig;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Stream interface tests. Snowflake JDBC specific API */\n@Tag(TestTags.OTHERS)\npublic class StreamIT extends BaseJDBCTest {\n  /**\n   * Test Upload Stream\n   *\n   * @throws Throwable if any error occurs.\n   */\n  @Test\n  public void testUploadStream() throws Throwable {\n    final String DEST_PREFIX = TEST_UUID + \"/testUploadStream\";\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        FileBackedOutputStream outputStream = new FileBackedOutputStream(1000000);\n        outputStream.write(\"hello\".getBytes(StandardCharsets.UTF_8));\n        outputStream.flush();\n\n        // upload the data to user stage under testUploadStream with name hello.txt\n        connection\n            .unwrap(SnowflakeConnection.class)\n            .uploadStream(\n                \"~\",\n                \"hello.txt\",\n                outputStream.asByteSource().openStream(),\n                UploadStreamConfig.builder()\n                    .setDestPrefix(DEST_PREFIX)\n                    .setCompressData(false)\n                    .build());\n\n        // select from the file to make sure the data is uploaded\n        try (ResultSet rset = statement.executeQuery(\"SELECT $1 FROM @~/\" + DEST_PREFIX)) {\n          String ret = null;\n\n          while (rset.next()) {\n            ret = rset.getString(1);\n          }\n          assertEquals(\"hello\", ret, \"Unexpected string value: \" + ret + \" expect: hello\");\n        }\n      } finally {\n        statement.execute(\"rm @~/\" + DEST_PREFIX);\n      }\n    }\n  }\n\n  /**\n   * Test Download Stream\n   *\n   * <p>NOTE: this doesn't work on testaccount using the local FS.\n   *\n   * @throws Throwable if any error occurs.\n   */\n  @Test\n  @DontRunOnGithubActions\n  public void testDownloadStream() throws Throwable {\n    final String DEST_PREFIX = TEST_UUID + \"/testUploadStream\";\n    List<String> supportedAccounts = Arrays.asList(\"s3testaccount\", \"azureaccount\");\n    for (String accountName : supportedAccounts) {\n      try (Connection connection = getConnection(accountName);\n          Statement statement = connection.createStatement()) {\n        try {\n          try (ResultSet rset =\n              statement.executeQuery(\n                  \"PUT file://\"\n                      + getFullPathFileInResource(TEST_DATA_FILE)\n                      + \" @~/\"\n                      + DEST_PREFIX)) {\n            assertTrue(rset.next());\n            assertEquals(\"UPLOADED\", rset.getString(7));\n\n            InputStream out =\n                connection\n                    .unwrap(SnowflakeConnection.class)\n                    .downloadStream(\n                        \"~\",\n                        DEST_PREFIX + \"/\" + TEST_DATA_FILE + \".gz\",\n                        DownloadStreamConfig.builder().setDecompress(true).build());\n            StringWriter writer = new StringWriter();\n            IOUtils.copy(out, writer, \"UTF-8\");\n            String output = writer.toString();\n            // the first 2 characters\n            assertEquals(\"1|\", output.substring(0, 2));\n\n            // the number of lines\n            String[] lines = output.split(\"\\n\");\n            assertEquals(28, lines.length);\n          }\n        } finally {\n          statement.execute(\"rm @~/\" + DEST_PREFIX);\n        }\n      }\n    }\n  }\n\n  @Test\n  public void testCompressAndUploadStream() throws Throwable {\n    final String DEST_PREFIX = TEST_UUID + \"/\" + \"testCompressAndUploadStream\";\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        FileBackedOutputStream outputStream = new FileBackedOutputStream(1000000);\n        outputStream.write(\"hello\".getBytes(StandardCharsets.UTF_8));\n        outputStream.flush();\n\n        // upload the data to user stage under testCompressAndUploadStream\n        // with name hello.txt\n        // upload the data to user stage under testUploadStream with name hello.txt\n        connection\n            .unwrap(SnowflakeConnectionImpl.class)\n            .uploadStream(\n                \"~\",\n                \"hello.txt\",\n                outputStream.asByteSource().openStream(),\n                UploadStreamConfig.builder()\n                    .setDestPrefix(DEST_PREFIX)\n                    .setCompressData(true)\n                    .build());\n\n        // select from the file to make sure the data is uploaded\n        try (ResultSet rset = statement.executeQuery(\"SELECT $1 FROM @~/\" + DEST_PREFIX)) {\n\n          String ret = null;\n          while (rset.next()) {\n            ret = rset.getString(1);\n          }\n          assertEquals(\"hello\", ret, \"Unexpected string value: \" + ret + \" expect: hello\");\n        }\n\n      } finally {\n        statement.execute(\"rm @~/\" + DEST_PREFIX);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/StreamLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.StringWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.api.connection.UploadStreamConfig;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n/**\n * Stream API tests for the latest JDBC driver. This doesn't work for the oldest supported driver.\n * Revisit this tests whenever bumping up the oldest supported driver to examine if the tests still\n * is not applicable. If it is applicable, move tests to StreamIT so that both the latest and oldest\n * supported driver run the tests.\n */\n@Tag(TestTags.OTHERS)\npublic class StreamLatestIT extends BaseJDBCTest {\n\n  @TempDir private File tmpFolder;\n\n  /**\n   * Test Upload Stream with atypical stage names\n   *\n   * @throws Throwable if any error occurs.\n   */\n  @Test\n  public void testUnusualStageName() throws Throwable {\n    String ret = null;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n\n      try {\n        statement.execute(\"CREATE or replace TABLE \\\"ice cream (nice)\\\" (types STRING)\");\n\n        FileBackedOutputStream outputStream = new FileBackedOutputStream(1000000);\n        outputStream.write(\"hello\".getBytes(StandardCharsets.UTF_8));\n        outputStream.flush();\n\n        // upload the data to user stage under testUploadStream with name hello.txt\n        connection\n            .unwrap(SnowflakeConnection.class)\n            .uploadStream(\n                \"'@%\\\"ice cream (nice)\\\"'\",\n                \"hello.txt\",\n                outputStream.asByteSource().openStream(),\n                UploadStreamConfig.builder().setCompressData(false).build());\n\n        // select from the file to make sure the data is uploaded\n        try (ResultSet rset = statement.executeQuery(\"SELECT $1 FROM '@%\\\"ice cream (nice)\\\"/'\")) {\n          ret = null;\n\n          while (rset.next()) {\n            ret = rset.getString(1);\n          }\n          assertEquals(\"hello\", ret, \"Unexpected string value: \" + ret + \" expect: hello\");\n        }\n        statement.execute(\"CREATE or replace TABLE \\\"ice cream (nice)\\\" (types STRING)\");\n\n        // upload the data to user stage under testUploadStream with name hello.txt\n        connection\n            .unwrap(SnowflakeConnection.class)\n            .uploadStream(\n                \"$$@%\\\"ice cream (nice)\\\"$$\",\n                \"hello.txt\",\n                outputStream.asByteSource().openStream(),\n                UploadStreamConfig.builder().setCompressData(false).build());\n\n        // select from the file to make sure the data is uploaded\n        try (ResultSet rset =\n            statement.executeQuery(\"SELECT $1 FROM $$@%\\\"ice cream (nice)\\\"/$$\")) {\n\n          ret = null;\n\n          while (rset.next()) {\n            ret = rset.getString(1);\n          }\n          assertEquals(\"hello\", ret, \"Unexpected string value: \" + ret + \" expect: hello\");\n        }\n      } finally {\n        statement.execute(\"DROP TABLE IF EXISTS \\\"ice cream (nice)\\\"\");\n      }\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDownloadToStreamBlobNotFoundGCS() throws SQLException {\n    final String DEST_PREFIX = TEST_UUID + \"/testUploadStream\";\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement()) {\n\n      try {\n        SQLException ex =\n            assertThrows(\n                SQLException.class,\n                () ->\n                    connection\n                        .unwrap(SnowflakeConnection.class)\n                        .downloadStream(\n                            \"~\",\n                            DEST_PREFIX + \"/abc.gz\",\n                            DownloadStreamConfig.builder().setDecompress(true).build()));\n        assertTrue(\n            ex.getMessage().contains(\"File not found\"),\n            \"Wrong exception message: \" + ex.getMessage());\n      } finally {\n        statement.execute(\"rm @~/\" + DEST_PREFIX);\n      }\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testDownloadToStreamGCSPresignedUrl() throws SQLException, IOException {\n    final String DEST_PREFIX = \"testUploadStream\";\n\n    try (Connection connection = getConnection(\"gcpaccount\");\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"create or replace stage testgcpstage\");\n      try (ResultSet rset =\n          statement.executeQuery(\n              \"PUT file://\"\n                  + getFullPathFileInResource(TEST_DATA_FILE)\n                  + \" @testgcpstage/\"\n                  + DEST_PREFIX)) {\n        assertTrue(rset.next());\n        assertEquals(\"UPLOADED\", rset.getString(7), \"Error message:\" + rset.getString(8));\n\n        InputStream out =\n            connection\n                .unwrap(SnowflakeConnection.class)\n                .downloadStream(\n                    \"@testgcpstage\",\n                    DEST_PREFIX + \"/\" + TEST_DATA_FILE + \".gz\",\n                    DownloadStreamConfig.builder().setDecompress(true).build());\n        StringWriter writer = new StringWriter();\n        IOUtils.copy(out, writer, \"UTF-8\");\n        String output = writer.toString();\n        // the first 2 characters\n        assertEquals(\"1|\", output.substring(0, 2));\n\n        // the number of lines\n        String[] lines = output.split(\"\\n\");\n        assertEquals(28, lines.length);\n      }\n      statement.execute(\"rm @~/\" + DEST_PREFIX);\n    }\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDownloadToStreamGCS() throws SQLException, IOException {\n    final String DEST_PREFIX = TEST_UUID + \"/testUploadStream\";\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"GCS_USE_DOWNSCOPED_CREDENTIAL\", true);\n\n    try (Connection connection = getConnection(\"gcpaccount\", paramProperties);\n        Statement statement = connection.createStatement();\n        ResultSet rset =\n            statement.executeQuery(\n                \"PUT file://\" + getFullPathFileInResource(TEST_DATA_FILE) + \" @~/\" + DEST_PREFIX)) {\n      try {\n        assertTrue(rset.next());\n        assertEquals(\"UPLOADED\", rset.getString(7));\n\n        InputStream out =\n            connection\n                .unwrap(SnowflakeConnection.class)\n                .downloadStream(\n                    \"~\",\n                    DEST_PREFIX + \"/\" + TEST_DATA_FILE + \".gz\",\n                    DownloadStreamConfig.builder().setDecompress(true).build());\n        StringWriter writer = new StringWriter();\n        IOUtils.copy(out, writer, \"UTF-8\");\n        String output = writer.toString();\n        // the first 2 characters\n        assertEquals(\"1|\", output.substring(0, 2));\n\n        // the number of lines\n        String[] lines = output.split(\"\\n\");\n        assertEquals(28, lines.length);\n      } finally {\n        statement.execute(\"rm @~/\" + DEST_PREFIX);\n      }\n    }\n  }\n\n  @Test\n  public void testSpecialCharactersInFileName() throws SQLException, IOException {\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        // Create a temporary file with special characters in the name and write to it\n        File specialCharFile = new File(tmpFolder, \"(special char@).txt\");\n        specialCharFile.createNewFile();\n        try (BufferedWriter bw = new BufferedWriter(new FileWriter(specialCharFile))) {\n          bw.write(\"Creating test file for downloadStream test\");\n        }\n\n        String sourceFilePath = specialCharFile.getCanonicalPath();\n        String sourcePathEscaped;\n        if (System.getProperty(\"file.separator\").equals(\"\\\\\")) {\n          // windows separator needs to be escaped because of quotes\n          sourcePathEscaped = sourceFilePath.replace(\"\\\\\", \"\\\\\\\\\");\n        } else {\n          sourcePathEscaped = sourceFilePath;\n        }\n\n        // create a stage to put the file in\n        statement.execute(\"CREATE OR REPLACE STAGE downloadStream_stage\");\n        statement.execute(\n            \"PUT 'file://\" + sourcePathEscaped + \"' @~/downloadStream_stage auto_compress=false\");\n\n        // download file stream\n        try (InputStream out =\n            connection\n                .unwrap(SnowflakeConnection.class)\n                .downloadStream(\n                    \"~\",\n                    \"/downloadStream_stage/\" + specialCharFile.getName(),\n                    DownloadStreamConfig.builder().setDecompress(false).build())) {\n\n          // Read file stream and check the result\n          StringWriter writer = new StringWriter();\n          IOUtils.copy(out, writer, \"UTF-8\");\n          String output = writer.toString();\n          assertEquals(\"Creating test file for downloadStream test\", output);\n        }\n      } finally {\n        statement.execute(\"DROP STAGE IF EXISTS downloadStream_stage\");\n      }\n    }\n  }\n\n  /** Added > 3.21.0. Fixed regression introduced in 3.19.1 */\n  @Test\n  public void shouldDownloadStreamInDeterministicWay() throws Exception {\n    try (Connection conn = getConnection();\n        Statement stat = conn.createStatement()) {\n      String randomStage = \"test\" + UUID.randomUUID().toString().replaceAll(\"-\", \"\");\n      try {\n        stat.execute(\"CREATE OR REPLACE STAGE \" + randomStage);\n        String randomDir = UUID.randomUUID().toString();\n        String sourceFilePathWithoutExtension = getFullPathFileInResource(\"test_file\");\n        String sourceFilePathWithExtension = getFullPathFileInResource(\"test_file.csv\");\n        String stageDest = String.format(\"@%s/%s\", randomStage, randomDir);\n        putFile(stat, sourceFilePathWithExtension, stageDest, false);\n        putFile(stat, sourceFilePathWithoutExtension, stageDest, false);\n        putFile(stat, sourceFilePathWithExtension, stageDest, true);\n        putFile(stat, sourceFilePathWithoutExtension, stageDest, true);\n        expectsFilesOnStage(stat, stageDest, 4);\n        String stageName = \"@\" + randomStage;\n        downloadStreamExpectingContent(\n            conn, stageName, randomDir + \"/test_file.gz\", true, \"I am a file without extension\");\n        downloadStreamExpectingContent(\n            conn, stageName, randomDir + \"/test_file.csv.gz\", true, \"I am a file with extension\");\n        downloadStreamExpectingContent(\n            conn, stageName, randomDir + \"/test_file\", false, \"I am a file without extension\");\n        downloadStreamExpectingContent(\n            conn, stageName, randomDir + \"/test_file.csv\", false, \"I am a file with extension\");\n      } finally {\n        stat.execute(\"DROP STAGE IF EXISTS \" + randomStage);\n      }\n    }\n  }\n\n  @Test\n  public void shouldFindLowercasedStageFileWhenQuotedIdentifiersIgnoreCaseIsEnabled()\n      throws Exception {\n    Properties props = new Properties();\n    props.setProperty(\"QUOTED_IDENTIFIERS_IGNORE_CASE\", \"true\");\n    try (Connection conn = getConnection(props);\n        Statement stmt = conn.createStatement()) {\n      SnowflakeConnection connection = conn.unwrap(SnowflakeConnection.class);\n      String randomStage = \"test\" + UUID.randomUUID().toString().replaceAll(\"-\", \"\");\n      stmt.execute(\"CREATE OR REPLACE STAGE \" + randomStage);\n      connection.uploadStream(\n          randomStage,\n          \"testfile.csv\",\n          new ByteArrayInputStream(\"some text\".getBytes()),\n          UploadStreamConfig.builder().setDestPrefix(\"PREFIX\").setCompressData(false).build());\n      assertDoesNotThrow(\n          () ->\n              connection\n                  .downloadStream(\n                      randomStage,\n                      \"PREFIX/testfile.csv\",\n                      DownloadStreamConfig.builder().setDecompress(false).build())\n                  .close());\n    }\n  }\n\n  private static void downloadStreamExpectingContent(\n      Connection conn,\n      String stageName,\n      String fileName,\n      boolean decompress,\n      String expectedFileContent)\n      throws IOException, SQLException {\n    try (InputStream inputStream =\n            conn.unwrap(SnowflakeConnectionImpl.class)\n                .downloadStream(\n                    stageName,\n                    fileName,\n                    DownloadStreamConfig.builder().setDecompress(decompress).build());\n        InputStreamReader isr = new InputStreamReader(inputStream);\n        BufferedReader br = new BufferedReader(isr)) {\n      String content = br.lines().collect(Collectors.joining(\"\\n\"));\n      assertEquals(expectedFileContent, content);\n    }\n  }\n\n  private static void expectsFilesOnStage(Statement stmt, String stageDest, int expectCount)\n      throws SQLException {\n    int filesInStageDir = 0;\n    try (ResultSet rs = stmt.executeQuery(\"LIST \" + stageDest)) {\n      while (rs.next()) {\n        ++filesInStageDir;\n      }\n    }\n    assertEquals(expectCount, filesInStageDir);\n  }\n\n  private static boolean putFile(\n      Statement stmt, String localFileName, String stageDest, boolean autoCompress)\n      throws SQLException {\n    return stmt.execute(\n        String.format(\n            \"PUT file://%s %s AUTO_COMPRESS=%s\",\n            localFileName, stageDest, String.valueOf(autoCompress).toUpperCase()));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/AwsSdkGCPSignerTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.List;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\nimport software.amazon.awssdk.http.SdkHttpFullRequest;\nimport software.amazon.awssdk.http.SdkHttpMethod;\n\nclass AwsSdkGCPSignerTest {\n  @Test\n  void testSign() {\n    AwsSdkGCPSigner signer = new AwsSdkGCPSigner(\"test_token\");\n\n    SdkHttpFullRequest.Builder request =\n        SdkHttpFullRequest.builder().protocol(\"https\").host(\"localhost\").method(SdkHttpMethod.GET);\n\n    request.putHeader(\"x-amz-storage-class\", \"storage_class\");\n    request.putHeader(\"x-amz-meta-custom\", \"custom_meta\");\n\n    SdkHttpFullRequest signed = signer.sign(request.build(), null);\n    Map<String, List<String>> changedHeaders = signed.headers();\n\n    assertEquals(\"Bearer test_token\", changedHeaders.get(\"Authorization\").get(0));\n    assertEquals(\"gzip,deflate\", changedHeaders.get(\"Accept-Encoding\").get(0));\n    assertEquals(\"storage_class\", changedHeaders.get(\"x-goog-storage-class\").get(0));\n    assertEquals(\"custom_meta\", changedHeaders.get(\"x-goog-meta-custom\").get(0));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/CloudStorageClientLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.UUID;\nimport net.snowflake.client.api.connection.DownloadStreamConfig;\nimport net.snowflake.client.api.connection.SnowflakeConnection;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\n\n@Tag(TestTags.OTHERS)\npublic class CloudStorageClientLatestIT extends BaseJDBCTest {\n\n  /**\n   * Test for SNOW-565154 - it was waiting for ~5 minutes so the test is waiting much shorter time\n   */\n  @Test\n  @Timeout(30)\n  public void testDownloadStreamShouldFailFastOnNotExistingFile() throws Throwable {\n    String stageName =\n        \"testDownloadStream_stage_\" + UUID.randomUUID().toString().replaceAll(\"-\", \"_\");\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.execute(\"CREATE OR REPLACE TEMP STAGE \" + stageName);\n\n        assertThrows(\n            SQLException.class,\n            () ->\n                connection\n                    .unwrap(SnowflakeConnection.class)\n                    .downloadStream(\n                        \"@\" + stageName,\n                        \"/fileNotExist.gz\",\n                        DownloadStreamConfig.builder().setDecompress(true).build()));\n      } finally {\n        statement.execute(\"DROP STAGE IF EXISTS \" + stageName);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/CloudStorageProxyFactoryTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpProtocol;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nclass CloudStorageProxyFactoryTest {\n  static Stream<Arguments> prepareNonProxyHostsTestCases() {\n    return Stream.of(\n        Arguments.of(\"example.com\", new HashSet<>(Arrays.asList(\"\\\\Qexample.com\\\\E\"))),\n        Arguments.of(\n            \"example.com|test.org | localhost\",\n            new HashSet<>(Arrays.asList(\"\\\\Qexample.com\\\\E\", \"\\\\Qtest.org\\\\E\", \"\\\\Qlocalhost\\\\E\"))),\n        Arguments.of(\"*.example.com\", new HashSet<>(Arrays.asList(\"\\\\Q\\\\E.*\\\\Q.example.com\\\\E\"))),\n        Arguments.of(\n            \"example.com|*.test.org|localhost|*.internal.*\",\n            new HashSet<>(\n                Arrays.asList(\n                    \"\\\\Qexample.com\\\\E\",\n                    \"\\\\Q\\\\E.*\\\\Q.test.org\\\\E\",\n                    \"\\\\Qlocalhost\\\\E\",\n                    \"\\\\Q\\\\E.*\\\\Q.internal.\\\\E.*\\\\Q\\\\E\"))));\n  }\n\n  @ParameterizedTest\n  @MethodSource(\"prepareNonProxyHostsTestCases\")\n  void testPrepareNonProxyHosts(String input, Set<String> expected) {\n    Set<String> result = CloudStorageProxyFactory.prepareNonProxyHostsForS3(input);\n    assertEquals(expected, result);\n  }\n\n  // ── extractFromKey ─────────────────────────────────────────────────────────\n\n  @Test\n  void extractFromKey_returnsNullWhenKeyIsNull() {\n    assertNull(CloudStorageProxyFactory.extractFromKey(null));\n  }\n\n  @Test\n  void extractFromKey_returnsNullWhenProxyNotUsed() {\n    HttpClientSettingsKey key = new HttpClientSettingsKey(OCSPMode.FAIL_OPEN);\n    assertNull(CloudStorageProxyFactory.extractFromKey(key));\n  }\n\n  @Test\n  void extractFromKey_returnsSettingsWithAllFields() {\n    HttpClientSettingsKey key =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN,\n            \"proxy.example.com\",\n            8080,\n            \"*.internal.com\",\n            \"proxyuser\",\n            \"proxypass\",\n            \"https\",\n            null,\n            false);\n\n    ProxySettings s = CloudStorageProxyFactory.extractFromKey(key);\n\n    assertNotNull(s);\n    assertEquals(\"proxy.example.com\", s.getHost());\n    assertEquals(8080, s.getPort());\n    assertEquals(HttpProtocol.HTTPS, s.getProtocol());\n    assertEquals(\"proxyuser\", s.getUser());\n    assertEquals(\"proxypass\", s.getPassword());\n    assertEquals(\"*.internal.com\", s.getNonProxyHosts());\n  }\n\n  @Test\n  void extractFromKey_returnsSettingsWithHttpProtocol() {\n    HttpClientSettingsKey key =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN, \"proxy.host\", 3128, null, null, null, \"http\", null, false);\n\n    ProxySettings s = CloudStorageProxyFactory.extractFromKey(key);\n\n    assertNotNull(s);\n    assertEquals(HttpProtocol.HTTP, s.getProtocol());\n  }\n\n  @Test\n  void extractFromKey_hasCredentialsReturnsFalseWhenNoCredentials() {\n    HttpClientSettingsKey key =\n        new HttpClientSettingsKey(\n            OCSPMode.FAIL_OPEN, \"proxy.host\", 3128, null, null, null, \"http\", null, false);\n\n    ProxySettings s = CloudStorageProxyFactory.extractFromKey(key);\n\n    assertNotNull(s);\n    // empty user/password strings from the key should map to no credentials\n    assertEquals(false, s.hasCredentials());\n  }\n\n  // ── extractFromProperties ──────────────────────────────────────────────────\n\n  @Test\n  void extractFromProperties_returnsNullWhenPropertiesNull() throws SnowflakeSQLException {\n    assertNull(CloudStorageProxyFactory.extractFromProperties(null));\n  }\n\n  @Test\n  void extractFromProperties_returnsNullWhenPropertiesEmpty() throws SnowflakeSQLException {\n    assertNull(CloudStorageProxyFactory.extractFromProperties(new Properties()));\n  }\n\n  @Test\n  void extractFromProperties_returnsNullWhenUseProxyAbsent() throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.host\");\n    assertNull(CloudStorageProxyFactory.extractFromProperties(props));\n  }\n\n  @Test\n  void extractFromProperties_returnsNullWhenUseProxyFalse() throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USE_PROXY.getPropertyKey(), \"false\");\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.host\");\n    props.setProperty(SFSessionProperty.PROXY_PORT.getPropertyKey(), \"8080\");\n    assertNull(CloudStorageProxyFactory.extractFromProperties(props));\n  }\n\n  @Test\n  void extractFromProperties_returnsSettingsWithAllFields() throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USE_PROXY.getPropertyKey(), \"true\");\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.example.com\");\n    props.setProperty(SFSessionProperty.PROXY_PORT.getPropertyKey(), \"8080\");\n    props.setProperty(SFSessionProperty.PROXY_USER.getPropertyKey(), \"proxyuser\");\n    props.setProperty(SFSessionProperty.PROXY_PASSWORD.getPropertyKey(), \"proxypass\");\n    props.setProperty(SFSessionProperty.NON_PROXY_HOSTS.getPropertyKey(), \"*.internal.com\");\n    props.setProperty(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey(), \"https\");\n\n    ProxySettings s = CloudStorageProxyFactory.extractFromProperties(props);\n\n    assertNotNull(s);\n    assertEquals(\"proxy.example.com\", s.getHost());\n    assertEquals(8080, s.getPort());\n    assertEquals(HttpProtocol.HTTPS, s.getProtocol());\n    assertEquals(\"proxyuser\", s.getUser());\n    assertEquals(\"proxypass\", s.getPassword());\n    assertEquals(\"*.internal.com\", s.getNonProxyHosts());\n  }\n\n  @Test\n  void extractFromProperties_defaultsToHttpProtocolWhenProtocolAbsent()\n      throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USE_PROXY.getPropertyKey(), \"true\");\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.host\");\n    props.setProperty(SFSessionProperty.PROXY_PORT.getPropertyKey(), \"3128\");\n\n    ProxySettings s = CloudStorageProxyFactory.extractFromProperties(props);\n\n    assertNotNull(s);\n    assertEquals(HttpProtocol.HTTP, s.getProtocol());\n  }\n\n  @Test\n  void extractFromProperties_defaultsToHttpProtocolWhenProtocolIsHttp()\n      throws SnowflakeSQLException {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USE_PROXY.getPropertyKey(), \"true\");\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.host\");\n    props.setProperty(SFSessionProperty.PROXY_PORT.getPropertyKey(), \"3128\");\n    props.setProperty(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey(), \"http\");\n\n    ProxySettings s = CloudStorageProxyFactory.extractFromProperties(props);\n\n    assertNotNull(s);\n    assertEquals(HttpProtocol.HTTP, s.getProtocol());\n  }\n\n  @Test\n  void extractFromProperties_throwsOnInvalidPort() {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USE_PROXY.getPropertyKey(), \"true\");\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.host\");\n    props.setProperty(SFSessionProperty.PROXY_PORT.getPropertyKey(), \"not-a-number\");\n\n    assertThrows(\n        SnowflakeSQLException.class, () -> CloudStorageProxyFactory.extractFromProperties(props));\n  }\n\n  @Test\n  void extractFromProperties_throwsWhenPortAbsent() {\n    Properties props = new Properties();\n    props.setProperty(SFSessionProperty.USE_PROXY.getPropertyKey(), \"true\");\n    props.setProperty(SFSessionProperty.PROXY_HOST.getPropertyKey(), \"proxy.host\");\n\n    assertThrows(\n        SnowflakeSQLException.class, () -> CloudStorageProxyFactory.extractFromProperties(props));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/EncryptionProviderTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.security.SecureRandom;\nimport java.util.Base64;\nimport javax.crypto.CipherInputStream;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\npublic class EncryptionProviderTest {\n  private final SecureRandom random = new SecureRandom();\n\n  private final ArgumentCaptor<StorageObjectMetadata> storageObjectMetadataArgumentCaptor =\n      ArgumentCaptor.forClass(StorageObjectMetadata.class);\n  private final ArgumentCaptor<MatDesc> matDescArgumentCaptor =\n      ArgumentCaptor.forClass(MatDesc.class);\n  private final ArgumentCaptor<byte[]> ivDataArgumentCaptor = ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<byte[]> encKekArgumentCaptor = ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<Long> contentLengthArgumentCaptor =\n      ArgumentCaptor.forClass(Long.class);\n\n  private final StorageObjectMetadata meta = mock(StorageObjectMetadata.class);\n  private final SnowflakeStorageClient storageClient = mock(SnowflakeStorageClient.class);\n\n  private final String queryStageMasterKey =\n      Base64.getEncoder().encodeToString(random.generateSeed(16));\n  private final RemoteStoreFileEncryptionMaterial encMat = new RemoteStoreFileEncryptionMaterial();\n\n  byte[] plainText = \"the quick brown fox jumps over the lazy dog\".getBytes(StandardCharsets.UTF_8);\n\n  @BeforeEach\n  public void setUp() {\n    encMat.setQueryStageMasterKey(queryStageMasterKey);\n    encMat.setSmkId(123);\n    encMat.setQueryId(\"query-id\");\n  }\n\n  @Test\n  public void testEncryptAndDecryptStream() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    CipherInputStream encrypted =\n        EncryptionProvider.encrypt(meta, plainText.length, plainTextStream, encMat, storageClient);\n    byte[] cipherText = IOUtils.toByteArray(encrypted);\n    verify(storageClient)\n        .addEncryptionMetadata(\n            storageObjectMetadataArgumentCaptor.capture(),\n            matDescArgumentCaptor.capture(),\n            ivDataArgumentCaptor.capture(),\n            encKekArgumentCaptor.capture(),\n            contentLengthArgumentCaptor.capture());\n\n    InputStream inputStream =\n        EncryptionProvider.decryptStream(\n            new ByteArrayInputStream(cipherText),\n            Base64.getEncoder().encodeToString(encKekArgumentCaptor.getValue()),\n            Base64.getEncoder().encodeToString(ivDataArgumentCaptor.getValue()),\n            encMat);\n    byte[] decryptedPlainText = IOUtils.toByteArray(inputStream);\n    assertArrayEquals(plainText, decryptedPlainText);\n  }\n\n  @Test\n  public void testEncryptAndDecryptFile() throws Exception {\n    File tempFile = Files.createTempFile(\"encryption\", \"\").toFile();\n    tempFile.deleteOnExit();\n\n    CipherInputStream encrypted =\n        EncryptionProvider.encrypt(\n            meta, plainText.length, new ByteArrayInputStream(plainText), encMat, storageClient);\n    FileUtils.writeByteArrayToFile(tempFile, IOUtils.toByteArray(encrypted));\n    verify(storageClient)\n        .addEncryptionMetadata(\n            storageObjectMetadataArgumentCaptor.capture(),\n            matDescArgumentCaptor.capture(),\n            ivDataArgumentCaptor.capture(),\n            encKekArgumentCaptor.capture(),\n            contentLengthArgumentCaptor.capture());\n\n    EncryptionProvider.decrypt(\n        tempFile,\n        Base64.getEncoder().encodeToString(encKekArgumentCaptor.getValue()),\n        Base64.getEncoder().encodeToString(ivDataArgumentCaptor.getValue()),\n        encMat);\n    byte[] decryptedCipherText = FileUtils.readFileToByteArray(tempFile);\n    assertArrayEquals(plainText, decryptedCipherText);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/GcmEncryptionProviderTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.util.Base64;\nimport javax.crypto.AEADBadTagException;\nimport javax.crypto.BadPaddingException;\nimport javax.crypto.IllegalBlockSizeException;\nimport javax.crypto.NoSuchPaddingException;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\npublic class GcmEncryptionProviderTest {\n  private final SecureRandom random = new SecureRandom();\n\n  private final ArgumentCaptor<StorageObjectMetadata> storageObjectMetadataArgumentCaptor =\n      ArgumentCaptor.forClass(StorageObjectMetadata.class);\n  private final ArgumentCaptor<MatDesc> matDescArgumentCaptor =\n      ArgumentCaptor.forClass(MatDesc.class);\n  private final ArgumentCaptor<byte[]> dataIvDataArgumentCaptor =\n      ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<byte[]> keyIvDataArgumentCaptor =\n      ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<byte[]> encKeyArgumentCaptor = ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<byte[]> keyAadArgumentCaptor = ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<byte[]> dataAadArgumentCaptor =\n      ArgumentCaptor.forClass(byte[].class);\n  private final ArgumentCaptor<Long> contentLengthArgumentCaptor =\n      ArgumentCaptor.forClass(Long.class);\n\n  private final StorageObjectMetadata meta = mock(StorageObjectMetadata.class);\n  private final SnowflakeStorageClient storageClient = mock(SnowflakeStorageClient.class);\n\n  private final String queryStageMasterKey =\n      Base64.getEncoder().encodeToString(random.generateSeed(16));\n  private final RemoteStoreFileEncryptionMaterial encMat = new RemoteStoreFileEncryptionMaterial();\n\n  byte[] plainText = \"the quick brown fox jumps over the lazy dog\".getBytes(StandardCharsets.UTF_8);\n  byte[] dataAad = \"data aad\".getBytes(StandardCharsets.UTF_8);\n  byte[] keyAad = \"key aad\".getBytes(StandardCharsets.UTF_8);\n\n  @BeforeEach\n  public void setUp() {\n    encMat.setQueryStageMasterKey(queryStageMasterKey);\n    encMat.setSmkId(123);\n    encMat.setQueryId(\"query-id\");\n  }\n\n  @Test\n  public void testEncryptAndDecryptStreamWithoutAad() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, null, null);\n\n    InputStream inputStream = decryptStream(cipherText, null, null);\n    byte[] decryptedPlainText = IOUtils.toByteArray(inputStream);\n    assertArrayEquals(plainText, decryptedPlainText);\n  }\n\n  @Test\n  public void testEncryptAndDecryptStreamWithAad() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    InputStream inputStream = decryptStream(cipherText, dataAad, keyAad);\n    byte[] decryptedPlainText = IOUtils.toByteArray(inputStream);\n    assertArrayEquals(plainText, decryptedPlainText);\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidKeyAad() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    assertThrows(\n        AEADBadTagException.class, () -> decryptStream(cipherText, dataAad, new byte[] {'a'}));\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidDataAad() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    IOException ioException =\n        assertThrows(\n            IOException.class,\n            () -> IOUtils.toByteArray(decryptStream(cipherText, new byte[] {'a'}, keyAad)));\n    assertEquals(ioException.getCause().getClass(), AEADBadTagException.class);\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidCipherText() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    cipherText[0] = (byte) ((cipherText[0] + 1) % 255);\n    IOException ioException =\n        assertThrows(\n            IOException.class,\n            () -> IOUtils.toByteArray(decryptStream(cipherText, dataAad, keyAad)));\n    assertEquals(ioException.getCause().getClass(), AEADBadTagException.class);\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidTag() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    cipherText[cipherText.length - 1] = (byte) ((cipherText[cipherText.length - 1] + 1) % 255);\n    IOException ioException =\n        assertThrows(\n            IOException.class,\n            () -> IOUtils.toByteArray(decryptStream(cipherText, dataAad, keyAad)));\n    assertEquals(ioException.getCause().getClass(), AEADBadTagException.class);\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidKey() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n\n    byte[] encryptedKey = encKeyArgumentCaptor.getValue();\n    encryptedKey[0] = (byte) ((encryptedKey[0] + 1) % 255);\n    assertThrows(\n        AEADBadTagException.class,\n        () ->\n            IOUtils.toByteArray(\n                GcmEncryptionProvider.decryptStream(\n                    new ByteArrayInputStream(cipherText),\n                    Base64.getEncoder().encodeToString(encryptedKey),\n                    Base64.getEncoder().encodeToString(dataIvDataArgumentCaptor.getValue()),\n                    Base64.getEncoder().encodeToString(keyIvDataArgumentCaptor.getValue()),\n                    encMat,\n                    dataAad == null ? \"\" : Base64.getEncoder().encodeToString(dataAad),\n                    keyAad == null ? \"\" : Base64.getEncoder().encodeToString(keyAad))));\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidDataIV() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    byte[] dataIvBase64 = dataIvDataArgumentCaptor.getValue();\n    dataIvBase64[0] = (byte) ((dataIvBase64[0] + 1) % 255);\n    IOException ioException =\n        assertThrows(\n            IOException.class,\n            () -> {\n              IOUtils.toByteArray(\n                  GcmEncryptionProvider.decryptStream(\n                      new ByteArrayInputStream(cipherText),\n                      Base64.getEncoder().encodeToString(encKeyArgumentCaptor.getValue()),\n                      Base64.getEncoder().encodeToString(dataIvBase64),\n                      Base64.getEncoder().encodeToString(keyIvDataArgumentCaptor.getValue()),\n                      encMat,\n                      dataAad == null ? \"\" : Base64.getEncoder().encodeToString(dataAad),\n                      keyAad == null ? \"\" : Base64.getEncoder().encodeToString(keyAad)));\n            });\n    assertEquals(ioException.getCause().getClass(), AEADBadTagException.class);\n  }\n\n  @Test\n  public void testDecryptStreamWithInvalidKeyIV() throws Exception {\n    InputStream plainTextStream = new ByteArrayInputStream(plainText);\n\n    byte[] cipherText = encryptStream(plainTextStream, dataAad, keyAad);\n    byte[] keyIvBase64 = keyIvDataArgumentCaptor.getValue();\n    keyIvBase64[0] = (byte) ((keyIvBase64[0] + 1) % 255);\n    assertThrows(\n        AEADBadTagException.class,\n        () -> {\n          IOUtils.toByteArray(\n              GcmEncryptionProvider.decryptStream(\n                  new ByteArrayInputStream(cipherText),\n                  Base64.getEncoder().encodeToString(encKeyArgumentCaptor.getValue()),\n                  Base64.getEncoder().encodeToString(dataIvDataArgumentCaptor.getValue()),\n                  Base64.getEncoder().encodeToString(keyIvBase64),\n                  encMat,\n                  dataAad == null ? \"\" : Base64.getEncoder().encodeToString(dataAad),\n                  keyAad == null ? \"\" : Base64.getEncoder().encodeToString(keyAad)));\n        });\n  }\n\n  private byte[] encryptStream(InputStream plainTextStream, byte[] dataAad, byte[] keyAad)\n      throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException,\n          BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException, IOException {\n    InputStream encrypted =\n        GcmEncryptionProvider.encrypt(\n            meta, plainText.length, plainTextStream, encMat, storageClient, dataAad, keyAad);\n    byte[] cipherText = IOUtils.toByteArray(encrypted);\n    captureKeysAndIvs();\n    return cipherText;\n  }\n\n  private InputStream decryptStream(byte[] cipherText, byte[] dataAad, byte[] keyAad)\n      throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException,\n          InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException {\n    return GcmEncryptionProvider.decryptStream(\n        new ByteArrayInputStream(cipherText),\n        Base64.getEncoder().encodeToString(encKeyArgumentCaptor.getValue()),\n        Base64.getEncoder().encodeToString(dataIvDataArgumentCaptor.getValue()),\n        Base64.getEncoder().encodeToString(keyIvDataArgumentCaptor.getValue()),\n        encMat,\n        dataAad == null ? \"\" : Base64.getEncoder().encodeToString(dataAad),\n        keyAad == null ? \"\" : Base64.getEncoder().encodeToString(keyAad));\n  }\n\n  @Test\n  public void testEncryptAndDecryptFileWithoutAad() throws Exception {\n    File tempFile = Files.createTempFile(\"encryption\", \"\").toFile();\n    tempFile.deleteOnExit();\n\n    InputStream encrypted =\n        new ByteArrayInputStream(encryptStream(new ByteArrayInputStream(plainText), null, null));\n    FileUtils.writeByteArrayToFile(tempFile, IOUtils.toByteArray(encrypted));\n    captureKeysAndIvs();\n    decryptFile(tempFile, null, null);\n    byte[] decryptedCipherText = FileUtils.readFileToByteArray(tempFile);\n    assertArrayEquals(plainText, decryptedCipherText);\n  }\n\n  @Test\n  public void testEncryptAndDecryptFileWithAad() throws Exception {\n    File tempFile = Files.createTempFile(\"encryption\", \"\").toFile();\n    tempFile.deleteOnExit();\n\n    InputStream encrypted =\n        new ByteArrayInputStream(\n            encryptStream(new ByteArrayInputStream(plainText), dataAad, keyAad));\n    FileUtils.writeByteArrayToFile(tempFile, IOUtils.toByteArray(encrypted));\n    captureKeysAndIvs();\n    decryptFile(tempFile, dataAad, keyAad);\n    byte[] decryptedCipherText = FileUtils.readFileToByteArray(tempFile);\n    assertArrayEquals(plainText, decryptedCipherText);\n  }\n\n  private void decryptFile(File tempFile, byte[] dataAad, byte[] keyAad)\n      throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,\n          InvalidAlgorithmParameterException, IOException, NoSuchPaddingException,\n          NoSuchAlgorithmException {\n    GcmEncryptionProvider.decryptFile(\n        tempFile,\n        Base64.getEncoder().encodeToString(encKeyArgumentCaptor.getValue()),\n        Base64.getEncoder().encodeToString(dataIvDataArgumentCaptor.getValue()),\n        Base64.getEncoder().encodeToString(keyIvDataArgumentCaptor.getValue()),\n        encMat,\n        dataAadArgumentCaptor.getValue() == null\n            ? \"\"\n            : Base64.getEncoder().encodeToString(dataAadArgumentCaptor.getValue()),\n        keyAadArgumentCaptor.getValue() == null\n            ? \"\"\n            : Base64.getEncoder().encodeToString(keyAadArgumentCaptor.getValue()));\n  }\n\n  private void captureKeysAndIvs() {\n    verify(storageClient)\n        .addEncryptionMetadataForGcm(\n            storageObjectMetadataArgumentCaptor.capture(),\n            matDescArgumentCaptor.capture(),\n            encKeyArgumentCaptor.capture(),\n            dataIvDataArgumentCaptor.capture(),\n            keyIvDataArgumentCaptor.capture(),\n            keyAadArgumentCaptor.capture(),\n            dataAadArgumentCaptor.capture(),\n            contentLengthArgumentCaptor.capture());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeAzureClientLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.spy;\n\nimport com.azure.storage.blob.models.BlobItem;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.ArrayList;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\npublic class SnowflakeAzureClientLatestIT extends BaseJDBCTest {\n  @Test\n  @DontRunOnGithubActions\n  public void testAzureClientSetupInvalidEncryptionKeySize() throws SQLException {\n    try (Connection connection = getConnection(\"azureaccount\")) {\n      SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      String putCommand = \"put file:///dummy/path/file1.gz @~\";\n      SnowflakeFileTransferAgent sfAgent =\n          new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n      RemoteStoreFileEncryptionMaterial content =\n          new RemoteStoreFileEncryptionMaterial(\n              \"EXAMPLEQUERYSTAGEMASTERKEY\", \"EXAMPLEQUERYID\", 123L);\n\n      SnowflakeSQLLoggedException ex =\n          assertThrows(\n              SnowflakeSQLLoggedException.class,\n              () ->\n                  SnowflakeAzureClient.createSnowflakeAzureClient(\n                      sfAgent.getStageInfo(), content, sfSession));\n      assertEquals(ErrorCode.FILE_TRANSFER_ERROR.getMessageCode(), ex.getErrorCode());\n    }\n  }\n\n  @Test\n  public void testCloudExceptionTest() {\n    Iterable<BlobItem> mockList = new ArrayList<>();\n    AzureObjectSummariesIterator iterator = new AzureObjectSummariesIterator(mockList, null);\n    AzureObjectSummariesIterator spyIterator = spy(iterator);\n    UnsupportedOperationException ex =\n        assertThrows(UnsupportedOperationException.class, () -> spyIterator.remove());\n    assertTrue(ex.getMessage().startsWith(\"remove() method not supported\"));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeAzureClientTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static com.azure.storage.common.implementation.Constants.HeaderConstants.ERROR_CODE_HEADER_NAME;\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.CALLS_REAL_METHODS;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.Mockito.withSettings;\n\nimport com.azure.core.http.HttpHeaders;\nimport com.azure.core.http.HttpResponse;\nimport com.azure.storage.blob.models.BlobErrorCode;\nimport com.azure.storage.blob.models.BlobStorageException;\nimport java.io.IOException;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeAzureClientTest {\n  @Test\n  public void testFormatStorageExtendedErrorInformation() {\n    String expectedStr0 =\n        \"StorageExceptionExtendedErrorInformation: {ErrorCode=AuthorizationFailure, ErrorMessage=Server refuses\"\n            + \" to authorize the request}\";\n    BlobStorageException info = mock(BlobStorageException.class);\n    when(info.getErrorCode()).thenReturn(BlobErrorCode.AUTHORIZATION_FAILURE);\n    when(info.getServiceMessage()).thenReturn(\"Server refuses to authorize the request\");\n    String formatedStr = SnowflakeAzureClient.formatStorageExtendedErrorInformation(info);\n    assertEquals(expectedStr0, formatedStr);\n  }\n\n  // We should not throw a SnowflakeSQLException when a 503 occurs if we still haven't reached the\n  // maximum number of retries\n  @Test\n  public void testHandleStorageExceptionRetry503() throws Exception {\n    SnowflakeAzureClient client =\n        mock(SnowflakeAzureClient.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));\n    when(client.getMaxRetries()).thenReturn(1);\n    when(client.getRetryBackoffMin()).thenReturn(0);\n    when(client.getRetryBackoffMaxExponent()).thenReturn(0);\n\n    HttpResponse response = mock(HttpResponse.class);\n    when(response.getHeaders()).thenReturn(new HttpHeaders().add(ERROR_CODE_HEADER_NAME, \"503\"));\n    BlobStorageException storageException =\n        new BlobStorageException(\"Service Unavailable\", response, new Exception());\n\n    assertDoesNotThrow(\n        () ->\n            client.handleStorageException(\n                storageException, /* retryCount */\n                1,\n                \"upload\", /* session */\n                null, /* command */\n                \"PUT\",\n                null));\n  }\n\n  // We should throw a SnowflakeSQLException when we receive a 503 after reaching the maximum number\n  // of retries.\n  @Test\n  public void testHandleStorageException503WhenOverMaxRetries() throws Exception {\n    SnowflakeAzureClient client =\n        mock(SnowflakeAzureClient.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));\n    when(client.getMaxRetries()).thenReturn(1);\n\n    HttpResponse response = mock(HttpResponse.class);\n    when(response.getHeaders()).thenReturn(new HttpHeaders().add(ERROR_CODE_HEADER_NAME, \"503\"));\n    BlobStorageException storageException =\n        new BlobStorageException(\"Service Unavailable\", response, new Exception());\n\n    assertThrows(\n        SnowflakeSQLException.class,\n        () ->\n            client.handleStorageException(\n                storageException, /* retryCount */\n                2,\n                \"upload\", /* session */\n                null, /* command */\n                \"PUT\",\n                null));\n  }\n\n  // We should retry IOExceptions caused by StorageExceptions\n  @Test\n  public void testHandleStorageExceptionIOExceptionCausedBy503() throws Exception {\n    SnowflakeAzureClient client =\n        mock(SnowflakeAzureClient.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));\n    when(client.getMaxRetries()).thenReturn(1);\n    when(client.getRetryBackoffMin()).thenReturn(0);\n    when(client.getRetryBackoffMaxExponent()).thenReturn(0);\n\n    HttpResponse response = mock(HttpResponse.class);\n    when(response.getHeaders()).thenReturn(new HttpHeaders().add(ERROR_CODE_HEADER_NAME, \"503\"));\n    BlobStorageException storageException =\n        new BlobStorageException(\"Service Unavailable\", response, new Exception());\n    IOException ioException = new IOException(\"transport error\", storageException);\n\n    assertDoesNotThrow(\n        () ->\n            client.handleStorageException(\n                ioException, /* retryCount */\n                1,\n                \"upload\", /* session */\n                null, /* command */\n                \"PUT\",\n                null));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeS3ClientLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.common.core.RemoteStoreFileEncryptionMaterial;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport software.amazon.awssdk.core.exception.SdkServiceException;\n\n@Tag(TestTags.OTHERS)\npublic class SnowflakeS3ClientLatestIT extends BaseJDBCTest {\n\n  @Test\n  @DontRunOnGithubActions\n  public void testS3Client256Encryption() throws SQLException {\n    try (Connection connection = getConnection(\"s3testaccount\")) {\n      SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      String putCommand = \"put file:///dummy/path/file1.gz @~\";\n      SnowflakeFileTransferAgent sfAgent =\n          new SnowflakeFileTransferAgent(putCommand, sfSession, new SFStatement(sfSession));\n      // create encMat with encryption keysize 256 for master key\n      RemoteStoreFileEncryptionMaterial content =\n          new RemoteStoreFileEncryptionMaterial(\n              \"LHMTKHLETLKHPSTADDGAESLFKREYGHFHGHGSDHJKLMH\", \"123456\", 123L);\n      StageInfo info = sfAgent.getStageInfo();\n      SnowflakeS3Client.ClientConfiguration config =\n          new SnowflakeS3Client.ClientConfiguration(1, 1, 10_000, 10_000);\n      // AmazonS3EncryptionClient builder will create the client\n      SnowflakeS3Client client =\n          new SnowflakeS3Client(\n              info.getCredentials(),\n              config,\n              content,\n              info.getProxyProperties(),\n              info.getRegion(),\n              info.getEndPoint(),\n              info.getIsClientSideEncrypted(),\n              sfSession,\n              info.getUseS3RegionalUrl());\n      assertEquals(256, client.getEncryptionKeySize());\n    }\n  }\n\n  /**\n   * This is a manual test to confirm that the s3 client builder doesn't read from\n   * https_proxy/http_proxy environment variable.\n   *\n   * <p>Prerequisite: 1. Set HTTPS_PROXY/HTTP_PROXY to a proxy that won't connect i.e.\n   * HTTPS_PROXY=https://myproxy:8080\n   *\n   * <p>2. Connect to S3 host.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @Disabled\n  public void testS3ConnectionWithProxyEnvVariablesSet() throws SQLException {\n    String testStageName = \"s3TestStage\";\n\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      try (ResultSet resultSet = statement.executeQuery(\"select 1\")) {\n        assertTrue(resultSet.next());\n      }\n      try {\n        statement.execute(\"create or replace stage \" + testStageName);\n        try (ResultSet resultSet =\n            connection\n                .createStatement()\n                .executeQuery(\n                    \"PUT file://\"\n                        + getFullPathFileInResource(TEST_DATA_FILE)\n                        + \" @\"\n                        + testStageName)) {\n          while (resultSet.next()) {\n            assertEquals(\"UPLOADED\", resultSet.getString(\"status\"));\n          }\n        }\n      } finally {\n        statement.execute(\"DROP STAGE if exists \" + testStageName);\n      }\n    }\n  }\n\n  @Test\n  public void testIsClientException400Or404() {\n    SdkServiceException badRequest =\n        SdkServiceException.builder().message(\"Bad Request\").statusCode(400).build();\n    SdkServiceException notFound =\n        SdkServiceException.builder().message(\"Not Found\").statusCode(404).build();\n    SdkServiceException serverError =\n        SdkServiceException.builder().message(\"Internal Server Error\").statusCode(500).build();\n\n    assertTrue(S3ErrorHandler.isClientException400Or404(badRequest));\n    assertTrue(S3ErrorHandler.isClientException400Or404(notFound));\n    assertFalse(S3ErrorHandler.isClientException400Or404(serverError));\n    assertFalse(\n        S3ErrorHandler.isClientException400Or404(new RuntimeException(\"not an SDK exception\")));\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testPutGetMaxRetries() throws SQLException {\n    Properties props = new Properties();\n    props.put(\"putGetMaxRetries\", 1);\n    try (Connection connection = getConnection(\"s3testaccount\", props)) {\n      SFSession sfSession = connection.unwrap(SnowflakeConnectionImpl.class).getSfSession();\n      String command = \"GET '@~/testStage' 'file://C:/temp' PARALLEL=8\";\n      SnowflakeFileTransferAgent agent =\n          new SnowflakeFileTransferAgent(command, sfSession, new SFStatement(sfSession));\n      StageInfo info = agent.getStageInfo();\n      SnowflakeS3Client.ClientConfiguration clientConfig =\n          new SnowflakeS3Client.ClientConfiguration(1, 1, 10_000, 10_000);\n      RemoteStoreFileEncryptionMaterial content =\n          new RemoteStoreFileEncryptionMaterial(\n              \"LHMTKHLETLKHPSTADDGAESLFKREYGHFHGHGSDHJKLMH\", \"123456\", 123L);\n      SnowflakeS3Client client =\n          new SnowflakeS3Client(\n              info.getCredentials(),\n              clientConfig,\n              content,\n              info.getProxyProperties(),\n              info.getRegion(),\n              info.getEndPoint(),\n              info.getIsClientSideEncrypted(),\n              sfSession,\n              info.getUseS3RegionalUrl());\n      SnowflakeS3Client spy = Mockito.spy(client);\n\n      // Should retry one time, then throw error\n      spy.handleStorageException(\n          new InterruptedException(), 0, \"download\", sfSession, command, null);\n      Mockito.verify(spy, Mockito.never()).renew(Mockito.anyMap());\n      spy.handleStorageException(\n          new InterruptedException(), 1, \"download\", sfSession, command, null);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeS3ClientTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.lang.reflect.Field;\nimport java.net.URI;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport org.junit.jupiter.api.Test;\nimport software.amazon.awssdk.services.s3.S3AsyncClient;\n\n/**\n * Unit tests for {@link SnowflakeS3Client} endpoint selection ({@code stageInfo.endPoint} and\n * regional URL mode). Uses {@link S3AsyncClient#serviceClientConfiguration()}{@code\n * .endpointOverride()} to read the effective override.\n */\npublic class SnowflakeS3ClientTest {\n\n  @Test\n  public void shouldDetermineDomainForRegion() {\n    assertEquals(\"amazonaws.com\", SnowflakeS3Client.getDomainSuffixForRegionalUrl(\"us-east-1\"));\n    assertEquals(\n        \"amazonaws.com.cn\", SnowflakeS3Client.getDomainSuffixForRegionalUrl(\"cn-northwest-1\"));\n    assertEquals(\n        \"amazonaws.com.cn\", SnowflakeS3Client.getDomainSuffixForRegionalUrl(\"CN-NORTHWEST-1\"));\n  }\n\n  /** Documents why bare hostnames must be normalized before {@code endpointOverride(URI)}. */\n  @Test\n  public void shouldDocumentBareAwsHostnameHasNullUriScheme() {\n    assertNull(URI.create(\"s3.us-west-2.amazonaws.com\").getScheme());\n  }\n\n  @Test\n  public void shouldPrependHttpsWhenStageEndpointHasNoScheme() throws Exception {\n    SnowflakeS3Client client =\n        newClient(\n            \"eu-west-1\", \"s3.eu-west-1.amazonaws.com\", false, /* isClientSideEncrypted */ false);\n    try {\n      URI override = endpointOverride(client).get();\n      assertEquals(\"https\", override.getScheme());\n      assertEquals(\"s3.eu-west-1.amazonaws.com\", override.getHost());\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  @Test\n  public void shouldKeepHttpsStageEndpointUnchanged() throws Exception {\n    String url = \"https://s3-fips.us-gov-west-1.amazonaws.com\";\n    SnowflakeS3Client client =\n        newClient(\"us-gov-west-1\", url, false, /* isClientSideEncrypted */ false);\n    try {\n      URI override = endpointOverride(client).get();\n      assertEquals(URI.create(url), override);\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  /**\n   * RFC 3986: scheme is case-insensitive; must not prepend a second {@code https://} (e.g. to\n   * {@code HTTPS://...}).\n   */\n  @Test\n  public void shouldAcceptUppercaseHttpsSchemeInStageEndpoint() throws Exception {\n    String url = \"HTTPS://s3-fips.us-gov-west-1.amazonaws.com\";\n    SnowflakeS3Client client =\n        newClient(\"us-gov-west-1\", url, false, /* isClientSideEncrypted */ false);\n    try {\n      URI override = endpointOverride(client).get();\n      assertTrue(override.getScheme().equalsIgnoreCase(\"https\"));\n      assertEquals(\"s3-fips.us-gov-west-1.amazonaws.com\", override.getHost());\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  @Test\n  public void shouldKeepHttpStageEndpointUnchanged() throws Exception {\n    String url = \"http://minio.local:9000\";\n    SnowflakeS3Client client =\n        newClient(\"us-east-1\", url, false, /* isClientSideEncrypted */ false);\n    try {\n      URI override = endpointOverride(client).get();\n      assertEquals(\"http\", override.getScheme());\n      assertEquals(\"minio.local\", override.getHost());\n      assertEquals(9000, override.getPort());\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  @Test\n  public void shouldSetRegionalS3EndpointForCommercialRegion() throws Exception {\n    SnowflakeS3Client client =\n        newClient(\n            \"us-west-2\",\n            /* stageEndPoint */ null,\n            /* useS3RegionalUrl */ true,\n            /* isClientSideEncrypted */ false);\n    try {\n      URI override = endpointOverride(client).get();\n      assertEquals(URI.create(\"https://s3.us-west-2.amazonaws.com\"), override);\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  @Test\n  public void shouldSetRegionalS3EndpointForChinaRegion() throws Exception {\n    SnowflakeS3Client client =\n        newClient(\n            \"cn-northwest-1\",\n            /* stageEndPoint */ null,\n            /* useS3RegionalUrl */ true,\n            /* isClientSideEncrypted */ false);\n    try {\n      URI override = endpointOverride(client).get();\n      assertEquals(URI.create(\"https://s3.cn-northwest-1.amazonaws.com.cn\"), override);\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  @Test\n  public void shouldOmitEndpointOverrideWhenNotConfigured() throws Exception {\n    SnowflakeS3Client client =\n        newClient(\n            \"eu-central-1\",\n            /* stageEndPoint */ null,\n            /* useS3RegionalUrl */ false,\n            /* isClientSideEncrypted */ false);\n    try {\n      assertFalse(endpointOverride(client).isPresent());\n    } finally {\n      client.shutdown();\n    }\n  }\n\n  /** Regression: scheme-less {@code stageEndPoint} must not NPE inside AWS SDK v2 client build. */\n  @Test\n  public void shouldBuildClientWithoutNpeWhenStageEndpointIsSchemelessHost() throws Exception {\n    SnowflakeS3Client client = null;\n    try {\n      client =\n          newClient(\n              \"us-west-2\", \"s3.us-west-2.amazonaws.com\", false, /* isClientSideEncrypted */ false);\n      assertNotNull(client);\n      assertTrue(endpointOverride(client).isPresent());\n      assertNotNull(endpointOverride(client).get().getScheme());\n    } finally {\n      if (client != null) {\n        client.shutdown();\n      }\n    }\n  }\n\n  private static SnowflakeS3Client newClient(\n      String stageRegion,\n      String stageEndPoint,\n      boolean useS3RegionalUrl,\n      boolean isClientSideEncrypted)\n      throws SnowflakeSQLException {\n    Map<String, String> creds = new HashMap<>();\n    creds.put(\"AWS_KEY_ID\", \"AKIA_TEST\");\n    creds.put(\"AWS_SECRET_KEY\", \"testSecretAccessKey\");\n    SnowflakeS3Client.ClientConfiguration config =\n        new SnowflakeS3Client.ClientConfiguration(2, 3, 5_000, 5_000);\n    return new SnowflakeS3Client(\n        creds,\n        config,\n        /* encMat */ null,\n        /* proxyProperties */ null,\n        stageRegion,\n        stageEndPoint,\n        isClientSideEncrypted,\n        /* session */ null,\n        useS3RegionalUrl);\n  }\n\n  private static Optional<URI> endpointOverride(SnowflakeS3Client client) throws Exception {\n    Field f = SnowflakeS3Client.class.getDeclaredField(\"amazonClient\");\n    f.setAccessible(true);\n    S3AsyncClient aws = (S3AsyncClient) f.get(client);\n    return aws.serviceClientConfiguration().endpointOverride();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/SnowflakeStorageClientTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.mock;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.Map;\nimport net.snowflake.client.api.exception.ErrorCode;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.FileBackedOutputStream;\nimport net.snowflake.client.internal.jdbc.MatDesc;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nclass SnowflakeStorageClientTest {\n\n  private static SnowflakeStorageClient storageClient;\n\n  @BeforeAll\n  public static void setUp() {\n    // Mock an implementation of the interface\n    storageClient =\n        new SnowflakeStorageClient() {\n          @Override\n          public int getMaxRetries() {\n            return 0;\n          }\n\n          @Override\n          public int getRetryBackoffMaxExponent() {\n            return 0;\n          }\n\n          @Override\n          public int getRetryBackoffMin() {\n            return 0;\n          }\n\n          @Override\n          public boolean isEncrypting() {\n            return false;\n          }\n\n          @Override\n          public int getEncryptionKeySize() {\n            return 0;\n          }\n\n          @Override\n          public void renew(Map<?, ?> stageCredentials) {}\n\n          @Override\n          public void shutdown() {}\n\n          @Override\n          public StorageObjectSummaryCollection listObjects(\n              String remoteStorageLocation, String prefix) {\n            return null;\n          }\n\n          @Override\n          public StorageObjectMetadata getObjectMetadata(\n              String remoteStorageLocation, String prefix) {\n            return null;\n          }\n\n          @Override\n          public void download(\n              SFSession connection,\n              String command,\n              String localLocation,\n              String destFileName,\n              int parallelism,\n              String remoteStorageLocation,\n              String stageFilePath,\n              String stageRegion,\n              String presignedUrl,\n              String queryId) {}\n\n          @Override\n          public InputStream downloadToStream(\n              SFSession connection,\n              String command,\n              int parallelism,\n              String remoteStorageLocation,\n              String stageFilePath,\n              String stageRegion,\n              String presignedUrl,\n              String queryId) {\n            return null;\n          }\n\n          @Override\n          public void upload(\n              SFSession connection,\n              String command,\n              int parallelism,\n              boolean uploadFromStream,\n              String remoteStorageLocation,\n              File srcFile,\n              String destFileName,\n              InputStream inputStream,\n              FileBackedOutputStream fileBackedOutputStream,\n              StorageObjectMetadata meta,\n              String stageRegion,\n              String presignedUrl,\n              String queryId) {}\n\n          @Override\n          public void handleStorageException(\n              Exception ex,\n              int retryCount,\n              String operation,\n              SFSession connection,\n              String command,\n              String queryId) {}\n\n          @Override\n          public String getMatdescKey() {\n            return null;\n          }\n\n          @Override\n          public void addEncryptionMetadata(\n              StorageObjectMetadata meta,\n              MatDesc matDesc,\n              byte[] ivData,\n              byte[] encryptedKey,\n              long contentLength) {}\n\n          @Override\n          public void addDigestMetadata(StorageObjectMetadata meta, String digest) {}\n\n          @Override\n          public String getDigestMetadata(StorageObjectMetadata meta) {\n            return null;\n          }\n\n          @Override\n          public void addStreamingIngestMetadata(\n              StorageObjectMetadata meta, String clientName, String clientKey) {}\n\n          @Override\n          public String getStreamingIngestClientName(StorageObjectMetadata meta) {\n            return null;\n          }\n\n          @Override\n          public String getStreamingIngestClientKey(StorageObjectMetadata meta) {\n            return null;\n          }\n        };\n  }\n\n  @Test\n  void testRequirePresignedUrl_DefaultReturnsFalse() {\n    assertFalse(\n        storageClient.requirePresignedUrl(), \"Default requirePresignedUrl should return false.\");\n  }\n\n  @Test\n  void testDownload_DefaultMethodCallsOverloadedMethod() {\n    SFSession session = mock(SFSession.class);\n    assertDoesNotThrow(\n        () ->\n            storageClient.download(\n                session,\n                \"command\",\n                \"localPath\",\n                \"destFile\",\n                4,\n                \"remoteLocation\",\n                \"stagePath\",\n                \"stageRegion\",\n                \"presignedUrl\"));\n  }\n\n  @Test\n  void testDownloadToStream_DefaultMethodCallsOverloadedMethod() {\n    SFSession session = mock(SFSession.class);\n    assertDoesNotThrow(\n        () ->\n            storageClient.downloadToStream(\n                session,\n                \"command\",\n                4,\n                \"remoteLocation\",\n                \"stagePath\",\n                \"stageRegion\",\n                \"presignedUrl\"));\n  }\n\n  @Test\n  void testUpload_DefaultMethodCallsOverloadedMethod() {\n    SFSession session = mock(SFSession.class);\n    assertDoesNotThrow(\n        () ->\n            storageClient.upload(\n                session,\n                \"command\",\n                4,\n                true,\n                \"remoteLocation\",\n                new File(\"test.txt\"),\n                \"destFile\",\n                null,\n                null,\n                null,\n                \"stageRegion\",\n                \"presignedUrl\"));\n  }\n\n  @Test\n  void testUploadWithPresignedUrlWithoutConnection_ThrowsExceptionWhenNotPresignedUrl() {\n    SnowflakeSQLLoggedException exception =\n        assertThrows(\n            SnowflakeSQLLoggedException.class,\n            () ->\n                storageClient.uploadWithPresignedUrlWithoutConnection(\n                    5000,\n                    mock(HttpClientSettingsKey.class),\n                    4,\n                    true,\n                    \"remoteLocation\",\n                    new File(\"test.txt\"),\n                    \"destFile\",\n                    null,\n                    null,\n                    null,\n                    \"stageRegion\",\n                    \"presignedUrl\",\n                    \"queryId\"));\n    assertEquals(ErrorCode.INTERNAL_ERROR.getMessageCode(), exception.getErrorCode());\n  }\n\n  @Test\n  void testHandleStorageException_DefaultMethodCallsOverloadedMethod() {\n    SFSession session = mock(SFSession.class);\n    Exception exception = new Exception(\"Test exception\");\n    assertDoesNotThrow(\n        () -> storageClient.handleStorageException(exception, 3, \"upload\", session, \"command\"));\n  }\n\n  @Test\n  void testAddEncryptionMetadataForGcm_NoExceptionThrown() {\n    StorageObjectMetadata meta = mock(StorageObjectMetadata.class);\n    MatDesc matDesc = mock(MatDesc.class);\n    byte[] encryptedKey = new byte[16];\n    byte[] dataIvBytes = new byte[12];\n    byte[] keyIvBytes = new byte[12];\n    byte[] keyAad = new byte[16];\n    byte[] dataAad = new byte[16];\n\n    assertDoesNotThrow(\n        () ->\n            storageClient.addEncryptionMetadataForGcm(\n                meta, matDesc, encryptedKey, dataIvBytes, keyIvBytes, keyAad, dataAad, 100L));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/StageInfoGcsCustomEndpointTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Optional;\nimport net.snowflake.client.providers.SnowflakeArgumentsProvider;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\npublic class StageInfoGcsCustomEndpointTest {\n\n  private static class DataProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return Arrays.asList(\n          Arguments.of(\"US-CENTRAL1\", false, null, Optional.empty()),\n          Arguments.of(\"US-CENTRAL1\", false, \"\", Optional.empty()),\n          Arguments.of(\"US-CENTRAL1\", false, \"null\", Optional.empty()),\n          Arguments.of(\"US-CENTRAL1\", false, \"    \", Optional.empty()),\n          Arguments.of(\"US-CENTRAL1\", false, \"example.com\", Optional.of(\"example.com\")),\n          Arguments.of(\n              \"ME-CENTRAL2\", false, null, Optional.of(\"storage.me-central2.rep.googleapis.com\")),\n          Arguments.of(\n              \"ME-CENTRAL2\", true, null, Optional.of(\"storage.me-central2.rep.googleapis.com\")),\n          Arguments.of(\n              \"ME-CENTRAL2\", true, \"\", Optional.of(\"storage.me-central2.rep.googleapis.com\")),\n          Arguments.of(\n              \"ME-CENTRAL2\", true, \"  \", Optional.of(\"storage.me-central2.rep.googleapis.com\")),\n          Arguments.of(\"ME-CENTRAL2\", true, \"example.com\", Optional.of(\"example.com\")),\n          Arguments.of(\n              \"US-CENTRAL1\", true, null, Optional.of(\"storage.us-central1.rep.googleapis.com\")),\n          Arguments.of(\n              \"US-CENTRAL1\", true, \"\", Optional.of(\"storage.us-central1.rep.googleapis.com\")),\n          Arguments.of(\n              \"US-CENTRAL1\", true, \" \", Optional.of(\"storage.us-central1.rep.googleapis.com\")),\n          Arguments.of(\n              \"US-CENTRAL1\", true, \"null\", Optional.of(\"storage.us-central1.rep.googleapis.com\")),\n          Arguments.of(\"US-CENTRAL1\", true, \"example.com\", Optional.of(\"example.com\")));\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(DataProvider.class)\n  public void shouldReturnEmptyGCSRegionalUrlWhenNotMeCentral1AndNotUseRegionalUrl(\n      String region, boolean useRegionalUrl, String endPoint, Optional<String> expectedHost) {\n    StageInfo stageInfo =\n        StageInfo.createStageInfo(\"GCS\", \"bla\", new HashMap<>(), region, endPoint, \"account\", true);\n    stageInfo.setUseRegionalUrl(useRegionalUrl);\n    assertEquals(expectedHost, stageInfo.gcsCustomEndpoint());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/cloud/storage/StorageProviderExceptionTest.java",
    "content": "package net.snowflake.client.internal.jdbc.cloud.storage;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport org.apache.http.HttpStatus;\nimport org.junit.jupiter.api.Test;\nimport software.amazon.awssdk.core.exception.SdkServiceException;\n\n/** Tests for test coverage of StorageProviderException class. */\npublic class StorageProviderExceptionTest {\n\n  /** Test should construct original provider exception. */\n  @Test\n  void testStorageProviderExceptionConstructor() {\n    Exception originalException = new Exception(\"Original exception\");\n    StorageProviderException exception = new StorageProviderException(originalException);\n    assertNotNull(exception);\n  }\n\n  /** Test should return original provider exception. */\n  @Test\n  void testStorageProviderExceptionShouldReturnOriginalException() {\n    Exception originalException = new Exception(\"Original exception\");\n    StorageProviderException exception = new StorageProviderException(originalException);\n    assertEquals(originalException, exception.getOriginalProviderException());\n  }\n\n  /**\n   * Test when the cause is an SdkServiceException with a 404 status code. This should return true\n   * since the status code matches HTTP 404.\n   */\n  @Test\n  void testStorageProviderExceptionWhenSdkServiceException404() {\n    SdkServiceException mockException = mock(SdkServiceException.class);\n    when(mockException.statusCode()).thenReturn(HttpStatus.SC_NOT_FOUND);\n    StorageProviderException exception = new StorageProviderException(mockException);\n    // Assert: check if the method returns true when status code is 404\n    assertTrue(exception.isServiceException404());\n  }\n\n  /**\n   * Test when the cause is an SdkServiceException with a status code other than 404. This should\n   * return false since the status code does not match HTTP 404.\n   */\n  @Test\n  void testStorageProviderExceptionWhenSdkServiceExceptionNot404() {\n    SdkServiceException mockException = mock(SdkServiceException.class);\n    when(mockException.statusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR);\n    StorageProviderException exception = new StorageProviderException(mockException);\n    // Assert: check if the method returns false when status code is not 404\n    assertFalse(exception.isServiceException404());\n  }\n\n  /**\n   * Test when the cause is not an SdkServiceException (e.g., a general Exception). This should\n   * return false since the cause is not an instance of SdkServiceException.\n   */\n  @Test\n  void testStorageProviderExceptionWhenNotSdkServiceException() {\n    Exception mockException = mock(Exception.class);\n    StorageProviderException exception = new StorageProviderException(mockException);\n    // Assert: check if the method returns false when the exception is not SdkServiceException\n    assertFalse(exception.isServiceException404());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/diagnostic/DiagnosticContextLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.net.InetSocketAddress;\nimport java.net.Proxy;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.SFSessionProperty;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.DIAGNOSTIC)\npublic class DiagnosticContextLatestIT {\n\n  private static final String HTTP_NON_PROXY_HOSTS = \"http.nonProxyHosts\";\n  private static final String HTTP_PROXY_HOST = \"http.proxyHost\";\n  private static final String HTTP_PROXY_PORT = \"http.proxyPort\";\n  private static final String HTTPS_PROXY_HOST = \"https.proxyHost\";\n  private static final String HTTPS_PROXY_PORT = \"https.proxyPort\";\n\n  private static String oldJvmNonProxyHosts;\n  private static String oldJvmHttpProxyHost;\n  private static String oldJvmHttpProxyPort;\n  private static String oldJvmHttpsProxyHost;\n  private static String oldJvmHttpsProxyPort;\n\n  @BeforeAll\n  public static void init() {\n    oldJvmNonProxyHosts = System.getProperty(HTTP_NON_PROXY_HOSTS);\n    oldJvmHttpProxyHost = System.getProperty(HTTP_PROXY_HOST);\n    oldJvmHttpProxyPort = System.getProperty(HTTP_PROXY_PORT);\n    oldJvmHttpsProxyHost = System.getProperty(HTTPS_PROXY_HOST);\n    oldJvmHttpsProxyPort = System.getProperty(HTTPS_PROXY_PORT);\n  }\n\n  @BeforeEach\n  public void clearJvmProperties() {\n    System.clearProperty(HTTP_NON_PROXY_HOSTS);\n    System.clearProperty(HTTP_PROXY_HOST);\n    System.clearProperty(HTTP_PROXY_PORT);\n    System.clearProperty(HTTPS_PROXY_HOST);\n    System.clearProperty(HTTPS_PROXY_PORT);\n  }\n  /**\n   * Check that all the mock Snowflake Endpoints we manually created exist in the array returned to\n   * us by the DiagnosticContext class which it generated after it parsed the allowlist.json file\n   * during initialization.\n   *\n   * <p>Test added in version > 3.16.1\n   */\n  @Test\n  public void parseAllowListFileTest() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n    File allowlistFile = new File(\"src/test/resources/allowlist.json\");\n\n    DiagnosticContext diagnosticContext =\n        new DiagnosticContext(allowlistFile.getAbsolutePath(), connectionPropertiesMap);\n    List<SnowflakeEndpoint> endpointsFromTestFile = diagnosticContext.getEndpoints();\n    List<SnowflakeEndpoint> mockEndpoints = new ArrayList<>();\n\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\"SNOWFLAKE_DEPLOYMENT\", \"account_name.snowflakecomputing.com\", 443));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\n            \"SNOWFLAKE_DEPLOYMENT_REGIONLESS\", \"org-account_name.snowflakecomputing.com\", 443));\n    mockEndpoints.add(new SnowflakeEndpoint(\"STAGE\", \"stage-bucket.s3.amazonaws.com\", 443));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\"STAGE\", \"stage-bucket.s3.us-west-2.amazonaws.com\", 443));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\"STAGE\", \"stage-bucket.s3-us-west-2.amazonaws.com\", 443));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\"SNOWSQL_REPO\", \"snowsql_repo.snowflakecomputing.com\", 443));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\n            \"OUT_OF_BAND_TELEMETRY\", \"out_of_band_telemetry.snowflakecomputing.com\", 443));\n    mockEndpoints.add(new SnowflakeEndpoint(\"OCSP_CACHE\", \"ocsp_cache.snowflakecomputing.com\", 80));\n    mockEndpoints.add(new SnowflakeEndpoint(\"DUO_SECURITY\", \"duo_security.duosecurity.com\", 443));\n    mockEndpoints.add(new SnowflakeEndpoint(\"OCSP_RESPONDER\", \"ocsp.rootg2.amazontrust.com\", 80));\n    mockEndpoints.add(new SnowflakeEndpoint(\"OCSP_RESPONDER\", \"o.ss2.us\", 80));\n    mockEndpoints.add(new SnowflakeEndpoint(\"OCSP_RESPONDER\", \"ocsp.sca1b.amazontrust.com\", 80));\n    mockEndpoints.add(new SnowflakeEndpoint(\"OCSP_RESPONDER\", \"ocsp.r2m01.amazontrust.com\", 80));\n    mockEndpoints.add(new SnowflakeEndpoint(\"OCSP_RESPONDER\", \"ocsp.rootca1.amazontrust.com\", 80));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\"SNOWSIGHT_DEPLOYMENT\", \"snowsight_deployment.snowflake.com\", 443));\n    mockEndpoints.add(\n        new SnowflakeEndpoint(\"SNOWSIGHT_DEPLOYMENT\", \"snowsight_deployment_2.snowflake.com\", 443));\n\n    String testFailedMessage =\n        \"The lists of SnowflakeEndpoints in mockEndpoints and endpointsFromTestFile should be identical\";\n    assertTrue(endpointsFromTestFile.containsAll(mockEndpoints), testFailedMessage);\n  }\n\n  /**\n   * Test that we correctly determine that proxy settings are absent from both the JVM and the\n   * connections parameters (i.e. empty strings for hostnames, or -1 for ports).\n   *\n   * <p>Test added in version > 3.16.1\n   */\n  @Test\n  public void testEmptyProxyConfig() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    assertFalse(diagnosticContext.isProxyEnabled(), \"Proxy configurations should be empty\");\n    assertTrue(\n        diagnosticContext.getHttpProxyHost().isEmpty(),\n        \"getHttpProxyHost() must return an empty string in the absence of proxy configuration\");\n    assertEquals(\n        -1,\n        diagnosticContext.getHttpProxyPort(),\n        \"getHttpProxyPort() must return -1 in the absence of proxy configuration\");\n    assertTrue(\n        diagnosticContext.getHttpsProxyHost().isEmpty(),\n        \"getHttpsProxyHost() must return an empty string in the absence of proxy configuration\");\n    assertEquals(\n        -1,\n        diagnosticContext.getHttpsProxyPort(),\n        \"getHttpsProxyPort() must return -1 in the absence of proxy configuration\");\n    assertTrue(\n        diagnosticContext.getHttpNonProxyHosts().isEmpty(),\n        \"getHttpNonProxyHosts() must return an empty string in the absence of proxy configuration\");\n  }\n\n  /** Test added in version > 3.16.1 */\n  @Test\n  public void testProxyConfigSetOnJvm() {\n    System.setProperty(HTTP_PROXY_HOST, \"http.proxyHost.com\");\n    System.setProperty(HTTP_PROXY_PORT, \"8080\");\n    System.setProperty(HTTPS_PROXY_HOST, \"https.proxyHost.com\");\n    System.setProperty(HTTPS_PROXY_PORT, \"8083\");\n    System.setProperty(HTTP_NON_PROXY_HOSTS, \"*.domain.com|localhost\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    assertTrue(diagnosticContext.isProxyEnabled());\n    assertTrue(diagnosticContext.isProxyEnabledOnJvm());\n    assertEquals(diagnosticContext.getHttpProxyHost(), \"http.proxyHost.com\");\n    assertEquals(diagnosticContext.getHttpProxyPort(), 8080);\n    assertEquals(diagnosticContext.getHttpsProxyHost(), \"https.proxyHost.com\");\n    assertEquals(diagnosticContext.getHttpsProxyPort(), 8083);\n    assertEquals(diagnosticContext.getHttpNonProxyHosts(), \"*.domain.com|localhost\");\n  }\n\n  /**\n   * If Proxy settings are passed using JVM arguments and connection parameters then the connection\n   * parameters take precedence.\n   *\n   * <p>Test added in version > 3.16.1\n   */\n  @Test\n  public void testProxyOverrideWithConnectionParameter() {\n\n    System.setProperty(HTTP_PROXY_HOST, \"http.proxyHost.com\");\n    System.setProperty(HTTP_PROXY_PORT, \"8080\");\n    System.setProperty(HTTPS_PROXY_HOST, \"https.proxyHost.com\");\n    System.setProperty(HTTPS_PROXY_PORT, \"8083\");\n    System.setProperty(HTTP_NON_PROXY_HOSTS, \"*.domain.com|localhost\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    connectionPropertiesMap.put(SFSessionProperty.PROXY_HOST, \"override.proxyHost.com\");\n    connectionPropertiesMap.put(SFSessionProperty.PROXY_PORT, \"80\");\n    connectionPropertiesMap.put(SFSessionProperty.NON_PROXY_HOSTS, \"*.new_domain.com|localhost\");\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    assertTrue(diagnosticContext.isProxyEnabled());\n    assertFalse(diagnosticContext.isProxyEnabledOnJvm());\n    assertEquals(diagnosticContext.getHttpProxyHost(), \"override.proxyHost.com\");\n    assertEquals(diagnosticContext.getHttpProxyPort(), 80);\n    assertEquals(diagnosticContext.getHttpsProxyHost(), \"override.proxyHost.com\");\n    assertEquals(diagnosticContext.getHttpsProxyPort(), 80);\n    assertEquals(diagnosticContext.getHttpNonProxyHosts(), \"*.new_domain.com|localhost\");\n  }\n\n  /** Test added in version > 3.16.1 */\n  @Test\n  public void testGetProxy() {\n    System.setProperty(HTTP_PROXY_HOST, \"http.proxyHost.com\");\n    System.setProperty(HTTP_PROXY_PORT, \"8080\");\n    System.setProperty(HTTPS_PROXY_HOST, \"https.proxyHost.com\");\n    System.setProperty(HTTPS_PROXY_PORT, \"8083\");\n    System.setProperty(HTTP_NON_PROXY_HOSTS, \"*.domain.com|localhost|*.snowflakecomputing.com\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    String httpProxyHost = diagnosticContext.getHttpProxyHost();\n    int httpProxyPort = diagnosticContext.getHttpProxyPort();\n    String httpsProxyHost = diagnosticContext.getHttpsProxyHost();\n    int httpsProxyPort = diagnosticContext.getHttpsProxyPort();\n\n    SnowflakeEndpoint httpsHostBypassingProxy =\n        new SnowflakeEndpoint(\"SNOWFLAKE_DEPLOYMENT\", \"account_name.snowflakecomputing.com\", 443);\n    SnowflakeEndpoint httpHostBypassingProxy =\n        new SnowflakeEndpoint(\"OCSP_CACHE\", \"ocsp_cache.snowflakecomputing.com\", 80);\n    SnowflakeEndpoint hostWithHttpProxy =\n        new SnowflakeEndpoint(\"OCSP_RESPONDER\", \"ocsp.rootg2.amazontrust.com\", 80);\n    SnowflakeEndpoint hostWithHttpsProxy =\n        new SnowflakeEndpoint(\"STAGE\", \"stage-bucket.s3-us-west-2.amazonaws.com\", 443);\n\n    Proxy byPassProxy = Proxy.NO_PROXY;\n    Proxy httpProxy =\n        new Proxy(Proxy.Type.HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort));\n    Proxy httpsProxy =\n        new Proxy(Proxy.Type.HTTP, new InetSocketAddress(httpsProxyHost, httpsProxyPort));\n\n    assertEquals(byPassProxy, diagnosticContext.getProxy(httpsHostBypassingProxy));\n    assertEquals(byPassProxy, diagnosticContext.getProxy(httpHostBypassingProxy));\n    assertEquals(httpProxy, diagnosticContext.getProxy(hostWithHttpProxy));\n    assertEquals(httpsProxy, diagnosticContext.getProxy(hostWithHttpsProxy));\n  }\n\n  /**\n   * Test that we correctly create direct HTTPS connections and only route HTTP requests through a\n   * proxy server when we set only the -Dhttp.proxyHost and -Dhttp.proxyPort arguments\n   *\n   * <p>Test added in version > 3.16.1\n   */\n  @Test\n  public void testGetHttpProxyOnly() {\n    System.setProperty(HTTP_PROXY_HOST, \"http.proxyHost.com\");\n    System.setProperty(HTTP_PROXY_PORT, \"8080\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    System.clearProperty(HTTP_PROXY_HOST);\n    System.clearProperty(HTTP_PROXY_PORT);\n\n    String httpProxyHost = diagnosticContext.getHttpProxyHost();\n    int httpProxyPort = diagnosticContext.getHttpProxyPort();\n\n    Proxy noProxy = Proxy.NO_PROXY;\n    Proxy httpProxy =\n        new Proxy(Proxy.Type.HTTP, new InetSocketAddress(httpProxyHost, httpProxyPort));\n\n    SnowflakeEndpoint httpsHostDirectConnection =\n        new SnowflakeEndpoint(\"SNOWFLAKE_DEPLOYMENT\", \"account_name.snowflakecomputing.com\", 443);\n    SnowflakeEndpoint httpHostProxy =\n        new SnowflakeEndpoint(\"OCSP_CACHE\", \"ocsp_cache.snowflakecomputing.com\", 80);\n\n    assertEquals(noProxy, diagnosticContext.getProxy(httpsHostDirectConnection));\n    assertEquals(httpProxy, diagnosticContext.getProxy(httpHostProxy));\n  }\n\n  /**\n   * Test that we correctly create direct HTTP connections and only route HTTPS through a proxy\n   * server when we set only the -Dhttps.proxyHost and -Dhttps.proxyPort parameters\n   *\n   * <p>Test added in version > 3.16.1\n   */\n  @Test\n  public void testGetHttpsProxyOnly() {\n    System.setProperty(HTTPS_PROXY_HOST, \"https.proxyHost.com\");\n    System.setProperty(HTTPS_PROXY_PORT, \"8083\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    String httpsProxyHost = diagnosticContext.getHttpsProxyHost();\n    int httpsProxyPort = diagnosticContext.getHttpsProxyPort();\n\n    Proxy noProxy = Proxy.NO_PROXY;\n    Proxy httpsProxy =\n        new Proxy(Proxy.Type.HTTP, new InetSocketAddress(httpsProxyHost, httpsProxyPort));\n\n    SnowflakeEndpoint httpsHostProxy =\n        new SnowflakeEndpoint(\"SNOWFLAKE_DEPLOYMENT\", \"account_name.snowflakecomputing.com\", 443);\n    SnowflakeEndpoint httpHostDirectConnection =\n        new SnowflakeEndpoint(\"OCSP_CACHE\", \"ocsp_cache.snowflakecomputing.com\", 80);\n\n    assertEquals(noProxy, diagnosticContext.getProxy(httpHostDirectConnection));\n    assertEquals(httpsProxy, diagnosticContext.getProxy(httpsHostProxy));\n  }\n\n  /**\n   * Test that we create a direct connection to every host even though the JVM arguments are set. We\n   * override the JVM arguments with the nonProxyHosts connection parameter.\n   *\n   * <p>Test added in version > 3.16.1\n   */\n  @Test\n  public void testgetNoProxyAfterOverridingJvm() {\n    System.setProperty(HTTPS_PROXY_HOST, \"https.proxyHost.com\");\n    System.setProperty(HTTPS_PROXY_PORT, \"8083\");\n    System.setProperty(HTTP_PROXY_HOST, \"http.proxyHost.com\");\n    System.setProperty(HTTP_PROXY_PORT, \"8080\");\n\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n\n    connectionPropertiesMap.put(SFSessionProperty.PROXY_HOST, \"override.proxyHost.com\");\n    connectionPropertiesMap.put(SFSessionProperty.PROXY_PORT, \"80\");\n    connectionPropertiesMap.put(SFSessionProperty.NON_PROXY_HOSTS, \"*\");\n\n    DiagnosticContext diagnosticContext = new DiagnosticContext(connectionPropertiesMap);\n\n    Proxy noProxy = Proxy.NO_PROXY;\n\n    SnowflakeEndpoint host1 =\n        new SnowflakeEndpoint(\"SNOWFLAKE_DEPLOYMENT\", \"account_name.snowflakecomputing.com\", 443);\n    SnowflakeEndpoint host2 =\n        new SnowflakeEndpoint(\"OCSP_CACHE\", \"ocsp_cache.snowflakecomputing.com\", 80);\n    SnowflakeEndpoint host3 =\n        new SnowflakeEndpoint(\n            \"SNOWFLAKE_DEPLOYMENT\", \"account_name.privatelink.snowflakecomputing.com\", 443);\n    SnowflakeEndpoint host4 =\n        new SnowflakeEndpoint(\"STAGE\", \"stage-bucket.s3-us-west-2.amazonaws.com\", 443);\n\n    assertEquals(noProxy, diagnosticContext.getProxy(host1));\n    assertEquals(noProxy, diagnosticContext.getProxy(host2));\n    assertEquals(noProxy, diagnosticContext.getProxy(host3));\n    assertEquals(noProxy, diagnosticContext.getProxy(host4));\n  }\n\n  @AfterEach\n  public void restoreJvmArguments() {\n    System.clearProperty(HTTP_NON_PROXY_HOSTS);\n    System.clearProperty(HTTP_PROXY_HOST);\n    System.clearProperty(HTTP_PROXY_PORT);\n    System.clearProperty(HTTPS_PROXY_HOST);\n    System.clearProperty(HTTPS_PROXY_PORT);\n\n    if (oldJvmNonProxyHosts != null) {\n      System.setProperty(HTTP_NON_PROXY_HOSTS, oldJvmNonProxyHosts);\n    }\n    if (oldJvmHttpProxyHost != null) {\n      System.setProperty(HTTP_PROXY_HOST, oldJvmHttpProxyHost);\n    }\n    if (oldJvmHttpProxyPort != null) {\n      System.setProperty(HTTP_PROXY_PORT, oldJvmHttpProxyPort);\n    }\n    if (oldJvmHttpsProxyHost != null) {\n      System.setProperty(HTTPS_PROXY_HOST, oldJvmHttpsProxyHost);\n    }\n    if (oldJvmHttpsProxyPort != null) {\n      System.getProperty(HTTPS_PROXY_PORT, oldJvmHttpsProxyPort);\n    }\n  }\n\n  /** Test added in version > 3.16.1 */\n  @Test\n  public void testRunDiagnosticContextMethods() {\n    Map<SFSessionProperty, Object> connectionPropertiesMap = new HashMap<>();\n    File allowlistFile = new File(\"src/test/resources/allowlist.json\");\n\n    DiagnosticContext diagnosticContext =\n        new DiagnosticContext(allowlistFile.getAbsolutePath(), connectionPropertiesMap);\n    diagnosticContext.runDiagnostics();\n    diagnosticContext.logEnvironmentInfo();\n    assertNotNull(diagnosticContext.getEndpoints());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/diagnostic/SnowflakeEndpointTest.java",
    "content": "package net.snowflake.client.internal.jdbc.diagnostic;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\n\npublic class SnowflakeEndpointTest {\n\n  @Test\n  public void shouldDetectPrivateLinkEndpoint() {\n    Map<String, Boolean> hostsToPrivateLinks = new HashMap<>();\n    hostsToPrivateLinks.put(\"snowhouse.snowflakecomputing.com\", false);\n    hostsToPrivateLinks.put(\"snowhouse.privatelink.snowflakecomputing.com\", true);\n    hostsToPrivateLinks.put(\"snowhouse.snowflakecomputing.cn\", false);\n    hostsToPrivateLinks.put(\"snowhouse.PRIVATELINK.snowflakecomputing.cn\", true);\n\n    hostsToPrivateLinks.forEach(\n        (host, expectedToBePrivateLink) -> {\n          SnowflakeEndpoint endpoint = new SnowflakeEndpoint(\"SNOWFLAKE_DEPLOYMENT\", host, 443);\n          assertEquals(\n              expectedToBePrivateLink,\n              endpoint.isPrivateLink(),\n              String.format(\"Expecting %s to be private link: %s\", host, expectedToBePrivateLink));\n        });\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/ResultSetStructuredTypesLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.math.BigDecimal;\nimport java.sql.Connection;\nimport java.sql.Date;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport java.sql.Types;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.time.ZoneId;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.ThrowingConsumer;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetMetaData;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.resultset.SnowflakeBaseResultSet;\nimport net.snowflake.client.internal.core.structs.SnowflakeObjectTypeFactories;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.ResultSetFormatType;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.AllTypesClass;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.NestedStructSqlData;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.NullableFieldsSqlData;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.SimpleClass;\nimport net.snowflake.client.internal.jdbc.structuredtypes.sqldata.StringClass;\nimport net.snowflake.client.providers.ResultFormatProvider;\nimport org.junit.Assert;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.RESULT_SET)\npublic class ResultSetStructuredTypesLatestIT extends BaseJDBCTest {\n  @BeforeEach\n  public void setup() {\n    SnowflakeObjectTypeFactories.register(StringClass.class, StringClass::new);\n    SnowflakeObjectTypeFactories.register(SimpleClass.class, SimpleClass::new);\n    SnowflakeObjectTypeFactories.register(AllTypesClass.class, AllTypesClass::new);\n    SnowflakeObjectTypeFactories.register(NullableFieldsSqlData.class, NullableFieldsSqlData::new);\n  }\n\n  @AfterEach\n  public void clean() {\n    SnowflakeObjectTypeFactories.unregister(StringClass.class);\n    SnowflakeObjectTypeFactories.unregister(SimpleClass.class);\n    SnowflakeObjectTypeFactories.unregister(AllTypesClass.class);\n    SnowflakeObjectTypeFactories.unregister(NullableFieldsSqlData.class);\n  }\n\n  public Connection init(ResultSetFormatType format) throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection(BaseJDBCTest.DONT_INJECT_SOCKET_TIMEOUT);\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_IN_CLIENT_RESPONSE = true\");\n      stmt.execute(\"alter session set IGNORE_CLIENT_VESRION_IN_STRUCTURED_TYPES_RESPONSE = true\");\n      stmt.execute(\"ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'\");\n      stmt.execute(\n          \"alter session set jdbc_query_result_format = '\"\n              + format.sessionParameterTypeValue\n              + \"'\");\n      stmt.execute(\"ALTER SESSION SET ENABLE_STRUCTURED_TYPES_IN_FDN_TABLES = TRUE\");\n      if (format == ResultSetFormatType.NATIVE_ARROW) {\n        stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true\");\n        stmt.execute(\"alter session set FORCE_ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true\");\n      }\n    }\n    return conn;\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapStructToObjectWithFactory(ResultSetFormatType format) throws SQLException {\n    testMapJson(true, format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapStructToObjectWithReflection(ResultSetFormatType format) throws SQLException {\n    testMapJson(false, format);\n    testMapJson(true, format);\n  }\n\n  private void testMapJson(boolean registerFactory, ResultSetFormatType format)\n      throws SQLException {\n    if (registerFactory) {\n      SnowflakeObjectTypeFactories.register(StringClass.class, StringClass::new);\n    } else {\n      SnowflakeObjectTypeFactories.unregister(StringClass.class);\n    }\n    withFirstRow(\n        \"select {'string':'a'}::OBJECT(string VARCHAR)\",\n        (resultSet) -> {\n          StringClass object = resultSet.getObject(1, StringClass.class);\n          assertEquals(\"a\", object.getString());\n        },\n        format);\n    SnowflakeObjectTypeFactories.register(StringClass.class, StringClass::new);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapNullStruct(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select null::OBJECT(string VARCHAR)\",\n        (resultSet) -> {\n          StringClass object = resultSet.getObject(1, StringClass.class);\n          assertNull(object);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapStructAllTypes(ResultSetFormatType format) throws SQLException {\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'\");\n      statement.execute(\n          \"ALTER SESSION SET TIMESTAMP_OUTPUT_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM'\");\n      try (ResultSet resultSet = statement.executeQuery(AllTypesClass.ALL_TYPES_QUERY); ) {\n        resultSet.next();\n        AllTypesClass object = resultSet.getObject(1, AllTypesClass.class);\n        assertEquals(\"a\", object.getString());\n        assertEquals(new Byte(\"1\"), object.getB());\n        assertEquals(Short.valueOf(\"2\"), object.getS());\n        assertEquals(Integer.valueOf(3), object.getI());\n        assertEquals(Long.valueOf(4), object.getL());\n        assertEquals(Float.valueOf(1.1f), object.getF(), 0.01);\n        assertEquals(Double.valueOf(2.2), object.getD(), 0.01);\n        assertEquals(BigDecimal.valueOf(3.3), object.getBd());\n        assertEquals(\n            LocalDateTime.of(2021, 12, 22, 9, 43, 44)\n                .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                .toInstant(),\n            object.getTimestampLtz().toInstant());\n        assertEquals(\n            Timestamp.valueOf(LocalDateTime.of(2021, 12, 23, 9, 44, 44)), object.getTimestampNtz());\n        assertEquals(\n            LocalDateTime.of(2021, 12, 24, 2, 45, 45)\n                .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                .toInstant(),\n            object.getTimestampTz().toInstant());\n        // TODO uncomment after merge SNOW-928973: Date field is returning one day less when getting\n        // through getString method\n        //                assertEquals(\n        //                    Date.valueOf(LocalDate.of(2023, 12, 24)).toString(),\n        //         object.getDate().toString());\n        assertEquals(Time.valueOf(LocalTime.of(12, 34, 56)), object.getTime());\n        assertArrayEquals(new byte[] {'a', 'b', 'c'}, object.getBinary());\n        assertTrue(object.getBool());\n        assertEquals(\"b\", object.getSimpleClass().getString());\n        assertEquals(Integer.valueOf(2), object.getSimpleClass().getIntValue());\n\n        if (format == ResultSetFormatType.NATIVE_ARROW) {\n          // Only verify getString for Arrow since JSON representations have difficulties with\n          // floating point toString conversion (3.300000000000000e+00 vs 3.3 in native arrow)\n          String expectedArrowGetStringResult =\n              \"{\\\"string\\\": \\\"a\\\",\\\"b\\\": 1,\\\"s\\\": 2,\\\"i\\\": 3,\\\"l\\\": 4,\\\"f\\\": 1.1,\\\"d\\\": 2.2,\\\"bd\\\": 3.3,\\\"bool\\\": true,\\\"timestamp_ltz\\\": \\\"2021-12-22 09:43:44.000 +0100\\\",\\\"timestamp_ntz\\\": \\\"2021-12-23 09:44:44.000 Z\\\",\\\"timestamp_tz\\\": \\\"2021-12-24 09:45:45.000 +0800\\\",\\\"date\\\": \\\"2023-12-24\\\",\\\"time\\\": \\\"12:34:56\\\",\\\"binary\\\": \\\"616263\\\",\\\"simpleClass\\\": {\\\"string\\\": \\\"b\\\",\\\"intValue\\\": 2}}\";\n          assertEquals(expectedArrowGetStringResult, resultSet.getString(1));\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnStructAsStringIfTypeWasNotIndicated(ResultSetFormatType format)\n      throws SQLException {\n    Assumptions.assumeTrue(format != ResultSetFormatType.NATIVE_ARROW);\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\n          \"alter session set \"\n              + \"TIMEZONE='Europe/Warsaw',\"\n              + \"TIME_OUTPUT_FORMAT = 'HH24:MI:SS',\"\n              + \"DATE_OUTPUT_FORMAT = 'YYYY-MM-DD',\"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='YYYY-MM-DD HH24:MI:SS.FF3'\");\n\n      try (ResultSet resultSet = statement.executeQuery(AllTypesClass.ALL_TYPES_QUERY); ) {\n        resultSet.next();\n        String object = (String) resultSet.getObject(1);\n        String expectedJson =\n            \"{\\n\"\n                + \"  \\\"string\\\": \\\"a\\\",\\n\"\n                + \"  \\\"b\\\": 1,\\n\"\n                + \"  \\\"s\\\": 2,\\n\"\n                + \"  \\\"i\\\": 3,\\n\"\n                + \"  \\\"l\\\": 4,\\n\"\n                + \"  \\\"f\\\": 1.100000000000000e+00,\\n\"\n                + \"  \\\"d\\\": 2.200000000000000e+00,\\n\"\n                + \"  \\\"bd\\\": 3.300000000000000e+00,\\n\"\n                + \"  \\\"bool\\\": true,\\n\"\n                + \"  \\\"timestamp_ltz\\\": \\\"2021-12-22 09:43:44.000 +0100\\\",\\n\"\n                + \"  \\\"timestamp_ntz\\\": \\\"2021-12-23 09:44:44.000\\\",\\n\"\n                + \"  \\\"timestamp_tz\\\": \\\"2021-12-24 09:45:45.000 +0800\\\",\\n\"\n                + \"  \\\"date\\\": \\\"2023-12-24\\\",\\n\"\n                + \"  \\\"time\\\": \\\"12:34:56\\\",\\n\"\n                + \"  \\\"binary\\\": \\\"616263\\\",\\n\"\n                + \"  \\\"simpleClass\\\": {\\n\"\n                + \"    \\\"string\\\": \\\"b\\\",\\n\"\n                + \"    \\\"intValue\\\": 2\\n\"\n                + \"  }\\n\"\n                + \"}\";\n        String expectedJsonFromArrow =\n            \"{\\\"string\\\": \\\"a\\\",\\\"b\\\": 1,\\\"s\\\": 2,\\\"i\\\": 3,\\\"l\\\": 4,\\\"f\\\": 1.1,\\\"d\\\": 2.2,\\\"bd\\\": 3.3,\"\n                + \"\\\"bool\\\": true,\\\"timestamp_ltz\\\": \\\"2021-12-22 09:43:44.000 +0100\\\",\\\"timestamp_ntz\\\": \\\"2021-12-23 09:44:44.000\\\",\"\n                + \"\\\"timestamp_tz\\\": \\\"2021-12-24 09:45:45.000 +0800\\\",\\\"date\\\": \\\"2023-12-24\\\",\\\"time\\\": \\\"12:34:56\\\",\\\"binary\\\": \\\"616263\\\",\"\n                + \"\\\"simpleClass\\\": {\\\"string\\\": \\\"b\\\",\\\"intValue\\\": 2}}\";\n        if (format == ResultSetFormatType.NATIVE_ARROW) {\n          Assert.assertEquals(expectedJsonFromArrow, object);\n        } else {\n          Assert.assertEquals(expectedJson, object);\n        }\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsArrayOfSqlData(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT({'string':'one'}, {'string':'two'}, {'string':'three'})::ARRAY(OBJECT(string VARCHAR))\",\n        (resultSet) -> {\n          StringClass[] resultArray =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, StringClass.class);\n          assertEquals(\"one\", resultArray[0].getString());\n          assertEquals(\"two\", resultArray[1].getString());\n          assertEquals(\"three\", resultArray[2].getString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsArrayOfNullableFieldsInSqlData(ResultSetFormatType format)\n      throws SQLException {\n    withFirstRow(\n        \"SELECT OBJECT_CONSTRUCT_KEEP_NULL('string', null, 'nullableIntValue', null, 'nullableLongValue', null, \"\n            + \"'date', null, 'bd', null, 'bytes', null, 'longValue', null)\"\n            + \"::OBJECT(string VARCHAR, nullableIntValue INTEGER, nullableLongValue INTEGER, date DATE, bd DOUBLE, bytes BINARY, longValue INTEGER)\",\n        (resultSet) -> {\n          NullableFieldsSqlData result = resultSet.getObject(1, NullableFieldsSqlData.class);\n          assertNull(result.getString());\n          assertNull(result.getNullableIntValue());\n          assertNull(result.getNullableLongValue());\n          assertNull(result.getDate());\n          assertNull(result.getBd());\n          assertNull(result.getBytes());\n          assertEquals(Long.valueOf(0), result.getLongValue());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnNullsForAllTpesInSqlData(ResultSetFormatType format) throws SQLException {\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'\");\n      try (ResultSet resultSet =\n          statement.executeQuery(\n              \"SELECT OBJECT_CONSTRUCT_KEEP_NULL('string', null, 'b', null, 's', null, 'i', null, 'l', null, 'f', null,'d', null, 'bd', null, 'bool', null,\"\n                  + \" 'timestamp_ltz', null, 'timestamp_ntz', null, 'timestamp_tz', null, 'date', null, 'time', null, 'binary', null, 'StringClass', null)\"\n                  + \"::OBJECT(string VARCHAR, b TINYINT, s SMALLINT, i INTEGER, l BIGINT, f FLOAT, d DOUBLE, bd DOUBLE, bool BOOLEAN, timestamp_ltz TIMESTAMP_LTZ, \"\n                  + \"timestamp_ntz TIMESTAMP_NTZ, timestamp_tz TIMESTAMP_TZ, date DATE, time TIME, binary BINARY, StringClass OBJECT(string VARCHAR))\"); ) {\n        resultSet.next();\n        AllTypesClass object = resultSet.getObject(1, AllTypesClass.class);\n        assertNull(object.getString());\n        assertNull(object.getB());\n        assertNull(object.getS());\n        assertNull(object.getI());\n        assertNull(object.getL());\n        assertNull(object.getF());\n        assertNull(object.getD());\n        assertNull(object.getBd());\n        assertNull(object.getTimestampLtz());\n        assertNull(object.getTimestampNtz());\n        assertNull(object.getTimestampTz());\n        assertNull(object.getDate());\n        assertNull(object.getTime());\n        assertNull(object.getBinary());\n        assertNull(object.getBool());\n        assertNull(object.getSimpleClass());\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsArrayOfString(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT('one', 'two','three')::ARRAY(VARCHAR)\",\n        (resultSet) -> {\n          String[] resultArray =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, String.class);\n          assertEquals(\"one\", resultArray[0]);\n          assertEquals(\"two\", resultArray[1]);\n          assertEquals(\"three\", resultArray[2]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsArrayOfNullableString(ResultSetFormatType format) throws SQLException {\n    Assumptions.assumeTrue(format == ResultSetFormatType.NATIVE_ARROW);\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT('one', 'two', null)::ARRAY(VARCHAR)\",\n        (resultSet) -> {\n          String[] resultArray =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, String.class);\n          assertEquals(\"one\", resultArray[0]);\n          assertEquals(\"two\", resultArray[1]);\n          assertNull(resultArray[2]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnNullAsArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT null::ARRAY(VARCHAR)\",\n        (resultSet) -> {\n          String[] resultArray =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, String.class);\n          assertNull(resultArray);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsListOfIntegers(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(1,2,3)::ARRAY(INTEGER)\",\n        (resultSet) -> {\n          List<Integer> resultList =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, Integer.class);\n          assertEquals(Integer.valueOf(1), resultList.get(0));\n          assertEquals(Integer.valueOf(2), resultList.get(1));\n          assertEquals(Integer.valueOf(3), resultList.get(2));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsListOfFloat(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(1.1,2.2,3.3)::ARRAY(FLOAT)\",\n        (resultSet) -> {\n          Float[] resultList =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getArray(1, Float.class);\n          assertEquals(Float.valueOf(1.1f), resultList[0]);\n          assertEquals(Float.valueOf(2.2f), resultList[1]);\n          assertEquals(Float.valueOf(3.3f), resultList[2]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsListOfDouble(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(1.1,2.2,3.3)::ARRAY(DOUBLE)\",\n        (resultSet) -> {\n          List<Double> resultList =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, Double.class);\n          assertEquals(Double.valueOf(1.1), resultList.get(0));\n          assertEquals(Double.valueOf(2.2), resultList.get(1));\n          assertEquals(Double.valueOf(3.3), resultList.get(2));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMap(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select {'x':{'string':'one'},'y':{'string':'two'},'z':{'string':'three'}}::MAP(VARCHAR, OBJECT(string VARCHAR));\",\n        (resultSet) -> {\n          Map<String, StringClass> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, StringClass.class);\n          assertEquals(\"one\", map.get(\"x\").getString());\n          assertEquals(\"two\", map.get(\"y\").getString());\n          assertEquals(\"three\", map.get(\"z\").getString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapByGetObject(ResultSetFormatType format) throws SQLException {\n    Assumptions.assumeTrue(format != ResultSetFormatType.NATIVE_ARROW);\n    withFirstRow(\n        \"select {'x':{'string':'one'},'y':{'string':'two'},'z':{'string':'three'}}::MAP(VARCHAR, OBJECT(string VARCHAR));\",\n        (resultSet) -> {\n          Map<String, Map<String, Object>> map = resultSet.getObject(1, Map.class);\n          assertEquals(\"one\", map.get(\"x\").get(\"string\"));\n          assertEquals(\"two\", map.get(\"y\").get(\"string\"));\n          assertEquals(\"three\", map.get(\"z\").get(\"string\"));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapWithNullableValues(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select {'x':{'string':'one'},'y':null,'z':{'string':'three'}}::MAP(VARCHAR, OBJECT(string VARCHAR));\",\n        (resultSet) -> {\n          Map<String, StringClass> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, StringClass.class);\n          assertEquals(\"one\", map.get(\"x\").getString());\n          assertNull(map.get(\"y\"));\n          assertEquals(\"three\", map.get(\"z\").getString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnNullAsObjectOfTypeMap(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select null::MAP(VARCHAR, OBJECT(string VARCHAR));\",\n        (resultSet) -> {\n          Map<String, Object> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getObject(1, Map.class);\n          assertNull(map);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnNullAsMap(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select null::MAP(VARCHAR, OBJECT(string VARCHAR));\",\n        (resultSet) -> {\n          Map<String, StringClass> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, StringClass.class);\n          assertNull(map);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapOfTimestampsNtz(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT {'x': TO_TIMESTAMP_NTZ('2021-12-23 09:44:44'), 'y': TO_TIMESTAMP_NTZ('2021-12-24 09:55:55')}::MAP(VARCHAR, TIMESTAMP)\",\n        (resultSet) -> {\n          Map<String, Timestamp> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Timestamp.class);\n          assertEquals(\n              LocalDateTime.of(2021, 12, 23, 9, 44, 44)\n                  .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                  .toInstant(),\n              map.get(\"x\").toInstant());\n          assertEquals(\n              LocalDateTime.of(2021, 12, 24, 9, 55, 55)\n                  .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                  .toInstant(),\n              map.get(\"y\").toInstant());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapOfTimestampsLtz(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT {'x': TO_TIMESTAMP_LTZ('2021-12-23 09:44:44'), 'y': TO_TIMESTAMP_LTZ('2021-12-24 09:55:55')}::MAP(VARCHAR, TIMESTAMP_LTZ)\",\n        (resultSet) -> {\n          Map<String, Timestamp> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Timestamp.class);\n          assertEquals(\n              LocalDateTime.of(2021, 12, 23, 9, 44, 44)\n                  .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                  .toInstant(),\n              map.get(\"x\").toInstant());\n          assertEquals(\n              LocalDateTime.of(2021, 12, 24, 9, 55, 55)\n                  .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                  .toInstant(),\n              map.get(\"y\").toInstant());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapOfLong(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT {'x':1, 'y':2, 'z':3}::MAP(VARCHAR, BIGINT)\",\n        (resultSet) -> {\n          Map<String, Long> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Long.class);\n          assertEquals(Long.valueOf(1), map.get(\"x\"));\n          assertEquals(Long.valueOf(2), map.get(\"y\"));\n          assertEquals(Long.valueOf(3), map.get(\"z\"));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapOfDate(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT {'x':'2023-12-24', 'y':'2023-12-25'}::MAP(VARCHAR, DATE)\",\n        (resultSet) -> {\n          Map<String, Date> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Date.class);\n          assertEquals(\n              Date.valueOf(LocalDate.of(2023, 12, 24)).toString(), map.get(\"x\").toString());\n          assertEquals(\n              Date.valueOf(LocalDate.of(2023, 12, 25)).toString(), map.get(\"y\").toString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapOfTime(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT {'x':'12:34:56', 'y':'12:34:58'}::MAP(VARCHAR, TIME)\",\n        (resultSet) -> {\n          Map<String, Time> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Time.class);\n          assertEquals(Time.valueOf(LocalTime.of(12, 34, 56)), map.get(\"x\"));\n          assertEquals(Time.valueOf(LocalTime.of(12, 34, 58)), map.get(\"y\"));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsMapOfBoolean(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT {'x':'true', 'y':0}::MAP(VARCHAR, BOOLEAN)\",\n        (resultSet) -> {\n          Map<String, Boolean> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getMap(1, Boolean.class);\n          assertEquals(Boolean.TRUE, map.get(\"x\"));\n          assertEquals(Boolean.FALSE, map.get(\"y\"));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testReturnAsList(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select [{'string':'one'},{'string': 'two'}]::ARRAY(OBJECT(string varchar))\",\n        (resultSet) -> {\n          List<StringClass> map =\n              resultSet.unwrap(SnowflakeBaseResultSet.class).getList(1, StringClass.class);\n          assertEquals(\"one\", map.get(0).getString());\n          assertEquals(\"two\", map.get(1).getString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapStructsFromChunks(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"select {'string':'a'}::OBJECT(string VARCHAR) FROM TABLE(GENERATOR(ROWCOUNT=>30000))\",\n        (resultSet) -> {\n          while (resultSet.next()) {\n            StringClass object = resultSet.getObject(1, StringClass.class);\n            assertEquals(\"a\", object.getString());\n          }\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapIntegerArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(INTEGER)\",\n        (resultSet) -> {\n          Long[] resultArray = (Long[]) resultSet.getArray(1).getArray();\n          assertEquals(Long.valueOf(10), resultArray[0]);\n          assertEquals(Long.valueOf(20), resultArray[1]);\n          assertEquals(Long.valueOf(30), resultArray[2]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapIntegerArrayGetObject(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(INTEGER)\",\n        (resultSet) -> {\n          Object resultArray = resultSet.getObject(1);\n          TestUtil.assertEqualsIgnoringWhitespace(\"[10,20,30]\", (String) resultArray);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapFixedToLongArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(10, 20, 30)::ARRAY(SMALLINT)\",\n        (resultSet) -> {\n          Long[] resultArray = (Long[]) resultSet.getArray(1).getArray();\n          assertEquals(Long.valueOf(\"10\"), resultArray[0]);\n          assertEquals(Long.valueOf(\"20\"), resultArray[1]);\n          assertEquals(Long.valueOf(\"30\"), resultArray[2]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapDecimalArray(ResultSetFormatType format) throws SQLException {\n    //    when: jdbc_treat_decimal_as_int=true scale=0\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement();\n        ResultSet resultSet =\n            statement.executeQuery(\n                \"SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,0))\"); ) {\n      resultSet.next();\n      Long[] resultArray = (Long[]) resultSet.getArray(1).getArray();\n      assertEquals(resultArray[0], Long.valueOf(10));\n      assertEquals(resultArray[1], Long.valueOf(20));\n      assertEquals(resultArray[2], Long.valueOf(30));\n    }\n\n    //    when: jdbc_treat_decimal_as_int=true scale=2\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement();\n        ResultSet resultSet =\n            statement.executeQuery(\n                \"SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,2))\"); ) {\n      resultSet.next();\n      BigDecimal[] resultArray2 = (BigDecimal[]) resultSet.getArray(1).getArray();\n      assertEquals(BigDecimal.valueOf(10.20).doubleValue(), resultArray2[0].doubleValue(), 0);\n      assertEquals(BigDecimal.valueOf(20.02).doubleValue(), resultArray2[1].doubleValue(), 0);\n      assertEquals(BigDecimal.valueOf(30.00).doubleValue(), resultArray2[2].doubleValue(), 0);\n    }\n\n    //    when: jdbc_treat_decimal_as_int=false scale=0\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement(); ) {\n      statement.execute(\"alter session set jdbc_treat_decimal_as_int = false\");\n      try (ResultSet resultSet =\n          statement.executeQuery(\"SELECT ARRAY_CONSTRUCT(10.2, 20.02, 30)::ARRAY(DECIMAL(20,0))\")) {\n        resultSet.next();\n        BigDecimal[] resultArray = (BigDecimal[]) resultSet.getArray(1).getArray();\n        assertEquals(BigDecimal.valueOf(10), resultArray[0]);\n        assertEquals(BigDecimal.valueOf(20), resultArray[1]);\n        assertEquals(BigDecimal.valueOf(30), resultArray[2]);\n      }\n    }\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapVarcharArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT 'text', ARRAY_CONSTRUCT('10', '20','30')::ARRAY(VARCHAR)\",\n        (resultSet) -> {\n          String t = resultSet.getString(1);\n          String[] resultArray = (String[]) resultSet.getArray(2).getArray();\n          assertEquals(\"10\", resultArray[0]);\n          assertEquals(\"20\", resultArray[1]);\n          assertEquals(\"30\", resultArray[2]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapDatesArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(to_date('2023-12-24', 'YYYY-MM-DD'), to_date('2023-12-25', 'YYYY-MM-DD'))::ARRAY(DATE)\",\n        (resultSet) -> {\n          Date[] resultArray = (Date[]) resultSet.getArray(1).getArray();\n          assertEquals(\n              Date.valueOf(LocalDate.of(2023, 12, 24)).toString(), resultArray[0].toString());\n          assertEquals(\n              Date.valueOf(LocalDate.of(2023, 12, 25)).toString(), resultArray[1].toString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapTimeArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(to_time('15:39:20.123'), to_time('09:12:20.123'))::ARRAY(TIME)\",\n        (resultSet) -> {\n          Time[] resultArray = (Time[]) resultSet.getArray(1).getArray();\n          assertEquals(\n              Time.valueOf(LocalTime.of(15, 39, 20)).toString(), resultArray[0].toString());\n          assertEquals(Time.valueOf(LocalTime.of(9, 12, 20)).toString(), resultArray[1].toString());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapTimestampArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(TO_TIMESTAMP_NTZ('2021-12-23 09:44:44'), TO_TIMESTAMP_NTZ('2021-12-24 09:55:55'))::ARRAY(TIMESTAMP)\",\n        (resultSet) -> {\n          Timestamp[] resultArray = (Timestamp[]) resultSet.getArray(1).getArray();\n          assertEquals(\n              LocalDateTime.of(2021, 12, 23, 9, 44, 44)\n                  .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                  .toInstant(),\n              resultArray[0].toInstant());\n          assertEquals(\n              LocalDateTime.of(2021, 12, 24, 9, 55, 55)\n                  .atZone(ZoneId.of(\"Europe/Warsaw\"))\n                  .toInstant(),\n              resultArray[1].toInstant());\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapBooleanArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(true,false)::ARRAY(BOOLEAN)\",\n        (resultSet) -> {\n          Boolean[] resultArray = (Boolean[]) resultSet.getArray(1).getArray();\n          assertEquals(true, resultArray[0]);\n          assertEquals(false, resultArray[1]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapBinaryArray(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(TO_BINARY('616263', 'HEX'),TO_BINARY('616263', 'HEX'))::ARRAY(BINARY)\",\n        (resultSet) -> {\n          Byte[][] resultArray = (Byte[][]) resultSet.getArray(1).getArray();\n          assertArrayEquals(new Byte[] {'a', 'b', 'c'}, resultArray[0]);\n          assertArrayEquals(new Byte[] {'a', 'b', 'c'}, resultArray[1]);\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapArrayOfStructToMap(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT({'x': 'abc', 'y': 1}, {'x': 'def', 'y': 2} )::ARRAY(OBJECT(x VARCHAR, y INTEGER))\",\n        (resultSet) -> {\n          Map[] resultArray = (Map[]) resultSet.getArray(1).getArray();\n          Map<String, Object> firstEntry = resultArray[0];\n          Map<String, Object> secondEntry = resultArray[1];\n          assertEquals(firstEntry.get(\"x\").toString(), \"abc\");\n          assertEquals(firstEntry.get(\"y\").toString(), \"1\");\n          assertEquals(secondEntry.get(\"x\").toString(), \"def\");\n          assertEquals(secondEntry.get(\"y\").toString(), \"2\");\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapArrayOfArrays(ResultSetFormatType format) throws SQLException {\n    withFirstRow(\n        \"SELECT ARRAY_CONSTRUCT(ARRAY_CONSTRUCT({'x': 'abc', 'y': 1}, {'x': 'def', 'y': 2}) )::ARRAY(ARRAY(OBJECT(x VARCHAR, y INTEGER)))\",\n        (resultSet) -> {\n          Map[][] resultArray = (Map[][]) resultSet.getArray(1).getArray();\n          Map<String, Object> firstEntry = resultArray[0][0];\n          Map<String, Object> secondEntry = resultArray[0][1];\n          assertEquals(firstEntry.get(\"x\").toString(), \"abc\");\n          assertEquals(firstEntry.get(\"y\").toString(), \"1\");\n          assertEquals(secondEntry.get(\"x\").toString(), \"def\");\n          assertEquals(secondEntry.get(\"y\").toString(), \"2\");\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testMapNestedStructures(ResultSetFormatType format) throws SQLException {\n    String structSelectStatement =\n        \"SELECT {'simpleClass': {'string': 'a', 'intValue': 2}, \"\n            + \"'simpleClasses': ARRAY_CONSTRUCT({'string': 'a', 'intValue': 2}, {'string': 'b', 'intValue': 2}), \"\n            + \"'arrayOfSimpleClasses': ARRAY_CONSTRUCT({'string': 'a', 'intValue': 2}, {'string': 'b', 'intValue': 2}), \"\n            + \"'mapOfSimpleClasses':{'x':{'string': 'c', 'intValue': 2}, 'y':{'string': 'd', 'intValue': 2}},\"\n            + \"'texts': ARRAY_CONSTRUCT('string', 'a'), \"\n            + \"'arrayOfDates': ARRAY_CONSTRUCT(to_date('2023-12-24', 'YYYY-MM-DD'), to_date('2023-12-25', 'YYYY-MM-DD')), \"\n            + \"'mapOfIntegers':{'x':3, 'y':4}}\"\n            + \"::OBJECT(simpleClass OBJECT(string VARCHAR, intValue INTEGER), \"\n            + \"simpleClasses ARRAY(OBJECT(string VARCHAR, intValue INTEGER)),\"\n            + \"arrayOfSimpleClasses ARRAY(OBJECT(string VARCHAR, intValue INTEGER)),\"\n            + \"mapOfSimpleClasses MAP(VARCHAR, OBJECT(string VARCHAR, intValue INTEGER)),\"\n            + \"texts ARRAY(VARCHAR),\"\n            + \"arrayOfDates ARRAY(DATE),\"\n            + \"mapOfIntegers MAP(VARCHAR, INTEGER))\";\n    String expectedQueryResult =\n        \"{\\\"simpleClass\\\": {\\\"string\\\": \\\"a\\\",\\\"intValue\\\": 2},\\\"simpleClasses\\\": [{\\\"string\\\": \\\"a\\\",\\\"intValue\\\": 2},{\\\"string\\\": \\\"b\\\",\\\"intValue\\\": 2}],\\\"arrayOfSimpleClasses\\\": [{\\\"string\\\": \\\"a\\\",\\\"intValue\\\": 2},{\\\"string\\\": \\\"b\\\",\\\"intValue\\\": 2}],\\\"mapOfSimpleClasses\\\": {\\\"x\\\": {\\\"string\\\": \\\"c\\\",\\\"intValue\\\": 2},\\\"y\\\": {\\\"string\\\": \\\"d\\\",\\\"intValue\\\": 2}},\\\"texts\\\": [\\\"string\\\",\\\"a\\\"],\\\"arrayOfDates\\\": [\\\"2023-12-24\\\",\\\"2023-12-25\\\"],\\\"mapOfIntegers\\\": {\\\"x\\\": 3,\\\"y\\\": 4}}\";\n    withFirstRow(\n        structSelectStatement,\n        (resultSet) -> {\n          NestedStructSqlData nestedStructSqlData =\n              resultSet.getObject(1, NestedStructSqlData.class);\n          ;\n          assertEquals(\"a\", nestedStructSqlData.getSimpleClass().getString());\n          assertEquals(Integer.valueOf(2), nestedStructSqlData.getSimpleClass().getIntValue());\n\n          assertEquals(\"a\", nestedStructSqlData.getSimpleClassses().get(0).getString());\n          assertEquals(\n              Integer.valueOf(2), nestedStructSqlData.getSimpleClassses().get(0).getIntValue());\n          assertEquals(\"b\", nestedStructSqlData.getSimpleClassses().get(1).getString());\n          assertEquals(\n              Integer.valueOf(2), nestedStructSqlData.getSimpleClassses().get(1).getIntValue());\n\n          assertEquals(\"a\", nestedStructSqlData.getArrayOfSimpleClasses()[0].getString());\n          assertEquals(\n              Integer.valueOf(2), nestedStructSqlData.getArrayOfSimpleClasses()[0].getIntValue());\n          assertEquals(\"b\", nestedStructSqlData.getArrayOfSimpleClasses()[1].getString());\n          assertEquals(\n              Integer.valueOf(2), nestedStructSqlData.getArrayOfSimpleClasses()[1].getIntValue());\n\n          assertEquals(\"c\", nestedStructSqlData.getMapOfSimpleClasses().get(\"x\").getString());\n          assertEquals(\n              Integer.valueOf(2),\n              nestedStructSqlData.getMapOfSimpleClasses().get(\"x\").getIntValue());\n          assertEquals(\"d\", nestedStructSqlData.getMapOfSimpleClasses().get(\"y\").getString());\n          assertEquals(\n              Integer.valueOf(2),\n              nestedStructSqlData.getMapOfSimpleClasses().get(\"y\").getIntValue());\n\n          assertEquals(\"string\", nestedStructSqlData.getTexts().get(0));\n          assertEquals(\"a\", nestedStructSqlData.getTexts().get(1));\n\n          // TODO uncomment after merge SNOW-928973: Date field is returning one day less when\n          // getting\n          //          assertEquals(\n          //              Date.valueOf(LocalDate.of(2023, 12, 24)).toString(),\n          //              nestedStructSqlData.getArrayOfDates()[0].toString());\n          //          assertEquals(\n          //              Date.valueOf(LocalDate.of(2023, 12, 25)).toString(),\n          //              nestedStructSqlData.getArrayOfDates()[1].toString());\n\n          assertEquals(Integer.valueOf(3), nestedStructSqlData.getMapOfIntegers().get(\"x\"));\n          assertEquals(Integer.valueOf(4), nestedStructSqlData.getMapOfIntegers().get(\"y\"));\n          TestUtil.assertEqualsIgnoringWhitespace(expectedQueryResult, resultSet.getString(1));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testColumnTypeWhenStructureTypeIsDisabled(ResultSetFormatType format)\n      throws Exception {\n    withFirstRow(\n        \"SELECT {'string':'a'}\",\n        resultSet -> {\n          assertEquals(Types.VARCHAR, resultSet.getMetaData().getColumnType(1));\n        },\n        format);\n  }\n\n  @ParameterizedTest\n  @ArgumentsSource(ResultFormatProvider.class)\n  @DontRunOnGithubActions\n  public void testColumnTypeAndFieldsWhenStructureTypeIsReturned(ResultSetFormatType format)\n      throws Exception {\n    withFirstRow(\n        \"SELECT {'string':'a'}::OBJECT(string VARCHAR)\",\n        resultSet -> {\n          assertEquals(Types.STRUCT, resultSet.getMetaData().getColumnType(1));\n          assertEquals(\n              1,\n              resultSet\n                  .getMetaData()\n                  .unwrap(SnowflakeResultSetMetaData.class)\n                  .getColumnFields(1)\n                  .size());\n          assertEquals(\n              \"VARCHAR\",\n              resultSet\n                  .getMetaData()\n                  .unwrap(SnowflakeResultSetMetaData.class)\n                  .getColumnFields(1)\n                  .get(0)\n                  .getTypeName());\n          assertEquals(\n              \"string\",\n              resultSet\n                  .getMetaData()\n                  .unwrap(SnowflakeResultSetMetaData.class)\n                  .getColumnFields(1)\n                  .get(0)\n                  .getName());\n        },\n        format);\n  }\n\n  private void withFirstRow(\n      String sqlText,\n      ThrowingConsumer<ResultSet, SQLException> consumer,\n      ResultSetFormatType format)\n      throws SQLException {\n    try (Connection connection = init(format);\n        Statement statement = connection.createStatement();\n        ResultSet rs = statement.executeQuery(sqlText); ) {\n      assertTrue(rs.next());\n      consumer.accept(rs);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/StructuredTypesArrowJsonCompatibilityLatestIT.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.ResultSetFormatType;\nimport net.snowflake.client.providers.ProvidersUtil;\nimport net.snowflake.client.providers.ResultFormatProvider;\nimport net.snowflake.client.providers.SnowflakeArgumentsProvider;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsSource;\n\n@Tag(TestTags.RESULT_SET)\npublic class StructuredTypesArrowJsonCompatibilityLatestIT extends StructuredTypesGetStringBaseIT {\n  private static Map<ResultSetFormatType, Connection> connections = new HashMap<>();\n\n  @BeforeAll\n  public static void setUpConnections() throws SQLException {\n    // We initialize connection here since we need to set server properties that cannot be set in GH\n    // actions and before class is running even when all the tests have conditional ignore of tests\n    for (ResultSetFormatType queryResultFormat : ResultSetFormatType.values()) {\n      connections.put(queryResultFormat, initConnection(queryResultFormat));\n    }\n  }\n\n  @AfterAll\n  public static void closeConnections() throws SQLException {\n    for (Connection connection : connections.values()) {\n      connection.close();\n    }\n  }\n\n  @ParameterizedTest\n  @DontRunOnGithubActions\n  @ArgumentsSource(DataProvider.class)\n  public void testArrowJsonCompatibility(\n      ResultSetFormatType queryResultFormat,\n      String selectSql,\n      String expectedStructureTypeRepresentation)\n      throws SQLException {\n    withFirstRow(\n        connections.get(queryResultFormat),\n        selectSql,\n        (resultSet) -> assertResultSetIsCompatible(resultSet, expectedStructureTypeRepresentation));\n  }\n\n  public static class SampleProvider extends SnowflakeArgumentsProvider {\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      List<Arguments> samples = new LinkedList<>();\n      samples.add(Arguments.of(\"select {'a':3}::map(text, int);\", \"{\\\"a\\\":3}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'a':'zażółć gęślą jaźń'}::map(text, text);\",\n              \"{\\\"a\\\":\\\"zażółć gęślą jaźń\\\"}\"));\n      samples.add(Arguments.of(\"select {'a':'bla'}::map(text, text);\", \"{\\\"a\\\":\\\"bla\\\"}\"));\n      samples.add(Arguments.of(\"select {'1':'bla'}::map(int, text);\", \"{\\\"1\\\":\\\"bla\\\"}\"));\n      samples.add(Arguments.of(\"select {'1':[1,2,3]}::map(int, ARRAY(int));\", \"{\\\"1\\\":[1,2,3]}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'1':{'string':'a'}}::map(int, OBJECT(string VARCHAR));\",\n              \"{\\\"1\\\":{\\\"string\\\":\\\"a\\\"}}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'1':{'string':'a'}}::map(int, map(string, string));\",\n              \"{\\\"1\\\":{\\\"string\\\":\\\"a\\\"}}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'1':[{'string':'a'},{'bla':'ble'}]}::map(int, array(map(string, string)));\",\n              \"{\\\"1\\\":[{\\\"string\\\":\\\"a\\\"},{\\\"bla\\\":\\\"ble\\\"}]}\"));\n      samples.add(Arguments.of(\"select [1,2,3]::array(int)\", \"[1,2,3]\"));\n      samples.add(\n          Arguments.of(\n              \"select [{'a':'a'}, {'b':'b'}]::array(map(string, string))\",\n              \"[{\\\"a\\\":\\\"a\\\"}, {\\\"b\\\":\\\"b\\\"}]\"));\n      samples.add(\n          Arguments.of(\n              \"select [{'a':true}, {'b':false}]::array(map(string, boolean))\",\n              \"[{\\\"a\\\":true}, {\\\"b\\\":false}]\"));\n      samples.add(\n          Arguments.of(\n              \"select [{'string':'a'}, {'string':'b'}]::array(object(string varchar))\",\n              \"[{\\\"string\\\":\\\"a\\\"}, {\\\"string\\\":\\\"b\\\"}]\"));\n      samples.add(\n          Arguments.of(\"select {'string':'a'}::object(string varchar)\", \"{\\\"string\\\":\\\"a\\\"}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'x':'a','b':'a','c':'a','d':'a','e':'a'}::object(x varchar,b varchar,c varchar,d varchar,e varchar)\",\n              \"{\\\"x\\\":\\\"a\\\",\\\"b\\\":\\\"a\\\",\\\"c\\\":\\\"a\\\",\\\"d\\\":\\\"a\\\",\\\"e\\\":\\\"a\\\"}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'string':[1,2,3]}::object(string array(int))\", \"{\\\"string\\\":[1,2,3]}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'string':{'a':15}}::object(string object(a int))\",\n              \"{\\\"string\\\":{\\\"a\\\":15}}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'string':{'a':15}}::object(string map(string,int))\",\n              \"{\\\"string\\\":{\\\"a\\\":15}}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'string':{'a':{'b':15}}}::object(string object(a map(string, int)))\",\n              \"{\\\"string\\\":{\\\"a\\\":{\\\"b\\\":15}}}\"));\n\n      samples.add(\n          Arguments.of(\n              \"select {'string':{'a':{'b':[{'c': 15}]}}}::object(string map(string, object(b array(object(c int)))))\",\n              \"{\\\"string\\\":{\\\"a\\\":{\\\"b\\\":[{\\\"c\\\":15}]}}}\"));\n      // DY, DD MON YYYY HH24:MI:SS TZHTZM\n      samples.add(\n          Arguments.of(\n              \"select {'ltz': '2024-05-20 11:22:33'::TIMESTAMP_LTZ}::object(ltz TIMESTAMP_LTZ)\",\n              \"{\\\"ltz\\\":\\\"Mon, 20 May 2024 11:22:33 +0200\\\"}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'ntz': '2024-05-20 11:22:33'::TIMESTAMP_NTZ}::object(ntz TIMESTAMP_NTZ)\",\n              \"{\\\"ntz\\\":\\\"Mon, 20 May 2024 11:22:33 Z\\\"}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'tz': '2024-05-20 11:22:33+0800'::TIMESTAMP_TZ}::object(tz TIMESTAMP_TZ)\",\n              \"{\\\"tz\\\":\\\"Mon, 20 May 2024 11:22:33 +0800\\\"}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'date': '2024-05-20'::DATE}::object(date DATE)\",\n              \"{\\\"date\\\":\\\"2024-05-20\\\"}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'time': '22:14:55'::TIME}::object(time TIME)\", \"{\\\"time\\\":\\\"22:14:55\\\"}\"));\n      samples.add(Arguments.of(\"select {'bool': TRUE}::object(bool BOOLEAN)\", \"{\\\"bool\\\":true}\"));\n      samples.add(Arguments.of(\"select {'bool': 'y'}::object(bool BOOLEAN)\", \"{\\\"bool\\\":true}\"));\n      samples.add(\n          Arguments.of(\n              \"select {'binary': TO_BINARY('616263', 'HEX')}::object(binary BINARY)\",\n              \"{\\\"binary\\\":\\\"616263\\\"}\"));\n      samples.add(Arguments.of(\"select [1,2,3]::VECTOR(INT, 3)\", \"[1,2,3]\"));\n      samples.add(Arguments.of(\"select ['a','b','c']::ARRAY(varchar)\", \"[\\\"a\\\",\\\"b\\\",\\\"c\\\"]\"));\n      samples.add(Arguments.of(\"select ['a','b','c']::ARRAY(variant)\", \"[\\\"a\\\",\\\"b\\\",\\\"c\\\"]\"));\n\n      return samples;\n    }\n  }\n\n  private static class DataProvider extends SnowflakeArgumentsProvider {\n\n    @Override\n    protected List<Arguments> rawArguments(ExtensionContext context) {\n      return ProvidersUtil.cartesianProduct(\n          context, new ResultFormatProvider(), new SampleProvider());\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/StructuredTypesGetStringBaseIT.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport net.snowflake.client.TestUtil;\nimport net.snowflake.client.ThrowingConsumer;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.ResultSetFormatType;\n\nabstract class StructuredTypesGetStringBaseIT extends BaseJDBCTest {\n  public StructuredTypesGetStringBaseIT() {}\n\n  protected Connection init(ResultSetFormatType queryResultFormat) throws SQLException {\n    return initConnection(queryResultFormat);\n  }\n\n  protected static Connection initConnection(ResultSetFormatType queryResultFormat)\n      throws SQLException {\n    Connection conn = BaseJDBCTest.getConnection(BaseJDBCTest.DONT_INJECT_SOCKET_TIMEOUT);\n    try (Statement stmt = conn.createStatement()) {\n      stmt.execute(\"alter session set USE_CACHED_RESULT = false\");\n      stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_IN_CLIENT_RESPONSE = true\");\n      stmt.execute(\"alter session set IGNORE_CLIENT_VESRION_IN_STRUCTURED_TYPES_RESPONSE = true\");\n      stmt.execute(\"ALTER SESSION SET TIMEZONE = 'Europe/Warsaw'\");\n      stmt.execute(\n          \"alter session set \"\n              + \"TIMESTAMP_TYPE_MAPPING='TIMESTAMP_LTZ',\"\n              + \"TIMESTAMP_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_TZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_LTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM',\"\n              + \"TIMESTAMP_NTZ_OUTPUT_FORMAT='DY, DD MON YYYY HH24:MI:SS TZHTZM'\");\n      stmt.execute(\n          \"alter session set jdbc_query_result_format = '\"\n              + queryResultFormat.sessionParameterTypeValue\n              + \"'\");\n      if (queryResultFormat == ResultSetFormatType.NATIVE_ARROW) {\n        stmt.execute(\"alter session set ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true\");\n        stmt.execute(\"alter session set FORCE_ENABLE_STRUCTURED_TYPES_NATIVE_ARROW_FORMAT = true\");\n      }\n    }\n    return conn;\n  }\n\n  protected void assertResultSetIsCompatible(ResultSet resultSet, String expected)\n      throws SQLException {\n    // Test getString\n    String result = resultSet.getString(1);\n    TestUtil.assertEqualsIgnoringWhitespace(expected, result);\n\n    // Test getObject\n    result = resultSet.getObject(1, String.class);\n    String resultCasted = (String) resultSet.getObject(1);\n    TestUtil.assertEqualsIgnoringWhitespace(expected, result);\n    TestUtil.assertEqualsIgnoringWhitespace(expected, resultCasted);\n\n    // Test getBytes\n    TestUtil.assertEqualsIgnoringWhitespace(\n        expected, new String(resultSet.getBytes(1), StandardCharsets.UTF_8));\n  }\n\n  protected void withFirstRow(\n      Connection connection, String sqlText, ThrowingConsumer<ResultSet, SQLException> consumer)\n      throws SQLException {\n    try (Statement statement = connection.createStatement();\n        ResultSet rs = statement.executeQuery(sqlText); ) {\n      assertTrue(rs.next());\n      consumer.accept(rs);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/sqldata/AllTypesClass.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes.sqldata;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLOutput;\nimport java.sql.Time;\nimport java.sql.Timestamp;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumn;\n\npublic class AllTypesClass implements SQLData {\n  public static String ALL_TYPES_QUERY =\n      \"select {\"\n          + \"'string': 'a', \"\n          + \"'b': 1, \"\n          + \"'s': 2, \"\n          + \"'i': 3, \"\n          + \"'l': 4, \"\n          + \"'f': 1.1, \"\n          + \"'d': 2.2, \"\n          + \"'bd': 3.3, \"\n          + \"'bool': true, \"\n          + \"'timestamp_ltz': '2021-12-22 09:43:44'::TIMESTAMP_LTZ, \"\n          + \"'timestamp_ntz': '2021-12-23 09:44:44'::TIMESTAMP_NTZ, \"\n          + \"'timestamp_tz': '2021-12-24 09:45:45 +0800'::TIMESTAMP_TZ, \"\n          + \"'date': '2023-12-24'::DATE, \"\n          + \"'time': '12:34:56'::TIME, \"\n          + \"'binary': TO_BINARY('616263', 'HEX'), \"\n          + \"'simpleClass': {'string': 'b', 'intValue': 2}\"\n          + \"}::OBJECT(\"\n          + \"string VARCHAR, \"\n          + \"b TINYINT, \"\n          + \"s SMALLINT, \"\n          + \"i INTEGER, \"\n          + \"l BIGINT, \"\n          + \"f FLOAT, \"\n          + \"d DOUBLE, \"\n          + \"bd DOUBLE, \"\n          + \"bool BOOLEAN, \"\n          + \"timestamp_ltz TIMESTAMP_LTZ, \"\n          + \"timestamp_ntz TIMESTAMP_NTZ, \"\n          + \"timestamp_tz TIMESTAMP_TZ, \"\n          + \"date DATE, \"\n          + \"time TIME, \"\n          + \"binary BINARY, \"\n          + \"simpleClass OBJECT(string VARCHAR, intValue INTEGER)\"\n          + \")\";\n\n  private String string;\n  private Byte b;\n  private Short s;\n  private Integer i;\n  private Long l;\n  private Float f;\n  private Double d;\n  private BigDecimal bd;\n  private Boolean bool;\n\n  @SnowflakeColumn(type = \"timestamp_ltz\")\n  private Timestamp timestampLtz;\n\n  @SnowflakeColumn(type = \"timestamp_ntz\")\n  private Timestamp timestampNtz;\n\n  @SnowflakeColumn(type = \"timestamp_tz\")\n  private Timestamp timestampTz;\n\n  private Date date;\n  private Time time;\n  private byte[] binary;\n  private SimpleClass simpleClass;\n\n  public AllTypesClass() {}\n\n  public AllTypesClass(\n      String string,\n      Byte b,\n      Short s,\n      Integer i,\n      Long l,\n      Float f,\n      Double d,\n      BigDecimal bd,\n      Boolean bool,\n      Timestamp timestampLtz,\n      Timestamp timestampNtz,\n      Timestamp timestampTz,\n      Date date,\n      Time time,\n      byte[] binary,\n      SimpleClass simpleClass) {\n    this.string = string;\n    this.b = b;\n    this.s = s;\n    this.i = i;\n    this.l = l;\n    this.f = f;\n    this.d = d;\n    this.bd = bd;\n    this.bool = bool;\n    this.timestampLtz = timestampLtz;\n    this.timestampNtz = timestampNtz;\n    this.timestampTz = timestampTz;\n    this.date = date;\n    this.time = time;\n    this.binary = binary;\n    this.simpleClass = simpleClass;\n  }\n\n  @Override\n  public String getSQLTypeName() throws SQLException {\n    return null;\n  }\n\n  @Override\n  public void readSQL(SQLInput sqlInput, String typeName) throws SQLException {\n    string = sqlInput.readString();\n    if (sqlInput.wasNull()) {\n      string = null;\n    }\n    b = sqlInput.readByte();\n    if (sqlInput.wasNull()) {\n      b = null;\n    }\n    s = sqlInput.readShort();\n    if (sqlInput.wasNull()) {\n      s = null;\n    }\n    i = sqlInput.readInt();\n    if (sqlInput.wasNull()) {\n      i = null;\n    }\n    l = sqlInput.readLong();\n    if (sqlInput.wasNull()) {\n      l = null;\n    }\n    f = sqlInput.readFloat();\n    if (sqlInput.wasNull()) {\n      f = null;\n    }\n    d = sqlInput.readDouble();\n    if (sqlInput.wasNull()) {\n      d = null;\n    }\n    bd = sqlInput.readBigDecimal();\n    bool = sqlInput.readBoolean();\n    if (sqlInput.wasNull()) {\n      bool = null;\n    }\n    timestampLtz = sqlInput.readTimestamp();\n    timestampNtz = sqlInput.readTimestamp();\n    timestampTz = sqlInput.readTimestamp();\n    date = sqlInput.readDate();\n    time = sqlInput.readTime();\n    binary = sqlInput.readBytes();\n    simpleClass = sqlInput.readObject(SimpleClass.class);\n  }\n\n  @Override\n  public void writeSQL(SQLOutput stream) throws SQLException {\n    stream.writeString(string);\n    stream.writeByte(b);\n    stream.writeShort(s);\n    stream.writeInt(i);\n    stream.writeLong(l);\n    stream.writeFloat(f);\n    stream.writeDouble(d);\n    stream.writeBigDecimal(bd);\n    stream.writeBoolean(bool);\n    stream.writeTimestamp(timestampLtz);\n    stream.writeTimestamp(timestampNtz);\n    stream.writeTimestamp(timestampTz);\n    stream.writeDate(date);\n    stream.writeTime(time);\n    stream.writeBytes(binary);\n    stream.writeObject(simpleClass);\n  }\n\n  public String getString() {\n    return string;\n  }\n\n  public Byte getB() {\n    return b;\n  }\n\n  public Short getS() {\n    return s;\n  }\n\n  public Integer getI() {\n    return i;\n  }\n\n  public Long getL() {\n    return l;\n  }\n\n  public Float getF() {\n    return f;\n  }\n\n  public Double getD() {\n    return d;\n  }\n\n  public BigDecimal getBd() {\n    return bd;\n  }\n\n  public Boolean getBool() {\n    return bool;\n  }\n\n  public Timestamp getTimestampLtz() {\n    return timestampLtz;\n  }\n\n  public Timestamp getTimestampNtz() {\n    return timestampNtz;\n  }\n\n  public Timestamp getTimestampTz() {\n    return timestampTz;\n  }\n\n  public Date getDate() {\n    return date;\n  }\n\n  public Time getTime() {\n    return time;\n  }\n\n  public byte[] getBinary() {\n    return binary;\n  }\n\n  public SimpleClass getSimpleClass() {\n    return simpleClass;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/sqldata/NestedStructSqlData.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes.sqldata;\n\nimport java.sql.Date;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLOutput;\nimport java.util.List;\nimport java.util.Map;\nimport net.snowflake.client.internal.core.SFSqlInput;\n\npublic class NestedStructSqlData implements SQLData {\n\n  private SimpleClass simpleClass;\n  private List<SimpleClass> simpleClassses;\n  private SimpleClass[] arrayOfSimpleClasses;\n  private Map<String, SimpleClass> mapOfSimpleClasses;\n  private List<String> texts;\n  private Date[] arrayOfDates;\n  private Map<String, Integer> mapOfIntegers;\n\n  @Override\n  public String getSQLTypeName() throws SQLException {\n    return null;\n  }\n\n  @Override\n  public void readSQL(SQLInput sqlInput, String typeName) throws SQLException {\n    simpleClass = sqlInput.readObject(SimpleClass.class);\n    simpleClassses = SFSqlInput.unwrap(sqlInput).readList(SimpleClass.class);\n    arrayOfSimpleClasses = SFSqlInput.unwrap(sqlInput).readArray(SimpleClass.class);\n    mapOfSimpleClasses = SFSqlInput.unwrap(sqlInput).readMap(SimpleClass.class);\n    texts = SFSqlInput.unwrap(sqlInput).readList(String.class);\n    arrayOfDates = SFSqlInput.unwrap(sqlInput).readArray(Date.class);\n    mapOfIntegers = SFSqlInput.unwrap(sqlInput).readMap(Integer.class);\n  }\n\n  @Override\n  public void writeSQL(SQLOutput stream) throws SQLException {}\n\n  public SimpleClass getSimpleClass() {\n    return simpleClass;\n  }\n\n  public List<SimpleClass> getSimpleClassses() {\n    return simpleClassses;\n  }\n\n  public Map<String, SimpleClass> getMapOfSimpleClasses() {\n    return mapOfSimpleClasses;\n  }\n\n  public List<String> getTexts() {\n    return texts;\n  }\n\n  public Map<String, Integer> getMapOfIntegers() {\n    return mapOfIntegers;\n  }\n\n  public SimpleClass[] getArrayOfSimpleClasses() {\n    return arrayOfSimpleClasses;\n  }\n\n  public Date[] getArrayOfDates() {\n    return arrayOfDates;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/sqldata/NullableFieldsSqlData.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes.sqldata;\n\nimport java.math.BigDecimal;\nimport java.sql.Date;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLOutput;\n\npublic class NullableFieldsSqlData implements SQLData {\n\n  private String string;\n  private Integer nullableIntValue;\n  private Long nullableLongValue;\n  private Date date;\n  private BigDecimal bd;\n  private byte[] bytes;\n  private long longValue;\n\n  public NullableFieldsSqlData() {}\n\n  @Override\n  public String getSQLTypeName() throws SQLException {\n    return null;\n  }\n\n  @Override\n  public void readSQL(SQLInput stream, String typeName) throws SQLException {\n    string = stream.readString();\n    nullableIntValue = stream.readObject(Integer.class);\n    nullableLongValue = stream.readObject(Long.class);\n    date = stream.readObject(Date.class);\n    bd = stream.readBigDecimal();\n    bytes = stream.readObject(byte[].class);\n    longValue = stream.readLong();\n  }\n\n  @Override\n  public void writeSQL(SQLOutput stream) throws SQLException {\n    stream.writeString(string);\n    stream.writeInt(nullableIntValue);\n    stream.writeLong(longValue);\n  }\n\n  public String getString() {\n    return string;\n  }\n\n  public Integer getNullableIntValue() {\n    return nullableIntValue;\n  }\n\n  public Long getNullableLongValue() {\n    return nullableLongValue;\n  }\n\n  public Long getLongValue() {\n    return longValue;\n  }\n\n  public Date getDate() {\n    return date;\n  }\n\n  public BigDecimal getBd() {\n    return bd;\n  }\n\n  public byte[] getBytes() {\n    return bytes;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/sqldata/SimpleClass.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes.sqldata;\n\nimport java.beans.Transient;\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLOutput;\nimport net.snowflake.client.internal.jdbc.SnowflakeColumn;\n\npublic class SimpleClass implements SQLData {\n\n  @SnowflakeColumn(length = 12)\n  private String string;\n\n  @SnowflakeColumn(precision = 36, scale = 4)\n  private Integer intValue;\n\n  public SimpleClass() {}\n\n  public SimpleClass(String x, Integer y) {\n    this.string = x;\n    this.intValue = y;\n  }\n\n  @Override\n  @Transient\n  public String getSQLTypeName() throws SQLException {\n    return null;\n  }\n\n  @Override\n  public void readSQL(SQLInput stream, String typeName) throws SQLException {\n    string = stream.readString();\n    intValue = stream.readInt();\n  }\n\n  @Override\n  public void writeSQL(SQLOutput stream) throws SQLException {\n    stream.writeString(string);\n    stream.writeInt(intValue);\n  }\n\n  public String getString() {\n    return string;\n  }\n\n  public Integer getIntValue() {\n    return intValue;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/structuredtypes/sqldata/StringClass.java",
    "content": "package net.snowflake.client.internal.jdbc.structuredtypes.sqldata;\n\nimport java.sql.SQLData;\nimport java.sql.SQLException;\nimport java.sql.SQLInput;\nimport java.sql.SQLOutput;\n\npublic class StringClass implements SQLData {\n  public String getString() {\n    return string;\n  }\n\n  private String string;\n\n  public StringClass() {}\n\n  @Override\n  public String getSQLTypeName() throws SQLException {\n    return null;\n  }\n\n  @Override\n  public void readSQL(SQLInput stream, String typeName) throws SQLException {\n    string = stream.readString();\n  }\n\n  @Override\n  public void writeSQL(SQLOutput stream) throws SQLException {\n    stream.writeString(string);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/CSVMetricsExporterTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.regex.Pattern;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.Test;\n\nclass CSVMetricsExporterTest {\n  @Test\n  void shouldSaveMetrics() throws Exception {\n    Path tempDirectory = Files.createTempDirectory(\"csv-metrics-exporter-test\");\n    Path csvPath = tempDirectory.resolve(\"metrics.csv\");\n    CSVMetricsExporter exporter = new CSVMetricsExporter(csvPath.toString(), 2);\n    saveTelemetryData(exporter, \"SELECT 1\");\n    saveTelemetryData(exporter, \"SELECT 2\");\n    String content = IOUtils.toString(Files.newInputStream(csvPath));\n    String[] lines = content.split(System.lineSeparator());\n    assertEquals(3, lines.length);\n    assertEquals(\n        \"timestamp,sessionId,requestId,queryId,queryText,executeToSendTime,bindTime,gzipTime,httpClientTime,responseIOStreamTime,processResultChunkTime,createResultSetTime,queryTime\",\n        lines[0]);\n    assertTrue(lines[1].contains(\"SELECT 1\"));\n    assertTrue(lines[2].contains(\"SELECT 2\"));\n    // number of fields in a header and a line should be equal\n    assertEquals(lines[0].split(\",\").length, lines[1].split(\",\").length);\n    assertTrue(\n        Pattern.matches(\n            \"^[\\\\d\\\\-T:.]+,sessId,reqId,queryId,SELECT 1,([-\\\\d]+,){7}[-\\\\d]+$\", lines[1]));\n  }\n\n  @Test\n  void shouldNotSaveAlreadyFlushedMetrics() throws Exception {\n    Path tempDirectory = Files.createTempDirectory(\"csv-metrics-exporter-test\");\n    Path csvPath = tempDirectory.resolve(\"metrics.csv\");\n    CSVMetricsExporter exporter = new CSVMetricsExporter(csvPath.toString(), 2);\n    saveTelemetryData(exporter, \"SELECT 1\");\n    saveTelemetryData(exporter, \"SELECT 2\");\n    saveTelemetryData(exporter, \"SELECT 3\");\n    saveTelemetryData(exporter, \"SELECT 4\");\n    saveTelemetryData(exporter, \"SELECT 5\");\n    String content = IOUtils.toString(Files.newInputStream(csvPath));\n    String[] lines = content.split(System.lineSeparator());\n    assertEquals(5, lines.length);\n    assertTrue(lines[1].contains(\"SELECT 1\"));\n    assertTrue(lines[2].contains(\"SELECT 2\"));\n    assertTrue(lines[3].contains(\"SELECT 3\"));\n    assertTrue(lines[4].contains(\"SELECT 4\"));\n  }\n\n  private void saveTelemetryData(CSVMetricsExporter exporter, String queryText) {\n    ExecTimeTelemetryData data = new ExecTimeTelemetryData();\n    data.setQueryText(queryText);\n    data.setSessionId(\"sessId\");\n    data.setRequestId(\"reqId\");\n    data.setQueryId(\"queryId\");\n    exporter.save(data);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/ExecTimeTelemetryDataTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\nimport net.minidev.json.JSONObject;\nimport net.minidev.json.parser.JSONParser;\nimport net.minidev.json.parser.ParseException;\nimport net.snowflake.client.internal.jdbc.telemetryOOB.TelemetryService;\nimport org.junit.jupiter.api.Test;\n\npublic class ExecTimeTelemetryDataTest {\n\n  @Test\n  public void testExecTimeTelemetryData() throws ParseException {\n    ExecTimeTelemetryData execTimeTelemetryData = new ExecTimeTelemetryData();\n    execTimeTelemetryData.sendData = true;\n    execTimeTelemetryData.setBindStart();\n    execTimeTelemetryData.setOCSPStatus(true);\n    execTimeTelemetryData.setBindEnd();\n    execTimeTelemetryData.setHttpClientStart();\n    execTimeTelemetryData.setHttpClientEnd();\n    execTimeTelemetryData.setGzipStart();\n    execTimeTelemetryData.setGzipEnd();\n    execTimeTelemetryData.setQueryEnd();\n    execTimeTelemetryData.setQueryId(\"queryid\");\n    execTimeTelemetryData.setProcessResultChunkStart();\n    execTimeTelemetryData.setProcessResultChunkEnd();\n    execTimeTelemetryData.setResponseIOStreamStart();\n    execTimeTelemetryData.setResponseIOStreamEnd();\n    execTimeTelemetryData.setCreateResultSetStart();\n    execTimeTelemetryData.setCreateResultSetEnd();\n    execTimeTelemetryData.incrementRetryCount();\n    execTimeTelemetryData.setRequestId(\"mockId\");\n    execTimeTelemetryData.addRetryLocation(\"retry\");\n\n    String telemetry = execTimeTelemetryData.generateTelemetry();\n    JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);\n    JSONObject json = (JSONObject) parser.parse(telemetry);\n    assertNotNull(json.get(\"BindStart\"));\n    assertNotNull(json.get(\"BindEnd\"));\n    assertEquals(json.get(\"ocspEnabled\"), true);\n    assertNotNull(json.get(\"HttpClientStart\"));\n    assertNotNull(json.get(\"HttpClientEnd\"));\n    assertNotNull(json.get(\"GzipStart\"));\n    assertNotNull(json.get(\"GzipEnd\"));\n    assertNotNull(json.get(\"QueryEnd\"));\n    assertEquals(json.get(\"QueryID\"), \"queryid\");\n    assertNotNull(json.get(\"ProcessResultChunkStart\"));\n    assertNotNull(json.get(\"ProcessResultChunkEnd\"));\n    assertNotNull(json.get(\"ResponseIOStreamStart\"));\n    assertNotNull(json.get(\"CreateResultSetStart\"));\n    assertNotNull(json.get(\"CreateResultSetEnd\"));\n    assertNotNull(json.get(\"ElapsedQueryTime\"));\n    assertNotNull(json.get(\"ElapsedResultProcessTime\"));\n    assertNull(json.get(\"QueryFunction\"));\n    assertNull(json.get(\"BatchID\"));\n    assertEquals(((Long) json.get(\"RetryCount\")).intValue(), 1);\n    assertEquals(json.get(\"RequestID\"), \"mockId\");\n    assertEquals(json.get(\"RetryLocations\"), \"retry\");\n    assertEquals(json.get(\"Urgent\"), true);\n    assertEquals(json.get(\"eventType\"), \"ExecutionTimeRecord\");\n  }\n\n  @Test\n  public void testRetryLocation() throws ParseException {\n    TelemetryService.enableHTAP();\n    ExecTimeTelemetryData execTimeTelemetryData =\n        new ExecTimeTelemetryData(\"queryFunction\", \"batchId\");\n    execTimeTelemetryData.addRetryLocation(\"hello\");\n    execTimeTelemetryData.addRetryLocation(\"world\");\n    execTimeTelemetryData.sendData = true;\n    String telemetry = execTimeTelemetryData.generateTelemetry();\n\n    JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);\n    JSONObject json = (JSONObject) parser.parse(telemetry);\n    assertEquals(json.get(\"QueryFunction\"), \"queryFunction\");\n    assertEquals(json.get(\"BatchID\"), \"batchId\");\n    assertNotNull(json.get(\"QueryStart\"));\n    assertEquals(json.get(\"RetryLocations\"), \"hello, world\");\n    TelemetryService.disableHTAP();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/InternalApiMarkerUsageArchTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.tngtech.archunit.core.domain.JavaClasses;\nimport com.tngtech.archunit.core.importer.ClassFileImporter;\nimport com.tngtech.archunit.core.importer.ImportOption;\nimport com.tngtech.archunit.lang.ArchRule;\nimport net.snowflake.client.api.resultset.SnowflakeResultSetSerializable;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.SFBaseSession;\nimport net.snowflake.client.internal.core.SFBaseStatement;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SFStatement;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport net.snowflake.client.internal.jdbc.SnowflakeFileTransferAgent;\nimport net.snowflake.client.internal.jdbc.SnowflakeResultSetSerializableV1;\nimport org.junit.jupiter.api.Test;\n\nclass InternalApiMarkerUsageArchTest {\n  private static final String INTERNAL_PACKAGE = \"net.snowflake.client.internal..\";\n\n  private static final JavaClasses INTERNAL_CLASSES =\n      new ClassFileImporter()\n          .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)\n          .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)\n          .importPackages(\"net.snowflake.client.internal\");\n\n  @Test\n  void internalClassesMustUseMarkerAwareTrackedApis() {\n    noInternalCallsTo(SnowflakeConnectionImpl.class, \"getHandler\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SnowflakeConnectionImpl.class, \"getSFBaseSession\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SnowflakeConnectionImpl.class, \"getSfSession\").check(INTERNAL_CLASSES);\n\n    noInternalCallsTo(SFSession.class, \"open\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFSession.class, \"close\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFSession.class, \"getSessionToken\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFSession.class, \"getTelemetryClient\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFSession.class, \"getIdToken\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFSession.class, \"getAccessToken\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFSession.class, \"getMfaToken\").check(INTERNAL_CLASSES);\n\n    noInternalCallsTo(SFStatement.class, \"getSFBaseSession\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFBaseSession.class, \"getTelemetryClient\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFBaseSession.class, \"close\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(SFBaseStatement.class, \"getSFBaseSession\").check(INTERNAL_CLASSES);\n\n    noInternalCallsTo(\n            SessionUtil.class,\n            \"generateJWTToken\",\n            java.security.PrivateKey.class,\n            String.class,\n            String.class,\n            String.class,\n            String.class,\n            String.class)\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(\n            SessionUtil.class,\n            \"generateJWTToken\",\n            java.security.PrivateKey.class,\n            String.class,\n            String.class,\n            String.class,\n            String.class)\n        .check(INTERNAL_CLASSES);\n\n    noInternalCallsToConstructor(\n            SnowflakeFileTransferAgent.class, String.class, SFSession.class, SFStatement.class)\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(SnowflakeFileTransferAgent.class, \"getFileTransferMetadatas\")\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(SnowflakeFileTransferAgent.class, \"getFileTransferMetadatas\", JsonNode.class)\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(\n            SnowflakeFileTransferAgent.class,\n            \"getFileTransferMetadatas\",\n            JsonNode.class,\n            String.class)\n        .check(INTERNAL_CLASSES);\n\n    noInternalCallsTo(SnowflakeResultSetSerializableV1.class, \"getResultStreamProvider\")\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(SnowflakeResultSetSerializableV1.class, \"getSFResultSetMetaData\")\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(SnowflakeResultSetSerializableV1.class, \"getSession\").check(INTERNAL_CLASSES);\n    noInternalCallsTo(\n            SnowflakeResultSetSerializableV1.class,\n            \"create\",\n            JsonNode.class,\n            SFBaseSession.class,\n            SFBaseStatement.class)\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(\n            SnowflakeResultSetSerializableV1.class,\n            \"create\",\n            JsonNode.class,\n            SFBaseSession.class,\n            SFBaseStatement.class,\n            net.snowflake.client.internal.jdbc.ResultStreamProvider.class)\n        .check(INTERNAL_CLASSES);\n    noInternalCallsTo(\n            SnowflakeResultSetSerializableV1.class,\n            \"getResultSet\",\n            SnowflakeResultSetSerializable.ResultSetRetrieveConfig.class)\n        .check(INTERNAL_CLASSES);\n  }\n\n  private static ArchRule noInternalCallsTo(\n      Class<?> ownerClass, String methodName, Class<?>... parameterTypes) {\n    return noClasses()\n        .that()\n        .resideInAPackage(INTERNAL_PACKAGE)\n        .and()\n        .doNotHaveFullyQualifiedName(ownerClass.getName())\n        .should()\n        .callMethod(ownerClass, methodName, parameterTypes)\n        .because(\n            \"internal call sites must use marker-aware overloads to avoid external telemetry false positives\")\n        .allowEmptyShould(true);\n  }\n\n  private static ArchRule noInternalCallsToConstructor(\n      Class<?> ownerClass, Class<?>... parameterTypes) {\n    return noClasses()\n        .that()\n        .resideInAPackage(INTERNAL_PACKAGE)\n        .and()\n        .doNotHaveFullyQualifiedName(ownerClass.getName())\n        .should()\n        .callConstructor(ownerClass, parameterTypes)\n        .because(\n            \"internal call sites must use marker-aware overloads to avoid external telemetry false positives\")\n        .allowEmptyShould(true);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/InternalApiTelemetryTrackerTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentCaptor.forClass;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\n@Tag(TestTags.CORE)\nclass InternalApiTelemetryTrackerTest {\n  private Telemetry mockClient;\n\n  @BeforeEach\n  void setUp() {\n    InternalApiTelemetryTracker.resetForTesting();\n    mockClient = mock(Telemetry.class);\n  }\n\n  @Test\n  void shouldFlushAggregatedDataThroughProvidedClient() {\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n    InternalApiTelemetryTracker.record(\"SFStatement\", \"execute\");\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    ArgumentCaptor<TelemetryData> captor = forClass(TelemetryData.class);\n    verify(mockClient, times(1)).addLogToBatch(captor.capture());\n\n    ObjectNode message = captor.getValue().getMessage();\n    assertEquals(\"client_internal_api_usage\", message.get(\"type\").asText());\n    assertEquals(\"JDBC\", message.get(\"source\").asText());\n\n    JsonNode methods = message.get(\"methods\");\n    assertNotNull(methods);\n    assertEquals(1, methods.get(\"SFSession#getDatabase\").asLong());\n    assertEquals(1, methods.get(\"SFStatement#execute\").asLong());\n  }\n\n  @Test\n  void shouldAggregateMultipleCallsToSameMethod() {\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    ArgumentCaptor<TelemetryData> captor = forClass(TelemetryData.class);\n    verify(mockClient).addLogToBatch(captor.capture());\n\n    JsonNode methods = captor.getValue().getMessage().get(\"methods\");\n    assertEquals(3, methods.get(\"SFSession#getDatabase\").asLong());\n  }\n\n  @Test\n  void shouldNotFlushWhenNoCallsRecorded() {\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    verify(mockClient, never()).addLogToBatch(any());\n  }\n\n  @Test\n  void shouldDrainCountsOnFlushAndAccumulateAgain() {\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n    InternalApiTelemetryTracker.record(\"SFStatement\", \"execute\");\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    ArgumentCaptor<TelemetryData> captor = forClass(TelemetryData.class);\n    verify(mockClient, times(2)).addLogToBatch(captor.capture());\n\n    JsonNode firstMethods = captor.getAllValues().get(0).getMessage().get(\"methods\");\n    assertEquals(1, firstMethods.get(\"SFSession#getDatabase\").asLong());\n    assertNull(firstMethods.get(\"SFStatement#execute\"));\n\n    JsonNode secondMethods = captor.getAllValues().get(1).getMessage().get(\"methods\");\n    assertEquals(1, secondMethods.get(\"SFSession#getDatabase\").asLong());\n    assertEquals(1, secondMethods.get(\"SFStatement#execute\").asLong());\n  }\n\n  @Test\n  void concurrentSessionsEachFlushIndependently() {\n    Telemetry clientA = mock(Telemetry.class);\n    Telemetry clientB = mock(Telemetry.class);\n\n    InternalApiTelemetryTracker.record(\"SFSession\", \"getDatabase\");\n    InternalApiTelemetryTracker.flush(clientA);\n\n    InternalApiTelemetryTracker.record(\"SFStatement\", \"execute\");\n    InternalApiTelemetryTracker.flush(clientB);\n\n    ArgumentCaptor<TelemetryData> captorA = forClass(TelemetryData.class);\n    verify(clientA, times(1)).addLogToBatch(captorA.capture());\n    assertEquals(\n        1, captorA.getValue().getMessage().get(\"methods\").get(\"SFSession#getDatabase\").asLong());\n\n    ArgumentCaptor<TelemetryData> captorB = forClass(TelemetryData.class);\n    verify(clientB, times(1)).addLogToBatch(captorB.capture());\n    assertEquals(\n        1, captorB.getValue().getMessage().get(\"methods\").get(\"SFStatement#execute\").asLong());\n  }\n\n  @Test\n  void markerAwareRecordingShouldRecordWhenMarkerIsMissing() {\n    InternalApiTelemetryTracker.recordIfExternal(\"SFSession\", \"getRole\", null);\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    ArgumentCaptor<TelemetryData> captor = forClass(TelemetryData.class);\n    verify(mockClient).addLogToBatch(captor.capture());\n    assertEquals(\n        1, captor.getValue().getMessage().get(\"methods\").get(\"SFSession#getRole\").asLong());\n  }\n\n  @Test\n  void markerAwareRecordingShouldNotRecordWhenMarkerIsProvided() {\n    InternalApiTelemetryTracker.recordIfExternal(\n        \"SFSession\", \"getRole\", InternalApiTelemetryTracker.internalCallMarker());\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    verify(mockClient, never()).addLogToBatch(any());\n  }\n\n  @Test\n  void sfSessionMethodShouldRecordWithoutMarker() {\n    SFSession session = new SFSession();\n    session.getSessionToken();\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    ArgumentCaptor<TelemetryData> captor = forClass(TelemetryData.class);\n    verify(mockClient).addLogToBatch(captor.capture());\n    assertEquals(\n        1, captor.getValue().getMessage().get(\"methods\").get(\"SFSession#getSessionToken\").asLong());\n  }\n\n  @Test\n  void sfSessionMethodShouldNotRecordWithMarker() {\n    SFSession session = new SFSession();\n    session.getSessionToken(InternalApiTelemetryTracker.internalCallMarker());\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    verify(mockClient, never()).addLogToBatch(any());\n  }\n\n  @Test\n  void sessionUtilGenerateJwtShouldRecordWithoutMarkerEvenWhenItFails() {\n    assertThrows(\n        SFException.class,\n        () -> SessionUtil.generateJWTToken(null, null, null, null, \"account\", \"user\"));\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    ArgumentCaptor<TelemetryData> captor = forClass(TelemetryData.class);\n    verify(mockClient).addLogToBatch(captor.capture());\n    assertEquals(\n        1,\n        captor.getValue().getMessage().get(\"methods\").get(\"SessionUtil#generateJWTToken\").asLong());\n  }\n\n  @Test\n  void sessionUtilGenerateJwtShouldNotRecordWithMarker() {\n    assertThrows(\n        SFException.class,\n        () ->\n            SessionUtil.generateJWTToken(\n                null,\n                null,\n                null,\n                null,\n                \"account\",\n                \"user\",\n                InternalApiTelemetryTracker.internalCallMarker()));\n\n    InternalApiTelemetryTracker.flush(mockClient);\n\n    verify(mockClient, never()).addLogToBatch(any());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/NoOpTelemetryClientTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertDoesNotThrow;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.concurrent.Future;\nimport org.junit.jupiter.api.Test;\n\nclass NoOpTelemetryClientTest {\n\n  @Test\n  void testAddLogToBatch() {\n    NoOpTelemetryClient client = new NoOpTelemetryClient();\n    assertDoesNotThrow(() -> client.addLogToBatch(null)); // Should not throw an exception\n  }\n\n  @Test\n  void testClose() {\n    NoOpTelemetryClient client = new NoOpTelemetryClient();\n    assertDoesNotThrow(client::close); // Should not throw an exception\n  }\n\n  @Test\n  void testSendBatchAsync() throws Exception {\n    NoOpTelemetryClient client = new NoOpTelemetryClient();\n    Future<Boolean> result = client.sendBatchAsync();\n    assertNotNull(result);\n    assertTrue(result.get()); // Should return true\n  }\n\n  @Test\n  void testPostProcess() {\n    NoOpTelemetryClient client = new NoOpTelemetryClient();\n    assertDoesNotThrow(\n        () ->\n            client.postProcess(\n                \"query123\", \"SQLSTATE\", 1001, new Exception(\"Test Exception\"))); // Should not throw\n    assertDoesNotThrow(\n        () -> client.postProcess(null, null, 0, null)); // Should handle null values gracefully\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/PreSessionTelemetryClientTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\nclass PreSessionTelemetryClientTest {\n  private PreSessionTelemetryClient preSessionTelemetryClient;\n  private Telemetry mockRealTelemetryClient;\n  private TelemetryData mockTelemetryData;\n\n  @BeforeEach\n  void setUp() {\n    preSessionTelemetryClient = new PreSessionTelemetryClient();\n    mockRealTelemetryClient = mock(Telemetry.class);\n    mockTelemetryData = mock(TelemetryData.class);\n  }\n\n  @Test\n  void shouldFlushBufferedDataAndPassThroughAfterRealClientSet() {\n    preSessionTelemetryClient.addLogToBatch(mockTelemetryData);\n    preSessionTelemetryClient.addLogToBatch(mockTelemetryData);\n\n    preSessionTelemetryClient.setRealTelemetryClient(mockRealTelemetryClient);\n\n    assertTrue(preSessionTelemetryClient.hasRealTelemetryClient());\n    verify(mockRealTelemetryClient, times(2)).addLogToBatch(mockTelemetryData);\n\n    preSessionTelemetryClient.addLogToBatch(mockTelemetryData);\n    verify(mockRealTelemetryClient, times(3)).addLogToBatch(mockTelemetryData);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryIT.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Map;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.HttpClientSettingsKey;\nimport net.snowflake.client.internal.core.HttpUtil;\nimport net.snowflake.client.internal.core.OCSPMode;\nimport net.snowflake.client.internal.core.SFException;\nimport net.snowflake.client.internal.core.SessionUtil;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class TelemetryIT extends AbstractDriverIT {\n  private static final String OAUTH_SCOPE_FORMAT = \"session:role:%s\";\n  private Connection connection = null;\n  private static final ObjectMapper mapper = new ObjectMapper();\n\n  @BeforeEach\n  public void init() throws SQLException, IOException {\n    this.connection = getConnection();\n  }\n\n  @Test\n  public void testTelemetry() throws Exception {\n    TelemetryClient telemetry = (TelemetryClient) TelemetryClient.createTelemetry(connection, 100);\n    testTelemetryInternal(telemetry);\n  }\n\n  @Disabled\n  @Test\n  @DontRunOnGithubActions\n  public void testSessionlessTelemetry() throws Exception, SFException {\n    testTelemetryInternal(createSessionlessTelemetry());\n  }\n\n  @Disabled\n  @Test\n  @DontRunOnGithubActions\n  public void testJWTSessionlessTelemetry() throws Exception, SFException {\n    testTelemetryInternal(createJWTSessionlessTelemetry());\n  }\n\n  @Disabled\n  @Test\n  @DontRunOnGithubActions\n  public void testOAuthSessionlessTelemetry() throws Exception, SFException {\n    testTelemetryInternal(createOAuthSessionlessTelemetry());\n  }\n\n  private void testTelemetryInternal(TelemetryClient telemetry) throws Exception {\n    ObjectNode node1 = mapper.createObjectNode();\n    node1.put(\"type\", \"query\");\n    node1.put(\"query_id\", \"sdasdasdasdasds\");\n    ObjectNode node2 = mapper.createObjectNode();\n    node2.put(\"type\", \"query\");\n    node2.put(\"query_id\", \"eqweqweqweqwe\");\n    telemetry.addLogToBatch(node1, 1234567);\n    telemetry.addLogToBatch(new TelemetryData(node2, 22345678));\n    assertEquals(telemetry.bufferSize(), 2);\n\n    assertTrue(telemetry.sendBatchAsync().get());\n    assertEquals(telemetry.bufferSize(), 0);\n\n    assertTrue(telemetry.sendLog(node1, 1234567));\n    assertEquals(telemetry.bufferSize(), 0);\n\n    assertTrue(telemetry.sendLog(new TelemetryData(node2, 22345678)));\n    assertEquals(telemetry.bufferSize(), 0);\n\n    // reach flush threshold\n    for (int i = 0; i < 99; i++) {\n      telemetry.addLogToBatch(node1, 1111);\n    }\n    assertEquals(telemetry.bufferSize(), 99);\n    telemetry.addLogToBatch(node1, 222);\n\n    // flush is async, sleep some time and then check buffer size\n    Thread.sleep(1000);\n    assertEquals(telemetry.bufferSize(), 0);\n\n    telemetry.addLogToBatch(node1, 111);\n    assertEquals(telemetry.bufferSize(), 1);\n\n    assertFalse(telemetry.isClosed());\n    telemetry.close();\n    assertTrue(telemetry.isClosed());\n    // close function sends the metrics to the server\n    assertEquals(telemetry.bufferSize(), 0);\n  }\n\n  @Test\n  public void close1() {\n    TelemetryClient telemetry = (TelemetryClient) TelemetryClient.createTelemetry(connection);\n    telemetry.close();\n    ObjectNode node = mapper.createObjectNode();\n    node.put(\"type\", \"query\");\n    node.put(\"query_id\", \"sdasdasdasdasds\");\n    telemetry.addLogToBatch(node, 1234567);\n  }\n\n  @Test\n  public void close2() {\n    TelemetryClient telemetry = (TelemetryClient) TelemetryClient.createTelemetry(connection);\n    telemetry.close();\n    ObjectNode node = mapper.createObjectNode();\n    node.put(\"type\", \"query\");\n    node.put(\"query_id\", \"sdasdasdasdasds\");\n    telemetry.addLogToBatch(new TelemetryData(node, 1234567));\n  }\n\n  @Test\n  public void close3() {\n    TelemetryClient telemetry = (TelemetryClient) TelemetryClient.createTelemetry(connection);\n    telemetry.close();\n    telemetry.close();\n  }\n\n  @Test\n  public void testDisableTelemetry() throws Exception {\n    TelemetryClient telemetry = (TelemetryClient) TelemetryClient.createTelemetry(connection, 100);\n    testDisableTelemetryInternal(telemetry);\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDisableJWTSessionlessTelemetry() throws Exception, SFException {\n    testDisableTelemetryInternal(createJWTSessionlessTelemetry());\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testDisableOAuthSessionlessTelemetry() throws Exception, SFException {\n    testDisableTelemetryInternal(createOAuthSessionlessTelemetry());\n  }\n\n  public void testDisableTelemetryInternal(TelemetryClient telemetry) throws Exception {\n    ObjectNode node1 = mapper.createObjectNode();\n    node1.put(\"type\", \"query\");\n    node1.put(\"query_id\", \"sdasdasdasdasds\");\n    ObjectNode node2 = mapper.createObjectNode();\n    node2.put(\"type\", \"query\");\n    node2.put(\"query_id\", \"eqweqweqweqwe\");\n    telemetry.addLogToBatch(node1, 1234567);\n\n    assertEquals(telemetry.bufferSize(), 1);\n    telemetry.disableTelemetry();\n    assertFalse(telemetry.isTelemetryEnabled());\n\n    telemetry.addLogToBatch(new TelemetryData(node2, 22345678));\n    assertEquals(telemetry.bufferSize(), 1);\n\n    assertFalse(telemetry.sendBatchAsync().get());\n    assertEquals(telemetry.bufferSize(), 1);\n\n    assertFalse(telemetry.sendLog(node1, 1234567));\n    assertEquals(telemetry.bufferSize(), 1);\n\n    assertFalse(telemetry.sendLog(new TelemetryData(node2, 22345678)));\n    assertEquals(telemetry.bufferSize(), 1);\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testClosedJWTSessionlessTelemetry() throws Exception, SFException {\n    TelemetryClient telemetry = createJWTSessionlessTelemetry();\n    telemetry.close();\n    ObjectNode node = mapper.createObjectNode();\n    node.put(\"type\", \"query\");\n    node.put(\"query_id\", \"sdasdasdasdasds\");\n    telemetry.addLogToBatch(node, 1234567);\n    assertFalse(telemetry.sendBatchAsync().get());\n  }\n\n  @Test\n  @DontRunOnGithubActions\n  public void testClosedOAuthSessionlessTelemetry() throws Exception, SFException {\n    TelemetryClient telemetry = createOAuthSessionlessTelemetry();\n    telemetry.close();\n    ObjectNode node = mapper.createObjectNode();\n    node.put(\"type\", \"query\");\n    node.put(\"query_id\", \"sdasdasdasdasds\");\n    telemetry.addLogToBatch(node, 1234567);\n    assertFalse(telemetry.sendBatchAsync().get());\n  }\n\n  // Helper function to create a sessionless telemetry\n  // using createSessionlessTelemetry(CloseableHttpClient httpClient, String serverUrl)\n  private TelemetryClient createSessionlessTelemetry()\n      throws SFException, SQLException, IOException {\n    setUpPublicKey();\n    String privateKeyLocation = getFullPathFileInResource(\"rsa_key.p8\");\n    Map<String, String> parameters = getConnectionParameters();\n    String jwtToken =\n        SessionUtil.generateJWTToken(\n            null,\n            privateKeyLocation,\n            null,\n            null,\n            parameters.get(\"account\"),\n            parameters.get(\"user\"));\n\n    CloseableHttpClient httpClient =\n        HttpUtil.buildHttpClient(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN), null, false);\n    TelemetryClient telemetry =\n        (TelemetryClient)\n            TelemetryClient.createSessionlessTelemetry(\n                httpClient, String.format(\"%s:%s\", parameters.get(\"host\"), parameters.get(\"port\")));\n    telemetry.refreshToken(jwtToken);\n    return telemetry;\n  }\n\n  // Helper function to create a sessionless telemetry using keypair JWT\n  private TelemetryClient createJWTSessionlessTelemetry()\n      throws SFException, SQLException, IOException {\n    setUpPublicKey();\n    String privateKeyLocation = getFullPathFileInResource(\"rsa_key.p8\");\n    Map<String, String> parameters = getConnectionParameters();\n    String jwtToken =\n        SessionUtil.generateJWTToken(\n            null,\n            privateKeyLocation,\n            null,\n            null,\n            parameters.get(\"account\"),\n            parameters.get(\"user\"));\n\n    CloseableHttpClient httpClient =\n        HttpUtil.buildHttpClient(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN), null, false);\n    TelemetryClient telemetry =\n        (TelemetryClient)\n            TelemetryClient.createSessionlessTelemetry(\n                httpClient,\n                String.format(\"%s:%s\", parameters.get(\"host\"), parameters.get(\"port\")),\n                \"KEYPAIR_JWT\");\n    telemetry.refreshToken(jwtToken);\n    return telemetry;\n  }\n\n  // Helper function to set up the public key\n  private void setUpPublicKey() throws SQLException, IOException {\n    Map<String, String> parameters = getConnectionParameters();\n    String testUser = parameters.get(\"user\");\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n      String pathfile = getFullPathFileInResource(\"rsa_key.pub\");\n      String pubKey = new String(Files.readAllBytes(Paths.get(pathfile)));\n      pubKey = pubKey.replace(\"-----BEGIN PUBLIC KEY-----\", \"\");\n      pubKey = pubKey.replace(\"-----END PUBLIC KEY-----\", \"\");\n      statement.execute(String.format(\"alter user %s set rsa_public_key='%s'\", testUser, pubKey));\n    }\n  }\n\n  // Helper function to create a sessionless telemetry using OAuth\n  private TelemetryClient createOAuthSessionlessTelemetry()\n      throws SFException, SQLException, IOException {\n    String oAuthToken = getOAuthToken();\n    Map<String, String> parameters = getConnectionParameters();\n    CloseableHttpClient httpClient =\n        HttpUtil.buildHttpClient(new HttpClientSettingsKey(OCSPMode.FAIL_OPEN), null, false);\n    TelemetryClient telemetry =\n        (TelemetryClient)\n            TelemetryClient.createSessionlessTelemetry(\n                httpClient,\n                String.format(\"%s:%s\", parameters.get(\"host\"), parameters.get(\"port\")),\n                \"OAUTH\");\n    telemetry.refreshToken(oAuthToken);\n    return telemetry;\n  }\n\n  // Helper function to set up and get OAuth token\n  private String getOAuthToken() throws SQLException {\n    Map<String, String> parameters = getConnectionParameters();\n    String token = null;\n    try (Connection connection = getConnection();\n        Statement statement = connection.createStatement()) {\n      statement.execute(\"use role accountadmin\");\n      statement.execute(\n          \"create or replace security integration telemetry_oauth_integration\\n\"\n              + \"  type=oauth\\n\"\n              + \"  oauth_client=CUSTOM\\n\"\n              + \"  oauth_client_type=CONFIDENTIAL\\n\"\n              + \"  oauth_redirect_uri='https://localhost.com/oauth'\\n\"\n              + \"  oauth_issue_refresh_tokens=true\\n\"\n              + \"  enabled=true oauth_refresh_token_validity=86400;\");\n\n      String role = String.format(OAUTH_SCOPE_FORMAT, parameters.get(\"role\"));\n      try (ResultSet resultSet =\n          statement.executeQuery(\n              \"select system$it('create_oauth_access_token', 'TELEMETRY_OAUTH_INTEGRATION', '\"\n                  + role\n                  + \"')\")) {\n        assertTrue(resultSet.next());\n        token = resultSet.getString(1);\n      }\n    }\n    return token;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.LinkedList;\nimport org.junit.jupiter.api.Test;\n\n/** Telemetry unit tests */\npublic class TelemetryTest {\n  private ObjectMapper mapper = new ObjectMapper();\n\n  @Test\n  public void testJsonConversion() {\n\n    ObjectNode log1 = mapper.createObjectNode();\n    log1.put(\"type\", \"query\");\n    log1.put(\"query_id\", \"sdasdasdasdasds\");\n    long timestamp1 = 12345678;\n    ObjectNode log2 = mapper.createObjectNode();\n    log2.put(\"type\", \"query\");\n    log2.put(\"query_id\", \"eqweqweqweqwe\");\n    long timestamp2 = 22345678;\n\n    LinkedList<TelemetryData> list = new LinkedList<>();\n    list.add(new TelemetryData(log1, timestamp1));\n    list.add(new TelemetryData(log2, timestamp2));\n\n    String result = TelemetryClient.logsToString(list);\n\n    ObjectNode expect = mapper.createObjectNode();\n    ArrayNode logs = mapper.createArrayNode();\n    ObjectNode message1 = mapper.createObjectNode();\n    message1.put(\"timestamp\", timestamp1 + \"\");\n    message1.set(\"message\", log1);\n    logs.add(message1);\n    ObjectNode message2 = mapper.createObjectNode();\n    message2.put(\"timestamp\", timestamp2 + \"\");\n    message2.set(\"message\", log2);\n    logs.add(message2);\n    expect.set(\"logs\", logs);\n\n    assertEquals(expect.toString(), result);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetry/TelemetryUtilTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetry;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNull;\n\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport net.snowflake.client.api.driver.SnowflakeDriver;\nimport net.snowflake.common.core.LoginInfoDTO;\nimport org.junit.Test;\n\npublic class TelemetryUtilTest {\n  String queryId = \"testQueryId\";\n  String sqlState = \"00000\";\n  int errorNumber = 1000;\n  TelemetryField type = TelemetryField.HTTP_EXCEPTION;\n  String errorMessage = \"Test error message\";\n  String reason = \"reason\";\n\n  @Test\n  public void testCreateIBValueWithAllValues() {\n    ObjectNode on =\n        TelemetryUtil.createIBValue(queryId, sqlState, errorNumber, type, errorMessage, reason);\n\n    assertEquals(type.toString(), on.get(TelemetryField.TYPE.toString()).asText());\n    assertEquals(\n        LoginInfoDTO.SF_JDBC_APP_ID, on.get(TelemetryField.DRIVER_TYPE.toString()).asText());\n    assertEquals(\n        SnowflakeDriver.getImplementationVersion(),\n        on.get(TelemetryField.DRIVER_VERSION.toString()).asText());\n    assertEquals(queryId, on.get(TelemetryField.QUERY_ID.toString()).asText());\n    assertEquals(sqlState, on.get(TelemetryField.SQL_STATE.toString()).asText());\n    assertEquals(errorNumber, on.get(TelemetryField.ERROR_NUMBER.toString()).asInt());\n    assertEquals(errorMessage, on.get(TelemetryField.ERROR_MESSAGE.toString()).asText());\n    assertEquals(reason, on.get(TelemetryField.REASON.toString()).asText());\n  }\n\n  @Test\n  public void testCreateIBValueWithNullValue() {\n    ObjectNode on =\n        TelemetryUtil.createIBValue(null, sqlState, errorNumber, type, errorMessage, null);\n    assertNull(on.get(TelemetryField.QUERY_ID.toString()));\n    assertNull(on.get(TelemetryField.REASON.toString()));\n  }\n\n  @Test\n  public void testBuildCrlData() {\n    TelemetryData telemetryData = TelemetryUtil.buildCrlData(\"url\", 123, 1, 100, 25);\n\n    ObjectNode message = telemetryData.getMessage();\n    assertNotNull(message);\n    assertEquals(\n        TelemetryField.CLIENT_CRL_STATS.toString(),\n        message.get(TelemetryField.TYPE.toString()).asText());\n    assertEquals(\"url\", message.get(TelemetryField.CRL_URL.toString()).asText());\n    assertEquals(123, message.get(TelemetryField.CRL_BYTES.toString()).asInt());\n    assertEquals(1, message.get(TelemetryField.CRL_REVOKED_CERTIFICATES.toString()).asInt());\n    assertEquals(100, message.get(TelemetryField.TIME_DOWNLOADING_CRL.toString()).asInt());\n    assertEquals(25, message.get(TelemetryField.TIME_PARSING_CRL.toString()).asInt());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetryOOB/TelemetryServiceIT.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetryOOB;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.sql.Statement;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.concurrent.TimeUnit;\nimport net.snowflake.client.annotations.RunOnTestaccountNotOnGithubActions;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\nimport net.snowflake.client.internal.core.SFSession;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport net.snowflake.client.internal.jdbc.SnowflakeLoggedFeatureNotSupportedException;\nimport net.snowflake.common.core.SqlState;\nimport org.apache.commons.lang3.time.StopWatch;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Standalone test cases for the out of band telemetry service */\n@Tag(TestTags.CORE)\npublic class TelemetryServiceIT extends BaseJDBCTest {\n  public static final int WAIT_FOR_TELEMETRY_REPORT_IN_MILLISECS = 5000;\n  private boolean defaultState;\n\n  @BeforeEach\n  public void setUp() {\n    TelemetryService service = TelemetryService.getInstance();\n    Map<String, String> connectionParams = getConnectionParameters();\n    connectionParams.put(\"TELEMETRYDEPLOYMENT\", \"K8TEST\");\n    service.updateContextForIT(connectionParams);\n    defaultState = service.isEnabled();\n    service.enable();\n  }\n\n  @AfterEach\n  public void tearDown() throws InterruptedException {\n    // wait 5 seconds while the service is flushing\n    TimeUnit.SECONDS.sleep(5);\n    TelemetryService service = TelemetryService.getInstance();\n    if (defaultState) {\n      service.enable();\n    } else {\n      service.disable();\n    }\n  }\n\n  @SuppressWarnings(\"divzero\")\n  @Disabled\n  @Test\n  public void testCreateException() {\n    TelemetryService service = TelemetryService.getInstance();\n    try {\n      int a = 10 / 0;\n    } catch (Exception ex) {\n      // example for an exception log\n      // this log will be delivered to snowflake\n      TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n      TelemetryEvent log = logBuilder.withException(ex).build();\n      System.out.println(log);\n      service.report(log);\n\n      // example for an exception metric\n      // this metric will be delivered to snowflake and wavefront\n      TelemetryEvent.MetricBuilder mBuilder = new TelemetryEvent.MetricBuilder();\n      TelemetryEvent metric = mBuilder.withException(ex).withTag(\"domain\", \"test\").build();\n      System.out.println(metric);\n      service.report(metric);\n    }\n  }\n\n  /** test wrong server url. */\n  @Disabled\n  @Test\n  public void testWrongServerURL() throws InterruptedException {\n    TelemetryService service = TelemetryService.getInstance();\n    TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n    TelemetryEvent log =\n        logBuilder.withName(\"ExampleLog\").withValue(\"This is an example log\").build();\n    int count = service.getEventCount();\n    service.report(log);\n    // wait for at most 30 seconds\n    int i = 6;\n    while (i-- > 0) {\n      TimeUnit.SECONDS.sleep(5);\n      if (service.getEventCount() > count) {\n        break;\n      }\n    }\n    assertThat(\"WrongServerURL do not block.\", service.getEventCount() > count);\n  }\n\n  @Disabled\n  @Test\n  public void testCreateLog() {\n    // this log will be delivered to snowflake\n    TelemetryService service = TelemetryService.getInstance();\n    TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n    TelemetryEvent log =\n        logBuilder.withName(\"ExampleLog\").withValue(\"This is an example log\").build();\n    assertThat(\"check log value\", log.get(\"Value\").equals(\"This is an example log\"));\n    service.report(log);\n  }\n\n  @Disabled\n  @Test\n  public void testCreateLogWithAWSSecret() {\n    // this log will be delivered to snowflake\n    TelemetryService service = TelemetryService.getInstance();\n    TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n    TelemetryEvent log =\n        logBuilder\n            .withName(\"ExampleLog\")\n            .withValue(\n                \"This is an example log: credentials=(\\n\"\n                    + \"  aws_key_id='xxdsdfsafds'\\n\"\n                    + \"  aws_secret_key='safas+asfsad+safasf')\\n\")\n            .build();\n    String marked = service.exportQueueToString(log);\n\n    assertThat(\"marked aws_key_id\", !marked.contains(\"xxdsdfsafds\"));\n    assertThat(\"marked aws_secret_key\", !marked.contains(\"safas+asfsad+safasf\"));\n    service.report(log);\n  }\n\n  @Disabled\n  @Test\n  public void stressTestCreateLog() {\n    // this log will be delivered to snowflake\n    TelemetryService service = TelemetryService.getInstance();\n    // send one http request for each event\n    StopWatch sw = new StopWatch();\n    sw.start();\n    int rate = 50;\n    int sent = 0;\n    int duration = 60;\n    while (sw.getTime() < duration * 1000) {\n      int toSend = (int) (sw.getTime() / 1000) * rate - sent;\n      for (int i = 0; i < toSend; i++) {\n        TelemetryEvent log =\n            new TelemetryEvent.LogBuilder()\n                .withName(\"StressTestLog\")\n                .withValue(\"This is an example log for stress test \" + sent)\n                .build();\n        System.out.println(\"stress test: \" + sent++ + \" sent.\");\n        service.report(log);\n      }\n    }\n    sw.stop();\n  }\n\n  @Disabled\n  @Test\n  public void testCreateLogInBlackList() {\n    // this log will be delivered to snowflake\n    TelemetryService service = TelemetryService.getInstance();\n    TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n    TelemetryEvent log =\n        logBuilder.withName(\"unknown\").withValue(\"This is a log in blacklist\").build();\n    service.report(log);\n  }\n\n  @Disabled\n  @Test\n  public void testCreateUrgentEvent() {\n    // this log will be delivered to snowflake\n    TelemetryService service = TelemetryService.getInstance();\n    TelemetryEvent.LogBuilder logBuilder = new TelemetryEvent.LogBuilder();\n    TelemetryEvent log =\n        logBuilder.withName(\"UrgentLog\").withValue(\"This is an example urgent log\").build();\n    assertThat(\"check log value\", log.get(\"Value\").equals(\"This is an example urgent log\"));\n    service.report(log);\n  }\n\n  @Disabled\n  @Test\n  public void stressTestCreateUrgentEvent() {\n    // this log will be delivered to snowflake\n    TelemetryService service = TelemetryService.getInstance();\n    // send one http request for each event\n    StopWatch sw = new StopWatch();\n    sw.start();\n    int rate = 1;\n    int sent = 0;\n    int duration = 5;\n    while (sw.getTime() < duration * 1000) {\n      int toSend = (int) (sw.getTime() / 1000) * rate - sent;\n      for (int i = 0; i < toSend; i++) {\n        TelemetryEvent log =\n            new TelemetryEvent.LogBuilder()\n                .withName(\"StressUrgentTestLog\")\n                .withValue(\"This is an example urgent log for stress test \" + sent)\n                .build();\n        System.out.println(\"stress test: \" + sent++ + \" sent.\");\n        service.report(log);\n      }\n    }\n    sw.stop();\n  }\n\n  private void generateDummyException(int vendorCode, SFSession session)\n      throws SnowflakeSQLLoggedException {\n    String queryID = \"01234567-1234-1234-1234-00001abcdefg\";\n    String reason = \"This is a test exception.\";\n    String sqlState = SqlState.NO_DATA;\n    throw new SnowflakeSQLLoggedException(session, reason, sqlState, vendorCode, queryID);\n  }\n\n  private int generateSQLFeatureNotSupportedException() throws SQLFeatureNotSupportedException {\n    throw new SnowflakeLoggedFeatureNotSupportedException(null);\n  }\n\n  /**\n   * Test case for checking telemetry message for SnowflakeSQLExceptions. Assert that telemetry OOB\n   * endpoint is reached after a SnowflakeSQLLoggedException is thrown.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @RunOnTestaccountNotOnGithubActions\n  public void testSnowflakeSQLLoggedExceptionOOBTelemetry()\n      throws SQLException, InterruptedException {\n    // make a connection to initialize telemetry instance\n    int fakeVendorCode = 27;\n    SnowflakeSQLLoggedException e =\n        assertThrows(\n            SnowflakeSQLLoggedException.class, () -> generateDummyException(fakeVendorCode, null));\n    // The error response has the same code as the fakeErrorCode\n    assertThat(\"Communication error\", e.getErrorCode(), equalTo(fakeVendorCode));\n\n    // since it returns normal response,\n    // the telemetry does not create new event\n    Thread.sleep(WAIT_FOR_TELEMETRY_REPORT_IN_MILLISECS);\n    if (TelemetryService.getInstance().isDeploymentEnabled()) {\n      assertThat(\n          \"Telemetry event has not been reported successfully. Error: \"\n              + TelemetryService.getInstance().getLastClientError(),\n          TelemetryService.getInstance().getClientFailureCount(),\n          equalTo(0));\n    }\n  }\n\n  /**\n   * Test case for checking telemetry message for SqlFeatureNotSupportedExceptions. Assert that\n   * telemetry OOB endpoint is reached after a SnowflakeSQLLoggedException is thrown.\n   *\n   * <p>After running test, check for result in client_telemetry_oob table with type\n   * client_sql_exception.\n   *\n   * @throws SQLException\n   */\n  @Test\n  @RunOnTestaccountNotOnGithubActions\n  public void testSQLFeatureNotSupportedOOBTelemetry() throws InterruptedException {\n    // with null session, OOB telemetry will be thrown\n    SQLFeatureNotSupportedException e =\n        assertThrows(\n            SQLFeatureNotSupportedException.class, this::generateSQLFeatureNotSupportedException);\n    // since it returns normal response,\n    // the telemetry does not create new event\n    Thread.sleep(WAIT_FOR_TELEMETRY_REPORT_IN_MILLISECS);\n    if (TelemetryService.getInstance().isDeploymentEnabled()) {\n      assertThat(\n          \"Telemetry event has not been reported successfully. Error: \"\n              + TelemetryService.getInstance().getLastClientError(),\n          TelemetryService.getInstance().getClientFailureCount(),\n          equalTo(0));\n    }\n  }\n\n  /**\n   * This is a manual test for ensuring that the HTAP OOB telemetry parameter and the telemetry\n   * deployment specification parameters are working properly. Debug through test\n   *\n   * @throws SQLException\n   */\n  @Disabled\n  @Test\n  public void testHTAPTelemetry() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"htapOOBTelemetryEnabled\", \"true\");\n    properties.put(\"telemetryDeployment\", \"qa1\");\n    // properties.put(\"useProxy\", \"true\");\n    // properties.put(\"proxyHost\", \"localhost\");\n    // properties.put(\"proxyPort\", \"8181\");\n    try (Connection con = getConnection(properties)) {\n      Statement statement = con.createStatement();\n      statement.execute(\"alter session set CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED=false\");\n      // Ensure that HTAP telemetry is collected for below select 1 statement\n      statement.execute(\"select 1\");\n      // Ensure that HTAP telemetry is collected for below select 2 statement\n      statement.execute(\"alter session set CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED=true\");\n      statement.execute(\"select 2\");\n      TelemetryService.disableHTAP();\n      // Ensure that no telemetry is collected for below select 3 statement\n      statement.execute(\"select 3\");\n    }\n  }\n\n  /**\n   * Requires part 2 of SNOW-844477. Make sure CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED is true at\n   * account level. Tests connection property CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED=true\n   */\n  @Disabled\n  @Test\n  public void testOOBTelemetryEnabled() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\", \"true\");\n    try (Connection con = getConnection(properties)) {\n      Statement statement = con.createStatement();\n      statement.execute(\"select 1\");\n      // Make sure OOB telemetry is enabled\n      assertTrue(TelemetryService.getInstance().isEnabled());\n    }\n  }\n\n  /**\n   * Requires part 2 of SNOW-844477. Make sure CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED is false at\n   * account level. Tests connection property CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED=false\n   */\n  @Disabled\n  @Test\n  public void testOOBTelemetryDisabled() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\", \"false\");\n    try (Connection con = getConnection(properties)) {\n      Statement statement = con.createStatement();\n      statement.execute(\"select 1\");\n      // Make sure OOB telemetry is disabled\n      assertFalse(TelemetryService.getInstance().isEnabled());\n    }\n  }\n\n  /**\n   * Requires part 2 of SNOW-844477. Make sure CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED is true at\n   * account level. Tests connection property CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED=false but\n   * CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED is enabled on account level\n   */\n  @Disabled\n  @Test\n  public void testOOBTelemetryEnabledOnServerDisabledOnClient() throws SQLException {\n    Properties properties = new Properties();\n    properties.put(\"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\", \"false\");\n    try (Connection con = getConnection(properties)) {\n      Statement statement = con.createStatement();\n      statement.execute(\"select 1\");\n      // Make sure OOB telemetry is enabled\n      assertTrue(TelemetryService.getInstance().isEnabled());\n    }\n  }\n\n  /**\n   * Test case for checking telemetry message for SnowflakeSQLExceptions. In-band telemetry should\n   * be used.\n   *\n   * @throws SQLException\n   */\n  @Test\n  public void testSnowflakeSQLLoggedExceptionIBTelemetry() throws SQLException {\n    // make a connection to initialize telemetry instance\n    try (Connection con = getConnection()) {\n      int fakeErrorCode = 27;\n      SnowflakeSQLLoggedException e =\n          assertThrows(\n              SnowflakeSQLLoggedException.class,\n              () ->\n                  generateDummyException(\n                      fakeErrorCode, con.unwrap(SnowflakeConnectionImpl.class).getSfSession()));\n      // The error response has the same code as the fakeErrorCode\n      assertThat(\"Communication error\", e.getErrorCode(), equalTo(fakeErrorCode));\n    }\n  }\n\n  /**\n   * Test case for checking telemetry message for SnowflakeFeatureNotSupporteExceptions. In-band\n   * telemetry should be used.\n   *\n   * <p>After running test, check for telemetry message in client_telemetry_v table.\n   */\n  @Test\n  public void testSqlFeatureNotSupportedExceptionIBTelemetry() throws SQLException {\n    // make a connection to initialize telemetry instance\n    try (Connection con = getConnection()) {\n      Statement statement = con.createStatement();\n      // try to execute a statement that throws a SQLFeatureNotSupportedException\n      assertThrows(\n          SQLFeatureNotSupportedException.class, () -> statement.execute(\"select 1\", new int[] {}));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetryOOB/TelemetryServiceTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetryOOB;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.not;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class TelemetryServiceTest {\n  private boolean defaultState;\n\n  @BeforeEach\n  public void setUp() {\n    TelemetryService service = TelemetryService.getInstance();\n    defaultState = service.isEnabled();\n    service.enable();\n  }\n\n  @AfterEach\n  public void tearDown() throws InterruptedException {\n    TelemetryService service = TelemetryService.getInstance();\n    if (defaultState) {\n      service.enable();\n    } else {\n      service.disable();\n    }\n  }\n\n  @Test\n  public void testTelemetryInitErrors() {\n    TelemetryService service = TelemetryService.getInstance();\n    assertThat(\n        \"Telemetry server failure count should be zero at first.\",\n        service.getServerFailureCount(),\n        equalTo(0));\n    assertThat(\n        \"Telemetry client failure count should be zero at first.\",\n        service.getClientFailureCount(),\n        equalTo(0));\n  }\n\n  @Test\n  public void testTelemetryEnableDisable() {\n    TelemetryService service = TelemetryService.getInstance();\n    // We already enabled telemetry in setup phase.\n    assertThat(\"Telemetry should be already enabled here.\", service.isEnabled(), equalTo(true));\n    service.disable();\n    assertThat(\"Telemetry should be already disabled here.\", service.isEnabled(), equalTo(false));\n    service.enable();\n    assertThat(\"Telemetry should be enabled back\", service.isEnabled(), equalTo(true));\n  }\n\n  @Test\n  public void testTelemetryConnectionString() {\n    String INVALID_CONNECTION_STRING =\n        \"://:-1\"; // INVALID_CONNECT_STRING in SnowflakeConnectionString.java\n    Map<String, String> param = new HashMap<>();\n    param.put(\"uri\", \"jdbc:snowflake://snowflake.reg.local:8082\");\n    param.put(\"host\", \"snowflake.reg.local\");\n    param.put(\"port\", \"8082\");\n    param.put(\"account\", \"fakeaccount\");\n    param.put(\"user\", \"fakeuser\");\n    param.put(\"password\", \"fakepassword\");\n    param.put(\"database\", \"fakedatabase\");\n    param.put(\"schema\", \"fakeschema\");\n    param.put(\"role\", \"fakerole\");\n    TelemetryService service = TelemetryService.getInstance();\n    service.updateContextForIT(param);\n    assertThat(\n        \"Telemetry failed to generate sfConnectionStr.\",\n        service.getSnowflakeConnectionString().toString(),\n        not(INVALID_CONNECTION_STRING));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/jdbc/telemetryOOB/TelemetryThreadPoolTest.java",
    "content": "package net.snowflake.client.internal.jdbc.telemetryOOB;\n\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.lang.reflect.Field;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.OTHERS)\nclass TelemetryThreadPoolTest {\n\n  /**\n   * This test confirms fixing the issue where the TelemetryThreadPool did not scale up to its\n   * maximum pool size due to its configuration with a core pool size of 0 and an unbounded work\n   * queue (LinkedBlockingQueue). https://github.com/snowflakedb/snowflake-jdbc/issues/2219\n   *\n   * <p>This test submits 100 tasks that each sleep for a short duration. If the pool were scaling,\n   * it would create up to 10 threads to handle the load concurrently. Previously only a single\n   * thread was ever created to process all tasks sequentially from the queue.\n   *\n   * @throws InterruptedException if the waiting thread is interrupted.\n   */\n  @Test\n  void testThreadPoolScaling() throws InterruptedException {\n    TelemetryThreadPool telemetryPool = TelemetryThreadPool.getInstance();\n    int taskCount = 100; // Submit more tasks than the max pool size (10)\n    ThreadPoolExecutor executor = getThreadPoolExecutor(telemetryPool);\n\n    final Set<Long> uniqueThreadIds = Collections.newSetFromMap(new ConcurrentHashMap<>());\n    final CountDownLatch completionLatch = new CountDownLatch(taskCount);\n\n    // Submit a burst of tasks. These tasks will be queued up and the pool should scale up to handle\n    // them\n    for (int i = 0; i < taskCount; i++) {\n      telemetryPool.execute(\n          () -> {\n            uniqueThreadIds.add(Thread.currentThread().getId());\n            try {\n              Thread.sleep(10);\n            } catch (InterruptedException e) {\n              Thread.currentThread().interrupt();\n            } finally {\n              completionLatch.countDown();\n            }\n          });\n    }\n\n    // Wait for all tasks to finish processing.\n    completionLatch.await(5, TimeUnit.SECONDS);\n\n    assertEquals(\n        10,\n        uniqueThreadIds.size(),\n        \"The thread pool should have used 10 threads, but it used \" + uniqueThreadIds.size());\n    assertEquals(10, executor.getPoolSize());\n\n    executor.setKeepAliveTime(\n        1L, TimeUnit.MILLISECONDS); // Reset keep-alive time to 0 to allow threads to terminate.\n\n    await()\n        .atMost(5, TimeUnit.SECONDS) // Max time to wait for the condition\n        .pollInterval(100, TimeUnit.MILLISECONDS) // Check every 100ms\n        .untilAsserted(\n            () -> {\n              assertEquals(\n                  0,\n                  executor.getPoolSize(),\n                  \"The thread pool should have scaled down to 0 idle threads.\");\n            });\n  }\n\n  @Test\n  void testTelemetryThreadsAreDaemonThreads() throws InterruptedException {\n    TelemetryThreadPool telemetryPool = TelemetryThreadPool.getInstance();\n    final AtomicBoolean isDaemonThread = new AtomicBoolean(false);\n    final CountDownLatch latch = new CountDownLatch(1);\n\n    telemetryPool.execute(\n        () -> {\n          isDaemonThread.set(Thread.currentThread().isDaemon());\n          latch.countDown();\n        });\n\n    // Wait for the task to complete\n    latch.await(1, TimeUnit.SECONDS);\n\n    assertTrue(isDaemonThread.get(), \"TelemetryThreadPool threads should be daemon threads\");\n  }\n\n  private ThreadPoolExecutor getThreadPoolExecutor(TelemetryThreadPool telemetryThreadPool) {\n    try {\n      Field uploaderField = TelemetryThreadPool.class.getDeclaredField(\"uploader\");\n      uploaderField.setAccessible(true);\n      return (ThreadPoolExecutor) uploaderField.get(telemetryThreadPool);\n    } catch (NoSuchFieldException | IllegalAccessException e) {\n      throw new RuntimeException(\"Failed to access the uploader field in TelemetryThreadPool\", e);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/FlatfileReadMultithreadIT.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.api.loader.LoadResultListener;\nimport net.snowflake.client.api.loader.LoaderFactory;\nimport net.snowflake.client.api.loader.LoaderProperty;\nimport net.snowflake.client.api.loader.LoadingError;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.LOADER)\npublic class FlatfileReadMultithreadIT {\n  private final int NUM_RECORDS = 100000;\n\n  private static final String TARGET_STAGE = \"STAGE_MULTITHREAD_LOADER\";\n  private static String TARGET_SCHEMA;\n  private static String TARGET_DB;\n\n  @BeforeAll\n  public static void setUpClass() throws Throwable {\n    try (Connection testConnection = AbstractDriverIT.getConnection();\n        // NOTE: the stage object must be created right after the connection\n        // because the Loader API assumes the stage object exists in the default\n        // namespace of the connection.\n        Statement statement = testConnection.createStatement()) {\n      statement.execute(String.format(\"CREATE OR REPLACE STAGE %s\", TARGET_STAGE));\n      TARGET_SCHEMA = testConnection.getSchema();\n      TARGET_DB = testConnection.getCatalog();\n    }\n  }\n\n  @AfterAll\n  public static void tearDownClass() throws Throwable {\n    try (Connection testConnection = AbstractDriverIT.getConnection();\n        Statement statement = testConnection.createStatement()) {\n      statement.execute(String.format(\"DROP STAGE IF EXISTS %s\", TARGET_STAGE));\n    }\n  }\n\n  /**\n   * Run loadAPI concurrently to ensure no race condition occurs.\n   *\n   * @throws Throwable raises an exception if any error occurs.\n   */\n  @Test\n  public void testIssueSimpleDateFormat() throws Throwable {\n    final String targetTable = \"TABLE_ISSUE_SIMPLEDATEFORMAT\";\n    try (Connection testConnection = AbstractDriverIT.getConnection();\n        Statement statement = testConnection.createStatement()) {\n      try {\n        statement.execute(\n            String.format(\n                \"CREATE OR REPLACE TABLE %s.%s.%s (\" + \"ID int, \" + \"C1 timestamp)\",\n                TARGET_DB, TARGET_SCHEMA, targetTable));\n        Thread t1 =\n            new Thread(\n                new FlatfileRead(NUM_RECORDS, TARGET_DB, TARGET_SCHEMA, TARGET_STAGE, targetTable));\n        Thread t2 =\n            new Thread(\n                new FlatfileRead(NUM_RECORDS, TARGET_DB, TARGET_SCHEMA, TARGET_STAGE, targetTable));\n\n        t1.start();\n        t2.start();\n        t1.join();\n        t2.join();\n        try (ResultSet rs =\n            statement.executeQuery(\n                String.format(\n                    \"select count(*) from %s.%s.%s\", TARGET_DB, TARGET_SCHEMA, targetTable))) {\n          rs.next();\n          assertThat(\"total number of records\", rs.getInt(1), equalTo(NUM_RECORDS * 2));\n        }\n\n      } finally {\n        statement.execute(\n            String.format(\"DROP TABLE IF EXISTS %s.%s.%s\", TARGET_DB, TARGET_SCHEMA, targetTable));\n      }\n    }\n  }\n\n  class FlatfileRead implements Runnable {\n    private final int totalRows;\n    private final String dbName;\n    private final String schemaName;\n    private final String tableName;\n    private final String stageName;\n\n    FlatfileRead(\n        int totalRows, String dbName, String schemaName, String stageName, String tableName) {\n      this.totalRows = totalRows;\n      this.dbName = dbName;\n      this.schemaName = schemaName;\n      this.stageName = stageName;\n      this.tableName = tableName;\n    }\n\n    @Override\n    public void run() {\n      try (Connection testConnection = AbstractDriverIT.getConnection();\n          Connection putConnection = AbstractDriverIT.getConnection()) {\n\n        ResultListener _resultListener = new ResultListener();\n\n        // init properties\n        Map<LoaderProperty, Object> prop = new HashMap<>();\n        prop.put(LoaderProperty.tableName, this.tableName);\n        prop.put(LoaderProperty.schemaName, this.schemaName);\n        prop.put(LoaderProperty.databaseName, this.dbName);\n        prop.put(LoaderProperty.remoteStage, this.stageName);\n        prop.put(LoaderProperty.operation, Operation.INSERT);\n\n        StreamLoader underTest =\n            (StreamLoader) LoaderFactory.createLoader(prop, putConnection, testConnection);\n        underTest.setProperty(LoaderProperty.startTransaction, true);\n        underTest.setProperty(LoaderProperty.truncateTable, false);\n\n        underTest.setProperty(LoaderProperty.columns, Arrays.asList(\"ID\", \"C1\"));\n\n        underTest.setListener(_resultListener);\n        underTest.start();\n\n        Random rnd = new Random();\n        for (int i = 0; i < this.totalRows; ++i) {\n          Object[] row = new Object[2];\n          row[0] = i;\n          // random timestamp data\n          long ms = -946771200000L + (Math.abs(rnd.nextLong()) % (70L * 365 * 24 * 60 * 60 * 1000));\n          row[1] = new Date(ms);\n          underTest.submitRow(row);\n        }\n\n        try {\n          underTest.finish();\n          underTest.close();\n        } catch (Exception e) {\n          e.printStackTrace();\n        }\n        assertThat(\"must be no error\", _resultListener.getErrorCount(), equalTo(0));\n        assertThat(\n            \"total number of rows\",\n            _resultListener.getSubmittedRowCount(),\n            equalTo(this.totalRows));\n      } catch (SQLException e) {\n        e.printStackTrace();\n      }\n    }\n\n    class ResultListener implements LoadResultListener {\n\n      private final List<LoadingError> errors = new ArrayList<>();\n\n      private final AtomicInteger errorCount = new AtomicInteger(0);\n      private final AtomicInteger errorRecordCount = new AtomicInteger(0);\n\n      private final AtomicInteger counter = new AtomicInteger(0);\n      private final AtomicInteger processed = new AtomicInteger(0);\n      private final AtomicInteger deleted = new AtomicInteger(0);\n      private final AtomicInteger updated = new AtomicInteger(0);\n      private final AtomicInteger submittedRowCount = new AtomicInteger(0);\n\n      private Object[] lastRecord = null;\n\n      public boolean throwOnError = false; // should not trigger rollback\n\n      @Override\n      public boolean needErrors() {\n        return true;\n      }\n\n      @Override\n      public boolean needSuccessRecords() {\n        return true;\n      }\n\n      @Override\n      public void addError(LoadingError error) {\n        errors.add(error);\n      }\n\n      @Override\n      public boolean throwOnError() {\n        return throwOnError;\n      }\n\n      public List<LoadingError> getErrors() {\n        return errors;\n      }\n\n      @Override\n      public void recordProvided(Operation op, Object[] record) {\n        lastRecord = record;\n      }\n\n      @Override\n      public void addProcessedRecordCount(Operation op, int i) {\n        processed.addAndGet(i);\n      }\n\n      @Override\n      public void addOperationRecordCount(Operation op, int i) {\n        counter.addAndGet(i);\n        if (op == Operation.DELETE) {\n          deleted.addAndGet(i);\n        } else if (op == Operation.MODIFY || op == Operation.UPSERT) {\n          updated.addAndGet(i);\n        }\n      }\n\n      public Object[] getLastRecord() {\n        return lastRecord;\n      }\n\n      @Override\n      public int getErrorCount() {\n        return errorCount.get();\n      }\n\n      @Override\n      public int getErrorRecordCount() {\n        return errorRecordCount.get();\n      }\n\n      @Override\n      public void resetErrorCount() {\n        errorCount.set(0);\n      }\n\n      @Override\n      public void resetErrorRecordCount() {\n        errorRecordCount.set(0);\n      }\n\n      @Override\n      public void addErrorCount(int count) {\n        errorCount.addAndGet(count);\n      }\n\n      @Override\n      public void addErrorRecordCount(int count) {\n        errorRecordCount.addAndGet(count);\n      }\n\n      @Override\n      public void resetSubmittedRowCount() {\n        submittedRowCount.set(0);\n      }\n\n      @Override\n      public void addSubmittedRowCount(int count) {\n        submittedRowCount.addAndGet(count);\n      }\n\n      @Override\n      public int getSubmittedRowCount() {\n        return submittedRowCount.get();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/LoaderBase.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Properties;\nimport net.snowflake.client.AbstractDriverIT;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\n\npublic class LoaderBase {\n  static final String TARGET_TABLE_NAME = \"LOADER_test_TABLE\";\n\n  static Connection testConnection;\n  static Connection putConnection;\n  static String SCHEMA_NAME;\n\n  @BeforeAll\n  public static void setUpClass() throws Throwable {\n    Properties props = new Properties();\n    // Disable wildcards in SHOW METADATA commands to run more performant queries without need to\n    // escape wildcards\n    props.put(\"ENABLE_WILDCARDS_IN_SHOW_METADATA_COMMANDS\", false);\n    testConnection = AbstractDriverIT.getConnection(props);\n    putConnection = AbstractDriverIT.getConnection(props);\n\n    SCHEMA_NAME = testConnection.getSchema();\n    testConnection\n        .createStatement()\n        .execute(\n            String.format(\n                \"CREATE OR REPLACE TABLE \\\"%s\\\" (\"\n                    + \"ID int, \"\n                    + \"C1 varchar(255), \"\n                    + \"C2 varchar(255) DEFAULT 'X', \"\n                    + \"C3 double, \"\n                    + \"C4 timestamp, \"\n                    + \"C5 variant)\",\n                LoaderIT.TARGET_TABLE_NAME));\n\n    testConnection\n        .createStatement()\n        .execute(\"alter session set JDBC_QUERY_RESULT_FORMAT='ARROW', QUERY_RESULT_FORMAT='ARROW'\");\n  }\n\n  @AfterAll\n  public static void tearDownClass() throws SQLException {\n    testConnection\n        .createStatement()\n        .execute(String.format(\"DROP TABLE IF EXISTS \\\"%s\\\"\", LoaderIT.TARGET_TABLE_NAME));\n\n    testConnection.close();\n    putConnection.close();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/LoaderIT.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.instanceOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.CoreMatchers.nullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.sql.Time;\nimport java.util.Arrays;\nimport java.util.Calendar;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.Random;\nimport java.util.TimeZone;\nimport net.snowflake.client.api.loader.Loader;\nimport net.snowflake.client.api.loader.LoaderProperty;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** Loader IT */\n@Tag(TestTags.LOADER)\npublic class LoaderIT extends LoaderBase {\n  @Test\n  public void testInjectBadStagedFileInsert() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n    StreamLoader loader = tdcb.setOnError(\"ABORT_STATEMENT\").getStreamLoader();\n    listener.throwOnError = true;\n    int numberOfRows = 1000;\n    loader.setProperty(LoaderProperty.testRemoteBadCSV, true);\n    loader.setProperty(LoaderProperty.startTransaction, true);\n    loader.start();\n    Random rnd = new Random();\n\n    // generates a new data set and ingest\n    for (int i = 0; i < numberOfRows; i++) {\n      final String json = \"{\\\"key\\\":\" + rnd.nextInt() + \",\" + \"\\\"bar\\\":\" + i + \"}\";\n      Object[] row = new Object[] {i, \"foo_\" + i, rnd.nextInt() / 3, new Date(), json};\n      loader.submitRow(row);\n    }\n    assertThrows(Loader.DataError.class, loader::finish);\n  }\n\n  @Test\n  public void testExecuteBeforeAfterSQLError() throws Exception {\n    TestDataConfigBuilder tdcbBefore = new TestDataConfigBuilder(testConnection, putConnection);\n    StreamLoader loaderBefore = tdcbBefore.setOnError(\"ABORT_STATEMENT\").getStreamLoader();\n    loaderBefore.setProperty(LoaderProperty.executeBefore, \"SELECT * FROOOOOM TBL\");\n    loaderBefore.start();\n    Loader.ConnectionError e = assertThrows(Loader.ConnectionError.class, loaderBefore::finish);\n    assertThat(e.getCause(), instanceOf(SQLException.class));\n\n    TestDataConfigBuilder tdcbAfter = new TestDataConfigBuilder(testConnection, putConnection);\n    StreamLoader loaderAfter = tdcbAfter.setOnError(\"ABORT_STATEMENT\").getStreamLoader();\n    loaderAfter.setProperty(LoaderProperty.executeBefore, \"select current_version()\");\n    loaderAfter.setProperty(LoaderProperty.executeAfter, \"SELECT * FROM TBBBBBBL\");\n    loaderAfter.start();\n    e = assertThrows(Loader.ConnectionError.class, loaderAfter::finish);\n    assertThat(e.getCause(), instanceOf(SQLException.class));\n  }\n\n  /**\n   * This is to run performance tests. Set the environment variables and run this by VisualVM,\n   * YourKit, or any profiler.\n   *\n   * <p>SNOWFLAKE_TEST_ACCOUNT=testaccount SNOWFLAKE_TEST_USER=testuser\n   * SNOWFLAKE_TEST_PASSWORD=testpassword SNOWFLAKE_TEST_HOST=testaccount.snowflakecomputing.com\n   * SNOWFLAKE_TEST_PORT=443 SNOWFLAKE_TEST_DATABASE=testdb SNOWFLAKE_TEST_SCHEMA=public\n   * SNOWFLAKE_TEST_WAREHOUSE=testwh SNOWFLAKE_TEST_ROLE=sysadmin SNOWFLAKE_TEST_PROTOCOL=https\n   *\n   * @throws Exception raises an exception if any error occurs.\n   */\n  @Disabled(\"Performance test\")\n  @Test\n  public void testLoaderLargeInsert() throws Exception {\n    new TestDataConfigBuilder(testConnection, putConnection)\n        .setDatabaseName(\"INFORMATICA_DB\")\n        .setCompressDataBeforePut(false)\n        .setCompressFileByPut(true)\n        .setNumberOfRows(10000000)\n        .setCsvFileSize(100000000L)\n        .setCsvFileBucketSize(64)\n        .populate();\n  }\n\n  @Test\n  public void testLoaderInsert() throws Exception {\n    // mostly just populate test data but with delay injection to test\n    // PUT retry\n    new TestDataConfigBuilder(testConnection, putConnection).setTestMode(true).populate();\n  }\n\n  @Test\n  public void testLoadTime() throws Exception {\n    String tableName = \"LOADER_TIME_TEST\";\n    try {\n      testConnection\n          .createStatement()\n          .execute(\n              String.format(\n                  \"CREATE OR REPLACE TABLE %s (\" + \"ID int, \" + \"C1 time, C2 date)\", tableName));\n\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcb.setTableName(tableName)\n          .setStartTransaction(true)\n          .setTruncateTable(true)\n          .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\"))\n          .setKeys(Collections.singletonList(\"ID\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n      loader.start();\n      Time tm = new Time(3723000);\n      Date dt = new Date();\n      for (int i = 0; i < 10; i++) {\n        Object[] row = new Object[] {i, tm, dt};\n        loader.submitRow(row);\n      }\n      loader.finish();\n      String errorMessage = \"\";\n      if (listener.getErrorCount() > 0) {\n        errorMessage = listener.getErrors().get(0).getException().toString();\n      }\n      assertThat(String.format(\"Error: %s\", errorMessage), listener.getErrorCount(), equalTo(0));\n      try (Statement statement = testConnection.createStatement()) {\n        try (ResultSet rs =\n            statement.executeQuery(String.format(\"SELECT c1, c2 FROM %s LIMIT 1\", tableName))) {\n          assertTrue(rs.next());\n          Time rsTm = rs.getTime(1);\n          Date rsDt = rs.getDate(2);\n          assertThat(\"Time column didn't match\", rsTm, equalTo(tm));\n\n          Calendar cal = cutOffTimeFromDate(dt);\n          long dtEpoch = cal.getTimeInMillis();\n          long rsDtEpoch = rsDt.getTime();\n          assertThat(\"Date column didn't match\", rsDtEpoch, equalTo(dtEpoch));\n        }\n      }\n    } finally {\n      testConnection.createStatement().execute(String.format(\"DROP TABLE IF EXISTS %s\", tableName));\n    }\n  }\n\n  private Calendar cutOffTimeFromDate(Date dt) {\n    Calendar cal = Calendar.getInstance(); // locale-specific\n    cal.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n    cal.setTime(dt);\n    cal.set(Calendar.HOUR_OF_DAY, 0);\n    cal.set(Calendar.MINUTE, 0);\n    cal.set(Calendar.SECOND, 0);\n    cal.set(Calendar.MILLISECOND, 0);\n    return cal;\n  }\n\n  @Test\n  public void testLoaderDelete() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.populate();\n\n    TestDataConfigBuilder tdcbDelete = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcbDelete\n        .setOperation(Operation.DELETE)\n        .setTruncateTable(false)\n        .setColumns(Arrays.asList(\"ID\", \"C1\"))\n        .setKeys(Arrays.asList(\"ID\", \"C1\"));\n    StreamLoader loader = tdcbDelete.getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcbDelete.getListener();\n    loader.start();\n\n    Object[] del =\n        new Object[] {\n          42, \"foo_42\" // deleted\n        };\n    loader.submitRow(del);\n\n    del =\n        new Object[] {\n          41, \"blah\" // ignored, and should not raise any error/warning\n        };\n    loader.submitRow(del);\n    loader.finish();\n\n    assertThat(\"error count\", listener.getErrorCount(), equalTo(0));\n    assertThat(\"error record count\", listener.getErrorRecordCount(), equalTo(0));\n    assertThat(\"submitted row count\", listener.getSubmittedRowCount(), equalTo(2));\n    assertThat(\"processed\", listener.processed.get(), equalTo(1));\n    assertThat(\"deleted rows\", listener.deleted.get(), equalTo(1));\n  }\n\n  @Test\n  public void testLoaderModify() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.populate();\n\n    TestDataConfigBuilder tdcbModify = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcbModify\n        .setOperation(Operation.MODIFY)\n        .setTruncateTable(false)\n        .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\", \"C3\", \"C4\", \"C5\"))\n        .setKeys(Collections.singletonList(\"ID\"));\n    StreamLoader loader = tdcbModify.getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcbModify.getListener();\n    loader.start();\n\n    Object[] mod = new Object[] {41, \"modified\", \"some\\nthi\\\"ng\\\\\", 41.6, new Date(), \"{}\"};\n    loader.submitRow(mod);\n    mod = new Object[] {40, \"modified\", \"\\\"something,\", 40.2, new Date(), \"{}\"};\n    loader.submitRow(mod);\n    loader.finish();\n\n    assertThat(\"processed\", listener.processed.get(), equalTo(2));\n    assertThat(\"submitted row\", listener.getSubmittedRowCount(), equalTo(2));\n    assertThat(\"updated\", listener.updated.get(), equalTo(2));\n    assertThat(\"error count\", listener.getErrorCount(), equalTo(0));\n    assertThat(\"error record count\", listener.getErrorRecordCount(), equalTo(0));\n\n    // Test deletion\n    ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT COUNT(*) AS N\" + \" FROM \\\"%s\\\"\", TARGET_TABLE_NAME));\n    rs.next();\n    assertThat(\"count is not correct\", rs.getInt(\"N\"), equalTo(10000));\n\n    rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT C1 AS N\" + \" FROM \\\"%s\\\" WHERE ID=40\", TARGET_TABLE_NAME));\n\n    rs.next();\n    assertThat(\"status is not correct\", rs.getString(\"N\"), equalTo(\"modified\"));\n\n    rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT C1, C2\" + \" FROM \\\"%s\\\" WHERE ID=41\", TARGET_TABLE_NAME));\n    rs.next();\n    assertThat(\"C1 is not correct\", rs.getString(\"C1\"), equalTo(\"modified\"));\n    assertThat(\"C2 is not correct\", rs.getString(\"C2\"), equalTo(\"some\\nthi\\\"ng\\\\\"));\n  }\n\n  @Test\n  public void testLoaderModifyWithOneMatchOneNot() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.populate();\n\n    TestDataConfigBuilder tdcbModify = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcbModify\n        .setTruncateTable(false)\n        .setOperation(Operation.MODIFY)\n        .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\", \"C3\", \"C4\", \"C5\"))\n        .setKeys(Collections.singletonList(\"ID\"));\n    StreamLoader loader = tdcbModify.getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcbModify.getListener();\n    loader.start();\n\n    Object[] mod = new Object[] {20000, \"modified\", \"some\\nthi\\\"ng\\\\\", 41.6, new Date(), \"{}\"};\n    loader.submitRow(mod);\n    mod = new Object[] {45, \"modified\", \"\\\"something2,\", 40.2, new Date(), \"{}\"};\n    loader.submitRow(mod);\n    loader.finish();\n\n    assertThat(\"processed\", listener.processed.get(), equalTo(1));\n    assertThat(\"submitted row\", listener.getSubmittedRowCount(), equalTo(2));\n    assertThat(\"updated\", listener.updated.get(), equalTo(1));\n    assertThat(\"error count\", listener.getErrorCount(), equalTo(0));\n    assertThat(\"error record count\", listener.getErrorRecordCount(), equalTo(0));\n\n    // Test deletion\n    ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT COUNT(*) AS N\" + \" FROM \\\"%s\\\"\", TARGET_TABLE_NAME));\n    rs.next();\n    assertThat(\"count is not correct\", rs.getInt(\"N\"), equalTo(10000));\n\n    rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT C1, C2\" + \" FROM \\\"%s\\\" WHERE ID=45\", TARGET_TABLE_NAME));\n    rs.next();\n    assertThat(\"C1 is not correct\", rs.getString(\"C1\"), equalTo(\"modified\"));\n    assertThat(\"C2 is not correct\", rs.getString(\"C2\"), equalTo(\"\\\"something2,\"));\n  }\n\n  @Test\n  public void testLoaderUpsertWithError() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.populate();\n\n    TestDataConfigBuilder tdcbUpsert = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcbUpsert\n        .setOperation(Operation.UPSERT)\n        .setTruncateTable(false)\n        .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\", \"C3\", \"C4\", \"C5\"))\n        .setKeys(Collections.singletonList(\"ID\"));\n    StreamLoader loader = tdcbUpsert.getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcbUpsert.getListener();\n    loader.start();\n\n    Object[] upse = new Object[] {\"10001-\", \"inserted\", \"something\", \"42-\", new Date(), \"{}\"};\n    loader.submitRow(upse);\n    upse = new Object[] {10002, \"inserted\", \"something\", 43, new Date(), \"{}\"};\n    loader.submitRow(upse);\n    upse = new Object[] {45, \"modified\", \"something\", 46.1, new Date(), \"{}\"};\n    loader.submitRow(upse);\n    loader.finish();\n\n    assertThat(\"processed\", listener.processed.get(), equalTo(3));\n    assertThat(\"counter\", listener.counter.get(), equalTo(2));\n    assertThat(\"submitted row\", listener.getSubmittedRowCount(), equalTo(3));\n    assertThat(\"updated/inserted\", listener.updated.get(), equalTo(2));\n    assertThat(\"error count\", listener.getErrorCount(), equalTo(2));\n    assertThat(\"error record count\", listener.getErrorRecordCount(), equalTo(1));\n    assertThat(\n        \"Target table name is not correct\",\n        listener.getErrors().get(0).getTarget(),\n        equalTo(TARGET_TABLE_NAME));\n\n    ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT COUNT(*) AS N\" + \" FROM \\\"%s\\\"\", TARGET_TABLE_NAME));\n\n    rs.next();\n    int c = rs.getInt(\"N\");\n    assertThat(\"N is not correct\", c, equalTo(10001));\n\n    rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT C1 AS N\" + \" FROM \\\"%s\\\" WHERE ID=45\", TARGET_TABLE_NAME));\n\n    rs.next();\n    assertThat(\"N is not correct\", rs.getString(\"N\"), equalTo(\"modified\"));\n  }\n\n  @Test\n  public void testEmptyFieldAsEmpty() throws Exception {\n    _testEmptyFieldAsEmpty(true);\n    _testEmptyFieldAsEmpty(false);\n  }\n\n  private void _testEmptyFieldAsEmpty(boolean copyEmptyFieldAsEmpty) throws Exception {\n    String tableName = \"LOADER_EMPTY_FIELD_AS_NULL\";\n    try {\n      testConnection\n          .createStatement()\n          .execute(\n              String.format(\n                  \"CREATE OR REPLACE TABLE %s (\" + \"ID int, \" + \"C1 string, C2 string)\",\n                  tableName));\n\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcb.setOperation(Operation.INSERT)\n          .setStartTransaction(true)\n          .setTruncateTable(true)\n          .setTableName(tableName)\n          .setCopyEmptyFieldAsEmpty(copyEmptyFieldAsEmpty)\n          .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n      loader.start();\n\n      // insert null\n      loader.submitRow(new Object[] {1, null, \"\"});\n      loader.finish();\n      int submitted = listener.getSubmittedRowCount();\n      assertThat(\"submitted rows\", submitted, equalTo(1));\n\n      ResultSet rs =\n          testConnection\n              .createStatement()\n              .executeQuery(String.format(\"SELECT C1, C2 FROM %s\", tableName));\n\n      rs.next();\n      String c1 = rs.getString(1); // null\n      String c2 = rs.getString(2); // empty\n      if (!copyEmptyFieldAsEmpty) {\n        // COPY ... EMPTY_FIELD_AS_NULL = TRUE /* default */\n        assertThat(c1, is(nullValue())); // null => null\n        assertThat(c2, equalTo(\"\")); // empty => empty\n      } else {\n        // COPY EMPTY_FIELD_AS_NULL = FALSE\n        assertThat(c1, equalTo(\"\")); // null => empty\n        assertThat(c2, equalTo(\"\")); // empty => empty\n      }\n      rs.close();\n    } finally {\n      testConnection.createStatement().execute(String.format(\"DROP TABLE IF EXISTS %s\", tableName));\n    }\n  }\n\n  /**\n   * Test a target table name including spaces.\n   *\n   * @throws Exception raises if any error occurs\n   */\n  @Test\n  public void testSpacesInColumnTable() throws Exception {\n    String targetTableName = \"Load Test Spaces In Columns\";\n\n    // create table with spaces in column names\n    testConnection\n        .createStatement()\n        .execute(\n            String.format(\n                \"CREATE OR REPLACE TABLE \\\"%s\\\" (\" + \"ID int, \" + \"\\\"Column 1\\\" varchar(255))\",\n                targetTableName));\n\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.setTableName(targetTableName).setColumns(Arrays.asList(\"ID\", \"Column 1\"));\n\n    StreamLoader loader = tdcb.getStreamLoader();\n    loader.start();\n\n    for (int i = 0; i < 5; ++i) {\n      Object[] row = new Object[] {i, \"foo_\" + i};\n      loader.submitRow(row);\n    }\n    loader.finish();\n\n    ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT * FROM \\\"%s\\\" ORDER BY \\\"Column 1\\\"\", targetTableName));\n\n    rs.next();\n    assertThat(\"The first id\", rs.getInt(1), equalTo(0));\n    assertThat(\"The first str\", rs.getString(2), equalTo(\"foo_0\"));\n  }\n\n  @Test\n  public void testLoaderInsertAbortStatement() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n    StreamLoader loader = tdcb.setOnError(\"ABORT_STATEMENT\").getStreamLoader();\n    listener.throwOnError = true;\n\n    loader.start();\n    Random rnd = new Random();\n\n    // generates a new data set and ingest\n    for (int i = 0; i < 10; i++) {\n      final String json = \"{\\\"key\\\":\" + String.valueOf(rnd.nextInt()) + \",\" + \"\\\"bar\\\":\" + i + \"}\";\n      Object v = rnd.nextInt() / 3;\n      if (i == 7) {\n        v = \"INVALID_INTEGER\";\n      }\n      Object[] row = new Object[] {i, \"foo_\" + i, v, new Date(), json};\n      loader.submitRow(row);\n    }\n    Loader.DataError ex = assertThrows(Loader.DataError.class, loader::finish);\n    assertThat(ex.toString(), containsString(\"INVALID_INTEGER\"));\n  }\n\n  @Test\n  public void testLoadTimestampMilliseconds() throws Exception {\n    String srcTable = \"LOAD_TIMESTAMP_MS_SRC\";\n    String dstTable = \"LOAD_TIMESTAMP_MS_DST\";\n\n    testConnection\n        .createStatement()\n        .execute(\n            String.format(\"create or replace table %s(c1 int, c2 timestamp_ntz(9))\", srcTable));\n    testConnection\n        .createStatement()\n        .execute(String.format(\"create or replace table %s like %s\", dstTable, srcTable));\n    testConnection\n        .createStatement()\n        .execute(\n            String.format(\n                \"insert into %s(c1,c2) values\"\n                    + \"(1, '2018-05-12 12:34:56.123456789'),\"\n                    + \"(2, '2018-05-13 03:45:27.988'),\"\n                    + \"(3, '2018-05-14 07:12:34'),\"\n                    + \"(4, '2018-12-15 10:43:45.000000876')\",\n                srcTable));\n\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    StreamLoader loader =\n        tdcb.setOnError(\"ABORT_STATEMENT\")\n            .setSchemaName(SCHEMA_NAME)\n            .setTableName(dstTable)\n            .setPreserveStageFile(true)\n            .setColumns(Arrays.asList(\"C1\", \"C2\"))\n            .getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n    listener.throwOnError = true;\n\n    loader.start();\n\n    ResultSet rs =\n        testConnection.createStatement().executeQuery(String.format(\"select * from %s\", srcTable));\n    while (rs.next()) {\n      Object c1 = rs.getObject(1);\n      Object c2 = rs.getObject(2);\n      Object[] row = new Object[] {c1, c2};\n      loader.submitRow(row);\n    }\n    loader.finish();\n\n    rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"select * from %s minus select * from %s\", dstTable, srcTable));\n    assertThat(\"No result\", !rs.next());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/LoaderLatestIT.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.CoreMatchers.allOf;\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Date;\nimport net.snowflake.client.api.loader.Loader;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Loader API tests for the latest JDBC driver. This doesn't work for the oldest supported driver.\n * Revisit this tests whenever bumping up the oldest supported driver to examine if the tests still\n * is not applicable. If it is applicable, move tests to LoaderIT so that both the latest and oldest\n * supported driver run the tests.\n */\n@Tag(TestTags.LOADER)\npublic class LoaderLatestIT extends LoaderBase {\n  @Test\n  public void testLoaderUpsert() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.populate();\n\n    TestDataConfigBuilder tdcbUpsert = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcbUpsert\n        .setOperation(Operation.UPSERT)\n        .setTruncateTable(false)\n        .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\", \"C3\", \"C4\", \"C5\"))\n        .setKeys(Collections.singletonList(\"ID\"));\n    StreamLoader loader = tdcbUpsert.getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcbUpsert.getListener();\n    loader.start();\n\n    Date d = new Date();\n\n    Object[] ups = new Object[] {10001, \"inserted\\\\,\", \"something\", 0x4.11_33p2, d, \"{}\"};\n    loader.submitRow(ups);\n    ups = new Object[] {39, \"modified\", \"something\", 40.1, d, \"{}\"};\n    loader.submitRow(ups);\n    loader.finish();\n\n    assertThat(\"processed\", listener.processed.get(), equalTo(2));\n    assertThat(\"submitted row\", listener.getSubmittedRowCount(), equalTo(2));\n    assertThat(\"updated/inserted\", listener.updated.get(), equalTo(2));\n    assertThat(\"error count\", listener.getErrorCount(), equalTo(0));\n    assertThat(\"error record count\", listener.getErrorRecordCount(), equalTo(0));\n\n    try (ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\n                    \"SELECT C1, C4, C3\" + \" FROM \\\"%s\\\" WHERE ID=10001\", TARGET_TABLE_NAME))) {\n\n      assertTrue(rs.next());\n      assertThat(\"C1 is not correct\", rs.getString(\"C1\"), equalTo(\"inserted\\\\,\"));\n\n      long l = rs.getTimestamp(\"C4\").getTime();\n      assertThat(\"C4 is not correct\", l, equalTo(d.getTime()));\n      assertThat(\n          \"C3 is not correct\", Double.toHexString((rs.getDouble(\"C3\"))), equalTo(\"0x1.044ccp4\"));\n    }\n    try (ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(\n                String.format(\"SELECT C1 AS N\" + \" FROM \\\"%s\\\" WHERE ID=39\", TARGET_TABLE_NAME))) {\n\n      assertTrue(rs.next());\n      assertThat(\"N is not correct\", rs.getString(\"N\"), equalTo(\"modified\"));\n    }\n  }\n\n  @Test\n  public void testLoaderUpsertWithErrorAndRollback() throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n    tdcb.populate();\n\n    try (PreparedStatement pstmt =\n        testConnection.prepareStatement(\n            String.format(\n                \"INSERT INTO \\\"%s\\\"(ID,C1,C2,C3,C4,C5)\"\n                    + \" SELECT column1, column2, column3, column4,\"\n                    + \" column5, parse_json(column6)\"\n                    + \" FROM VALUES(?,?,?,?,?,?)\",\n                TARGET_TABLE_NAME))) {\n      pstmt.setInt(1, 10001);\n      pstmt.setString(2, \"inserted\\\\,\");\n      pstmt.setString(3, \"something\");\n      pstmt.setDouble(4, 0x4.11_33p2);\n      pstmt.setDate(5, new java.sql.Date(new Date().getTime()));\n      pstmt.setObject(6, \"{}\");\n      pstmt.execute();\n      testConnection.commit();\n\n      TestDataConfigBuilder tdcbUpsert = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcbUpsert\n          .setOperation(Operation.UPSERT)\n          .setTruncateTable(false)\n          .setStartTransaction(true)\n          .setPreserveStageFile(true)\n          .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\", \"C3\", \"C4\", \"C5\"))\n          .setKeys(Collections.singletonList(\"ID\"));\n      StreamLoader loader = tdcbUpsert.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcbUpsert.getListener();\n      listener.throwOnError = true; // should trigger rollback\n      loader.start();\n      Object[] noerr = new Object[] {\"10001\", \"inserted\", \"something\", \"42\", new Date(), \"{}\"};\n      loader.submitRow(noerr);\n\n      Object[] err = new Object[] {\"10002-\", \"inserted\", \"something\", \"42-\", new Date(), \"{}\"};\n      loader.submitRow(err);\n\n      Loader.DataError e = assertThrows(Loader.DataError.class, loader::finish);\n      assertThat(\n          \"error message\",\n          e.getMessage(),\n          allOf(containsString(\"10002-\"), containsString(\"not recognized\")));\n\n      assertThat(\"processed\", listener.processed.get(), equalTo(0));\n      assertThat(\"submitted row\", listener.getSubmittedRowCount(), equalTo(2));\n      assertThat(\"updated/inserted\", listener.updated.get(), equalTo(0));\n      assertThat(\"error count\", listener.getErrorCount(), equalTo(2));\n      assertThat(\"error record count\", listener.getErrorRecordCount(), equalTo(1));\n\n      try (ResultSet rs =\n          testConnection\n              .createStatement()\n              .executeQuery(String.format(\"SELECT COUNT(*) AS N FROM \\\"%s\\\"\", TARGET_TABLE_NAME))) {\n        assertTrue(rs.next());\n        assertThat(\"N\", rs.getInt(\"N\"), equalTo(10001));\n      }\n      try (ResultSet rs =\n          testConnection\n              .createStatement()\n              .executeQuery(\n                  String.format(\"SELECT C3 FROM \\\"%s\\\" WHERE id=10001\", TARGET_TABLE_NAME))) {\n        assertTrue(rs.next());\n        assertThat(\n            \"C3. No commit should happen\",\n            Double.toHexString((rs.getDouble(\"C3\"))),\n            equalTo(\"0x1.044ccp4\"));\n      }\n    }\n  }\n\n  /**\n   * Test loading data with a table containing a clustering key. The test would fail if the\n   * clustering key assigned to the temp table was not first dropped in ProcessQueue.java.\n   *\n   * @throws Exception raises if any error occurs\n   */\n  @Test\n  public void testKeyClusteringTable() throws Exception {\n    String targetTableName = \"CLUSTERED_TABLE\";\n    try (Statement statement = testConnection.createStatement()) {\n      // create table with spaces in column names\n      statement.execute(\n          String.format(\n              \"CREATE OR REPLACE TABLE \\\"%s\\\" (\"\n                  + \"ID int, \"\n                  + \"\\\"Column1\\\" varchar(255), \"\n                  + \"\\\"Column2\\\" varchar(255))\",\n              targetTableName));\n      // Add the clustering key; all columns clustered together\n      statement.execute(\n          String.format(\n              \"alter table %s cluster by (ID, \\\"Column1\\\", \\\"Column2\\\")\", targetTableName));\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      // Only submit data for 2 columns out of 3 in the table so that 1 column will be dropped in\n      // temp\n      // table\n      tdcb.setTableName(targetTableName).setColumns(Arrays.asList(\"ID\", \"Column1\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      loader.start();\n\n      for (int i = 0; i < 5; ++i) {\n        Object[] row = new Object[] {i, \"foo_\" + i};\n        loader.submitRow(row);\n      }\n      loader.finish();\n\n      try (ResultSet rs =\n          testConnection\n              .createStatement()\n              .executeQuery(\n                  String.format(\"SELECT * FROM \\\"%s\\\" ORDER BY \\\"Column1\\\"\", targetTableName))) {\n\n        assertTrue(rs.next());\n        assertThat(\"The first id\", rs.getInt(1), equalTo(0));\n        assertThat(\"The first str\", rs.getString(2), equalTo(\"foo_0\"));\n      }\n    }\n  }\n\n  @Test\n  private void testVectorColumnInTable() throws Exception {\n    String tableName = \"VECTOR_TABLE\";\n    try {\n      testConnection\n          .createStatement()\n          .execute(\n              String.format(\"CREATE OR REPLACE TABLE %s (vector_col VECTOR(FLOAT, 3))\", tableName));\n\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcb.setOperation(Operation.INSERT)\n          .setStartTransaction(true)\n          .setTruncateTable(true)\n          .setTableName(tableName)\n          .setColumns(Arrays.asList(\"vector_col\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n      loader.start();\n\n      loader.submitRow(new Object[] {\"[12, 14.0, 100]\"});\n      loader.setVectorColumnType(\"FLOAT\");\n      loader.finish();\n      int submitted = listener.getSubmittedRowCount();\n      assertThat(\"submitted rows\", submitted, equalTo(1));\n\n    } finally {\n      testConnection.createStatement().execute(String.format(\"DROP TABLE IF EXISTS %s\", tableName));\n    }\n  }\n\n  @Test\n  private void testMultipleFloatVectorColumnsInTable() throws Exception {\n    String tableName = \"VECTOR_TABLE\";\n    try {\n      testConnection\n          .createStatement()\n          .execute(\n              String.format(\n                  \"CREATE OR REPLACE TABLE %s (vec1 VECTOR(FLOAT, 3), vec2 VECTOR(FLOAT, 3))\",\n                  tableName));\n\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcb.setOperation(Operation.INSERT)\n          .setStartTransaction(true)\n          .setTruncateTable(true)\n          .setTableName(tableName)\n          .setColumns(Arrays.asList(\"vector_col\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n      loader.start();\n\n      loader.submitRow(new Object[] {\"[12, 14.0, 100]\", \"[12, 14.0, 100]\"});\n      loader.setVectorColumnType(\"FLOAT\");\n      loader.finish();\n      int submitted = listener.getSubmittedRowCount();\n      assertThat(\"submitted rows\", submitted, equalTo(1));\n\n    } finally {\n      testConnection.createStatement().execute(String.format(\"DROP TABLE IF EXISTS %s\", tableName));\n    }\n  }\n\n  @Test\n  private void testMultipleIntVectorColumnsInTable() throws Exception {\n    String tableName = \"VECTOR_TABLE\";\n    try {\n      testConnection\n          .createStatement()\n          .execute(\n              String.format(\n                  \"CREATE OR REPLACE TABLE %s (vec1 VECTOR(INT, 3), vec2 VECTOR(INT, 3))\",\n                  tableName));\n\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcb.setOperation(Operation.INSERT)\n          .setStartTransaction(true)\n          .setTruncateTable(true)\n          .setTableName(tableName)\n          .setColumns(Arrays.asList(\"vector_col\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n      loader.start();\n\n      loader.submitRow(new Object[] {\"[12, 14, 100]\", \"[12, 14, 100]\"});\n      loader.setVectorColumnType(\"INT\");\n      loader.finish();\n      int submitted = listener.getSubmittedRowCount();\n      assertThat(\"submitted rows\", submitted, equalTo(1));\n\n    } finally {\n      testConnection.createStatement().execute(String.format(\"DROP TABLE IF EXISTS %s\", tableName));\n    }\n  }\n\n  @Test\n  private void testMultipleTypesWithVectorColumnsInTable() throws Exception {\n    String tableName = \"VECTOR_TABLE\";\n    try {\n      testConnection\n          .createStatement()\n          .execute(\n              String.format(\n                  \"CREATE OR REPLACE TABLE %s (vec1 VECTOR(FLOAT, 3), vec2 VECTOR(FLOAT, 3), ID int, colA varchar(255), colB date)\",\n                  tableName));\n\n      TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n      tdcb.setOperation(Operation.INSERT)\n          .setStartTransaction(true)\n          .setTruncateTable(true)\n          .setTableName(tableName)\n          .setColumns(Arrays.asList(\"vector_col\"));\n      StreamLoader loader = tdcb.getStreamLoader();\n      TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n      loader.start();\n\n      loader.setVectorColumnType(\"FLOAT\");\n      loader.submitRow(new Object[] {\"[12, 14.0, 100]\", \"[12, 14.0, 100]\", 10, \"abc\", new Date()});\n      loader.finish();\n      int submitted = listener.getSubmittedRowCount();\n      assertThat(\"submitted rows\", submitted, equalTo(1));\n\n    } finally {\n      testConnection.createStatement().execute(String.format(\"DROP TABLE IF EXISTS %s\", tableName));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/LoaderMultipleBatchIT.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.LOADER)\npublic class LoaderMultipleBatchIT extends LoaderBase {\n  @Test\n  public void testLoaderMultipleBatch() throws Exception {\n    String refTableName = \"LOADER_TEST_TABLE_REF\";\n    try (Statement statement = testConnection.createStatement()) {\n      statement.execute(\n          String.format(\n              \"CREATE OR REPLACE TABLE \\\"%s\\\" (\"\n                  + \"ID int, \"\n                  + \"C1 varchar(255), \"\n                  + \"C2 varchar(255) DEFAULT 'X', \"\n                  + \"C3 double, \"\n                  + \"C4 timestamp, \"\n                  + \"C5 variant)\",\n              refTableName));\n\n      try {\n        TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n        List<Object[]> dataSet = tdcb.populateReturnData();\n\n        TestDataConfigBuilder tdcbRef = new TestDataConfigBuilder(testConnection, putConnection);\n        tdcbRef\n            .setDataSet(dataSet)\n            .setTableName(refTableName)\n            .setCsvFileBucketSize(2)\n            .setCsvFileSize(30000)\n            .populate();\n\n        try (ResultSet rsReference =\n            statement.executeQuery(\n                String.format(\"SELECT hash_agg(*) FROM \\\"%s\\\"\", TARGET_TABLE_NAME))) {\n          assertTrue(rsReference.next());\n          long hashValueReference = rsReference.getLong(1);\n          try (ResultSet rsTarget =\n              statement.executeQuery(\n                  String.format(\"SELECT hash_agg(*) FROM \\\"%s\\\"\", refTableName))) {\n            assertTrue(rsTarget.next());\n            long hashValueTarget = rsTarget.getLong(1);\n            assertThat(\"hash values\", hashValueTarget, equalTo(hashValueReference));\n          }\n        }\n      } finally {\n        statement.execute(String.format(\"DROP TABLE IF EXISTS %s\", refTableName));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/LoaderTimestampIT.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.sql.ResultSet;\nimport java.sql.Statement;\nimport java.sql.Timestamp;\nimport java.text.SimpleDateFormat;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.TimeZone;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.LOADER)\npublic class LoaderTimestampIT extends LoaderBase {\n  @Test\n  public void testLoadTimestamp() throws Exception {\n    final String targetTableName = \"LOADER_TEST_TIMESTAMP\";\n\n    // create table including TIMESTAMP_NTZ\n    try (Statement statement = testConnection.createStatement()) {\n      statement.execute(\n          String.format(\n              \"CREATE OR REPLACE TABLE %s (\"\n                  + \"ID int, \"\n                  + \"C1 varchar(255), \"\n                  + \"C2 timestamp_ntz)\",\n              targetTableName));\n\n      // Binding java.util.Date, Timestamp and java.sql.Date with TIMESTAMP\n      // datatype. No java.sql.Time binding is supported for TIMESTAMP.\n      // For java.sql.Time, the target data type must be TIME.\n      Object[] testData =\n          new Object[] {\n            new Date(),\n            java.sql.Timestamp.valueOf(\"0001-01-01 08:00:00\"),\n            java.sql.Date.valueOf(\"2001-01-02\")\n          };\n\n      for (int i = 0; i < 2; ++i) {\n        boolean useLocalTimezone = false;\n        TimeZone originalTimeZone;\n        TimeZone targetTimeZone;\n\n        if (i == 0) {\n          useLocalTimezone = true;\n          originalTimeZone = TimeZone.getDefault();\n          targetTimeZone = TimeZone.getTimeZone(\"America/Los_Angeles\");\n        } else {\n          originalTimeZone = TimeZone.getTimeZone(\"UTC\");\n          targetTimeZone = TimeZone.getTimeZone(\"UTC\");\n        }\n\n        // input timestamp associated with the target timezone, America/Los_Angeles\n        for (Object testTs : testData) {\n          _testLoadTimestamp(\n              targetTableName, originalTimeZone, targetTimeZone, testTs, useLocalTimezone, false);\n        }\n      }\n    }\n  }\n\n  private void _testLoadTimestamp(\n      String targetTableName,\n      TimeZone originalTimeZone,\n      TimeZone targetTimeZone,\n      Object testTs,\n      boolean useLocalTimeZone,\n      boolean mapTimeToTimestamp)\n      throws Exception {\n    TestDataConfigBuilder tdcb = new TestDataConfigBuilder(testConnection, putConnection);\n\n    tdcb.setStartTransaction(true)\n        .setTruncateTable(true)\n        .setTableName(targetTableName)\n        .setUseLocalTimezone(useLocalTimeZone)\n        .setMapTimeToTimestamp(mapTimeToTimestamp)\n        .setColumns(Arrays.asList(\"ID\", \"C1\", \"C2\"));\n    StreamLoader loader = tdcb.getStreamLoader();\n    TestDataConfigBuilder.ResultListener listener = tdcb.getListener();\n\n    TimeZone.setDefault(targetTimeZone); // change default timezone before start\n\n    loader.start();\n\n    for (int i = 0; i < 5; ++i) {\n      Object[] row = new Object[] {i, \"foo_\" + i, testTs};\n      loader.submitRow(row);\n    }\n    loader.finish();\n    TimeZone.setDefault(originalTimeZone);\n\n    assertThat(\"Loader detected errors\", listener.getErrorCount(), equalTo(0));\n\n    try (ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(String.format(\"SELECT * FROM \\\"%s\\\"\", targetTableName))) {\n\n      assertTrue(rs.next());\n      Timestamp ts = rs.getTimestamp(\"C2\");\n\n      // format the input TS with the target timezone\n      SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd'T'HH:mm:ss.SSS\");\n      sdf.setTimeZone(targetTimeZone);\n      String currentTsStr = sdf.format(testTs);\n\n      // format the retrieved TS with the original timezone\n      sdf.setTimeZone(originalTimeZone);\n      String retrievedTsStr = sdf.format(new Date(ts.getTime()));\n\n      // They must be identical.\n      assertThat(\n          \"Input and retrieved timestamp are different\", retrievedTsStr, equalTo(currentTsStr));\n    }\n  }\n\n  @Test\n  public void testLoadTimestampV1() throws Exception {\n    final String targetTableName = \"LOADER_TEST_TIMESTAMP_V1\";\n\n    // create table including TIMESTAMP_NTZ\n    try (Statement statement = testConnection.createStatement()) {\n      statement.execute(\n          String.format(\n              \"CREATE OR REPLACE TABLE %s (\"\n                  + \"ID int, \"\n                  + \"C1 varchar(255), \"\n                  + \"C2 timestamp_ntz)\",\n              targetTableName));\n\n      // Binding java.sql.Time with TIMESTAMP is supported only if\n      // mapTimeToTimestamp flag is enabled. This is required to keep the\n      // old behavior of Informatica V1 connector.\n      Object[] testData =\n          new Object[] {\n            // full timestamp in Time object. Interestingly all values are\n            // preserved.\n            new java.sql.Time(1502931205000L),\n            java.sql.Time.valueOf(\"12:34:56\") // a basic test case\n          };\n\n      for (int i = 0; i < 2; ++i) {\n        boolean useLocalTimezone;\n        TimeZone originalTimeZone;\n        TimeZone targetTimeZone;\n\n        if (i == 0) {\n          useLocalTimezone = true;\n          originalTimeZone = TimeZone.getDefault();\n          targetTimeZone = TimeZone.getTimeZone(\"America/Los_Angeles\");\n        } else {\n          useLocalTimezone = false;\n          originalTimeZone = TimeZone.getTimeZone(\"UTC\");\n          targetTimeZone = TimeZone.getTimeZone(\"UTC\");\n        }\n\n        // input timestamp associated with the target timezone, America/Los_Angeles\n        for (Object testTs : testData) {\n          _testLoadTimestamp(\n              targetTableName, originalTimeZone, targetTimeZone, testTs, useLocalTimezone, true);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/OnErrorTest.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.core.Is.is;\n\nimport org.junit.jupiter.api.Test;\n\npublic class OnErrorTest {\n  @Test\n  public void testValidate() {\n    // positive\n    assertThat(OnError.validate(\"ABORT_STATEMENT\"), is(Boolean.TRUE));\n    assertThat(OnError.validate(\"ABORT_STATeMENT\"), is(Boolean.TRUE));\n    assertThat(OnError.validate(\"CONTINUE\"), is(Boolean.TRUE));\n    assertThat(OnError.validate(\"cOnTinue\"), is(Boolean.TRUE));\n    assertThat(OnError.validate(\"SKIP_FILE\"), is(Boolean.TRUE));\n    assertThat(OnError.validate(\"Skip_File_10\"), is(Boolean.TRUE));\n\n    // Out of range error should be detected by the server.\n    assertThat(OnError.validate(\"Skip_File_123%\"), is(Boolean.TRUE));\n\n    // negative\n    assertThat(OnError.validate(null), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"Bogus\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"abortstatement\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"continues\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"skipfile\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"skip_file_abcdef\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"skip_file_abcdef%\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"skip_file_25.0%\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"skip_file_-10%\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"continue; delete from t\"), is(Boolean.FALSE));\n    assertThat(OnError.validate(\"ABORT_STATEMENT; drop table t\"), is(Boolean.FALSE));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/loader/TestDataConfigBuilder.java",
    "content": "package net.snowflake.client.internal.loader;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Random;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport net.snowflake.client.api.loader.LoadResultListener;\nimport net.snowflake.client.api.loader.LoaderFactory;\nimport net.snowflake.client.api.loader.LoaderProperty;\nimport net.snowflake.client.api.loader.LoadingError;\nimport net.snowflake.client.api.loader.Operation;\nimport net.snowflake.client.internal.api.implementation.connection.SnowflakeConnectionImpl;\n\nclass TestDataConfigBuilder {\n  static final String TARGET_TABLE_NAME = \"LOADER_test_TABLE\";\n\n  private StreamLoader streamLoader;\n  private final Connection testConnection;\n  private final Connection putConnection;\n\n  private Operation operation = Operation.INSERT;\n  private boolean testMode = false;\n  private int numberOfRows = 10000;\n  private List<Object[]> dataSet;\n\n  private List<String> columns = Arrays.asList(\"ID\", \"C1\", \"C3\", \"C4\", \"C5\");\n  private List<String> keys;\n  private String tableName = TARGET_TABLE_NAME;\n  private String schemaName;\n  private String databaseName;\n  private String remoteStage = \"~\";\n  private long csvFileBucketSize = 64;\n  private long csvFileSize = 50 * 1024 * 1024;\n  private String onError = OnError.DEFAULT;\n  private boolean startTransaction = false;\n  private boolean truncateTable = true;\n  private boolean preserveStageFile = false;\n  private boolean useLocalTimezone = false;\n  private boolean mapTimeToTimestamp = false;\n  private boolean copyEmptyFieldAsEmpty = false;\n  private boolean compressFileByPut = false;\n  private boolean compressDataBeforePut = true;\n  private long compressLevel = 1L;\n\n  private ResultListener listener;\n\n  TestDataConfigBuilder(Connection testConnection, Connection putConnection) throws Exception {\n    this.testConnection = testConnection;\n    this.putConnection = putConnection;\n    this.databaseName = testConnection.getCatalog();\n    this.schemaName = testConnection.getSchema();\n  }\n\n  TestDataConfigBuilder setTestMode(boolean testMode) {\n    this.testMode = testMode;\n    return this;\n  }\n\n  TestDataConfigBuilder setTableName(String tableName) {\n    this.tableName = tableName;\n    return this;\n  }\n\n  TestDataConfigBuilder setSchemaName(String schemaName) {\n    this.schemaName = schemaName;\n    return this;\n  }\n\n  TestDataConfigBuilder setDatabaseName(String databaseName) {\n    this.databaseName = databaseName;\n    return this;\n  }\n\n  TestDataConfigBuilder setRemoteStage(String remoteStage) {\n    this.remoteStage = remoteStage;\n    return this;\n  }\n\n  TestDataConfigBuilder setCsvFileBucketSize(long csvFileBucketSize) {\n    this.csvFileBucketSize = csvFileBucketSize;\n    return this;\n  }\n\n  TestDataConfigBuilder setCsvFileSize(long csvFileSize) {\n    this.csvFileSize = csvFileSize;\n    return this;\n  }\n\n  TestDataConfigBuilder setOnError(String onError) {\n    this.onError = onError;\n    return this;\n  }\n\n  TestDataConfigBuilder setNumberOfRows(int numberOfRows) {\n    this.numberOfRows = numberOfRows;\n    return this;\n  }\n\n  TestDataConfigBuilder setDataSet(List<Object[]> dataSet) {\n    this.dataSet = dataSet;\n    return this;\n  }\n\n  TestDataConfigBuilder setColumns(List<String> columns) {\n    this.columns = columns;\n    return this;\n  }\n\n  TestDataConfigBuilder setKeys(List<String> keys) {\n    this.keys = keys;\n    return this;\n  }\n\n  TestDataConfigBuilder setOperation(Operation operation) {\n    this.operation = operation;\n    return this;\n  }\n\n  TestDataConfigBuilder setTruncateTable(boolean truncateTable) {\n    this.truncateTable = truncateTable;\n    return this;\n  }\n\n  TestDataConfigBuilder setPreserveStageFile(boolean preserveStageFile) {\n    this.preserveStageFile = preserveStageFile;\n    return this;\n  }\n\n  TestDataConfigBuilder setUseLocalTimezone(boolean useLocalTimezone) {\n    this.useLocalTimezone = useLocalTimezone;\n    return this;\n  }\n\n  TestDataConfigBuilder setMapTimeToTimestamp(boolean mapTimeToTimestamp) {\n    this.mapTimeToTimestamp = mapTimeToTimestamp;\n    return this;\n  }\n\n  TestDataConfigBuilder setStartTransaction(boolean startTransaction) {\n    this.startTransaction = startTransaction;\n    return this;\n  }\n\n  TestDataConfigBuilder setCopyEmptyFieldAsEmpty(boolean copyEmptyFieldAsEmpty) {\n    this.copyEmptyFieldAsEmpty = copyEmptyFieldAsEmpty;\n    return this;\n  }\n\n  TestDataConfigBuilder setCompressFileByPut(boolean compressFileByPut) {\n    this.compressFileByPut = compressFileByPut;\n    return this;\n  }\n\n  TestDataConfigBuilder setCompressDataBeforePut(boolean compressDataBeforePut) {\n    this.compressDataBeforePut = compressDataBeforePut;\n    return this;\n  }\n\n  TestDataConfigBuilder setCompressLevel(long compressLevel) {\n    this.compressLevel = compressLevel;\n    return this;\n  }\n\n  StreamLoader getStreamLoader() throws Exception {\n    getListener();\n    return streamLoader;\n  }\n\n  synchronized ResultListener getListener() throws Exception {\n    if (listener == null) {\n      Map<LoaderProperty, Object> prop = this.initLoaderProperties();\n      listener = this.initLoader(prop);\n    }\n    return listener;\n  }\n\n  void populate() throws Exception {\n    populate(false);\n  }\n\n  List<Object[]> populateReturnData() throws Exception {\n    return populate(true);\n  }\n\n  List<Object[]> populate(boolean returnDataSet) throws Exception {\n    getListener();\n    streamLoader.start();\n    Random rnd = new Random();\n\n    List<Object[]> newDataSet = new ArrayList<>();\n    if (dataSet == null) {\n      // generates a new data set and ingest\n      for (int i = 0; i < numberOfRows; i++) {\n        final String json = \"{\\\"key\\\":\" + rnd.nextInt() + \",\" + \"\\\"bar\\\":\" + i + \"}\";\n        Object[] row = new Object[] {i, \"foo_\" + i, rnd.nextInt() / 3, new Date(), json};\n        if (returnDataSet) {\n          newDataSet.add(row);\n        }\n        streamLoader.submitRow(row);\n      }\n    } else {\n      for (Object[] row : dataSet) {\n        // ingest the same data set\n        streamLoader.submitRow(row);\n      }\n    }\n    streamLoader.finish();\n    int submitted = listener.getSubmittedRowCount();\n\n    assertThat(\"submitted rows\", submitted, equalTo(numberOfRows));\n    assertThat(\n        \"_resultListener.counter is not correct\", listener.counter.get(), equalTo(numberOfRows));\n    assertThat(\"_resultListener.getErrors() was not 0\", listener.getErrors().size(), equalTo(0));\n\n    ResultSet rs =\n        testConnection\n            .createStatement()\n            .executeQuery(String.format(\"SELECT COUNT(*) AS N\" + \" FROM \\\"%s\\\"\", tableName));\n\n    rs.next();\n    int count = rs.getInt(\"N\");\n    assertThat(\"count is not correct\", count, equalTo(numberOfRows));\n    assertThat(\n        \"_resultListener.processed didn't match count\", listener.processed.get(), equalTo(count));\n    assertThat(\n        \"_resultListener.counter didn't match count\", listener.counter.get(), equalTo(count));\n    assertThat(\n        \"_resultListener.getErrors().size() was not 0\", listener.getErrors().size(), equalTo(0));\n    assertThat(\n        \"_resultListener.getLastRecord()[0] was not 9999\",\n        (Integer) listener.getLastRecord()[0],\n        equalTo(numberOfRows - 1));\n    return newDataSet;\n  }\n\n  private Map<LoaderProperty, Object> initLoaderProperties() {\n    Map<LoaderProperty, Object> prop = new HashMap<>();\n    prop.put(LoaderProperty.tableName, tableName);\n    prop.put(LoaderProperty.schemaName, schemaName);\n    prop.put(LoaderProperty.databaseName, databaseName);\n    prop.put(LoaderProperty.remoteStage, remoteStage);\n    prop.put(LoaderProperty.operation, operation);\n    prop.put(LoaderProperty.columns, columns);\n    prop.put(LoaderProperty.keys, keys);\n    return prop;\n  }\n\n  private ResultListener initLoader(Map<LoaderProperty, Object> prop) throws Exception {\n    ResultListener _resultListener = new ResultListener();\n\n    // Set up Test parameters\n    streamLoader = (StreamLoader) LoaderFactory.createLoader(prop, putConnection, testConnection);\n\n    streamLoader.setProperty(LoaderProperty.startTransaction, startTransaction);\n    streamLoader.setProperty(LoaderProperty.truncateTable, truncateTable);\n    streamLoader.setProperty(LoaderProperty.preserveStageFile, preserveStageFile);\n    streamLoader.setProperty(LoaderProperty.useLocalTimezone, useLocalTimezone);\n    streamLoader.setProperty(LoaderProperty.mapTimeToTimestamp, mapTimeToTimestamp);\n    streamLoader.setProperty(LoaderProperty.copyEmptyFieldAsEmpty, copyEmptyFieldAsEmpty);\n    // file bucket size\n    streamLoader.setProperty(LoaderProperty.csvFileBucketSize, Long.toString(csvFileBucketSize));\n    // file batch\n    streamLoader.setProperty(LoaderProperty.csvFileSize, Long.toString(csvFileSize));\n    streamLoader.setProperty(LoaderProperty.compressFileByPut, compressFileByPut);\n    streamLoader.setProperty(LoaderProperty.compressDataBeforePut, compressDataBeforePut);\n    streamLoader.setProperty(LoaderProperty.compressLevel, compressLevel);\n\n    // ON_ERROR option\n    streamLoader.setProperty(LoaderProperty.onError, onError);\n\n    streamLoader.setListener(_resultListener);\n\n    // causes upload to fail\n    streamLoader.setTestMode(testMode);\n\n    // Wait for 5 seconds on first put to buffer everything up.\n    putConnection.unwrap(SnowflakeConnectionImpl.class).setInjectedDelay(5000);\n\n    return _resultListener;\n  }\n\n  class ResultListener implements LoadResultListener {\n\n    private final List<LoadingError> errors = new ArrayList<>();\n\n    private final AtomicInteger errorCount = new AtomicInteger(0);\n    private final AtomicInteger errorRecordCount = new AtomicInteger(0);\n\n    public final AtomicInteger counter = new AtomicInteger(0);\n    public final AtomicInteger processed = new AtomicInteger(0);\n    public final AtomicInteger deleted = new AtomicInteger(0);\n    public final AtomicInteger updated = new AtomicInteger(0);\n    private final AtomicInteger submittedRowCount = new AtomicInteger(0);\n\n    private Object[] lastRecord = null;\n\n    public boolean throwOnError = false; // should not trigger rollback\n\n    @Override\n    public boolean needErrors() {\n      return true;\n    }\n\n    @Override\n    public boolean needSuccessRecords() {\n      return true;\n    }\n\n    @Override\n    public void addError(LoadingError error) {\n      errors.add(error);\n    }\n\n    @Override\n    public boolean throwOnError() {\n      return throwOnError;\n    }\n\n    public List<LoadingError> getErrors() {\n      return errors;\n    }\n\n    @Override\n    public void recordProvided(Operation op, Object[] record) {\n      lastRecord = record;\n    }\n\n    @Override\n    public void addProcessedRecordCount(Operation op, int i) {\n      processed.addAndGet(i);\n    }\n\n    @Override\n    public void addOperationRecordCount(Operation op, int i) {\n      counter.addAndGet(i);\n      if (op == Operation.DELETE) {\n        deleted.addAndGet(i);\n      } else if (op == Operation.MODIFY || op == Operation.UPSERT) {\n        updated.addAndGet(i);\n      }\n    }\n\n    public Object[] getLastRecord() {\n      return lastRecord;\n    }\n\n    @Override\n    public int getErrorCount() {\n      return errorCount.get();\n    }\n\n    @Override\n    public int getErrorRecordCount() {\n      return errorRecordCount.get();\n    }\n\n    @Override\n    public void resetErrorCount() {\n      errorCount.set(0);\n    }\n\n    @Override\n    public void resetErrorRecordCount() {\n      errorRecordCount.set(0);\n    }\n\n    @Override\n    public void addErrorCount(int count) {\n      errorCount.addAndGet(count);\n    }\n\n    @Override\n    public void addErrorRecordCount(int count) {\n      errorRecordCount.addAndGet(count);\n    }\n\n    @Override\n    public void resetSubmittedRowCount() {\n      submittedRowCount.set(0);\n    }\n\n    @Override\n    public void addSubmittedRowCount(int count) {\n      submittedRowCount.addAndGet(count);\n    }\n\n    @Override\n    public int getSubmittedRowCount() {\n      return submittedRowCount.get();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/AbstractLoggerIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** A base class for testing implementations of {@link SFLogger} */\n@Tag(TestTags.CORE)\npublic abstract class AbstractLoggerIT {\n  public static final String fakeCreds =\n      \"credentials=(aws_key_id='abc123' aws_secret_key='rtyuiop')\";\n\n  @BeforeEach\n  void setUp() {\n    setLogLevel(LogLevel.TRACE);\n  }\n\n  /**\n   * Message logging will be skipped if the message level is below the level set for the logger.\n   * This method tests that the lambda arguments provided along with the message format are not\n   * evaluated if logging is skipped.\n   */\n  @Test\n  public void TestLambdaIsNotEvaluatedIfMsgIsNotLogged() {\n    setLogLevel(LogLevel.ERROR);\n\n    logMessage(\n        LogLevel.TRACE,\n        \"Value: {}\",\n        (ArgSupplier)\n            () -> {\n              fail(\"Lambda expression evaluated even though message is not logged\");\n              return 0;\n            });\n  }\n\n  @Test\n  public void TestWithSingleLambdaArg() {\n    logAndVerifyAtEachLogLevel(\"Value: 5\", \"Value: {}\", (ArgSupplier) () -> 2 + 3);\n  }\n\n  @Test\n  public void TestWithMultipleLambdaArgs() {\n    int a = 2, b = 3, c = 5;\n\n    logAndVerifyAtEachLogLevel(\n        String.format(\"Sum of %s and %s is %s\", a, b + c, a + b + c),\n        \"Sum of {} and {} is {}\",\n        a,\n        (ArgSupplier) () -> b + c,\n        (ArgSupplier) () -> a + b + c);\n  }\n\n  @Test\n  public void TestWithNullArgs() {\n    logAndVerifyAtEachLogLevel(\n        \"Values are null and null\", \"Values are {} and {}\", null, (ArgSupplier) () -> null);\n  }\n\n  @Test\n  public void TestWithIsMaskedTrue() {\n    for (LogLevel level : LogLevel.values()) {\n      logMessage(level, fakeCreds, true);\n      String loggedMsg = getLoggedMessage();\n      String expectedMessage = \"credentials=(aws_key_id='****' aws_secret_key='****')\";\n      assertEquals(expectedMessage, loggedMsg);\n    }\n  }\n\n  @Test\n  public void TestWithIsMaskedFalse() {\n    for (LogLevel level : LogLevel.values()) {\n      logMessage(level, fakeCreds, false);\n      String loggedMsg = getLoggedMessage();\n      // message doesn't change since it's not masked\n      assertEquals(fakeCreds, loggedMsg);\n    }\n  }\n\n  @Test\n  public void testWithThrowable() {\n    for (LogLevel level : LogLevel.values()) {\n      logMessage(level, \"sample message\", (Throwable) null);\n    }\n  }\n\n  /**\n   * Logs the given message format and its arguments at each of the logging levels and verifies that\n   * the logger logged the message correctly.\n   */\n  private void logAndVerifyAtEachLogLevel(String expectedLogMsg, String msg, Object... args) {\n    for (LogLevel level : LogLevel.values()) {\n      clearLastLoggedMessageAndLevel();\n\n      logMessage(level, msg, args);\n\n      String loggedMsg = getLoggedMessage();\n      assertEquals(\n          expectedLogMsg,\n          loggedMsg,\n          String.format(\n              \"Message logged did not match expected value. \" + \"expected=%s actual=%s\",\n              expectedLogMsg, loggedMsg));\n\n      LogLevel loggedMsgLevel = getLoggedMessageLevel();\n      assertEquals(\n          level,\n          loggedMsgLevel,\n          String.format(\n              \"Message was not logged at expected log level. \" + \"expected=%s actual=%s\",\n              level.toString(), loggedMsgLevel.toString()));\n    }\n  }\n\n  /**\n   * Log message at the given level.\n   *\n   * @param level level at which the message is to be logged\n   * @param message message or message format\n   * @param args values for placeholders in the message format\n   */\n  abstract void logMessage(LogLevel level, String message, Object... args);\n\n  /**\n   * Log message at the given level.\n   *\n   * @param level level at which the message is to be logged\n   * @param message message or message format\n   * @param isMasked for masking secrets\n   */\n  abstract void logMessage(LogLevel level, String message, boolean isMasked);\n\n  /**\n   * Log message at the given level.\n   *\n   * @param level level at which the message is to be logged\n   * @param message message or message format\n   * @param throwable for exception thrown\n   */\n  abstract void logMessage(LogLevel level, String message, Throwable throwable);\n\n  /**\n   * Set minimum log level on the logger instance at which a message will be accepted.\n   *\n   * @param level Minimum log level\n   */\n  abstract void setLogLevel(LogLevel level);\n\n  /** Gets message last logged by the logger instance */\n  abstract String getLoggedMessage();\n\n  /** Gets level at which the last message was logged. */\n  abstract LogLevel getLoggedMessageLevel();\n\n  /** Clears cached last logged message and its level. */\n  abstract void clearLastLoggedMessageAndLevel();\n\n  /** Logging levels */\n  protected enum LogLevel {\n    ERROR,\n    WARNING,\n    INFO,\n    DEBUG,\n    TRACE\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/CommonsLoggingWrapperModeTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.EnumSource;\n\npublic class CommonsLoggingWrapperModeTest {\n\n  private String propertyValue;\n\n  @BeforeEach\n  public void setUp() {\n    propertyValue = System.getProperty(CommonsLoggingWrapperMode.JAVA_PROPERTY);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    if (propertyValue != null) {\n      System.setProperty(CommonsLoggingWrapperMode.JAVA_PROPERTY, propertyValue);\n    } else {\n      System.clearProperty(CommonsLoggingWrapperMode.JAVA_PROPERTY);\n    }\n  }\n\n  @ParameterizedTest\n  @EnumSource(CommonsLoggingWrapperMode.class)\n  public void shouldDetectMode(CommonsLoggingWrapperMode value) {\n    System.setProperty(CommonsLoggingWrapperMode.JAVA_PROPERTY, value.name());\n    assertEquals(value, CommonsLoggingWrapperMode.detect());\n  }\n\n  @Test\n  public void shouldPickDefaultMode() {\n    System.clearProperty(CommonsLoggingWrapperMode.JAVA_PROPERTY);\n    assertEquals(CommonsLoggingWrapperMode.DEFAULT, CommonsLoggingWrapperMode.detect());\n  }\n\n  @Test\n  public void shouldThrowOnUnknownMode() {\n    System.setProperty(CommonsLoggingWrapperMode.JAVA_PROPERTY, \"invalid\");\n    IllegalArgumentException illegalArgumentException =\n        assertThrows(IllegalArgumentException.class, CommonsLoggingWrapperMode::detect);\n    assertEquals(\n        \"Unknown commons logging wrapper value 'invalid', expected one of: ALL, DEFAULT, OFF\",\n        illegalArgumentException.getMessage());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/CommonsLoggingWrapperTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.Assert.assertNotNull;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/** A test class created for test case coverage for the class CommonsLoggingWrapper. */\nclass CommonsLoggingWrapperTest {\n  private CommonsLoggingWrapper logWrapper;\n\n  @BeforeEach\n  void setUp() {\n    logWrapper = new CommonsLoggingWrapper(\"test.class\");\n  }\n\n  @Test\n  void testDebug() {\n    String message = \"Debug Message\";\n\n    // Call the method\n    logWrapper.debug(message);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testDebugWithThrowable() {\n    String message = \"Debug Message with Throwable\";\n    Throwable throwable = new Throwable(\"Throwable\");\n\n    // Call the method\n    logWrapper.debug(message, throwable);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testError() {\n    String message = \"Error Message\";\n\n    // Call the method\n    logWrapper.error(message);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testErrorWithThrowable() {\n    String message = \"Error Message with Throwable\";\n    Throwable throwable = new Throwable(\"Throwable\");\n\n    // Call the method\n    logWrapper.error(message, throwable);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testFatal() {\n    String message = \"Fatal Message\";\n\n    // Call the method\n    logWrapper.fatal(message);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testFatalWithThrowable() {\n    String message = \"Fatal Message with Throwable\";\n    Throwable throwable = new Throwable(\"Throwable\");\n\n    // Call the method\n    logWrapper.fatal(message, throwable);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testInfo() {\n    String message = \"Info Message\";\n\n    // Call the method\n    logWrapper.info(message);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testInfoWithThrowable() {\n    String message = \"Info Message with Throwable\";\n    Throwable throwable = new Throwable(\"Throwable\");\n\n    logWrapper.info(message, throwable);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testIsDebugEnabled() {\n\n    logWrapper.isDebugEnabled();\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testIsErrorEnabled() {\n    // Call the method\n    logWrapper.isErrorEnabled();\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testIsFatalEnabled() {\n    logWrapper.isFatalEnabled();\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testIsInfoEnabled() {\n\n    // Call the method\n    logWrapper.isInfoEnabled();\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testIsTraceEnabled() {\n\n    logWrapper.isTraceEnabled();\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testIsWarnEnabled() {\n\n    // Call the method\n    logWrapper.isWarnEnabled();\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testTrace() {\n    String message = \"Trace Message\";\n\n    // Call the method\n    logWrapper.trace(message);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testTraceWithThrowable() {\n    String message = \"Trace Message with Throwable\";\n    Throwable throwable = new Throwable(\"Throwable\");\n\n    // Call the method\n    logWrapper.trace(message, throwable);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testWarn() {\n    String message = \"Warn Message\";\n\n    // Call the method\n    logWrapper.warn(message);\n    assertNotNull(logWrapper);\n  }\n\n  @Test\n  void testWarnWithThrowable() {\n    String message = \"Warn Message with Throwable\";\n    Throwable throwable = new Throwable(\"Throwable\");\n\n    // Call the method\n    logWrapper.warn(message, throwable);\n    assertNotNull(logWrapper);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/JDK14JCLWrapperLatestIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.logging.Formatter;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class JDK14JCLWrapperLatestIT {\n  JDK14JCLWrapper wrapper = new JDK14JCLWrapper(JDK14JCLWrapperLatestIT.class.getName());\n  JDK14Logger logger = (JDK14Logger) wrapper.getLogger();\n\n  /** Message last logged using JDK14Logger. */\n  private String lastLogMessage = null;\n\n  /** Level at which last message was logged using JDK14Logger. */\n  private Level logLevelToRestore = null;\n\n  private class TestJDK14LogHandler extends Handler {\n    /**\n     * Creates an instance of {@link TestJDK14LogHandler}\n     *\n     * @param formatter Formatter that will be used for formatting log records in this handler\n     *     instance\n     */\n    TestJDK14LogHandler(Formatter formatter) {\n      super();\n      super.setFormatter(formatter);\n    }\n\n    @Override\n    public void publish(LogRecord record) {\n      // Assign the log message and its level to the outer class instance\n      // variables so that it can see the messages logged\n      lastLogMessage = getFormatter().formatMessage(record);\n    }\n\n    @Override\n    public void flush() {}\n\n    @Override\n    public void close() throws SecurityException {}\n  }\n\n  /** Logging levels */\n  private enum LogLevel {\n    FATAL,\n    ERROR,\n    WARN,\n    INFO,\n    DEBUG,\n    TRACE\n  }\n\n  private TestJDK14LogHandler handler = new TestJDK14LogHandler(new SFFormatter());\n\n  @BeforeEach\n  public void setUp() {\n    logLevelToRestore = logger.getLevel();\n    // Set debug level to lowest so that all possible messages can be sent.\n    logger.setLevel(Level.FINEST);\n    logger.addHandler(this.handler);\n    logger.setUseParentHandlers(false);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    logger.setUseParentHandlers(true);\n    logger.setLevel(logLevelToRestore);\n    logger.removeHandler(this.handler);\n  }\n\n  String getLoggedMessage() {\n    return this.lastLogMessage;\n  }\n\n  private void testLogMessagesWithThrowable(LogLevel level, String message, Throwable t) {\n    switch (level) {\n      case FATAL:\n        wrapper.fatal(message, t);\n        break;\n      case ERROR:\n        wrapper.error(message, t);\n        break;\n      case WARN:\n        wrapper.warn(message, t);\n        break;\n      case INFO:\n        wrapper.info(message, t);\n        break;\n    }\n    assertEquals(message, getLoggedMessage());\n  }\n\n  private void testLogMessagesNoThrowable(LogLevel level, String message) {\n    switch (level) {\n      case FATAL:\n        wrapper.fatal(message);\n        break;\n      case ERROR:\n        wrapper.error(message);\n        break;\n      case WARN:\n        wrapper.warn(message);\n        break;\n      case INFO:\n        wrapper.info(message);\n        break;\n    }\n    assertEquals(message, getLoggedMessage());\n  }\n\n  private void testNullLogMessagesWithThrowable(LogLevel level, String message, Throwable t) {\n    switch (level) {\n      case TRACE:\n        wrapper.trace(message, t);\n        break;\n      case DEBUG:\n        wrapper.debug(message, t);\n        break;\n    }\n    assertEquals(null, getLoggedMessage());\n  }\n\n  private void testNullLogMessagesNoThrowable(LogLevel level, String message) {\n    switch (level) {\n      case TRACE:\n        wrapper.trace(message);\n        break;\n      case DEBUG:\n        wrapper.debug(message);\n        break;\n    }\n    assertEquals(null, getLoggedMessage());\n  }\n\n  /**\n   * Test that trace and debug levels never display messages in wrapper (disabled for apache\n   * overall). Other 3 levels of error, warn, and info can display logs if debug level is set\n   * at/below them.\n   */\n  @Test\n  public void testNullLogMessages() {\n    LogLevel[] levelsDisplayingOutput = {\n      LogLevel.FATAL, LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO\n    };\n    LogLevel[] levelsWithNoOutput = {LogLevel.TRACE, LogLevel.DEBUG};\n    for (LogLevel level : levelsWithNoOutput) {\n      testNullLogMessagesWithThrowable(level, \"sample message\", null);\n      testNullLogMessagesNoThrowable(level, \"sample message\");\n    }\n    for (LogLevel level : levelsDisplayingOutput) {\n      testLogMessagesWithThrowable(level, \"sample message\", null);\n      testLogMessagesNoThrowable(level, \"sample message\");\n    }\n  }\n\n  /**\n   * Test that trace and debug are always disabled, while other 3 levels are enabled when debug\n   * level is set at/below that level.\n   */\n  @Test\n  public void testEnabledMessaging() {\n    assertFalse(wrapper.isTraceEnabled());\n    assertFalse(wrapper.isDebugEnabled());\n    assertTrue(wrapper.isInfoEnabled());\n    assertTrue(wrapper.isWarnEnabled());\n    assertTrue(wrapper.isErrorEnabled());\n    assertTrue(wrapper.isFatalEnabled());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/JDK14LoggerConsoleHandlerOverrideLatestIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.PrintStream;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Arrays;\nimport java.util.Properties;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.jdbc.BaseJDBCTest;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class JDK14LoggerConsoleHandlerOverrideLatestIT extends BaseJDBCTest {\n  private static final PrintStream standardOut = System.out;\n  private static final PrintStream standardErr = System.err;\n  private static final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n  private static final ByteArrayOutputStream errStream = new ByteArrayOutputStream();\n  private static final String errorMessage = \"error message 1\";\n  private static final String warningMessage = \"warning message 1\";\n  private static final String infoMessage = \"info message 1\";\n  private static final String debugMessage = \"debug message 1\";\n\n  @BeforeAll\n  public static void replaceStreams() {\n    System.setOut(new PrintStream(outputStream));\n    System.setErr(new PrintStream(errStream));\n  }\n\n  @AfterAll\n  public static void resetStreams() {\n    System.setOut(standardOut);\n    System.setErr(standardErr);\n  }\n\n  /** Added in > 3.20.0 */\n  @Test\n  public void shouldLogAllToStdErr() throws Exception {\n    Properties paramProperties = new Properties();\n\n    connectAndLog(paramProperties);\n\n    Handler[] handlers = Logger.getLogger(\"\").getHandlers();\n    assertTrue(handlers.length > 0);\n    assertTrue(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler));\n\n    System.out.flush();\n    System.err.flush();\n    assertEquals(\"\", outputStream.toString());\n    // overriding stderr does not work correctly with maven\n    // String errString = errStream.toString();\n    // assertTrue(errString.contains(errorMessage), () -> \"STDERR: \" + errString);\n    // assertTrue(errString.contains(warningMessage), () -> \"STDERR: \" + errString);\n    // assertTrue(errString.contains(infoMessage), () -> \"STDERR: \" + errString);\n    // assertFalse(errString.contains(debugMessage), () -> \"STDERR: \" + errString);\n  }\n\n  /** Added in > 3.20.0 */\n  @Test\n  public void shouldOverrideConsoleLoggerToStdOut() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"JAVA_LOGGING_CONSOLE_STD_OUT\", true);\n\n    connectAndLog(paramProperties);\n\n    Handler[] handlers = Logger.getLogger(\"\").getHandlers();\n    assertTrue(handlers.length > 0);\n    assertFalse(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler));\n    assertTrue(Arrays.stream(handlers).anyMatch(h -> h instanceof StdOutConsoleHandler));\n\n    System.out.flush();\n    System.err.flush();\n    String outString = outputStream.toString();\n    assertTrue(outString.contains(errorMessage), () -> \"STDOUT: \" + outString);\n    assertTrue(outString.contains(warningMessage), () -> \"STDOUT: \" + outString);\n    assertTrue(outString.contains(infoMessage), () -> \"STDOUT: \" + outString);\n    assertFalse(outString.contains(debugMessage), () -> \"STDOUT: \" + outString);\n    // overriding stderr does not work correctly with maven\n    // assertEquals(\"\", errStream.toString());\n  }\n\n  /** Added in > 3.20.0 */\n  @Test\n  public void shouldOverrideConsoleLoggerWithSpecificThreshold() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"JAVA_LOGGING_CONSOLE_STD_OUT\", true);\n    paramProperties.put(\"JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD\", \"WARNING\");\n\n    connectAndLog(paramProperties);\n\n    Handler[] handlers = Logger.getLogger(\"\").getHandlers();\n    assertTrue(handlers.length > 0);\n    assertFalse(Arrays.stream(handlers).anyMatch(h -> h instanceof ConsoleHandler));\n    assertTrue(\n        Arrays.stream(handlers)\n            .anyMatch(\n                h ->\n                    h instanceof StdErrOutThresholdAwareConsoleHandler\n                        && ((StdErrOutThresholdAwareConsoleHandler) h)\n                            .getThreshold()\n                            .equals(Level.WARNING)));\n\n    System.out.flush();\n    System.err.flush();\n    String outString = outputStream.toString();\n    assertFalse(outString.contains(errorMessage), () -> \"STDOUT: \" + outString);\n    assertTrue(outString.contains(warningMessage), () -> \"STDOUT: \" + outString);\n    assertTrue(outString.contains(infoMessage), () -> \"STDOUT: \" + outString);\n    assertFalse(outString.contains(debugMessage), () -> \"STDOUT: \" + outString);\n    // overriding stderr does not work correctly with maven\n    // String errString = errStream.toString();\n    // assertTrue(errString.contains(errorMessage), () -> \"STDERR: \" + errString);\n    // assertFalse(errString.contains(warningMessage), () -> \"STDERR: \" + errString);\n    // assertFalse(errString.contains(infoMessage), () -> \"STDERR: \" + errString);\n    // assertFalse(errString.contains(debugMessage), () -> \"STDERR: \" + errString);\n  }\n\n  private static void connectAndLog(Properties paramProperties) throws SQLException {\n    try (Connection con = getConnection(paramProperties)) {\n      SFLogger logger = SFLoggerFactory.getLogger(JDK14LoggerConsoleHandlerOverrideLatestIT.class);\n      logger.error(errorMessage);\n      logger.warn(warningMessage);\n      logger.info(infoMessage);\n      logger.debug(debugMessage);\n    }\n  }\n\n  /** Added in > 3.20.0 */\n  @Test\n  public void shouldThrowExceptionOnUnknownLevel() throws Exception {\n    Properties paramProperties = new Properties();\n    paramProperties.put(\"JAVA_LOGGING_CONSOLE_STD_OUT\", true);\n    paramProperties.put(\"JAVA_LOGGING_CONSOLE_STD_OUT_THRESHOLD\", \"UNKNOWN\");\n    assertThrows(UnknownJavaUtilLoggingLevelException.class, () -> getConnection(paramProperties));\n  }\n\n  @BeforeEach\n  public void reset() {\n    JDK14Logger.resetToDefaultConsoleHandler();\n    outputStream.reset();\n    errStream.reset();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/JDK14LoggerLatestIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.util.logging.Formatter;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport java.util.logging.Logger;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n/** A class for testing {@link JDK14Logger} */\n@Tag(TestTags.CORE)\npublic class JDK14LoggerLatestIT extends AbstractLoggerIT {\n  /** {@link JDK14Logger} instance that will be tested in this class */\n  private static final JDK14Logger LOGGER = new JDK14Logger(JDK14LoggerLatestIT.class.getName());\n\n  /**\n   * Used for storing the log level set on the logger instance before starting the tests. Once the\n   * tests are run, this log level will be restored.\n   */\n  private static Level logLevelToRestore;\n\n  /**\n   * This is the logger instance internally used by the JDK14Logger instance. Logger.getLogger()\n   * returns the cached instance if an instance by the same name was created before. The name used\n   * for creating JDK14Logger instance is also used here to get the same internal logger instance.\n   *\n   * <p>JDK14Logger doesn't expose methods to add handlers and set other configurations, hence\n   * direct access to the internal logger is required.\n   */\n  private static final Logger internalLogger =\n      Logger.getLogger(JDK14LoggerLatestIT.class.getName());\n\n  /**\n   * Used for storing whether the parent logger handlers were run in the logger instance before\n   * starting the tests. Once the tests are run, the behavior will be restored.\n   */\n  private static boolean useParentHandlersToRestore = true;\n\n  /** This handler will be added to the internal logger to get logged messages. */\n  private TestJDK14LogHandler handler = new TestJDK14LogHandler(new SFFormatter());\n\n  /** Message last logged using JDK14Logger. */\n  private String lastLogMessage = null;\n\n  /** Level at which last message was logged using JDK14Logger. */\n  private Level lastLogMessageLevel = null;\n\n  @BeforeAll\n  public static void oneTimeSetUp() {\n    logLevelToRestore = internalLogger.getLevel();\n    useParentHandlersToRestore = internalLogger.getUseParentHandlers();\n\n    internalLogger.setUseParentHandlers(false);\n  }\n\n  @AfterAll\n  public static void oneTimeTearDown() {\n    internalLogger.setLevel(logLevelToRestore);\n    internalLogger.setUseParentHandlers(useParentHandlersToRestore);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    super.setUp();\n    internalLogger.addHandler(this.handler);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    internalLogger.removeHandler(this.handler);\n  }\n\n  @Override\n  void logMessage(LogLevel level, String message, Object... args) {\n    switch (level) {\n      case ERROR:\n        LOGGER.error(message, args);\n        break;\n      case WARNING:\n        LOGGER.warn(message, args);\n        break;\n      case INFO:\n        LOGGER.info(message, args);\n        break;\n      case DEBUG:\n        LOGGER.debug(message, args);\n        break;\n      case TRACE:\n        LOGGER.trace(message, args);\n        break;\n    }\n  }\n\n  @Override\n  void logMessage(LogLevel level, String message, boolean isMasked) {\n    switch (level) {\n      case ERROR:\n        LOGGER.error(message, isMasked);\n        break;\n      case WARNING:\n        LOGGER.warn(message, isMasked);\n        break;\n      case INFO:\n        LOGGER.info(message, isMasked);\n        break;\n      case DEBUG:\n        LOGGER.debug(message, isMasked);\n        break;\n      case TRACE:\n        LOGGER.trace(message, isMasked);\n        break;\n    }\n  }\n\n  @Override\n  void logMessage(LogLevel level, String message, Throwable throwable) {\n    switch (level) {\n      case ERROR:\n        LOGGER.error(message, throwable);\n        break;\n      case WARNING:\n        LOGGER.warn(message, throwable);\n        break;\n      case INFO:\n        LOGGER.info(message, throwable);\n        break;\n      case DEBUG:\n        LOGGER.debug(message, throwable);\n        break;\n      case TRACE:\n        LOGGER.trace(message, throwable);\n        break;\n    }\n  }\n\n  @Override\n  void setLogLevel(LogLevel level) {\n    internalLogger.setLevel(toJavaCoreLoggerLevel(level));\n  }\n\n  @Override\n  String getLoggedMessage() {\n    return this.lastLogMessage;\n  }\n\n  @Override\n  LogLevel getLoggedMessageLevel() {\n    return fromJavaCoreLoggerLevel(this.lastLogMessageLevel);\n  }\n\n  @Override\n  void clearLastLoggedMessageAndLevel() {\n    this.lastLogMessage = null;\n    this.lastLogMessageLevel = null;\n  }\n\n  /** Converts log levels in {@link LogLevel} to appropriate levels in {@link Level}. */\n  private Level toJavaCoreLoggerLevel(LogLevel level) {\n    switch (level) {\n      case ERROR:\n        return Level.SEVERE;\n      case WARNING:\n        return Level.WARNING;\n      case INFO:\n        return Level.INFO;\n      case DEBUG:\n        return Level.FINE;\n      case TRACE:\n        return Level.FINEST;\n    }\n\n    return Level.FINEST;\n  }\n\n  /** Converts log levels in {@link Level} to appropriate levels in {@link LogLevel}. */\n  private LogLevel fromJavaCoreLoggerLevel(Level level) {\n    if (Level.SEVERE.equals(level)) {\n      return LogLevel.ERROR;\n    }\n    if (Level.WARNING.equals(level)) {\n      return LogLevel.WARNING;\n    }\n    if (Level.INFO.equals(level)) {\n      return LogLevel.INFO;\n    }\n    if (Level.FINE.equals(level) || Level.FINER.equals(level)) {\n      return LogLevel.DEBUG;\n    }\n    if (Level.FINEST.equals(level) || Level.ALL.equals(level)) {\n      return LogLevel.TRACE;\n    }\n\n    throw new IllegalArgumentException(\n        String.format(\"Specified log level '%s' not supported\", level.toString()));\n  }\n\n  /** An handler that will be used for getting messages logged by a {@link Logger} instance */\n  private class TestJDK14LogHandler extends Handler {\n    /**\n     * Creates an instance of {@link TestJDK14LogHandler}\n     *\n     * @param formatter Formatter that will be used for formatting log records in this handler\n     *     instance\n     */\n    TestJDK14LogHandler(Formatter formatter) {\n      super();\n      super.setFormatter(formatter);\n    }\n\n    @Override\n    public void publish(LogRecord record) {\n      // Assign the log message and it's level to the outer class instance\n      // variables so that it can see the messages logged\n      lastLogMessage = getFormatter().formatMessage(record);\n      lastLogMessageLevel = record.getLevel();\n    }\n\n    @Override\n    public void flush() {}\n\n    @Override\n    public void close() {}\n  }\n\n  @Test\n  public void testInstantiateLoggerForCodeCov() throws IOException {\n    System.setProperty(\"snowflake.jdbc.log.size\", \"100000\");\n    System.setProperty(\"snowflake.jdbc.log.count\", \"3\");\n    System.setProperty(\"net.snowflake.jdbc.loggerImpl\", \"net.snowflake.client.log.JDK14Logger\");\n\n    JDK14Logger logger = new JDK14Logger(JDK14LoggerLatestIT.class.getName());\n\n    String level = \"all\";\n    Level tracingLevel = Level.parse(level.toUpperCase());\n    String logOutputPath =\n        Paths.get(systemGetProperty(\"java.io.tmpdir\"), \"snowflake_jdbc.log\").toString();\n    JDK14Logger.instantiateLogger(tracingLevel, logOutputPath);\n    assertTrue(logger.isTraceEnabled());\n  }\n\n  @Test\n  public void testInstantiateLoggerForCodeCovSTDOUT() throws IOException {\n    String level = \"all\";\n    Level tracingLevel = Level.parse(level.toUpperCase());\n    JDK14Logger.instantiateLogger(tracingLevel, \"STDOUT\");\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/JDK14LoggerTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.nio.file.Paths;\nimport java.util.logging.Level;\nimport org.junit.jupiter.api.Test;\n\npublic class JDK14LoggerTest {\n\n  @Test\n  public void testLegacyLoggerInit() throws IOException {\n    System.setProperty(\"snowflake.jdbc.log.size\", \"100000\");\n    System.setProperty(\"snowflake.jdbc.log.count\", \"3\");\n    System.setProperty(\"net.snowflake.jdbc.loggerImpl\", \"net.snowflake.client.log.JDK14Logger\");\n\n    JDK14Logger logger = new JDK14Logger(JDK14LoggerTest.class.getName());\n    assertFalse(logger.isDebugEnabled());\n    assertTrue(logger.isInfoEnabled());\n\n    String level = \"all\";\n    Level tracingLevel = Level.parse(level.toUpperCase());\n    String logOutputPath =\n        Paths.get(systemGetProperty(\"java.io.tmpdir\"), \"snowflake_jdbc.log\").toString();\n    JDK14Logger.instantiateLogger(tracingLevel, logOutputPath);\n    assertTrue(logger.isDebugEnabled());\n    assertTrue(logger.isTraceEnabled());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/JDK14LoggerWithClientLatestIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.jdbc.SnowflakeUtil.systemGetProperty;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.HashSet;\nimport java.util.Properties;\nimport java.util.logging.Level;\nimport net.snowflake.client.AbstractDriverIT;\nimport net.snowflake.client.annotations.DontRunOnWindows;\nimport net.snowflake.client.api.exception.SnowflakeSQLException;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.exception.SnowflakeSQLLoggedException;\nimport org.apache.commons.io.FileUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\n@Tag(TestTags.OTHERS)\npublic class JDK14LoggerWithClientLatestIT extends AbstractDriverIT {\n\n  @TempDir public File tmpFolder;\n  String homePath = systemGetProperty(\"user.home\");\n  private static Level originalLevel;\n\n  @BeforeAll\n  static void saveLevel() {\n    originalLevel = JDK14Logger.getLevel();\n  }\n\n  @AfterAll\n  static void restoreLevel() {\n    JDK14Logger.setLevel(originalLevel);\n  }\n\n  @Test\n  @Disabled\n  public void testJDK14LoggingWithClientConfig() throws IOException, SQLException {\n    File configFile = new File(tmpFolder, \"config.json\");\n    configFile.createNewFile();\n    Path configFilePath = configFile.toPath();\n    File logFolder = new File(tmpFolder, \"logs\");\n    logFolder.createNewFile();\n    Path logFolderPath = logFolder.toPath();\n    String configJson =\n        \"{\\\"common\\\":{\\\"log_level\\\":\\\"debug\\\",\\\"log_path\\\":\\\"\" + logFolderPath + \"\\\"}}\";\n\n    Files.write(configFilePath, configJson.getBytes());\n    Properties properties = new Properties();\n    properties.put(\"client_config_file\", configFilePath.toString());\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      statement.executeQuery(\"select 1\");\n\n      File file = new File(Paths.get(logFolderPath.toString(), \"jdbc\").toString());\n      assertTrue(file.exists());\n    }\n  }\n\n  @Test\n  public void testJDK14LoggingWithClientConfigInvalidConfigFilePath() {\n    Path configFilePath = Paths.get(\"invalid.json\");\n    Properties properties = new Properties();\n    properties.put(\"client_config_file\", configFilePath.toString());\n    assertThrows(\n        SnowflakeSQLException.class,\n        () -> {\n          try (Connection connection = getConnection(properties)) {\n            connection.createStatement().executeQuery(\"select 1\");\n          }\n        });\n  }\n\n  @Test\n  @Disabled\n  @DontRunOnWindows\n  public void testJDK14LoggingWithClientConfigPermissionError() throws IOException {\n    File configFile = new File(tmpFolder, \"config.json\");\n    configFile.createNewFile();\n    Path configFilePath = configFile.toPath();\n    File logFolder = new File(tmpFolder, \"logs\");\n    logFolder.createNewFile();\n    Path logFolderPath = logFolder.toPath();\n    String configJson =\n        \"{\\\"common\\\":{\\\"log_level\\\":\\\"debug\\\",\\\"log_path\\\":\\\"\" + logFolderPath + \"\\\"}}\";\n    HashSet<PosixFilePermission> perms = new HashSet<>();\n    perms.add(PosixFilePermission.OWNER_READ);\n    perms.add(PosixFilePermission.GROUP_READ);\n    perms.add(PosixFilePermission.OTHERS_READ);\n    Files.setPosixFilePermissions(logFolderPath, perms);\n\n    Files.write(configFilePath, configJson.getBytes());\n    Properties properties = new Properties();\n    properties.put(\"client_config_file\", configFilePath.toString());\n    assertThrows(SQLException.class, () -> getConnection(properties));\n  }\n\n  @Test\n  public void testJDK14LoggerWithBracesInMessage() {\n    JDK14Logger logger = new JDK14Logger(JDK14LoggerWithClientLatestIT.class.getName());\n    JDK14Logger.setLevel(Level.FINE);\n    logger.debug(\"Returning column: 12: a: Group b) Hi {Hello World War} cant wait\");\n    JDK14Logger.setLevel(Level.OFF);\n  }\n\n  @Test\n  public void testJDK14LoggerWithQuotesInMessage() {\n    JDK14Logger logger = new JDK14Logger(JDK14LoggerWithClientLatestIT.class.getName());\n    JDK14Logger.setLevel(Level.FINE);\n    logger.debug(\"Returning column: 12: a: Group b) Hi {Hello 'World' War} cant wait\");\n    JDK14Logger.setLevel(Level.OFF);\n  }\n\n  @Test\n  @Disabled\n  public void testJDK14LoggingWithMissingLogPathClientConfig() throws Exception {\n    File configFile = new File(tmpFolder, \"config.json\");\n    configFile.createNewFile();\n    Path configFilePath = configFile.toPath();\n    String configJson = \"{\\\"common\\\":{\\\"log_level\\\":\\\"debug\\\"}}\";\n    Path home = tmpFolder.toPath();\n    System.setProperty(\"user.home\", home.toString());\n\n    Path homeLogPath = Paths.get(home.toString(), \"jdbc\");\n    Files.write(configFilePath, configJson.getBytes());\n    Properties properties = new Properties();\n    properties.put(\"client_config_file\", configFilePath.toString());\n    try (Connection connection = getConnection(properties);\n        Statement statement = connection.createStatement()) {\n      try {\n        statement.executeQuery(\"select 1\");\n\n        File file = new File(homeLogPath.toString());\n        assertTrue(file.exists());\n\n      } finally {\n        Files.deleteIfExists(configFilePath);\n        FileUtils.deleteDirectory(new File(homeLogPath.toString()));\n      }\n    } finally {\n      System.setProperty(\"user.home\", homePath);\n    }\n  }\n\n  @Test\n  @Disabled\n  public void testJDK14LoggingWithMissingLogPathNoHomeDirClientConfig() throws Exception {\n    System.clearProperty(\"user.home\");\n\n    File configFile = new File(tmpFolder, \"config.json\");\n    Path configFilePath = configFile.toPath();\n    String configJson = \"{\\\"common\\\":{\\\"log_level\\\":\\\"debug\\\"}}\";\n    Files.write(configFilePath, configJson.getBytes());\n    Properties properties = new Properties();\n    properties.put(\"client_config_file\", configFilePath.toString());\n    try {\n      assertThrows(SnowflakeSQLLoggedException.class, () -> getConnection(properties));\n    } finally {\n      System.setProperty(\"user.home\", homePath);\n      Files.deleteIfExists(configFilePath);\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/MaskedExceptionLoggerIntegrationTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport java.lang.reflect.Field;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport java.util.logging.Logger;\nimport net.snowflake.client.internal.util.MaskedException;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\n\npublic class MaskedExceptionLoggerIntegrationTest {\n  private static final String SECRET = \"abcdef\";\n  private static final String SECRET_MSG = \"password=\" + SECRET;\n\n  @Test\n  public void testJdk14LoggerMasksException() {\n    String loggerName = \"net.snowflake.client.log.MaskedExceptionLoggerIntegrationTest\";\n    Logger jul = Logger.getLogger(loggerName);\n\n    // Ensure our handler sees the record.\n    Level priorLevel = jul.getLevel();\n    boolean priorUseParentHandlers = jul.getUseParentHandlers();\n    jul.setLevel(Level.ALL);\n    jul.setUseParentHandlers(false);\n\n    AtomicReference<LogRecord> last = new AtomicReference<>();\n    Handler handler =\n        new Handler() {\n          @Override\n          public void publish(LogRecord record) {\n            last.set(record);\n          }\n\n          @Override\n          public void flush() {}\n\n          @Override\n          public void close() {}\n        };\n\n    jul.addHandler(handler);\n    try {\n      JDK14Logger logger = new JDK14Logger(loggerName);\n      logger.error(SECRET_MSG, new Exception(SECRET_MSG));\n\n      LogRecord record = last.get();\n      assertNotNull(record);\n      assertNotNull(record.getThrown());\n      assertTrue(record.getThrown() instanceof MaskedException);\n\n      String rendered = record.getThrown().toString();\n      assertNotNull(rendered);\n      assertTrue(rendered.contains(\"****\"));\n      assertFalse(rendered.contains(SECRET));\n\n      String msg = record.getThrown().getMessage();\n      assertNotNull(msg);\n      assertTrue(msg.contains(\"****\"));\n      assertFalse(msg.contains(SECRET));\n    } finally {\n      jul.removeHandler(handler);\n      jul.setUseParentHandlers(priorUseParentHandlers);\n      jul.setLevel(priorLevel);\n    }\n  }\n\n  @Test\n  public void testSlf4jLoggerMasksException() throws Exception {\n    SLF4JLogger logger = new SLF4JLogger(MaskedExceptionLoggerIntegrationTest.class.getName());\n\n    org.slf4j.Logger mockLogger = mock(org.slf4j.Logger.class);\n    injectSlf4jLogger(logger, mockLogger);\n\n    logger.error(SECRET_MSG, new Exception(SECRET_MSG));\n\n    ArgumentCaptor<Throwable> throwableCaptor = ArgumentCaptor.forClass(Throwable.class);\n    verify(mockLogger).error(anyString(), throwableCaptor.capture());\n    Throwable thrown = throwableCaptor.getValue();\n    assertNotNull(thrown);\n    assertTrue(thrown instanceof MaskedException);\n\n    String rendered = thrown.toString();\n    assertNotNull(rendered);\n    assertTrue(rendered.contains(\"****\"));\n    assertFalse(rendered.contains(SECRET));\n\n    String msg = thrown.getMessage();\n    assertNotNull(msg);\n    assertTrue(msg.contains(\"****\"));\n    assertFalse(msg.contains(SECRET));\n  }\n\n  private static void injectSlf4jLogger(SLF4JLogger target, org.slf4j.Logger newLogger)\n      throws Exception {\n    Field loggerField = SLF4JLogger.class.getDeclaredField(\"slf4jLogger\");\n    loggerField.setAccessible(true);\n    loggerField.set(target, newLogger);\n\n    Field locationAwareField = SLF4JLogger.class.getDeclaredField(\"isLocationAwareLogger\");\n    locationAwareField.setAccessible(true);\n    locationAwareField.setBoolean(target, false);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/SFFormatterTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.text.DateFormat;\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.TimeZone;\nimport java.util.logging.Formatter;\nimport java.util.logging.Level;\nimport java.util.logging.LogRecord;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class SFFormatterTest {\n  // Change these numbers if necessary\n  /** The maximum time difference in millisecond allowed for timestamp and now */\n  private static final long TIME_DIFFERENCE_BOUNDARY = 600000; // 10 minutes\n  /** Number of iterations for the stress test */\n  private static final int STRESS_TEST_ITERATION = 2000;\n\n  /** Log record generator */\n  private LRGenerator recordGenerator;\n\n  @BeforeEach\n  public void setUp() {\n    recordGenerator = new LRGenerator(SFFormatter.CLASS_NAME_PREFIX + \"TestClass\", \"TestMethod\");\n    recordGenerator.setFormatter(new SFFormatter());\n  }\n\n  /**\n   * This test intends to check if the timestamp generated in the SFFormatter is in UTC timezone\n   *\n   * <p>It would extract the timestamp from a log record using the SFFormatter and compare it\n   * against now;\n   *\n   * <p>Since the time difference between the log record's generated time and the current time is\n   * small, their difference would be limited within TIME_DIFFERENCE_BOUNDARY if the log record's\n   * timestamp is in UTC timezone\n   *\n   * @throws ParseException Will be thrown if date extraction fails\n   */\n  @Test\n  public void testUTCTimeStampSimple() throws ParseException {\n    TimeZone originalTz = TimeZone.getDefault();\n    TimeZone.setDefault(TimeZone.getTimeZone(\"Europe/Berlin\"));\n    try {\n      String record = recordGenerator.generateLogRecordString(Level.INFO, \"TestMessage\");\n\n      Date date = extractDate(record);\n      long nowInMs = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\")).getTimeInMillis();\n      assertTrue(\n          nowInMs - date.getTime() < TIME_DIFFERENCE_BOUNDARY,\n          \"Time difference boundary should be less than \" + TIME_DIFFERENCE_BOUNDARY + \"ms\");\n    } finally {\n      TimeZone.setDefault(originalTz);\n    }\n  }\n\n  /**\n   * The bulk version test of testUTCTimeStampSimple()\n   *\n   * @throws ParseException Will be thrown if timestamp parsing fails\n   */\n  @Test\n  public void testUTCTimeStampStress() throws ParseException {\n    for (int i = 0; i < STRESS_TEST_ITERATION; i++) {\n      testUTCTimeStampSimple();\n    }\n  }\n\n  /**\n   * A log generator could generate log record directly and fill in necessary field specified by\n   * constructor\n   */\n  private class LRGenerator {\n    // Required by SFFormatter as a log record without these fields would cause NullPointerException\n    // in\n    // our SF formatter\n    // add more fields to plug in the log record if required for testing\n    private String srcClassName;\n    private String srcMethodName;\n\n    private Formatter formatter;\n\n    public LRGenerator(String srcClassName, String srcMethodName) {\n      this.srcClassName = srcClassName;\n      this.srcMethodName = srcMethodName;\n      formatter = null;\n    }\n\n    /**\n     * No formatter is needed\n     *\n     * @param level level of log record priority\n     * @param message message of log record\n     * @return A LogRecord instance\n     */\n    public LogRecord generateLogRecord(Level level, String message) {\n      LogRecord record = new LogRecord(Level.INFO, \"null\");\n      record.setSourceClassName(this.srcClassName);\n      record.setSourceMethodName(this.srcMethodName);\n      return record;\n    }\n\n    /**\n     * Generate the string representation of the log record formatter is required to be not null!!\n     *\n     * @param level level of log record priority\n     * @param message message of log record\n     * @return A LogRecord instance\n     */\n    public String generateLogRecordString(Level level, String message) {\n      return formatter.format(this.generateLogRecord(level, message));\n    }\n\n    /**\n     * Formatter setter\n     *\n     * @param formatter The formatter to use for the log record generator\n     */\n    public void setFormatter(Formatter formatter) {\n      this.formatter = formatter;\n    }\n  }\n\n  /**\n   * Helper function to extract the date from the log record\n   *\n   * @param string log record representation\n   * @return a date specified by the log record\n   * @throws ParseException Will be thrown if parsing fails\n   */\n  private Date extractDate(String string) throws ParseException {\n    DateFormat df = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS\");\n    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(\"UTC\"));\n    df.setCalendar(cal);\n    Date date = df.parse(string);\n    return date;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/SFLogLevelTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport org.junit.jupiter.api.Test;\n\npublic class SFLogLevelTest {\n\n  @Test\n  public void testFromString() {\n    assertTrue(SFLogLevel.OFF.equals(SFLogLevel.getLogLevel(\"Off\")));\n    assertTrue(SFLogLevel.OFF.equals(SFLogLevel.getLogLevel(\"OFF\")));\n    assertTrue(SFLogLevel.ERROR.equals(SFLogLevel.getLogLevel(\"ERROR\")));\n    assertTrue(SFLogLevel.WARN.equals(SFLogLevel.getLogLevel(\"WARN\")));\n    assertTrue(SFLogLevel.INFO.equals(SFLogLevel.getLogLevel(\"INFO\")));\n    assertTrue(SFLogLevel.DEBUG.equals(SFLogLevel.getLogLevel(\"DEBUG\")));\n    assertTrue(SFLogLevel.TRACE.equals(SFLogLevel.getLogLevel(\"TRACE\")));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/SFLoggerFactoryTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport org.junit.jupiter.api.Test;\n\npublic class SFLoggerFactoryTest {\n\n  @Test\n  public void testGetLoggerByNameDefault() {\n    SFLogger sflogger = SFLoggerFactory.getLogger(\"SnowflakeConnectionImpl\");\n    assertTrue(sflogger instanceof JDK14Logger);\n  }\n\n  @Test\n  public void testGetLoggerImplementationNameDefaultsToJUL() {\n    assertEquals(\"JUL\", SFLoggerFactory.getLoggerImplementationName());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/SFToJavaLogMapperTest.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static net.snowflake.client.internal.log.SFToJavaLogMapper.toJavaUtilLoggingLevel;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.util.logging.Level;\nimport org.junit.jupiter.api.Test;\n\npublic class SFToJavaLogMapperTest {\n\n  @Test\n  public void testToJavaUtilLoggingLevel() {\n    assertEquals(toJavaUtilLoggingLevel(SFLogLevel.OFF), Level.OFF);\n    assertEquals(toJavaUtilLoggingLevel(SFLogLevel.ERROR), Level.SEVERE);\n    assertEquals(toJavaUtilLoggingLevel(SFLogLevel.WARN), Level.WARNING);\n    assertEquals(toJavaUtilLoggingLevel(SFLogLevel.INFO), Level.INFO);\n    assertEquals(toJavaUtilLoggingLevel(SFLogLevel.DEBUG), Level.FINE);\n    assertEquals(toJavaUtilLoggingLevel(SFLogLevel.TRACE), Level.FINEST);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/SLF4JJJCLWrapperLatestIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.Logger;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.Appender;\nimport ch.qos.logback.core.AppenderBase;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\n\n@Tag(TestTags.CORE)\npublic class SLF4JJJCLWrapperLatestIT {\n\n  /** Message last logged using SLF4JLogger. */\n  private String lastLogMessage = null;\n\n  /** Level of logging to restore at the end of testing. Most likely null. */\n  private Level levelToRestore = null;\n\n  /** An appender that will be used for getting messages logged by a {@link Logger} instance */\n  private class TestAppender extends AppenderBase<ILoggingEvent> {\n    @Override\n    public void append(ILoggingEvent event) {\n      // Assign the log message and it's level to the outer class instance\n      // variables so that it can see the messages logged\n      lastLogMessage = event.getFormattedMessage();\n    }\n  }\n\n  String getLoggedMessage() {\n    return this.lastLogMessage;\n  }\n\n  /** Logging levels */\n  private enum LogLevel {\n    FATAL,\n    ERROR,\n    WARN,\n    INFO,\n    DEBUG,\n    TRACE\n  }\n\n  SLF4JJCLWrapper wrapper = new SLF4JJCLWrapper(SLF4JJJCLWrapperLatestIT.class.getName());\n  Logger logger = (Logger) wrapper.getLogger();\n  private final Appender<ILoggingEvent> testAppender = new TestAppender();\n\n  @BeforeEach\n  public void setUp() {\n    levelToRestore = logger.getLevel();\n    if (!testAppender.isStarted()) {\n      testAppender.start();\n    }\n    // Set debug level to lowest possible level so all messages can be recorded.\n    logger.setLevel(Level.TRACE);\n    logger.addAppender(testAppender);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    logger.setLevel(levelToRestore);\n    logger.detachAppender(testAppender);\n  }\n\n  // helper function, throwables allowed\n  private void testNullLogMessagesWithThrowable(LogLevel level, String message, Throwable t) {\n    switch (level) {\n      case FATAL:\n        wrapper.fatal(message, t);\n        break;\n      case ERROR:\n        wrapper.error(message, t);\n        break;\n      case WARN:\n        wrapper.warn(message, t);\n        break;\n      case INFO:\n        wrapper.info(message, t);\n        break;\n      case DEBUG:\n        wrapper.debug(message, t);\n        break;\n      case TRACE:\n        wrapper.trace(message, t);\n        break;\n    }\n    assertEquals(null, getLoggedMessage());\n  }\n\n  // helper function, no throwables\n  private void testNullLogMessagesNoThrowable(LogLevel level, String message) {\n    switch (level) {\n      case FATAL:\n        wrapper.fatal(message);\n        break;\n      case ERROR:\n        wrapper.error(message);\n        break;\n      case WARN:\n        wrapper.warn(message);\n        break;\n      case INFO:\n        wrapper.info(message);\n        break;\n      case DEBUG:\n        wrapper.debug(message);\n        break;\n      case TRACE:\n        wrapper.trace(message);\n        break;\n    }\n    assertEquals(null, getLoggedMessage());\n  }\n\n  /** Test that all levels are disabled for wrapper class. No messages returned at any level. */\n  @Test\n  public void testNullLogMessages() {\n    for (LogLevel level : LogLevel.values()) {\n      testNullLogMessagesWithThrowable(level, \"sample message\", null);\n      testNullLogMessagesNoThrowable(level, \"sample message\");\n    }\n  }\n\n  /**\n   * Test that tracing and debugging are disabled for apache at all times. With other levels, pass\n   * in results from the actual logger to see if messages can be enabled.\n   */\n  @Test\n  public void testEnabledMessaging() {\n    assertFalse(wrapper.isTraceEnabled());\n    assertFalse(wrapper.isDebugEnabled());\n    assertTrue(wrapper.isInfoEnabled());\n    assertTrue(wrapper.isWarnEnabled());\n    assertTrue(wrapper.isErrorEnabled());\n    assertTrue(wrapper.isFatalEnabled());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/log/SLF4JLoggerLatestIT.java",
    "content": "package net.snowflake.client.internal.log;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.Logger;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.Appender;\nimport ch.qos.logback.core.AppenderBase;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Tag;\nimport org.slf4j.LoggerFactory;\n\n/** A class for testing {@link SLF4JLogger} */\n@Tag(TestTags.CORE)\npublic class SLF4JLoggerLatestIT extends AbstractLoggerIT {\n  /** {@link SLF4JLogger} instance that will be tested in this class */\n  private static final SLF4JLogger LOGGER = new SLF4JLogger(SLF4JLoggerLatestIT.class);\n\n  /**\n   * Used for storing the log level set on the logger instance before starting the tests. Once the\n   * tests are run, this log level will be restored.\n   */\n  private static Level logLevelToRestore;\n\n  /**\n   * This is the logger instance internally used by the SLF4JLogger instance. LoggerFactory returns\n   * the cached instance if an instance by the same name was created before. The name used for\n   * creating SLF4JLogger instance is also used here to get the same internal logger instance.\n   *\n   * <p>SLF4JLogger doesn't expose methods to set logging level and other configurations, hence\n   * direct access to the internal logger is required.\n   */\n  private static final Logger internalLogger =\n      (Logger) LoggerFactory.getLogger(SLF4JLoggerLatestIT.class);\n\n  /**\n   * Used for storing whether additive property was set on the logger instance before starting the\n   * tests. Once the tests are run, additivity will be restored if it was previously enabled.\n   *\n   * <p>If additivity is enabled, {@link Appender}s in the parent loggers are also invoked.\n   */\n  private static boolean additivityToRestore = true;\n\n  /**\n   * Used for storing any appenders present in the internal logger before starting the tests. They\n   * will be restored after running the tests.\n   */\n  private static final List<Appender<ILoggingEvent>> appendersToRestore = new ArrayList<>();\n\n  /** This appender will be added to the internal logger to get logged messages. */\n  private final Appender<ILoggingEvent> testAppender = new TestAppender();\n\n  /** Message last logged using SLF4JLogger. */\n  private String lastLogMessage = null;\n\n  /** Level at which last message was logged using SLF4JLogger. */\n  private Level lastLogMessageLevel = null;\n\n  @BeforeAll\n  public static void oneTimeSetUp() {\n    logLevelToRestore = internalLogger.getLevel();\n    additivityToRestore = internalLogger.isAdditive();\n\n    appendersToRestore.clear();\n\n    // Get all existing appenders and restore them once testing is done.\n    Iterator<Appender<ILoggingEvent>> itr = internalLogger.iteratorForAppenders();\n    while (itr.hasNext()) {\n      appendersToRestore.add(itr.next());\n    }\n\n    // Remove existing appenders to avoid unnecessary logging\n    appendersToRestore.forEach(internalLogger::detachAppender);\n\n    // Disable running appenders in parent loggers to avoid unnecessary logging\n    internalLogger.setAdditive(false);\n  }\n\n  @AfterAll\n  public static void oneTimeTearDown() {\n    // Restore original configuration\n    internalLogger.setLevel(logLevelToRestore);\n    internalLogger.setAdditive(additivityToRestore);\n\n    internalLogger.detachAndStopAllAppenders();\n\n    appendersToRestore.forEach(internalLogger::addAppender);\n  }\n\n  @BeforeEach\n  public void setUp() {\n    super.setUp();\n    if (!testAppender.isStarted()) {\n      testAppender.start();\n    }\n\n    internalLogger.addAppender(testAppender);\n  }\n\n  @AfterEach\n  public void tearDown() {\n    internalLogger.detachAppender(testAppender);\n  }\n\n  @Override\n  void logMessage(LogLevel level, String message, Object... args) {\n    switch (level) {\n      case ERROR:\n        LOGGER.error(message, args);\n        break;\n      case WARNING:\n        LOGGER.warn(message, args);\n        break;\n      case INFO:\n        LOGGER.info(message, args);\n        break;\n      case DEBUG:\n        LOGGER.debug(message, args);\n        break;\n      case TRACE:\n        LOGGER.trace(message, args);\n        break;\n    }\n  }\n\n  @Override\n  void logMessage(LogLevel level, String message, Throwable throwable) {\n    switch (level) {\n      case ERROR:\n        LOGGER.error(message, throwable);\n        break;\n      case WARNING:\n        LOGGER.warn(message, throwable);\n        break;\n      case INFO:\n        LOGGER.info(message, throwable);\n        break;\n      case DEBUG:\n        LOGGER.debug(message, throwable);\n        break;\n      case TRACE:\n        LOGGER.trace(message, throwable);\n        break;\n    }\n  }\n\n  @Override\n  void logMessage(LogLevel level, String message, boolean isMasked) {\n    switch (level) {\n      case ERROR:\n        LOGGER.error(message, isMasked);\n        break;\n      case WARNING:\n        LOGGER.warn(message, isMasked);\n        break;\n      case INFO:\n        LOGGER.info(message, isMasked);\n        break;\n      case DEBUG:\n        LOGGER.debug(message, isMasked);\n        break;\n      case TRACE:\n        LOGGER.trace(message, isMasked);\n        break;\n    }\n  }\n\n  @Override\n  void setLogLevel(LogLevel level) {\n    internalLogger.setLevel(toLogBackLevel(level));\n  }\n\n  @Override\n  String getLoggedMessage() {\n    return this.lastLogMessage;\n  }\n\n  @Override\n  LogLevel getLoggedMessageLevel() {\n    return fromLogBackLevel(this.lastLogMessageLevel);\n  }\n\n  @Override\n  void clearLastLoggedMessageAndLevel() {\n    this.lastLogMessage = null;\n    this.lastLogMessageLevel = null;\n  }\n\n  /** Converts log levels in {@link LogLevel} to appropriate levels in {@link Level}. */\n  private Level toLogBackLevel(LogLevel level) {\n    switch (level) {\n      case ERROR:\n        return Level.ERROR;\n      case WARNING:\n        return Level.WARN;\n      case INFO:\n        return Level.INFO;\n      case DEBUG:\n        return Level.DEBUG;\n      case TRACE:\n        return Level.TRACE;\n    }\n\n    return Level.TRACE;\n  }\n\n  /** Converts log levels in {@link Level} to appropriate levels in {@link LogLevel}. */\n  private LogLevel fromLogBackLevel(Level level) {\n    if (Level.ERROR.equals(level)) {\n      return LogLevel.ERROR;\n    }\n    if (Level.WARN.equals(level)) {\n      return LogLevel.WARNING;\n    }\n    if (Level.INFO.equals(level)) {\n      return LogLevel.INFO;\n    }\n    if (Level.DEBUG.equals(level)) {\n      return LogLevel.DEBUG;\n    }\n    if (Level.TRACE.equals(level) || Level.ALL.equals(level)) {\n      return LogLevel.TRACE;\n    }\n\n    throw new IllegalArgumentException(\n        String.format(\"Specified log level '%s' not supported\", level.toString()));\n  }\n\n  /** An appender that will be used for getting messages logged by a {@link Logger} instance */\n  private class TestAppender extends AppenderBase<ILoggingEvent> {\n    @Override\n    public void append(ILoggingEvent event) {\n      // Assign the log message and it's level to the outer class instance\n      // variables so that it can see the messages logged\n      lastLogMessage = event.getFormattedMessage();\n      lastLogMessageLevel = event.getLevel();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/DecorrelatedJitterBackoffTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.stream.Stream;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\npublic class DecorrelatedJitterBackoffTest {\n\n  static Stream<Arguments> testParameters() {\n    long base = 1000L; // 1 second base\n    long cap = 20000L; // 20 second cap\n\n    return Stream.of(\n        // attemptNumber, base, cap, currentSleepTime, expectedMin (always base), expectedMax\n        // (min(cap, sleep*3))\n        // Retry 1: Starting with 1 second\n        Arguments.of(1, base, cap, 1000L, base, 3000L),\n        // Retry 2: Sleep time has increased to ~2 seconds\n        Arguments.of(2, base, cap, 2000L, base, 6000L),\n        // Retry 3: Sleep time increased to ~3 seconds\n        Arguments.of(3, base, cap, 3000L, base, 9000L),\n        // Retry 4: Sleep time increased to ~4 seconds\n        Arguments.of(4, base, cap, 4000L, base, 12000L),\n        // Retry 5: Sleep time increased to ~5 seconds\n        Arguments.of(5, base, cap, 5000L, base, 15000L),\n        // Retry 6: Sleep time increased to ~6 seconds\n        Arguments.of(6, base, cap, 6000L, base, 18000L),\n        // Retry 7: Sleep time at 7 seconds - approaching cap\n        Arguments.of(7, base, cap, 7000L, base, cap), // 7000 * 3 = 21000 > cap, so capped at 20000\n        // Retry 8: Sleep time at 8 seconds - beyond cap\n        Arguments.of(8, base, cap, 8000L, base, cap), // 8000 * 3 = 24000 > cap, so capped at 20000\n        // Retry 9: Sleep time at 10 seconds - well beyond cap\n        Arguments.of(\n            9, base, cap, 10000L, base, cap), // 10000 * 3 = 30000 > cap, so capped at 20000\n        // Retry 10: Sleep time at 15 seconds\n        Arguments.of(\n            10, base, cap, 15000L, base, cap)); // 15000 * 3 = 45000 > cap, so capped at 20000\n  }\n\n  @ParameterizedTest(name = \"Retry {0}: currentSleep={3}ms, expecting [{4}ms, {5}ms]\")\n  @MethodSource(\"testParameters\")\n  public void testNextSleepTime(\n      int attemptNumber,\n      long base,\n      long cap,\n      long currentSleepTime,\n      long expectedMin,\n      long expectedMax) {\n    DecorrelatedJitterBackoff backoff = new DecorrelatedJitterBackoff(base, cap);\n\n    // Run the test multiple times since nextSleepTime uses random values\n    for (int i = 0; i < 100; i++) {\n      long result = backoff.nextSleepTime(currentSleepTime);\n\n      assertTrue(\n          result >= expectedMin,\n          String.format(\n              \"Retry %d, iteration %d: Result %dms should be >= %dms (base) for currentSleepTime=%dms\",\n              attemptNumber, i, result, expectedMin, currentSleepTime));\n\n      assertTrue(\n          result <= expectedMax,\n          String.format(\n              \"Retry %d, iteration %d: Result %dms should be <= %dms (min(cap=%d, sleep*3=%d)) for currentSleepTime=%dms\",\n              attemptNumber, i, result, expectedMax, cap, currentSleepTime * 3, currentSleepTime));\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/LibcDetailsTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.io.TempDir;\n\npublic class LibcDetailsTest {\n\n  @AfterEach\n  public void tearDown() {\n    LibcDetails.resetCacheForTesting();\n  }\n\n  // -------------------- parseLddContent: glibc --------------------\n\n  @Test\n  public void testParseLddContentGlibc() {\n    String content =\n        \"ldd (Ubuntu GLIBC 2.31-0ubuntu9.16) 2.31\\n\"\n            + \"Copyright (C) 2020 Free Software Foundation, Inc.\\n\"\n            + \"This is free software; see the source for copying conditions.  There is NO\\n\"\n            + \"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\\n\"\n            + \"Written by Roland McGrath and Ulrich Drepper.\\n\"\n            + \"GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.16) stable release version 2.31.\\n\";\n\n    LibcInfo result = LibcDetails.parseLddContent(content);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.GLIBC, result.getFamily());\n    assertEquals(\"2.31\", result.getVersion());\n  }\n\n  @Test\n  public void testParseLddContentGlibcRhel() {\n    String content =\n        \"GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al.\\n\";\n\n    LibcInfo result = LibcDetails.parseLddContent(content);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.GLIBC, result.getFamily());\n    assertEquals(\"2.17\", result.getVersion());\n  }\n\n  // -------------------- parseLddContent: musl --------------------\n\n  @Test\n  public void testParseLddContentMuslWithVersion() {\n    String content = \"musl libc (x86_64)\\nVersion 1.2.3\\n\";\n\n    LibcInfo result = LibcDetails.parseLddContent(content);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.MUSL, result.getFamily());\n    assertEquals(\"1.2.3\", result.getVersion());\n  }\n\n  @Test\n  public void testParseLddContentMuslWithoutVersion() {\n    String content = \"musl libc (x86_64)\\n\";\n\n    LibcInfo result = LibcDetails.parseLddContent(content);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.MUSL, result.getFamily());\n    assertNull(result.getVersion());\n  }\n\n  @Test\n  public void testParseLddContentMuslWithExtendedVersion() {\n    String content = \"musl libc (aarch64)\\nVersion 1.2.5_git20240924\\n\";\n\n    LibcInfo result = LibcDetails.parseLddContent(content);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.MUSL, result.getFamily());\n    // The regex grabs the numeric/dot prefix only.\n    assertEquals(\"1.2.5\", result.getVersion());\n  }\n\n  // -------------------- parseLddContent: unknown / empty --------------------\n\n  @Test\n  public void testParseLddContentEmpty() {\n    assertNull(LibcDetails.parseLddContent(\"\"));\n    assertNull(LibcDetails.parseLddContent(null));\n  }\n\n  @Test\n  public void testParseLddContentUnknown() {\n    assertNull(LibcDetails.parseLddContent(\"some unrelated content here\"));\n  }\n\n  @Test\n  public void testParseLddContentDoesNotMatchSubstringFalsePositives() {\n    // \"musl\" must be matched as a whole word, not as a substring of e.g. \"muslib\" or \"muscle\".\n    assertNull(LibcDetails.parseLddContent(\"muslib version 1.0\\n\"));\n    assertNull(LibcDetails.parseLddContent(\"muscle library v1\\n\"));\n    assertNull(LibcDetails.parseLddContent(\"emusl 0.1\\n\"));\n  }\n\n  // -------------------- detectFromFilesystem --------------------\n\n  @Test\n  public void testDetectFromFilesystemReadsAndParses(@TempDir Path tempDir) throws IOException {\n    Path lddFile = tempDir.resolve(\"ldd\");\n    Files.write(\n        lddFile,\n        \"GNU C Library (GNU libc) stable release version 2.34.\\n\".getBytes(StandardCharsets.UTF_8));\n\n    LibcInfo result = LibcDetails.detectFromFilesystem(lddFile);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.GLIBC, result.getFamily());\n    assertEquals(\"2.34\", result.getVersion());\n  }\n\n  @Test\n  public void testDetectFromFilesystemMissingFileReturnsNull(@TempDir Path tempDir) {\n    Path missing = tempDir.resolve(\"does-not-exist\");\n    assertNull(LibcDetails.detectFromFilesystem(missing));\n  }\n\n  @Test\n  public void testParseCommandOutputGlibcFromGetconf() {\n    String output = \"glibc 2.17\\nldd (GNU libc) 2.17\\n\";\n\n    LibcInfo result = LibcDetails.parseCommandOutput(output);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.GLIBC, result.getFamily());\n    assertEquals(\"2.17\", result.getVersion());\n  }\n\n  @Test\n  public void testParseCommandOutputMuslFromLdd() {\n    String output =\n        \"getconf: UNKNOWN variable GNU_LIBC_VERSION\\n\" + \"musl libc (x86_64)\\n\" + \"Version 1.2.2\\n\";\n\n    LibcInfo result = LibcDetails.parseCommandOutput(output);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.MUSL, result.getFamily());\n    assertEquals(\"1.2.2\", result.getVersion());\n  }\n\n  @Test\n  public void testParseCommandOutputMuslWithoutVersion() {\n    String output = \"getconf: UNKNOWN variable GNU_LIBC_VERSION\\nmusl libc (x86_64)\\n\";\n\n    LibcInfo result = LibcDetails.parseCommandOutput(output);\n\n    assertNotNull(result);\n    assertEquals(LibcDetails.MUSL, result.getFamily());\n    assertNull(result.getVersion());\n  }\n\n  @Test\n  public void testParseCommandOutputUnknown() {\n    assertNull(LibcDetails.parseCommandOutput(\"some unrelated\\noutput here\\n\"));\n    assertNull(LibcDetails.parseCommandOutput(\"\"));\n    assertNull(LibcDetails.parseCommandOutput(null));\n  }\n\n  @Test\n  @EnabledOnOs(OS.LINUX)\n  public void testLoadOnLinuxReturnsKnownFamily() {\n    LibcInfo result = LibcDetails.load();\n    assertNotNull(result);\n    // On Linux we expect to detect at least the family. Version may still be null if neither\n    // /usr/bin/ldd is present nor getconf/ldd are on PATH, so we don't assert on version.\n    String family = result.getFamily();\n    assertTrue(\n        LibcDetails.GLIBC.equals(family) || LibcDetails.MUSL.equals(family),\n        \"On Linux, family must be glibc or musl, got: \" + family);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/MaskedExceptionTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport org.junit.jupiter.api.Test;\n\npublic class MaskedExceptionTest {\n\n  @Test\n  public void testMasksMessage() {\n    Throwable inner = new Exception(\"password=abcdef\");\n    MaskedException masked = new MaskedException(inner);\n\n    String msg = masked.getMessage();\n    assertNotNull(msg);\n    assertTrue(msg.contains(\"****\"));\n    assertFalse(msg.contains(\"abcdef\"));\n  }\n\n  @Test\n  public void testMasksToString() {\n    Throwable inner = new Exception(\"password=abcdef\");\n    MaskedException masked = new MaskedException(inner);\n\n    String rendered = masked.toString();\n    assertNotNull(rendered);\n    assertTrue(rendered.contains(\"****\"));\n    assertFalse(rendered.contains(\"abcdef\"));\n  }\n\n  @Test\n  public void testPreservesStackFrames() {\n    Throwable inner = new Exception(\"password=abcdef\");\n    MaskedException masked = new MaskedException(inner);\n\n    assertArrayEquals(inner.getStackTrace(), masked.getStackTrace());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/OsReleaseDetailsTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Map;\nimport java.util.Objects;\nimport net.snowflake.client.annotations.RunOnLinux;\nimport org.junit.jupiter.api.Test;\n\npublic class OsReleaseDetailsTest {\n\n  @Test\n  public void testParseArchLinuxOsRelease() {\n    String content =\n        \"NAME=\\\"Arch Linux\\\"\\n\"\n            + \"PRETTY_NAME=\\\"Arch Linux\\\"\\n\"\n            + \"ID=arch\\n\"\n            + \"BUILD_ID=rolling\\n\"\n            + \"VERSION_ID=20251019.0.436919\\n\"\n            + \"ANSI_COLOR=\\\"38;2;23;147;209\\\"\\n\"\n            + \"HOME_URL=\\\"https://archlinux.org/\\\"\\n\"\n            + \"DOCUMENTATION_URL=\\\"https://wiki.archlinux.org/\\\"\\n\"\n            + \"SUPPORT_URL=\\\"https://bbs.archlinux.org/\\\"\\n\"\n            + \"BUG_REPORT_URL=\\\"https://gitlab.archlinux.org/groups/archlinux/-/issues\\\"\\n\"\n            + \"PRIVACY_POLICY_URL=\\\"https://terms.archlinux.org/docs/privacy-policy/\\\"\\n\"\n            + \"LOGO=archlinux-logo\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Arch Linux\", result.get(\"NAME\"));\n    assertEquals(\"Arch Linux\", result.get(\"PRETTY_NAME\"));\n    assertEquals(\"arch\", result.get(\"ID\"));\n    assertEquals(\"rolling\", result.get(\"BUILD_ID\"));\n    assertEquals(\"20251019.0.436919\", result.get(\"VERSION_ID\"));\n    assertEquals(5, result.size());\n  }\n\n  @Test\n  public void testParseUbuntuOsRelease() {\n    String content =\n        \"NAME=\\\"Ubuntu\\\"\\n\"\n            + \"VERSION=\\\"22.04.3 LTS (Jammy Jellyfish)\\\"\\n\"\n            + \"ID=ubuntu\\n\"\n            + \"ID_LIKE=debian\\n\"\n            + \"PRETTY_NAME=\\\"Ubuntu 22.04.3 LTS\\\"\\n\"\n            + \"VERSION_ID=\\\"22.04\\\"\\n\"\n            + \"VERSION_CODENAME=jammy\\n\"\n            + \"HOME_URL=\\\"https://www.ubuntu.com/\\\"\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Ubuntu\", result.get(\"NAME\"));\n    assertEquals(\"22.04.3 LTS (Jammy Jellyfish)\", result.get(\"VERSION\"));\n    assertEquals(\"ubuntu\", result.get(\"ID\"));\n    assertEquals(\"Ubuntu 22.04.3 LTS\", result.get(\"PRETTY_NAME\"));\n    assertEquals(\"22.04\", result.get(\"VERSION_ID\"));\n    assertEquals(5, result.size());\n  }\n\n  @Test\n  public void testParseAlpineLinuxOsRelease() {\n    String content =\n        \"NAME=\\\"Alpine Linux\\\"\\n\"\n            + \"ID=alpine\\n\"\n            + \"VERSION_ID=3.18.4\\n\"\n            + \"PRETTY_NAME=\\\"Alpine Linux v3.18\\\"\\n\"\n            + \"HOME_URL=\\\"https://alpinelinux.org/\\\"\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Alpine Linux\", result.get(\"NAME\"));\n    assertEquals(\"alpine\", result.get(\"ID\"));\n    assertEquals(\"3.18.4\", result.get(\"VERSION_ID\"));\n    assertEquals(\"Alpine Linux v3.18\", result.get(\"PRETTY_NAME\"));\n    assertEquals(4, result.size());\n  }\n\n  @Test\n  public void testParseWithImageFields() {\n    // Some container images have IMAGE_ID and IMAGE_VERSION\n    String content =\n        \"NAME=\\\"Container OS\\\"\\n\"\n            + \"ID=container\\n\"\n            + \"IMAGE_ID=\\\"myimage\\\"\\n\"\n            + \"IMAGE_VERSION=\\\"1.2.3\\\"\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Container OS\", result.get(\"NAME\"));\n    assertEquals(\"container\", result.get(\"ID\"));\n    assertEquals(\"myimage\", result.get(\"IMAGE_ID\"));\n    assertEquals(\"1.2.3\", result.get(\"IMAGE_VERSION\"));\n    assertEquals(4, result.size());\n  }\n\n  @Test\n  public void testParseWithEmptyLines() {\n    String content = \"NAME=\\\"Test Linux\\\"\\n\" + \"\\n\" + \"\\n\" + \"ID=test\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Test Linux\", result.get(\"NAME\"));\n    assertEquals(\"test\", result.get(\"ID\"));\n    assertEquals(2, result.size());\n  }\n\n  @Test\n  public void testParseUnquotedValues() {\n    String content = \"ID=arch\\n\" + \"BUILD_ID=rolling\\n\" + \"VERSION_ID=20251019.0.436919\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"arch\", result.get(\"ID\"));\n    assertEquals(\"rolling\", result.get(\"BUILD_ID\"));\n    assertEquals(\"20251019.0.436919\", result.get(\"VERSION_ID\"));\n  }\n\n  @Test\n  public void testParseQuotedValues() {\n    String content =\n        \"NAME=\\\"Ubuntu\\\"\\n\" + \"VERSION=\\\"22.04.3 LTS\\\"\\n\" + \"PRETTY_NAME=\\\"Ubuntu 22.04.3 LTS\\\"\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Ubuntu\", result.get(\"NAME\"));\n    assertEquals(\"22.04.3 LTS\", result.get(\"VERSION\"));\n    assertEquals(\"Ubuntu 22.04.3 LTS\", result.get(\"PRETTY_NAME\"));\n  }\n\n  @Test\n  public void testParseEmptyValue() {\n    // Some fields may have empty values\n    String content = \"NAME=\\\"\\\"\\n\" + \"ID=test\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    // Empty quoted value should be captured\n    assertEquals(\"\", result.get(\"NAME\"));\n    assertEquals(\"test\", result.get(\"ID\"));\n  }\n\n  @Test\n  public void testParseSingleQuotedValues() {\n    String content = \"NAME='Ubuntu'\\n\" + \"VERSION='22.04.3 LTS'\\n\" + \"ID='ubuntu'\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Ubuntu\", result.get(\"NAME\"));\n    assertEquals(\"22.04.3 LTS\", result.get(\"VERSION\"));\n    assertEquals(\"ubuntu\", result.get(\"ID\"));\n  }\n\n  @Test\n  public void testParseValueWithSpacesAroundEquals() {\n    // Some implementations might have spaces around the equals sign\n    String content = \"NAME= \\\"Ubuntu\\\"\\n\" + \"ID= ubuntu\\n\" + \"VERSION= '22.04'\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Ubuntu\", result.get(\"NAME\"));\n    assertEquals(\"ubuntu\", result.get(\"ID\"));\n    assertEquals(\"22.04\", result.get(\"VERSION\"));\n  }\n\n  @Test\n  public void testParseValueWithInlineComment() {\n    // Unquoted values can have inline comments\n    String content = \"ID=ubuntu # this is a comment\\n\" + \"VERSION_ID=22.04 # another comment\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"ubuntu\", result.get(\"ID\"));\n    assertEquals(\"22.04\", result.get(\"VERSION_ID\"));\n  }\n\n  @Test\n  public void testParseQuotedValueWithHashSymbol() {\n    // Hash inside quotes should not be treated as comment\n    String content = \"NAME=\\\"Ubuntu #1\\\"\\n\" + \"PRETTY_NAME='Test #2 Distro'\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Ubuntu #1\", result.get(\"NAME\"));\n    assertEquals(\"Test #2 Distro\", result.get(\"PRETTY_NAME\"));\n  }\n\n  @Test\n  public void testParseQuotedValueWithInlineComment() {\n    // Single-quoted value followed by inline comment\n    String content =\n        \"NAME='Ubuntu' # the OS name\\n\"\n            + \"ID=\\\"ubuntu\\\" # lowercase id\\n\"\n            + \"VERSION='22.04 LTS' # long term support\\n\";\n\n    Map<String, String> result = OsReleaseDetails.parse(content);\n\n    assertEquals(\"Ubuntu\", result.get(\"NAME\"));\n    assertEquals(\"ubuntu\", result.get(\"ID\"));\n    assertEquals(\"22.04 LTS\", result.get(\"VERSION\"));\n  }\n\n  @Test\n  @RunOnLinux\n  public void testLoadFromFile() throws Exception {\n    Path osReleaseFile =\n        Paths.get(Objects.requireNonNull(getClass().getResource(\"/os-release-test\")).toURI());\n\n    Map<String, String> result = OsReleaseDetails.loadFromPath(osReleaseFile);\n\n    assertEquals(\"Test Linux\", result.get(\"NAME\"));\n    assertEquals(\"Test Linux 1.0 LTS\", result.get(\"PRETTY_NAME\"));\n    assertEquals(\"testlinux\", result.get(\"ID\"));\n    assertEquals(\"testlinux-cloud\", result.get(\"IMAGE_ID\"));\n    assertEquals(\"1.0.0-cloud\", result.get(\"IMAGE_VERSION\"));\n    assertEquals(\"20260130\", result.get(\"BUILD_ID\"));\n    assertEquals(\"1.0 LTS (Test Release)\", result.get(\"VERSION\"));\n    assertEquals(\"1.0.0\", result.get(\"VERSION_ID\"));\n    assertEquals(8, result.size());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/PlatformDetectorLatestIT.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.mockito.Mockito.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.io.IOException;\nimport java.util.List;\nimport net.snowflake.client.category.TestTags;\nimport net.snowflake.client.internal.core.auth.wif.AwsAttestationService;\nimport net.snowflake.client.internal.jdbc.BaseWiremockTest;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\n\n@Tag(TestTags.CONNECTION)\npublic class PlatformDetectorLatestIT extends BaseWiremockTest {\n\n  private AwsAttestationService mockAwsAttestationService;\n  private EnvironmentProvider mockEnvironmentProvider;\n\n  @BeforeEach\n  public void setUp() {\n    mockAwsAttestationService = mock(AwsAttestationService.class);\n    mockEnvironmentProvider = mock(EnvironmentProvider.class);\n\n    // Default behavior for environment variables (return null unless overridden)\n    when(mockEnvironmentProvider.getEnv(anyString())).thenReturn(null);\n\n    // Default behavior for AWS attestation service (return null/empty unless overridden)\n    when(mockAwsAttestationService.getAWSCredentials()).thenReturn(null);\n\n    resetWiremock();\n  }\n\n  @Test\n  @DisplayName(\"Should detect AWS Lambda when LAMBDA_TASK_ROOT is set\")\n  public void testDetectAwsLambda() {\n    // Arrange\n    when(mockEnvironmentProvider.getEnv(\"LAMBDA_TASK_ROOT\")).thenReturn(\"/var/task\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(0, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"is_aws_lambda\"),\n        \"Should detect AWS Lambda when LAMBDA_TASK_ROOT is set\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect Azure Function when required environment variables are set\")\n  public void testDetectAzureFunction() {\n    // Arrange\n    when(mockEnvironmentProvider.getEnv(\"FUNCTIONS_WORKER_RUNTIME\")).thenReturn(\"java\");\n    when(mockEnvironmentProvider.getEnv(\"FUNCTIONS_EXTENSION_VERSION\")).thenReturn(\"~4\");\n    when(mockEnvironmentProvider.getEnv(\"AzureWebJobsStorage\"))\n        .thenReturn(\"DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(0, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"is_azure_function\"),\n        \"Should detect Azure Function when environment variables are set\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect GCP Cloud Run Service when environment variables are set\")\n  public void testDetectGcpCloudRunService() {\n    // Arrange\n    when(mockEnvironmentProvider.getEnv(\"K_SERVICE\")).thenReturn(\"my-service\");\n    when(mockEnvironmentProvider.getEnv(\"K_REVISION\")).thenReturn(\"my-revision\");\n    when(mockEnvironmentProvider.getEnv(\"K_CONFIGURATION\")).thenReturn(\"my-config\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(0, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"is_gce_cloud_run_service\"),\n        \"Should detect GCP Cloud Run Service when environment variables are set\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect GCP Cloud Run Job when environment variables are set\")\n  public void testDetectGcpCloudRunJob() {\n    // Arrange\n    when(mockEnvironmentProvider.getEnv(\"CLOUD_RUN_JOB\")).thenReturn(\"my-job\");\n    when(mockEnvironmentProvider.getEnv(\"CLOUD_RUN_EXECUTION\")).thenReturn(\"my-execution\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(0, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"is_gce_cloud_run_job\"),\n        \"Should detect GCP Cloud Run Job when environment variables are set\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect GitHub Action when GITHUB_ACTIONS is set\")\n  public void testDetectGithubAction() {\n    // Arrange\n    when(mockEnvironmentProvider.getEnv(\"GITHUB_ACTIONS\")).thenReturn(\"true\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(0, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"is_github_action\"),\n        \"Should detect GitHub Action when GITHUB_ACTIONS is set\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect EC2 instance with IMDSv2 using wiremock\")\n  public void testDetectEc2InstanceIMDSv2() throws IOException {\n    // Arrange\n    String mappingContent = loadMappingFile(\"platform-detection/ec2_successful_imdsv2\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(platforms.contains(\"is_ec2_instance\"), \"Should detect EC2 instance using IMDSv2\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect EC2 instance with IMDSv1 using wiremock\")\n  public void testDetectEc2InstanceIMDSv1() throws IOException {\n    // Arrange\n    String mappingContent = loadMappingFile(\"platform-detection/ec2_successful_imdsv1\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(platforms.contains(\"is_ec2_instance\"), \"Should detect EC2 instance using IMDSv1\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect EC2 via the IPv6 probe when the IPv4 endpoint is unreachable\")\n  public void testDetectEc2InstanceIpv6Probe() throws IOException {\n    // Arrange: IMDSv2 mapping served by wiremock, used as the IPv6 base URL. The IPv4 base URL\n    // points at a loopback port with no listener so its probe errors out while the IPv6 probe\n    // (racing in parallel) resolves wiremock and succeeds — mirroring what happens on an\n    // IPv6-only EC2 instance where the IPv4 link-local address is unreachable.\n    String mappingContent = loadMappingFile(\"platform-detection/ec2_successful_imdsv2\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            \"http://127.0.0.1:1\",\n            getBaseUrl(),\n            getBaseUrl(),\n            getBaseUrl(),\n            mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertTrue(\n        platforms.contains(\"is_ec2_instance\"),\n        \"Should detect EC2 instance via the parallel IPv6 IMDS probe\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect Azure VM using wiremock\")\n  public void testDetectAzureVm() throws IOException {\n    // Arrange\n    String mappingContent = loadMappingFile(\"platform-detection/azure_vm_successful\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(platforms.contains(\"is_azure_vm\"), \"Should detect Azure VM using wiremock\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect Azure managed identity using wiremock\")\n  public void testDetectAzureManagedIdentity() throws IOException {\n    // Arrange\n    String mappingContent = loadMappingFile(\"platform-detection/azure_managed_identity_successful\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"has_azure_managed_identity\"),\n        \"Should detect Azure managed identity using wiremock\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect GCE VM using wiremock\")\n  public void testDetectGceVm() throws IOException {\n    // Arrange\n    String mappingContent = loadMappingFile(\"platform-detection/gcp_vm_successful\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(platforms.contains(\"is_gce_vm\"), \"Should detect GCE VM using wiremock\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect GCP identity using wiremock\")\n  public void testDetectGcpIdentity() throws IOException {\n    // Arrange\n    String mappingContent = loadMappingFile(\"platform-detection/gcp_identity_successful\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(platforms.contains(\"has_gcp_identity\"), \"Should detect GCP identity using wiremock\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect Azure managed identity for Functions with async execution\")\n  public void testAzureFunctionWithAsyncExecution() {\n    // Arrange - Set up environment for Azure Function\n    when(mockEnvironmentProvider.getEnv(\"FUNCTIONS_WORKER_RUNTIME\")).thenReturn(\"java\");\n    when(mockEnvironmentProvider.getEnv(\"FUNCTIONS_EXTENSION_VERSION\")).thenReturn(\"~4\");\n    when(mockEnvironmentProvider.getEnv(\"AzureWebJobsStorage\"))\n        .thenReturn(\"DefaultEndpointsProtocol=https;AccountName=test;AccountKey=test;\");\n    when(mockEnvironmentProvider.getEnv(\"IDENTITY_HEADER\")).thenReturn(\"test-header-value\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act - Use timeout > 0 to trigger async execution\n    List<String> platforms = detector.detectPlatforms(1000, mockAwsAttestationService);\n\n    // Assert - Both Azure Function and managed identity should be detected\n    assertTrue(\n        platforms.size() == 2, \"Should detect at least 2 platforms, but detected: \" + platforms);\n    assertTrue(\n        platforms.contains(\"is_azure_function\"), \"Should detect Azure Function in async mode\");\n    assertTrue(\n        platforms.contains(\"has_azure_managed_identity\"),\n        \"Should detect Azure managed identity in async mode\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect AWS identity when AWS attestation service returns valid identity\")\n  public void testDetectAwsIdentity() {\n    // Arrange - Mock AWS attestation service to return valid credentials and ARN\n    AwsBasicCredentials awsCredentials =\n        // pragma: allowlist nextline secret\n        AwsBasicCredentials.create(\n            \"AKIAIOSFODNN7EXAMPLE\", \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\");\n    when(mockAwsAttestationService.getAWSCredentials()).thenReturn(awsCredentials);\n    when(mockAwsAttestationService.getCallerIdentityArn(awsCredentials, 200))\n        .thenReturn(\"arn:aws:iam::123456789012:user/testuser\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act\n    List<String> platforms = detector.detectPlatforms(200, mockAwsAttestationService);\n\n    // Assert\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(\n        platforms.contains(\"has_aws_identity\"),\n        \"Should detect AWS identity when attestation service returns valid credentials and ARN\");\n  }\n\n  @Test\n  @DisplayName(\"Should detect multiple platforms when conditions are met\")\n  public void testDetectMultiplePlatforms() throws IOException {\n    // Arrange - Set up environment for AWS Lambda\n    when(mockEnvironmentProvider.getEnv(\"LAMBDA_TASK_ROOT\")).thenReturn(\"/var/task\");\n\n    // Set up wiremock for EC2 detection\n    String mappingContent = loadMappingFile(\"platform-detection/ec2_successful_imdsv2\");\n    importMapping(mappingContent);\n    // Ensure other metadata endpoints return immediately to avoid wiremock timeouts\n    String gcpMetadataUnavailable = loadMappingFile(\"platform-detection/gcp_metadata_unavailable\");\n    importMapping(gcpMetadataUnavailable);\n    String azureMetadataUnavailable =\n        loadMappingFile(\"platform-detection/azure_metadata_unavailable\");\n    importMapping(azureMetadataUnavailable);\n\n    AwsBasicCredentials awsCredentials =\n        // pragma: allowlist nextline secret\n        AwsBasicCredentials.create(\n            \"AKIAIOSFODNN7EXAMPLE\", \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\");\n    when(mockAwsAttestationService.getAWSCredentials()).thenReturn(awsCredentials);\n    when(mockAwsAttestationService.getCallerIdentityArn(awsCredentials, 500))\n        .thenReturn(\"arn:aws:sts::123456789012:assumed-role/test-role/session\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act - Use small timeout to enable HTTP calls but ensure static mocks work in async context\n    List<String> platforms = detector.detectPlatforms(500, mockAwsAttestationService);\n\n    // Assert - Should detect multiple platforms\n    assertEquals(3, platforms.size(), \"Should detect exactly 3 platforms\");\n    assertTrue(\n        platforms.contains(\"is_aws_lambda\"), \"Should detect AWS Lambda from environment variables\");\n    assertTrue(platforms.contains(\"is_ec2_instance\"), \"Should detect EC2 instance from wiremock\");\n    assertTrue(\n        platforms.contains(\"has_aws_identity\"),\n        \"Should detect AWS identity from attestation service\");\n  }\n\n  @Test\n  @DisplayName(\"Should handle timeout scenarios gracefully with no network calls\")\n  public void testTimeoutScenariosNoNetwork() {\n    // Arrange - No mappings to simulate timeout\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act - Short timeout to force timeouts\n    List<String> platforms = detector.detectPlatforms(0, mockAwsAttestationService);\n\n    // Assert - Should not contain any network-dependent platforms\n    assertEquals(0, platforms.size(), \"Should detect no platforms with 0ms timeout\");\n  }\n\n  @Test\n  @DisplayName(\"Should timeout reasonably when endpoints respond slowly\")\n  public void testSlowResponseTimeout() throws IOException {\n    // Arrange - Load mapping with 5-second delay\n    String mappingContent = loadMappingFile(\"platform-detection/slow_response_timeout\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act - Use 200ms timeout, but server takes 5 seconds\n    long startTime = System.currentTimeMillis();\n    List<String> platforms = detector.detectPlatforms(200, mockAwsAttestationService);\n    long endTime = System.currentTimeMillis();\n    long totalTime = endTime - startTime;\n\n    // Assert - Should complete in reasonable time\n    // Note: Due to HTTP client retries and parallel execution, actual time may be longer than\n    // timeout\n    // but should still be much less than the 5-second server delay\n    assertTrue(\n        totalTime < 20000,\n        \"Detection should complete much faster than server delay * number of http calls (5s), but took \"\n            + totalTime\n            + \"ms\");\n\n    // Should not detect any network-dependent platforms due to timeout, but may detect timeout\n    // platforms\n    assertEquals(5, platforms.size(), \"Should detect only 5 timeouts: \" + platforms);\n    assertTrue(platforms.contains(\"is_ec2_instance_timeout\"));\n    assertTrue(platforms.contains(\"is_gce_vm_timeout\"));\n    assertTrue(platforms.contains(\"has_gcp_identity_timeout\"));\n    assertTrue(platforms.contains(\"is_azure_vm_timeout\"));\n    assertTrue(platforms.contains(\"is_ec2_instance_timeout\"));\n  }\n\n  @Test\n  @DisplayName(\"Should detect GCP VM when response is slower than timeout but still reasonable\")\n  public void testGcpVmSlowButWithinTimeout() throws IOException {\n    // Arrange - Load mapping with 1-second delay but use 2-second timeout\n    String mappingContent = loadMappingFile(\"platform-detection/gcp_vm_slow_response\");\n    importMapping(mappingContent);\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Act - Use 2000ms timeout, server takes 1 second\n    List<String> platforms = detector.detectPlatforms(2000, mockAwsAttestationService);\n\n    // Should detect GCP VM despite the delay\n    assertEquals(1, platforms.size(), \"Should detect exactly 1 platform\");\n    assertTrue(platforms.contains(\"is_gce_vm\"), \"Should detect GCE VM despite 1-second delay\");\n  }\n\n  @Test\n  @DisplayName(\"Should demonstrate env-only vs env + unreachable http endpoints time difference\")\n  public void testEnvVsEnvAndUnreachableEndpointsPerformance() {\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    // Test 1: Fast timeout with no network (should be very fast)\n    long startTime1 = System.currentTimeMillis();\n    List<String> platforms1 = detector.detectPlatforms(0, mockAwsAttestationService);\n    long timeNoNetwork = System.currentTimeMillis() - startTime1;\n\n    // Test 2: Short timeout with unreachable endpoints\n    long startTime2 = System.currentTimeMillis();\n    List<String> platforms2 = detector.detectPlatforms(1000, mockAwsAttestationService);\n    long timeUnreachableEndpoints = System.currentTimeMillis() - startTime2;\n\n    // Assert performance characteristics\n    assertTrue(\n        timeNoNetwork < 100,\n        \"No network detection should be very fast, took \" + timeNoNetwork + \"ms\");\n\n    // With short timeout and unreachable endpoints, should still be reasonably fast\n    // even with retries, much faster than if we waited for actual 5-second delays\n    assertTrue(\n        timeUnreachableEndpoints < 5000,\n        \"Unreachable endpoints should prevent long delays, took \"\n            + timeUnreachableEndpoints\n            + \"ms\");\n\n    // Both should return empty network-based platforms\n    assertEquals(0, platforms1.size(), \"No network test should detect no platforms\");\n    assertEquals(0, platforms2.size(), \"Unreachable endpoints test should detect no platforms\");\n  }\n\n  /** Helper method to load wiremock mapping files */\n  private String loadMappingFile(String mappingName) throws IOException {\n    String mappingFile = \"src/test/resources/wiremock/mappings/\" + mappingName + \".json\";\n    return new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(mappingFile)));\n  }\n\n  @Test\n  @DisplayName(\"Should cache platform detection results\")\n  public void testCachedPlatformDetections() {\n    when(mockEnvironmentProvider.getEnv(\"LAMBDA_TASK_ROOT\")).thenReturn(\"/var/task\");\n\n    PlatformDetector detector =\n        new PlatformDetector(\n            getBaseUrl(), getBaseUrl(), getBaseUrl(), getBaseUrl(), mockEnvironmentProvider);\n\n    long startTime1 = System.currentTimeMillis();\n    List<String> platforms1 =\n        PlatformDetector.detectPlatformsAndCache(detector, mockAwsAttestationService);\n    long duration1 = System.currentTimeMillis() - startTime1;\n\n    long startTime2 = System.currentTimeMillis();\n    List<String> platforms2 = PlatformDetector.getCachedPlatformDetection();\n    long duration2 = System.currentTimeMillis() - startTime2;\n\n    long startTime3 = System.currentTimeMillis();\n    List<String> platforms3 = PlatformDetector.getCachedPlatformDetection();\n    long duration3 = System.currentTimeMillis() - startTime3;\n\n    assertSame(platforms1, platforms2, \"Second call should return cached instance\");\n    assertSame(platforms2, platforms3, \"Third call should return cached instance\");\n\n    assertTrue(\n        duration2 < duration1 || duration2 < 10,\n        \"Cached call should be faster than initial detection. First: \"\n            + duration1\n            + \"ms, Second: \"\n            + duration2\n            + \"ms\");\n    assertTrue(\n        duration3 < duration1 || duration3 < 10,\n        \"Cached call should be faster than initial detection. First: \"\n            + duration1\n            + \"ms, Third: \"\n            + duration3\n            + \"ms\");\n  }\n\n  /** Get the base URL for wiremock */\n  private String getBaseUrl() {\n    return String.format(\"http://%s:%d\", WIREMOCK_HOST, wiremockHttpPort);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/SFPairTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport org.junit.jupiter.api.Test;\n\nclass SFPairTest {\n\n  @Test\n  void testOfMethod() {\n    SFPair<String, Integer> pair = SFPair.of(\"Test\", 123);\n    assertNotNull(pair);\n    assertEquals(\"Test\", pair.left);\n    assertEquals(123, pair.right);\n  }\n\n  @Test\n  void testEqualsMethod() {\n    SFPair<String, Integer> pair1 = SFPair.of(\"Test\", 123);\n    SFPair<String, Integer> pair2 = SFPair.of(\"Test\", 123);\n    SFPair<String, Integer> pair3 = SFPair.of(\"Different\", 123);\n    SFPair<String, Integer> pair4 = SFPair.of(\"Test\", 999);\n    SFPair<String, Integer> pair5 = SFPair.of(\"Different\", 999);\n\n    assertEquals(pair1, pair2);\n    assertNotEquals(pair1, pair3);\n    assertNotEquals(pair1, pair4);\n    assertNotEquals(pair1, pair5);\n    assertNotEquals(pair1, null);\n    assertNotEquals(pair1, \"Not an SFPair\");\n    assertEquals(pair1, pair1);\n  }\n\n  @Test\n  void testHashCodeMethod() {\n    SFPair<String, Integer> pair1 = SFPair.of(\"Test\", 123);\n    SFPair<String, Integer> pair2 = SFPair.of(\"Test\", 123);\n    SFPair<String, Integer> pair3 = SFPair.of(\"Test\", 999);\n\n    assertEquals(pair1.hashCode(), pair2.hashCode());\n    assertNotEquals(pair1.hashCode(), pair3.hashCode());\n  }\n\n  @Test\n  void testToStringMethod() {\n    SFPair<String, Integer> pair = SFPair.of(\"Test\", 123);\n    assertEquals(\"[ Test, 123 ]\", pair.toString());\n\n    SFPair<String, String> nullPair = SFPair.of(null, null);\n    assertEquals(\"[ null, null ]\", nullPair.toString());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/SecretDetectorTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport java.util.HashMap;\nimport java.util.Map;\nimport net.minidev.json.JSONArray;\nimport net.minidev.json.JSONObject;\nimport net.snowflake.client.internal.core.ObjectMapperFactory;\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.jupiter.api.Test;\n\npublic class SecretDetectorTest {\n  @Test\n  public void testMaskAWSSecret() {\n    String sql =\n        \"copy into 's3://xxxx/test' from \\n\"\n            + \"(select seq1(), random()\\n\"\n            + \", random(), random(), random(), random()\\n\"\n            + \", random(), random(), random(), random()\\n\"\n            + \", random() , random(), random(), random()\\n\"\n            + \"\\tfrom table(generator(rowcount => 10000)))\\n\"\n            + \"credentials=(\\n\"\n            + \"  aws_key_id='xxdsdfsafds'\\n\"\n            + \"  aws_secret_key='safas+asfsad+safasf'\\n\"\n            + \"  )\\n\"\n            + \"OVERWRITE = TRUE \\n\"\n            + \"MAX_FILE_SIZE = 500000000 \\n\"\n            + \"HEADER = TRUE \\n\"\n            + \"FILE_FORMAT = (TYPE = PARQUET SNAPPY_COMPRESSION = TRUE )\\n\"\n            + \";\";\n    String correct =\n        \"copy into 's3://xxxx/test' from \\n\"\n            + \"(select seq1(), random()\\n\"\n            + \", random(), random(), random(), random()\\n\"\n            + \", random(), random(), random(), random()\\n\"\n            + \", random() , random(), random(), random()\\n\"\n            + \"\\tfrom table(generator(rowcount => 10000)))\\n\"\n            + \"credentials=(\\n\"\n            + \"  aws_key_id='****'\\n\"\n            + \"  aws_secret_key='****'\\n\"\n            + \"  )\\n\"\n            + \"OVERWRITE = TRUE \\n\"\n            + \"MAX_FILE_SIZE = 500000000 \\n\"\n            + \"HEADER = TRUE \\n\"\n            + \"FILE_FORMAT = (TYPE = PARQUET SNAPPY_COMPRESSION = TRUE )\\n\"\n            + \";\";\n    String masked = SecretDetector.maskSecrets(sql);\n    assertThat(\"secret masked\", correct.compareTo(masked) == 0);\n  }\n\n  @Test\n  public void testMaskSASToken() {\n    // Initializing constants\n    final String azureSasToken =\n        \"https://someaccounts.blob.core.windows.net/results/018b90ab-0033-\"\n            + \"5f8e-0000-14f1000bd376_0/main/data_0_0_1?sv=2015-07-08&amp;\"\n            + \"sig=iCvQmdZngZNW%2F4vw43j6%2BVz6fndHF5LI639QJba4r8o%3D&amp;\"\n            + \"spr=https&amp;st=2016-04-12T03%3A24%3A31Z&amp;\"\n            + \"se=2016-04-13T03%3A29%3A31Z&amp;srt=s&amp;ss=bf&amp;sp=rwl\";\n\n    final String maskedAzureSasToken =\n        \"https://someaccounts.blob.core.windows.net/results/018b90ab-0033-\"\n            + \"5f8e-0000-14f1000bd376_0/main/data_0_0_1?sv=2015-07-08&amp;\"\n            + \"sig=****&amp;\"\n            + \"spr=https&amp;st=2016-04-12T03%3A24%3A31Z&amp;\"\n            + \"se=2016-04-13T03%3A29%3A31Z&amp;srt=s&amp;ss=bf&amp;sp=rwl\";\n\n    final String s3SasToken =\n        \"https://somebucket.s3.amazonaws.com/vzy1-s-va_demo0/results/018b92f3\"\n            + \"-01c2-02dd-0000-03d5000c8066_0/main/data_0_0_1?\"\n            + \"x-amz-server-side-encryption-customer-algorithm=AES256&\"\n            + \"response-content-encoding=gzip&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE\"\n            + \"&Expires=1555481960&Signature=zFiRkdB9RtRRYomppVes4fQ%2ByWw%3D\";\n\n    final String maskedS3SasToken =\n        \"https://somebucket.s3.amazonaws.com/vzy1-s-va_demo0/results/018b92f3\"\n            + \"-01c2-02dd-0000-03d5000c8066_0/main/data_0_0_1?\"\n            + \"x-amz-server-side-encryption-customer-algorithm=AES256&\"\n            + \"response-content-encoding=gzip&AWSAccessKeyId=****\"\n            + \"&Expires=1555481960&Signature=****\";\n\n    assertThat(\n        \"Azure SAS token is not masked\",\n        maskedAzureSasToken.equals(SecretDetector.maskSecrets(azureSasToken)));\n\n    assertThat(\n        \"S3 SAS token is not masked\",\n        maskedS3SasToken.equals(SecretDetector.maskSecrets(s3SasToken)));\n\n    String randomString = RandomStringUtils.random(200);\n    assertThat(\n        \"Text without secrets is not unmodified\",\n        randomString.equals(SecretDetector.maskSecrets(randomString)));\n\n    assertThat(\n        \"Text with 2 Azure SAS tokens is not masked\",\n        (maskedAzureSasToken + maskedAzureSasToken)\n            .equals(SecretDetector.maskSecrets(azureSasToken + azureSasToken)));\n\n    assertThat(\n        \"Text with 2 S3 SAS tokens is not masked\",\n        (maskedAzureSasToken + maskedAzureSasToken)\n            .equals(SecretDetector.maskSecrets(azureSasToken + azureSasToken)));\n\n    assertThat(\n        \"Text with Azure and S3 SAS tokens is not masked\",\n        (maskedAzureSasToken + maskedS3SasToken)\n            .equals(SecretDetector.maskSecrets(azureSasToken + s3SasToken)));\n  }\n\n  @Test\n  public void testMaskSecrets() {\n    // Text containing AWS secret and Azure SAS token\n    final String sqlText =\n        \"create stage mystage \"\n            + \"URL = 's3://mybucket/mypath/' \"\n            + \"credentials = (aws_key_id = 'AKIAIOSFODNN7EXAMPLE' \"\n            + \"aws_secret_key = 'frJIUN8DYpKDtOLCwo//yllqDzg='); \"\n            + \"create stage mystage2 \"\n            + \"URL = 'azure//mystorage.blob.core.windows.net/cont' \"\n            + \"credentials = (azure_sas_token = \"\n            + \"'?sv=2016-05-31&ss=b&srt=sco&sp=rwdl&se=2018-06-27T10:05:50Z&\"\n            + \"st=2017-06-27T02:05:50Z&spr=https,http&\"\n            + \"sig=bgqQwoXwxzuD2GJfagRg7VOS8hzNr3QLT7rhS8OFRLQ%3D')\";\n\n    final String maskedSqlText =\n        \"create stage mystage \"\n            + \"URL = 's3://mybucket/mypath/' \"\n            + \"credentials = (aws_key_id = '****' \"\n            + \"aws_secret_key = '****'); \"\n            + \"create stage mystage2 \"\n            + \"URL = 'azure//mystorage.blob.core.windows.net/cont' \"\n            + \"credentials = (azure_sas_token = \"\n            + \"'?sv=2016-05-31&ss=b&srt=sco&sp=rwdl&se=2018-06-27T10:05:50Z&\"\n            + \"st=2017-06-27T02:05:50Z&spr=https,http&\"\n            + \"sig=****')\";\n\n    String masked = SecretDetector.maskSecrets(sqlText);\n    System.out.println(masked);\n    System.out.println(maskedSqlText);\n    assertThat(\n        \"Text with AWS secret and Azure SAS token is not masked\", maskedSqlText.equals(masked));\n\n    String randomString = RandomStringUtils.random(500);\n    assertThat(\n        \"Text without secrets is not unmodified\",\n        randomString.equals(SecretDetector.maskSecrets(randomString)));\n  }\n\n  @Test\n  public void testMaskPasswordFromConnectionString() {\n    // Since we have `&` in password regex pattern, we will have false positive masking here\n    String connectionStr =\n        \"\\\"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&password=xxxxxx&role=xxx\\\"\";\n    String maskedConnectionStr =\n        \"\\\"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&password=**** \";\n    assertThat(\n        \"Text with password is not masked\",\n        maskedConnectionStr.equals(SecretDetector.maskSecrets(connectionStr)));\n\n    connectionStr = \"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&password=xxxxxx\";\n    maskedConnectionStr =\n        \"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&password=**** \";\n    assertThat(\n        \"Text with password is not masked\",\n        maskedConnectionStr.equals(SecretDetector.maskSecrets(connectionStr)));\n\n    connectionStr = \"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&passcode=xxxxxx\";\n    maskedConnectionStr =\n        \"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&passcode=**** \";\n    assertThat(\n        \"Text with password is not masked\",\n        maskedConnectionStr.equals(SecretDetector.maskSecrets(connectionStr)));\n\n    connectionStr = \"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&passWord=xxxxxx\";\n    maskedConnectionStr =\n        \"jdbc:snowflake://xxx.snowflakecomputing\" + \".com/?user=xxx&passWord=**** \";\n    assertThat(\n        \"Text with password is not masked\",\n        maskedConnectionStr.equals(SecretDetector.maskSecrets(connectionStr)));\n  }\n\n  @Test\n  public void sasTokenFilterTest() throws Exception {\n    String messageText = \"\\\"privateKeyData\\\": \\\"aslkjdflasjf\\\"\";\n\n    String filteredMessageText = \"\\\"privateKeyData\\\": \\\"XXXX\\\"\";\n\n    String result = SecretDetector.maskSecrets(messageText);\n\n    assertEquals(filteredMessageText, result);\n  }\n\n  @Test\n  public void testMaskParameterValue() {\n    Map<String, String> testParametersMasked = new HashMap<>();\n    testParametersMasked.put(\"passcodeInPassword\", \"test\");\n    testParametersMasked.put(\"passcode\", \"test\");\n    testParametersMasked.put(\"id_token\", \"test\");\n    testParametersMasked.put(\"private_key_pwd\", \"test\");\n    testParametersMasked.put(\"proxyPassword\", \"test\");\n    testParametersMasked.put(\"proxyUser\", \"test\");\n    testParametersMasked.put(\"privatekey\", \"test\");\n    testParametersMasked.put(\"private_key_base64\", \"test\");\n    testParametersMasked.put(\"privateKeyBase64\", \"test\");\n    testParametersMasked.put(\"id_token_password\", \"test\");\n    testParametersMasked.put(\"masterToken\", \"test\");\n    testParametersMasked.put(\"mfaToken\", \"test\");\n    testParametersMasked.put(\"password\", \"test\");\n    testParametersMasked.put(\"sessionToken\", \"test\");\n    testParametersMasked.put(\"token\", \"test\");\n    testParametersMasked.put(\"oauthClientId\", \"test\");\n    testParametersMasked.put(\"oauthClientSecret\", \"test\");\n\n    Map<String, String> testParametersUnmasked = new HashMap<>();\n    testParametersUnmasked.put(\"oktausername\", \"test\");\n    testParametersUnmasked.put(\"authenticator\", \"test\");\n    testParametersUnmasked.put(\"proxyHost\", \"test\");\n    testParametersUnmasked.put(\"user\", \"test\");\n    testParametersUnmasked.put(\"private_key_file\", \"test\");\n\n    for (Map.Entry<String, String> entry : testParametersMasked.entrySet()) {\n      assertEquals(\"****\", SecretDetector.maskParameterValue(entry.getKey(), entry.getValue()));\n    }\n    for (Map.Entry<String, String> entry : testParametersUnmasked.entrySet()) {\n      assertEquals(\"test\", SecretDetector.maskParameterValue(entry.getKey(), entry.getValue()));\n    }\n  }\n\n  @Test\n  public void testMaskconnectionToken() {\n    String connectionToken = \"\\\"Authorization: Snowflake Token=\\\"XXXXXXXXXX\\\"\\\"\";\n\n    String maskedConnectionToken = \"\\\"Authorization: Snowflake Token=\\\"****\\\"\\\"\";\n\n    assertThat(\n        \"Text with connection token is not masked\",\n        maskedConnectionToken.equals(SecretDetector.maskSecrets(connectionToken)));\n\n    connectionToken = \"\\\"{\\\"requestType\\\":\\\"ISSUE\\\",\\\"idToken\\\":\\\"XXXXXXXX\\\"}\\\"\";\n\n    maskedConnectionToken = \"\\\"{\\\"requestType\\\":\\\"ISSUE\\\",\\\"idToken\\\":\\\"****\\\"}\\\"\";\n\n    assertThat(\n        \"Text with connection token is not masked\",\n        maskedConnectionToken.equals(SecretDetector.maskSecrets(connectionToken)));\n  }\n\n  @Test\n  public void testMaskOAuthSecrets() {\n    String tokenResponseJson =\n        \"{\\n\"\n            + \"  \\\"access_token\\\" : \\\"some:FAKE_token123\\\",\\n\"\n            + \"  \\\"refresh_token\\\" : \\\"some:FAKE_token123\\\",\\n\"\n            + \"  \\\"token_type\\\" : \\\"Bearer\\\",\\n\"\n            + \"  \\\"username\\\" : \\\"OAUTH_TEST_AUTH_CODE\\\",\\n\"\n            + \"  \\\"scope\\\" : \\\"refresh_token session:role:ANALYST\\\",\\n\"\n            + \"  \\\"expires_in\\\" : 600,\\n\"\n            + \"  \\\"refresh_token_expires_in\\\" : 86399,\\n\"\n            + \"  \\\"idpInitiated\\\" : false\\n\"\n            + \"}\";\n\n    String maskedTokenResponseJson =\n        \"{\\n\"\n            + \"  \\\"access_token\\\":\\\"****\\\",\\n\"\n            + \"  \\\"refresh_token\\\":\\\"****\\\",\\n\"\n            + \"  \\\"token_type\\\" : \\\"Bearer\\\",\\n\"\n            + \"  \\\"username\\\" : \\\"OAUTH_TEST_AUTH_CODE\\\",\\n\"\n            + \"  \\\"scope\\\" : \\\"refresh_token session:role:ANALYST\\\",\\n\"\n            + \"  \\\"expires_in\\\" : 600,\\n\"\n            + \"  \\\"refresh_token_expires_in\\\" : 86399,\\n\"\n            + \"  \\\"idpInitiated\\\" : false\\n\"\n            + \"}\";\n\n    assertThat(\n        \"Text with OAuth tokens is not masked\",\n        maskedTokenResponseJson.equals(SecretDetector.maskSecrets(tokenResponseJson)));\n  }\n\n  private JSONObject generateJsonObject() {\n    // a base json object\n    JSONObject obj = new JSONObject();\n    obj.put(\"hello\", \"world\");\n    obj.put(\"number\", 256);\n    obj.put(\"boolean\", true);\n    return obj;\n  }\n\n  @Test\n  public void testMaskJsonObject() {\n    final String connStr =\n        \"https://snowflake.fakehostname.local:fakeport?LOGINTIMEOUT=20&ACCOUNT=fakeaccount&PASSWORD=fakepassword&USER=fakeuser\";\n    final String maskedConnStr =\n        \"https://snowflake.fakehostname.local:fakeport?LOGINTIMEOUT=20&ACCOUNT=fakeaccount&PASSWORD=**** \";\n\n    JSONObject obj = generateJsonObject();\n    obj.put(\"connStr\", connStr);\n    JSONObject maskedObj = generateJsonObject();\n    maskedObj.put(\"connStr\", maskedConnStr);\n\n    assertThat(\n        \"JSONObject is not masked successfully\",\n        maskedObj.equals(SecretDetector.maskJsonObject(obj)));\n\n    obj.put(\"connStr\", connStr);\n    JSONObject nestedObj = generateJsonObject();\n    nestedObj.put(\"subJson\", obj);\n    JSONObject maskedNestedObj = generateJsonObject();\n    maskedNestedObj.put(\"subJson\", maskedObj);\n\n    assertThat(\n        \"nested JSONObject is not masked successfully\",\n        maskedNestedObj.equals(SecretDetector.maskJsonObject(nestedObj)));\n  }\n\n  @Test\n  public void testMaskJsonArray() {\n    final String connStr =\n        \"https://snowflake.fakehostname.local:fakeport?LOGINTIMEOUT=20&ACCOUNT=fakeaccount&PASSWORD=fakepassword&USER=fakeuser\";\n    final String maskedConnStr =\n        \"https://snowflake.fakehostname.local:fakeport?LOGINTIMEOUT=20&ACCOUNT=fakeaccount&PASSWORD=**** \";\n    final String pwdStr = \"password=ThisShouldBeMasked\";\n    final String maskedPwdStr = \"password=****\";\n\n    JSONObject obj = generateJsonObject();\n    obj.put(\"connStr\", connStr);\n\n    JSONObject maskedObj = generateJsonObject();\n    maskedObj.put(\"connStr\", maskedConnStr);\n\n    JSONArray array = new JSONArray();\n    array.add(obj);\n    array.add(pwdStr);\n    JSONArray maskedArray = new JSONArray();\n    maskedArray.add(maskedObj);\n    maskedArray.add(maskedPwdStr);\n\n    assertThat(\n        \"jsonArray is not masked successfully\",\n        maskedArray.equals(SecretDetector.maskJsonArray(array)));\n\n    JSONObject obj1 = generateJsonObject();\n    obj1.put(\"connStr\", connStr);\n\n    JSONObject maskedObj1 = generateJsonObject();\n    maskedObj1.put(\"connStr\", maskedConnStr);\n\n    JSONArray array1 = new JSONArray();\n    array1.add(obj1);\n    array1.add(pwdStr);\n    JSONArray maskedArray1 = new JSONArray();\n    maskedArray1.add(maskedObj1);\n    maskedArray1.add(maskedPwdStr);\n\n    JSONObject nestedObjArray = generateJsonObject();\n    nestedObjArray.put(\"array\", array1);\n    JSONObject maskedNestedObjArray = generateJsonObject();\n    maskedNestedObjArray.put(\"array\", maskedArray1);\n\n    assertThat(\n        \"JSONArray within JSONObject is not masked successfully\",\n        maskedNestedObjArray.equals(SecretDetector.maskJsonObject(nestedObjArray)));\n  }\n\n  @Test\n  public void testMaskJacksonObject() {\n    final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper();\n    ObjectNode objNode = mapper.createObjectNode();\n    objNode.put(\"testInfo\", \"pwd=HelloWorld!\");\n\n    String maskedObjNodeStr = \"{\\\"testInfo\\\":\\\"pwd=**** \\\"}\";\n    assertThat(\n        \"Jackson ObjectNode is not masked successfully\",\n        maskedObjNodeStr.equals(SecretDetector.maskJacksonNode(objNode).toString()));\n\n    // test nested object node\n    ObjectNode objNode1 = mapper.createObjectNode();\n    objNode1.put(\"testInfo\", \"pwd=HelloWorld!\");\n    ObjectNode objNodeNested = mapper.createObjectNode();\n    objNodeNested.put(\"dummy\", \"dummy\");\n    objNodeNested.put(\"testInfo2\", \"sig=ajwAWD45%+ajrH92knKfsfakj\");\n    objNode1.set(\"testNested\", objNodeNested);\n\n    String maskedObjNestedStr =\n        \"{\\\"testInfo\\\":\\\"pwd=**** \\\",\\\"testNested\\\":{\\\"dummy\\\":\\\"dummy\\\",\\\"testInfo2\\\":\\\"sig=****\\\"}}\";\n    assertThat(\n        \"Nested Jackson ObjectNode is not masked successfully\",\n        maskedObjNestedStr.equals(SecretDetector.maskJacksonNode(objNode1).toString()));\n\n    // test array node\n    ObjectNode objNode2 = mapper.createObjectNode();\n    objNode2.put(\"testInfo\", \"aws_secret_key= 'fakeAWSsecretKey!123'\");\n    ArrayNode arrayObj = mapper.createArrayNode();\n    arrayObj.add(objNode2);\n    arrayObj.add(\"fake token: SDFADAVN4ASD28ASFG3234x\");\n\n    String maskedArrayNodeStr = \"[{\\\"testInfo\\\":\\\"aws_secret_key= '****'\\\"},\\\"fake token: ****\\\"]\";\n    assertThat(\n        \"Jackson ArrayNode is not masked successfully\",\n        maskedArrayNodeStr.equals(SecretDetector.maskJacksonNode(arrayObj).toString()));\n\n    // test nested array node\n    ObjectNode objNode3 = mapper.createObjectNode();\n    objNode3.put(\"testInfo\", \"\\\"privateKeyData\\\": \\\"abcdefg012345/=+\\\"\");\n    ArrayNode arrayObj1 = mapper.createArrayNode();\n    arrayObj1.add(objNode3);\n    ObjectNode objNode4 = mapper.createObjectNode();\n    objNode4.set(\"testArrayNode\", arrayObj1);\n    objNode4.put(\"hello\", \"world\");\n    objNode4.put(\"password\", \"password = HelloWorld!\");\n\n    String maskedNestedArrayStr =\n        \"{\\\"testArrayNode\\\":[{\\\"testInfo\\\":\\\"\\\\\\\"privateKeyData\\\\\\\": \\\\\\\"XXXX\\\\\\\"\\\"}],\\\"hello\\\":\\\"world\\\",\\\"password\\\":\\\"password = **** \\\"}\";\n    SecretDetector.maskJacksonNode(objNode4);\n    assertThat(\n        \"Nested Jackson array node is not masked successfully\",\n        maskedNestedArrayStr.equals(SecretDetector.maskJacksonNode(objNode4).toString()));\n  }\n\n  @Test\n  public void testEncryptionMaterialFilter() throws Exception {\n    String messageText =\n        \"{\\\"data\\\":\"\n            + \"{\\\"autoCompress\\\":true,\"\n            + \"\\\"overwrite\\\":false,\"\n            + \"\\\"clientShowEncryptionParameter\\\":true,\"\n            + \"\\\"encryptionMaterial\\\":{\\\"queryStageMasterKey\\\":\\\"asdfasdfasdfasdf==\\\",\\\"queryId\\\":\\\"01b6f5ba-0002-0181-0000-11111111da\\\",\\\"smkId\\\":1111},\"\n            + \"\\\"stageInfo\\\":{\\\"locationType\\\":\\\"AZURE\\\", \\\"region\\\":\\\"eastus2\\\"}\";\n\n    String filteredMessageText =\n        \"{\\\"data\\\":\"\n            + \"{\\\"autoCompress\\\":true,\"\n            + \"\\\"overwrite\\\":false,\"\n            + \"\\\"clientShowEncryptionParameter\\\":true,\"\n            + \"\\\"encryptionMaterial\\\" : ****,\"\n            + \"\\\"stageInfo\\\":{\\\"locationType\\\":\\\"AZURE\\\", \\\"region\\\":\\\"eastus2\\\"}\";\n\n    String result = SecretDetector.filterEncryptionMaterial(messageText);\n\n    assertEquals(filteredMessageText, result);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/internal/util/StopwatchTest.java",
    "content": "package net.snowflake.client.internal.util;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.greaterThanOrEqualTo;\nimport static org.hamcrest.Matchers.lessThanOrEqualTo;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class StopwatchTest {\n  Stopwatch stopwatch = new Stopwatch();\n\n  @BeforeEach\n  public void before() {\n    stopwatch = new Stopwatch();\n  }\n\n  @Test\n  public void testGetMillisWhenStopped() throws InterruptedException {\n    stopwatch.start();\n    TimeUnit.MILLISECONDS.sleep(20);\n    stopwatch.stop();\n\n    assertThat(\n        stopwatch.elapsedMillis(), allOf(greaterThanOrEqualTo(10L), lessThanOrEqualTo(500L)));\n  }\n\n  @Test\n  public void testGetMillisWithoutStopping() throws InterruptedException {\n    stopwatch.start();\n    TimeUnit.MILLISECONDS.sleep(100);\n    assertThat(\n        stopwatch.elapsedMillis(), allOf(greaterThanOrEqualTo(10L), lessThanOrEqualTo(500L)));\n  }\n\n  @Test\n  public void testShouldBeStarted() {\n    stopwatch.start();\n    assertTrue(stopwatch.isStarted());\n  }\n\n  @Test\n  public void testShouldBeStopped() {\n    assertFalse(stopwatch.isStarted());\n  }\n\n  @Test\n  public void testThrowsExceptionWhenStartedTwice() {\n    stopwatch.start();\n\n    Exception e = assertThrows(IllegalStateException.class, () -> stopwatch.start());\n\n    assertTrue(e.getMessage().contains(\"Stopwatch is already running\"));\n  }\n\n  @Test\n  public void testThrowsExceptionWhenStoppedTwice() {\n    stopwatch.start();\n    stopwatch.stop();\n\n    Exception e = assertThrows(IllegalStateException.class, () -> stopwatch.stop());\n\n    assertTrue(e.getMessage().contains(\"Stopwatch is already stopped\"));\n  }\n\n  @Test\n  public void testThrowsExceptionWhenStoppedWithoutStarting() {\n    Exception e = assertThrows(IllegalStateException.class, () -> stopwatch.stop());\n\n    assertTrue(e.getMessage().contains(\"Stopwatch has not been started\"));\n  }\n\n  @Test\n  public void testThrowsExceptionWhenElapsedMillisWithoutStarting() {\n    Exception e = assertThrows(IllegalStateException.class, () -> stopwatch.elapsedMillis());\n\n    assertTrue(e.getMessage().contains(\"Stopwatch has not been started\"));\n  }\n\n  @Test\n  public void testShouldReset() {\n    stopwatch.start();\n    assertTrue(stopwatch.isStarted());\n    stopwatch.reset();\n    assertFalse(stopwatch.isStarted());\n  }\n\n  @Test\n  public void testShouldRestart() {\n    stopwatch.start();\n    assertTrue(stopwatch.isStarted());\n    stopwatch.stop();\n    assertFalse(stopwatch.isStarted());\n    stopwatch.restart();\n    assertTrue(stopwatch.isStarted());\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/BooleanProvider.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\npublic class BooleanProvider extends SnowflakeArgumentsProvider {\n  @Override\n  protected List<Arguments> rawArguments(ExtensionContext context) {\n    return Arrays.asList(Arguments.of(true), Arguments.of(false));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/ProvidersUtil.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\npublic class ProvidersUtil {\n  private ProvidersUtil() {}\n\n  private static List<Arguments> cartesianProduct(\n      ExtensionContext context, List<Arguments> a, SnowflakeArgumentsProvider b) {\n    List<Arguments> argsB = b.rawArguments(context);\n    List<Arguments> result = new ArrayList<>();\n    for (Arguments args : a) {\n      for (Arguments args2 : argsB) {\n        result.add(Arguments.of(ArrayUtils.addAll(args.get(), args2.get())));\n      }\n    }\n    return result;\n  }\n\n  public static List<Arguments> cartesianProduct(\n      ExtensionContext context,\n      SnowflakeArgumentsProvider provider,\n      SnowflakeArgumentsProvider... providers) {\n    List<Arguments> args = provider.rawArguments(context);\n    for (SnowflakeArgumentsProvider argProvider : providers) {\n      args = cartesianProduct(context, args, argProvider);\n    }\n    return args;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/ResultFormatProvider.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport net.snowflake.client.internal.jdbc.ResultSetFormatType;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\npublic class ResultFormatProvider extends SnowflakeArgumentsProvider {\n  @Override\n  protected List<Arguments> rawArguments(ExtensionContext context) {\n    return Arrays.asList(\n        Arguments.of(ResultSetFormatType.JSON),\n        Arguments.of(ResultSetFormatType.ARROW_WITH_JSON_STRUCTURED_TYPES),\n        Arguments.of(ResultSetFormatType.NATIVE_ARROW));\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/ScaleProvider.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\npublic class ScaleProvider extends SnowflakeArgumentsProvider {\n  @Override\n  protected List<Arguments> rawArguments(ExtensionContext context) {\n    ArrayList<Arguments> scales = new ArrayList<>();\n    for (int scale = 0; scale < 10; scale++) {\n      scales.add(Arguments.of(scale));\n    }\n    return scales;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/SimpleResultFormatProvider.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\npublic class SimpleResultFormatProvider extends SnowflakeArgumentsProvider {\n  private static List<Arguments> arguments =\n      Arrays.asList(Arguments.of(\"JSON\"), Arguments.of(\"ARROW\"));\n\n  public static void setSupportedFormats(List<Arguments> supportedFormats) {\n    arguments = supportedFormats;\n  }\n\n  public static void resetSupportedFormats() {\n    setSupportedFormats(Arrays.asList(Arguments.of(\"JSON\"), Arguments.of(\"ARROW\")));\n  }\n\n  @Override\n  protected List<Arguments> rawArguments(ExtensionContext context) {\n    return arguments;\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/SnowflakeArgumentsProvider.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.List;\nimport java.util.stream.Stream;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.ArgumentsProvider;\n\npublic abstract class SnowflakeArgumentsProvider implements ArgumentsProvider {\n  protected abstract List<Arguments> rawArguments(ExtensionContext context);\n\n  @Override\n  public Stream<? extends Arguments> provideArguments(ExtensionContext context) {\n    return rawArguments(context).stream();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/providers/TimezoneProvider.java",
    "content": "package net.snowflake.client.providers;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.provider.Arguments;\n\npublic class TimezoneProvider extends SnowflakeArgumentsProvider {\n  private int length;\n\n  private static List<Arguments> timeZones =\n      Arrays.asList(\n          Arguments.of(\"UTC\"),\n          Arguments.of(\"America/Los_Angeles\"),\n          Arguments.of(\"America/New_York\"),\n          Arguments.of(\"Pacific/Honolulu\"),\n          Arguments.of(\"Asia/Singapore\"),\n          Arguments.of(\"CET\"),\n          Arguments.of(\"GMT+0200\"));\n\n  public TimezoneProvider(int length) {\n    this.length = length;\n  }\n\n  public TimezoneProvider() {\n    this.length = timeZones.size();\n  }\n\n  @Override\n  protected List<Arguments> rawArguments(ExtensionContext context) {\n    return timeZones.subList(0, length);\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/ArrowTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.ARROW)\npublic class ArrowTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/AuthenticationTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.AUTHENTICATION)\npublic class AuthenticationTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/BaseTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport org.junit.platform.suite.api.ExcludePackages;\nimport org.junit.platform.suite.api.IncludeClassNamePatterns;\nimport org.junit.platform.suite.api.SelectPackages;\nimport org.junit.platform.suite.api.Suite;\nimport org.junit.platform.suite.api.SuiteDisplayName;\n\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@Suite\n@SuiteDisplayName(\"Testowanie\")\n@SelectPackages(\"net.snowflake.client\")\n@ExcludePackages(\"net.snowflake.client.suites\")\n@IncludeClassNamePatterns(\".+\")\npublic @interface BaseTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/ConnectionOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.CONNECTION)\npublic class ConnectionOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/ConnectionTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.CONNECTION)\npublic class ConnectionTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/CoreOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.CORE)\npublic class CoreOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/CoreTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.CORE)\npublic class CoreTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/DatabaseMetaDataTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.DATABASE_META_DATA)\npublic class DatabaseMetaDataTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/DiagnosticOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.DIAGNOSTIC)\npublic class DiagnosticOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/DiagnosticTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.DIAGNOSTIC)\npublic class DiagnosticTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/LoaderOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.LOADER)\npublic class LoaderOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/LoaderTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.LOADER)\npublic class LoaderTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/OldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport java.util.Arrays;\nimport net.snowflake.client.providers.SimpleResultFormatProvider;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.platform.suite.api.AfterSuite;\nimport org.junit.platform.suite.api.BeforeSuite;\n\n@BaseTestSuite\npublic abstract class OldDriverTestSuite {\n  @BeforeSuite\n  public static void beforeAll() {\n    SimpleResultFormatProvider.setSupportedFormats(Arrays.asList(Arguments.of(\"JSON\")));\n  }\n\n  @AfterSuite\n  public static void afterAll() {\n    SimpleResultFormatProvider.resetSupportedFormats();\n  }\n}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/OnlyTestingTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.TESTING)\npublic class OnlyTestingTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/OthersOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.OTHERS)\npublic class OthersOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/OthersTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.OTHERS)\npublic class OthersTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/ResultSetOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.RESULT_SET)\npublic class ResultSetOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/ResultSetTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.RESULT_SET)\npublic class ResultSetTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/StatementOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@IncludeTags(TestTags.STATEMENT)\npublic class StatementOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/StatementTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.STATEMENT)\npublic class StatementTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/UnitOldDriverTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.ExcludeTags;\n\n@ExcludeTags({\n  TestTags.CORE,\n  TestTags.ARROW,\n  TestTags.DIAGNOSTIC,\n  TestTags.CONNECTION,\n  TestTags.DATABASE_META_DATA,\n  TestTags.LOADER,\n  TestTags.OTHERS,\n  TestTags.RESULT_SET,\n  TestTags.STATEMENT\n})\npublic class UnitOldDriverTestSuite extends OldDriverTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/UnitTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.DisplayName;\nimport org.junit.platform.suite.api.ExcludeTags;\n\n@BaseTestSuite\n@DisplayName(\"Unit tests\")\n@ExcludeTags({\n  TestTags.CORE,\n  TestTags.ARROW,\n  TestTags.DIAGNOSTIC,\n  TestTags.CONNECTION,\n  TestTags.DATABASE_META_DATA,\n  TestTags.LOADER,\n  TestTags.OTHERS,\n  TestTags.RESULT_SET,\n  TestTags.STATEMENT,\n  TestTags.AUTHENTICATION,\n  TestTags.WIF\n})\npublic class UnitTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/suites/WIFTestSuite.java",
    "content": "package net.snowflake.client.suites;\n\nimport net.snowflake.client.category.TestTags;\nimport org.junit.platform.suite.api.IncludeTags;\n\n@BaseTestSuite\n@IncludeTags(TestTags.WIF)\npublic class WIFTestSuite {}\n"
  },
  {
    "path": "src/test/java/net/snowflake/client/wif/WIFLatestIT.java",
    "content": "package net.snowflake.client.wif;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Objects;\nimport java.util.Properties;\nimport net.snowflake.client.category.TestTags;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.Tag;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.DisabledIf;\nimport org.junit.jupiter.api.condition.EnabledIf;\nimport org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;\n\n/**\n * Running tests locally:\n *\n * <ol>\n *   <li>Push branch to repository\n *   <li>Set environment variables PARAMETERS_SECRET and BRANCH\n *   <li>Run ci/test_wif.sh\n * </ol>\n */\n@Tag(TestTags.WIF)\npublic class WIFLatestIT {\n\n  private static final String ACCOUNT = System.getenv(\"SNOWFLAKE_TEST_WIF_ACCOUNT\");\n  private static final String HOST = System.getenv(\"SNOWFLAKE_TEST_WIF_HOST\");\n  private static final String PROVIDER = System.getenv(\"SNOWFLAKE_TEST_WIF_PROVIDER\");\n  private static final String IS_GCP_FUNCTION = System.getenv(\"IS_GCP_FUNCTION\");\n  private static final String IMPERSONATION_PATH =\n      System.getenv(\"SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH\");\n  private static final String IMPERSONATION_USER =\n      System.getenv(\"SNOWFLAKE_TEST_WIF_USERNAME_IMPERSONATION\");\n  private static final String AWS_EXTERNAL_ID = System.getenv(\"SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID\");\n  private static final String IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID =\n      System.getenv(\"SNOWFLAKE_TEST_WIF_IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID\");\n  private static final String IMPERSONATION_USER_WITH_EXTERNAL_ID =\n      System.getenv(\"SNOWFLAKE_TEST_WIF_IMPERSONATION_USER_WITH_EXTERNAL_ID\");\n\n  @Test\n  void shouldAuthenticateUsingWIFWithDefinedProvider() {\n    Properties properties = new Properties();\n    properties.put(\"account\", ACCOUNT);\n    properties.put(\"authenticator\", \"WORKLOAD_IDENTITY\");\n    properties.put(\"workloadIdentityProvider\", PROVIDER);\n    connectAndExecuteSimpleQuery(properties);\n  }\n\n  @Test\n  @DisabledIf(\"isProviderAzure\")\n  @EnabledIfEnvironmentVariable(named = \"SNOWFLAKE_TEST_WIF_IMPERSONATION_PATH\", matches = \".+\")\n  void shouldAuthenticateUsingWIFWithImpersonation() {\n    Properties properties = new Properties();\n    properties.put(\"account\", ACCOUNT);\n    properties.put(\"authenticator\", \"WORKLOAD_IDENTITY\");\n    properties.put(\"workloadIdentityProvider\", PROVIDER);\n    properties.put(\"workloadIdentityImpersonationPath\", IMPERSONATION_PATH);\n    connectAndExecuteSimpleQuery(properties, IMPERSONATION_USER);\n  }\n\n  @Test\n  @EnabledIf(\"isProviderGCP\")\n  void shouldAuthenticateUsingOIDC() {\n    Assumptions.assumeTrue(!Objects.equals(IS_GCP_FUNCTION, \"true\"));\n    Properties properties = new Properties();\n    properties.put(\"account\", ACCOUNT);\n    properties.put(\"authenticator\", \"WORKLOAD_IDENTITY\");\n    properties.put(\"workloadIdentityProvider\", \"OIDC\");\n    properties.put(\"token\", getGCPAccessToken());\n    connectAndExecuteSimpleQuery(properties);\n  }\n\n  private static boolean isProviderGCP() {\n    return Objects.equals(PROVIDER, \"GCP\");\n  }\n\n  private static boolean isProviderAzure() {\n    return Objects.equals(PROVIDER, \"AZURE\");\n  }\n\n  private static boolean isProviderAWS() {\n    return Objects.equals(PROVIDER, \"AWS\");\n  }\n\n  private String getGCPAccessToken() {\n    try {\n      String command =\n          \"curl -H \\\"Metadata-Flavor: Google\\\" \\\"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=snowflakecomputing.com\\\"\";\n      ProcessBuilder processBuilder = new ProcessBuilder(\"bash\", \"-c\", command);\n      Process process = processBuilder.start();\n\n      try (BufferedReader reader =\n          new BufferedReader(new InputStreamReader(process.getInputStream()))) {\n        String token = reader.readLine();\n        int exitCode = process.waitFor();\n\n        if (exitCode == 0 && token != null && !token.trim().isEmpty()) {\n          return token.trim();\n        } else {\n          throw new RuntimeException(\"Failed to retrieve GCP access token, exit code: \" + exitCode);\n        }\n      }\n    } catch (Exception e) {\n      throw new RuntimeException(\"Error executing GCP metadata request\", e);\n    }\n  }\n\n  private void connectAndExecuteSimpleQuery(Properties props) {\n    String url = String.format(\"jdbc:snowflake://%s\", HOST);\n    try (Connection con = DriverManager.getConnection(url, props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      int value = rs.getInt(1);\n      assertEquals(1, value);\n    } catch (SQLException e) {\n      throw new RuntimeException(\"Failed to execute query\", e);\n    }\n  }\n\n  private void connectAndExecuteSimpleQuery(Properties props, String expectedUser) {\n    String url = String.format(\"jdbc:snowflake://%s\", HOST);\n    try (Connection con = DriverManager.getConnection(url, props);\n        Statement stmt = con.createStatement();\n        ResultSet rs = stmt.executeQuery(\"select 1\")) {\n      assertTrue(rs.next());\n      int value = rs.getInt(1);\n      assertEquals(1, value);\n\n      ResultSet rs2 = stmt.executeQuery(\"select current_user()\");\n      assertTrue(rs2.next());\n      String username = rs2.getString(1);\n      assertEquals(expectedUser, username);\n    } catch (SQLException e) {\n      throw new RuntimeException(\"Failed to execute query\", e);\n    }\n  }\n\n  @Test\n  @EnabledIf(\"isProviderAWS\")\n  @EnabledIfEnvironmentVariable(\n      named = \"SNOWFLAKE_TEST_WIF_IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID\",\n      matches = \".+\")\n  @EnabledIfEnvironmentVariable(named = \"SNOWFLAKE_TEST_WIF_AWS_EXTERNAL_ID\", matches = \".+\")\n  @EnabledIfEnvironmentVariable(\n      named = \"SNOWFLAKE_TEST_WIF_IMPERSONATION_USER_WITH_EXTERNAL_ID\",\n      matches = \".+\")\n  void shouldAuthenticateUsingWIFWithImpersonationAndExternalId() {\n    Properties properties = new Properties();\n    properties.put(\"account\", ACCOUNT);\n    properties.put(\"authenticator\", \"WORKLOAD_IDENTITY\");\n    properties.put(\"workloadIdentityProvider\", PROVIDER);\n    properties.put(\"workloadIdentityImpersonationPath\", IMPERSONATION_ROLE_ARN_WITH_EXTERNAL_ID);\n    properties.put(\"workloadIdentityAwsExternalId\", AWS_EXTERNAL_ID);\n    connectAndExecuteSimpleQuery(properties, IMPERSONATION_USER_WITH_EXTERNAL_ID);\n  }\n}\n"
  },
  {
    "path": "src/test/resources/.gitignore",
    "content": "embedded-test-server.yml\n"
  },
  {
    "path": "src/test/resources/FileUploaderPrep/exampleAzure.json",
    "content": "{\n  \"data\": {\n    \"uploadInfo\": {\n      \"locationType\": \"AZURE\",\n      \"location\": \"EXAMPLE_LOCATION/\",\n      \"path\": \"EXAMPLE_PATH/\",\n      \"region\": \"westus\",\n      \"storageAccount\": \"sfcdev2stage\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {\n        \"AZURE_SAS_TOKEN\": \"EXAMPLE_AZURE_SAS_TOKEN\"\n      },\n      \"presignedUrl\": null,\n      \"endPoint\": \"blob.core.windows.net\"\n    },\n    \"src_locations\": [\n      \"/foo/orders_100.csv\"\n    ],\n    \"parallel\": 4,\n    \"threshold\": 209715200,\n    \"autoCompress\": true,\n    \"overwrite\": false,\n    \"sourceCompression\": \"auto_detect\",\n    \"clientShowEncryptionParameter\": false,\n    \"queryId\": \"EXAMPLE_QUERY_ID\",\n    \"encryptionMaterial\": {\n      \"queryStageMasterKey\": \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n      \"queryId\": \"EXAMPLE_QUERY_ID\",\n      \"smkId\": 123\n    },\n    \"stageInfo\": {\n      \"locationType\": \"AZURE\",\n      \"location\": \"EXAMPLE_LOCATION/\",\n      \"path\": \"EXAMPLE_PATH/\",\n      \"region\": \"westus\",\n      \"storageAccount\": \"EXAMPLE_STORAGE_ACCOUNT\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {\n        \"AZURE_SAS_TOKEN\": \"EXAMPLE_AZURE_SAS_TOKEN\"\n      },\n      \"presignedUrl\": null,\n      \"endPoint\": \"blob.core.windows.net\"\n    },\n    \"command\": \"UPLOAD\",\n    \"kind\": null,\n    \"operation\": \"Node\"\n  },\n  \"code\": null,\n  \"message\": null,\n  \"success\": true\n}"
  },
  {
    "path": "src/test/resources/FileUploaderPrep/exampleGCS.json",
    "content": "{\n  \"data\": {\n    \"uploadInfo\": {\n      \"locationType\": \"GCS\",\n      \"location\": \"foo/tables/9224/\",\n      \"path\": \"tables/9224/\",\n      \"region\": \"US-WEST1\",\n      \"storageAccount\": \"\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {},\n      \"presignedUrl\": \"EXAMPLE_PRESIGNED_URL\",\n      \"endPoint\": \"\"\n    },\n    \"src_locations\": [\n      \"/foo/bart/orders_100.csv\"\n    ],\n    \"parallel\": 4,\n    \"threshold\": 209715200,\n    \"autoCompress\": true,\n    \"overwrite\": false,\n    \"sourceCompression\": \"auto_detect\",\n    \"clientShowEncryptionParameter\": false,\n    \"queryId\": \"EXAMPLE_QUERY_ID\",\n    \"encryptionMaterial\": {\n      \"queryStageMasterKey\": \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n      \"queryId\": \"EXAMPLE_QUERY_ID\",\n      \"smkId\": 123\n    },\n    \"stageInfo\": {\n      \"locationType\": \"GCS\",\n      \"location\": \"foo/tables/9224/\",\n      \"path\": \"tables/9224/\",\n      \"region\": \"US-WEST1\",\n      \"storageAccount\": \"\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {},\n      \"presignedUrl\": \"EXAMPLE_PRESIGNED_URL\",\n      \"endPoint\": \"\"\n    },\n    \"command\": \"UPLOAD\",\n    \"kind\": null,\n    \"operation\": \"Node\"\n  },\n  \"code\": null,\n  \"message\": null,\n  \"success\": true\n}"
  },
  {
    "path": "src/test/resources/FileUploaderPrep/exampleGCSWithEndpoint.json",
    "content": "{\n  \"data\": {\n    \"uploadInfo\": {\n      \"locationType\": \"GCS\",\n      \"location\": \"foo/tables/9224/\",\n      \"path\": \"tables/9224/\",\n      \"region\": \"US-WEST1\",\n      \"storageAccount\": \"\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {},\n      \"presignedUrl\": \"EXAMPLE_PRESIGNED_URL\",\n      \"endPoint\": \"example.com\"\n    },\n    \"src_locations\": [\n      \"/foo/bart/orders_100.csv\"\n    ],\n    \"parallel\": 4,\n    \"threshold\": 209715200,\n    \"autoCompress\": true,\n    \"overwrite\": false,\n    \"sourceCompression\": \"auto_detect\",\n    \"clientShowEncryptionParameter\": false,\n    \"queryId\": \"EXAMPLE_QUERY_ID\",\n    \"encryptionMaterial\": {\n      \"queryStageMasterKey\": \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n      \"queryId\": \"EXAMPLE_QUERY_ID\",\n      \"smkId\": 123\n    },\n    \"stageInfo\": {\n      \"locationType\": \"GCS\",\n      \"location\": \"foo/tables/9224/\",\n      \"path\": \"tables/9224/\",\n      \"region\": \"US-WEST1\",\n      \"storageAccount\": \"\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {},\n      \"presignedUrl\": \"EXAMPLE_PRESIGNED_URL\",\n      \"endPoint\": \"example.com\"\n    },\n    \"command\": \"UPLOAD\",\n    \"kind\": null,\n    \"operation\": \"Node\"\n  },\n  \"code\": null,\n  \"message\": null,\n  \"success\": true\n}"
  },
  {
    "path": "src/test/resources/FileUploaderPrep/exampleGCSWithUseRegionalUrl.json",
    "content": "{\n  \"data\": {\n    \"uploadInfo\": {\n      \"locationType\": \"GCS\",\n      \"useRegionalUrl\": true,\n      \"location\": \"foo/tables/9224/\",\n      \"path\": \"tables/9224/\",\n      \"region\": \"US-WEST1\",\n      \"storageAccount\": \"\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {},\n      \"presignedUrl\": \"EXAMPLE_PRESIGNED_URL\",\n      \"endPoint\": \"\"\n    },\n    \"src_locations\": [\n      \"/foo/bart/orders_100.csv\"\n    ],\n    \"parallel\": 4,\n    \"threshold\": 209715200,\n    \"autoCompress\": true,\n    \"overwrite\": false,\n    \"sourceCompression\": \"auto_detect\",\n    \"clientShowEncryptionParameter\": false,\n    \"queryId\": \"EXAMPLE_QUERY_ID\",\n    \"encryptionMaterial\": {\n      \"queryStageMasterKey\": \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n      \"queryId\": \"EXAMPLE_QUERY_ID\",\n      \"smkId\": 123\n    },\n    \"stageInfo\": {\n      \"locationType\": \"GCS\",\n      \"useRegionalUrl\": true,\n      \"location\": \"foo/tables/9224/\",\n      \"path\": \"tables/9224/\",\n      \"region\": \"US-WEST1\",\n      \"storageAccount\": \"\",\n      \"isClientSideEncrypted\": true,\n      \"creds\": {},\n      \"presignedUrl\": \"EXAMPLE_PRESIGNED_URL\",\n      \"endPoint\": \"\"\n    },\n    \"command\": \"UPLOAD\",\n    \"kind\": null,\n    \"operation\": \"Node\"\n  },\n  \"code\": null,\n  \"message\": null,\n  \"success\": true\n}"
  },
  {
    "path": "src/test/resources/FileUploaderPrep/exampleS3.json",
    "content": "{\n  \"data\": {\n    \"uploadInfo\": {\n      \"locationType\": \"S3\",\n      \"location\": \"example/location\",\n      \"path\": \"tables/19805757505/\",\n      \"region\": \"us-west-2\",\n      \"storageAccount\": null,\n      \"isClientSideEncrypted\": true,\n      \"creds\": {\n        \"AWS_KEY_ID\": \"EXAMPLE_AWS_KEY_ID\",\n        \"AWS_SECRET_KEY\": \"EXAMPLE_AWS_SECRET_KEY\",\n        \"AWS_TOKEN\": \"EXAMPLE_AWS_TOKEN\",\n        \"AWS_ID\": \"EXAMPLE_AWS_ID\",\n        \"AWS_KEY\": \"EXAMPLE_AWS_KEY\"\n      },\n      \"presignedUrl\": null,\n      \"endPoint\": null\n    },\n    \"src_locations\": [\n      \"/tmp/files/orders_100.csv\"\n    ],\n    \"parallel\": 4,\n    \"threshold\": 209715200,\n    \"autoCompress\": true,\n    \"overwrite\": false,\n    \"sourceCompression\": \"auto_detect\",\n    \"clientShowEncryptionParameter\": true,\n    \"queryId\": \"EXAMPLE_QUERY_ID\",\n    \"encryptionMaterial\": {\n      \"queryStageMasterKey\": \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n      \"queryId\": \"EXAMPLE_QUERY_ID\",\n      \"smkId\": 123\n    },\n    \"stageInfo\": {\n      \"locationType\": \"S3\",\n      \"location\": \"stage/location/foo/\",\n      \"path\": \"tables/19805757505/\",\n      \"region\": \"us-west-2\",\n      \"storageAccount\": null,\n      \"isClientSideEncrypted\": true,\n      \"useS3RegionalUrl\": true,\n      \"creds\": {\n        \"AWS_KEY_ID\": \"EXAMPLE_AWS_KEY_ID\",\n        \"AWS_SECRET_KEY\": \"EXAMPLE_AWS_SECRET_KEY\",\n        \"AWS_TOKEN\": \"EXAMPLE_AWS_TOKEN\",\n        \"AWS_ID\": \"EXAMPLE_AWS_ID\",\n        \"AWS_KEY\": \"EXAMPLE_AWS_KEY\"\n      },\n      \"presignedUrl\": null,\n      \"endPoint\": null\n    },\n    \"command\": \"UPLOAD\",\n    \"kind\": null,\n    \"operation\": \"Node\"\n  },\n  \"code\": null,\n  \"message\": null,\n  \"success\": true\n}"
  },
  {
    "path": "src/test/resources/FileUploaderPrep/exampleS3WithStageEndpoint.json",
    "content": "{\n  \"data\": {\n    \"uploadInfo\": {\n      \"locationType\": \"S3\",\n      \"location\": \"example/location\",\n      \"path\": \"tables/19805757505/\",\n      \"region\": \"us-west-2\",\n      \"storageAccount\": null,\n      \"isClientSideEncrypted\": true,\n      \"creds\": {\n        \"AWS_KEY_ID\": \"EXAMPLE_AWS_KEY_ID\",\n        \"AWS_SECRET_KEY\": \"EXAMPLE_AWS_SECRET_KEY\",\n        \"AWS_TOKEN\": \"EXAMPLE_AWS_TOKEN\",\n        \"AWS_ID\": \"EXAMPLE_AWS_ID\",\n        \"AWS_KEY\": \"EXAMPLE_AWS_KEY\"\n      },\n      \"presignedUrl\": null,\n      \"endPoint\": null\n    },\n    \"src_locations\": [\n      \"/tmp/files/orders_100.csv\"\n    ],\n    \"parallel\": 4,\n    \"threshold\": 209715200,\n    \"autoCompress\": true,\n    \"overwrite\": false,\n    \"sourceCompression\": \"auto_detect\",\n    \"clientShowEncryptionParameter\": true,\n    \"queryId\": \"EXAMPLE_QUERY_ID\",\n    \"encryptionMaterial\": {\n      \"queryStageMasterKey\": \"EXAMPLE_QUERY_STAGE_MASTER_KEY\",\n      \"queryId\": \"EXAMPLE_QUERY_ID\",\n      \"smkId\": 123\n    },\n    \"stageInfo\": {\n      \"locationType\": \"S3\",\n      \"location\": \"stage/location/foo/\",\n      \"path\": \"tables/19805757505/\",\n      \"region\": \"us-west-2\",\n      \"storageAccount\": null,\n      \"isClientSideEncrypted\": true,\n      \"creds\": {\n        \"AWS_KEY_ID\": \"EXAMPLE_AWS_KEY_ID\",\n        \"AWS_SECRET_KEY\": \"EXAMPLE_AWS_SECRET_KEY\",\n        \"AWS_TOKEN\": \"EXAMPLE_AWS_TOKEN\",\n        \"AWS_ID\": \"EXAMPLE_AWS_ID\",\n        \"AWS_KEY\": \"EXAMPLE_AWS_KEY\"\n      },\n      \"presignedUrl\": null,\n      \"endPoint\": \"s3-fips.us-east-1.amazonaws.com\"\n    },\n    \"command\": \"UPLOAD\",\n    \"kind\": null,\n    \"operation\": \"Node\"\n  },\n  \"code\": null,\n  \"message\": null,\n  \"success\": true\n}"
  },
  {
    "path": "src/test/resources/allowlist.json",
    "content": "[\n  {\"host\":\"account_name.snowflakecomputing.com\",\"port\":443,\"type\":\"SNOWFLAKE_DEPLOYMENT\"},\n  {\"host\":\"org-account_name.snowflakecomputing.com\",\"port\":443,\"type\":\"SNOWFLAKE_DEPLOYMENT_REGIONLESS\"},\n  {\"host\":\"stage-bucket.s3.amazonaws.com\",\"port\":443,\"type\":\"STAGE\"},\n  {\"host\":\"stage-bucket.s3.us-west-2.amazonaws.com\",\"port\":443,\"type\":\"STAGE\"},\n  {\"host\":\"stage-bucket.s3-us-west-2.amazonaws.com\",\"port\":443,\"type\":\"STAGE\"},\n  {\"host\":\"snowsql_repo.snowflakecomputing.com\",\"port\":443,\"type\":\"SNOWSQL_REPO\"},\n  {\"host\":\"out_of_band_telemetry.snowflakecomputing.com\",\"port\":443,\"type\":\"OUT_OF_BAND_TELEMETRY\"},\n  {\"host\":\"ocsp_cache.snowflakecomputing.com\",\"port\":80,\"type\":\"OCSP_CACHE\"},\n  {\"host\":\"duo_security.duosecurity.com\",\"port\":443,\"type\":\"DUO_SECURITY\"},\n  {\"host\":\"ocsp.rootg2.amazontrust.com\",\"port\":80,\"type\":\"OCSP_RESPONDER\"},\n  {\"host\":\"o.ss2.us\",\"port\":80,\"type\":\"OCSP_RESPONDER\"},\n  {\"host\":\"ocsp.sca1b.amazontrust.com\",\"port\":80,\"type\":\"OCSP_RESPONDER\"},\n  {\"host\":\"ocsp.r2m01.amazontrust.com\",\"port\":80,\"type\":\"OCSP_RESPONDER\"},\n  {\"host\":\"ocsp.rootca1.amazontrust.com\",\"port\":80,\"type\":\"OCSP_RESPONDER\"},\n  {\"host\":\"snowsight_deployment.snowflake.com\",\"port\":443,\"type\":\"SNOWSIGHT_DEPLOYMENT\"},\n  {\"host\":\"snowsight_deployment_2.snowflake.com\",\"port\":443,\"type\":\"SNOWSIGHT_DEPLOYMENT\"}\n]\n"
  },
  {
    "path": "src/test/resources/encrypted_rsa_key.p8",
    "content": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIE6TAbBgkqhkiG9w0BBQMwDgQIyxJc1ncmHYkCAggABIIEyBX3C7TctcZIe6iN\nP7quhkvvoNGKQH4inSzyMeHJnFMKxklhIfucZSteJFCBAlxMUEk8bN3G02DE6jfP\nMADfoQL8hEVzEwQYCeEYrQ8J8scOn5YUWhx+AdWt1mxK2q8nVcDJRykMCPS08xIE\nNyJVVQc8i5qM3QRgwUD3YHUupoLZQhUP6qZ6CkxGzjnSBny49KXTtm2XL7385eoM\nuOjQqU1s7Uwy52IyMIVUHZhaOvkjH3CP56hAvWzmVHtYlpIiE4ek9qvEPx2Y70aZ\nXDWAxqb44AnOCF6QG4FeTr5FDp3qTOuQpqvWjBRRNqLbuhLqXk++To5CPw8ofGOb\neYJeTmrJX7KVeGWLNrY0QFdZC81VJW0K3b6FqalcPTfE2zU5rKowUauWSoM03MMX\nyrHw53R2G+qcHO35hUh3i/WUJD4Yj0SPrBrMIjJikH9DUU1o8LShZUCh5LcdChop\nmfAx57MCfTIdCHiL3LFOIMV6pyKGKJsHEnrgCZUxj/fJ0sEQ9wlH7uQWmdz0Gpne\ntAeYeIblARv2Yny7mbyUJ3Malyz6M3jPguWaQ2L76H5bylgJSkV/csnCx+h/COw/\nA+jsD1I3b608IzueUlRmcPfHrtdM0snYhm2R14XxUVrEwxGLsChorleZGXq7nLn8\nRaMW6ZT8vZSfR+lAVzC6f7AP9IQbfqFd06lUjErTuSvgHZa0/9oIug7EsA/vw+pW\n0eOdI+db5I5qr2R27IFqYb+q+iG5ReHjhZYwqxK+NeXtpYcglsk9ol0sObo9fwnA\nHjBNW+hovLv/XjhwW7uFY6pvBQ2m7ap+5OpA9EAvymJyPpcC6AqV0ZQF+TT8Marq\nVk5FjNSeWRRVSRbYTpidtRaXtRkRIBaBqaFsxuWnhDhyXWUJQRMrqpf5WDSj4BhC\nYShCzcE1UWj/6sUBxa73Nqb4Qj5o9OmmZ0JF6WjomwYj3cs5g3QV38Zd2+29Pds4\nkBSxLSgjeouwPTPTvGQ2XcnI2705O2QvYNBD4gmW43WlPxA7Clj0mrpMLl8dfH+1\nIngmnAlxRckdqAUix+HKpsN4zyfYCLVUmyDR8PRvQwXoiWLYUBScUiHfrLqGb95B\ndhF4YjP0SyZ81dGqVAfP29rna7ulfF2JZgxYUxhTosESf+Z1fxJJm3lOhLH6vsXE\nu3SoqyGnglz9jJa9SO1IjKjyZENPJv8Ia1PQLAtnLAdP6OyFiYBLbhWKNbgMCpaL\n/J/k0r8UlKBF6O5eEaan7EyMolV99Lk9qZL7uCRABr2pCTKXllG9WfI2YtILDJRh\nTMXVqxvEaJA1IVCKkArzjQlQlpe64V4jf/mD95b8lQiY3wFGTqiQtxGCpQex4tGW\n2I+Rwvu2FaYvacKLsmYTcq0+Au2IEGw+7lXCSFQixSlkE5lJzjpgokbKtUAuxFwU\nClmuDi1ek6HllSlV6hBJlqbHAt8k/MtyM5zy8YvObbAoUde1Y8H3s9/Kt6M+YXTw\n3pc8SU8fxbVM4za0ZYYVxJJl1/Vm3IC/lwt2EnnJGo0isP6GPMJ42h7wnpu6lDcb\ncjFtCivniat/7FP9nYFdVwNiKryzcIbjLZ47ZAu1sjaikaBQBV6RBIMIr/FYK2lf\nyO6TgRjqocJTlJ/o/w==\n-----END ENCRYPTED PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/encrypted_rsa_key.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwF2Lrbf4++YsKb4GCCMX\nt4L6+3a368Fxn9zIadL74khFJny6NYbKNFanv+PAPMEBE/k+K47G2FUk4OLXdwDx\nE0EU63CYfXIxowS0LLAKol8E/zj13Ie0Hy5JMGAeGYxb9omx5yDnElPcFsMmMNQV\n8lf8S6ZjflqDkSjIHA45EVgBW1vFHbn9JeuLd4SkPrtGW0fHlfFJoDEFVLiaGF20\nh4R+diQPNLXEYflajcBq9zgNyGbB9i5MNGZ+WRbwQd5y1Pi0tI+Uf9aGziWoQEFq\nubO12Jzi876b2cvefb3ymdBvhfbQOeK52ae3gOe711WLtBXx8jszS4uxjKb6EgU+\nTwIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/test/resources/invalid_private_key.pem",
    "content": "invalid key!\n"
  },
  {
    "path": "src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n  <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n    <!-- encoders are assigned the type\n         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n    <encoder>\n      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>\n    </encoder>\n  </appender>\n\n  <root level=\"OFF\">\n    <appender-ref ref=\"STDOUT\" />\n  </root>\n</configuration>"
  },
  {
    "path": "src/test/resources/logging.properties",
    "content": "handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler\n\n.level = ALL\n\njava.util.logging.ConsoleHandler.level=OFF\njava.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter\n\n# will be generated dynamically by ci/log_analyze_setup.sh\njava.util.logging.FileHandler.pattern = placeholder\njava.util.logging.FileHandler.limit = 5000000000000000\n# maybe we need more than one log file, if the regression test are split into multiple parts\njava.util.logging.FileHandler.count = 1\njava.util.logging.FileHandler.level = ALL\njava.util.logging.FileHandler.formatter = net.snowflake.client.internal.log.SFFormatter\n\nnet.snowflake.handler = java.util.logging.FileHandler\n"
  },
  {
    "path": "src/test/resources/net/snowflake/client/.gitignore",
    "content": "ingest\n"
  },
  {
    "path": "src/test/resources/net/snowflake/client/internal/jdbc/boomi.policy",
    "content": "// boomi cloud environment policy file\n\ngrant codeBase \"file:${{java.ext.dirs}}/*\" {\n  permission java.security.AllPermission;\n};\n\ngrant codeBase \"file:${com.boomi.container.libDir}/boomi-security.jar\" {\n  permission java.security.AllPermission;\n};\n\ngrant codeBase \"file:${com.boomi.container.libDir}/-\" {\n  // base container configuration\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}bin${/}build.number\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}conf${/}container.id\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}conf${/}container.properties\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}conf${/}container.${com.boomi.container.localHostId}.properties\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}conf${/}limits${/}${com.boomi.container.runAsAccount}\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}connector${/}-\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}work${/}-\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}userlib\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.baseDir}${/}userlib${/}-\", \"read\";\n  permission java.io.FilePermission \"${java.home}${/}-\", \"read\";\n\n  // this is to appease the xsl template compiler which likes to read the whole classpath (for no reason)\n  permission com.boomi.security.PathPermission \"${java.ext.dirs}\", \"read\";\n  permission com.boomi.security.PathPermission \"${java.class.path}\", \"read\";\n  permission com.boomi.security.PathPermission \"${sun.boot.class.path}\", \"read\";\n\n  // local network permissions\n  permission java.net.SocketPermission \"localhost:1380\", \"connect,resolve\";\n\n  // misc other permissions\n  permission java.lang.RuntimePermission \"com.boomi.container.control\";\n  permission java.lang.RuntimePermission \"createClassLoader\";\n  permission java.lang.RuntimePermission \"getClassLoader\";\n  permission java.lang.RuntimePermission \"modifyClassLoader\";\n  permission java.lang.RuntimePermission \"exitVM.*\";\n  permission java.lang.RuntimePermission \"setDefaultUncaughtExceptionHandler\";\n  permission java.lang.RuntimePermission \"modifyThread\";\n  permission java.lang.RuntimePermission \"modifyThreadGroup\";\n  permission java.lang.RuntimePermission \"setIO\";\n  permission java.lang.RuntimePermission \"accessClassInPackage.sun.misc\";\n  permission java.lang.RuntimePermission \"accessClassInPackage.com.sun.*\";\n  permission java.lang.RuntimePermission \"setFactory\";\n  permission java.security.SecurityPermission \"getPolicy\";\n  permission java.security.SecurityPermission \"createAccessControlContext\";\n  permission java.lang.reflect.ReflectPermission \"suppressAccessChecks\";\n  permission java.lang.RuntimePermission \"shutdownHooks\";\n  permission java.util.PropertyPermission \"*\", \"read,write\";\n  permission java.util.logging.LoggingPermission \"control\";\n  permission java.io.SerializablePermission \"enableSubstitution\";\n  permission java.security.SecurityPermission \"insertProvider.BC\";\n  permission java.security.SecurityPermission \"putProviderProperty.BC\";\n  permission java.security.SecurityPermission \"insertProvider.STRTransform\";\n  permission java.security.SecurityPermission \"putProviderProperty.STRTransform\";\n  permission java.security.SecurityPermission \"setProperty.jdk.*\";\n  permission javax.management.MBeanServerPermission \"createMBeanServer\";\n  permission javax.management.MBeanPermission \"com.boomi.execution.forker.*\", \"registerMBean\";\n  permission javax.management.MBeanTrustPermission \"register\";\n  permission java.lang.management.ManagementPermission \"monitor\";\n  permission java.lang.management.ManagementPermission \"control\";\n  permission java.net.NetPermission \"setCookieHandler\";\n  permission java.net.NetPermission \"setDefaultAuthenticator\";\n  permission java.lang.RuntimePermission \"getProtectionDomain\";\n};\n\n// these are additional perms granted temporarily to code calling into JAXB\ngrant codeBase \"file:${com.boomi.container.accountDir}/jaxbwrapper\" {\n  permission java.lang.reflect.ReflectPermission \"suppressAccessChecks\";\n};\n\ngrant {\n  // account file permissions\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}-\", \"read\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}data${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}doccache${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}message${/}-\", \"read,write\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}execution${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}work${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}counter${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}sequence${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}controlid${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}tmp${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}tmpdata${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.accountDir}${/}auth${/}-\", \"read,write,delete\";\n  permission java.io.FilePermission \"${com.boomi.container.localAccountDir}${/}-\", \"read,write,delete\";\n\n  // network permissions\n  permission com.boomi.security.RemoteSocketPermission \"*\", \"connect,resolve\";\n\n  // misc other permissions\n  permission java.lang.RuntimePermission \"accessDeclaredMembers\";\n  permission java.lang.RuntimePermission \"instantiateClassLoader\";\n  permission java.lang.RuntimePermission \"setContextClassLoader\";\n  permission java.util.PropertyPermission \"*\", \"read\";\n  permission javax.net.ssl.SSLPermission \"*\";\n  permission groovy.security.GroovyCodeSourcePermission \"/groovy/script\";\n  permission java.io.FilePermission \"/groovy/script\", \"read\";\n  permission java.net.NetPermission \"getProxySelector\";\n  permission java.net.NetPermission \"getCookieHandler\";\n  permission java.net.NetPermission \"getResponseCache\";\n  permission java.lang.RuntimePermission \"accessClassInPackage.sun.reflect\";\n  permission java.lang.RuntimePermission \"accessClassInPackage.com.sun.script.groovy\";\n  permission java.lang.RuntimePermission \"accessClassInPackage.com.sun.org.apache.*\";\n  permission java.lang.RuntimePermission \"accessClassInPackage.sun.security.krb5\";\n\n};\n\n// snowflake test specific\ngrant {\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_ACCOUNT\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_USER\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_PASSWORD\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_HOST\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_PORT\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_DATABASE\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_SCHEMA\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_ROLE\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_WAREHOUSE\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_PROTOCOL\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_ADMIN_USER\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_ADMIN_PASSWORD\";\n  permission java.lang.RuntimePermission \"getenv.SNOWFLAKE_TEST_SSO_USER\";\n\n  permission java.security.SecurityPermission \"putProviderProperty.BC\";\n  permission java.security.SecurityPermission \"insertProvider.BC\";\n  permission java.net.SocketPermission \"127.0.0.1:8080\", \"connect,resolve\";\n  permission java.net.SocketPermission \"127.0.0.1:8082\", \"connect,resolve\";\n};\n"
  },
  {
    "path": "src/test/resources/net/snowflake/client/internal/jdbc/test_copy.csv",
    "content": "1, 2, str1\n3, 4, str2"
  },
  {
    "path": "src/test/resources/net/snowflake/client/jenkins_it_logging.properties",
    "content": "###########################################################\n#   Default Logging Configuration File\n#\n# You can use a different file by specifying a filename\n# with the java.util.logging.config.file system property.\n# For example java -Djava.util.logging.config.file=myfile\n############################################################\n\n############################################################\n#   Global properties\n############################################################\n\n# \"handlers\" specifies a comma-separated list of log Handler\n# classes.  These handlers will be installed during VM startup.\n# Note that these classes must be on the system classpath.\n# ConsoleHandler and FileHandler are configured here such that\n# the logs are dumped into both a standard error and a file.\nhandlers = java.util.logging.FileHandler\n\n# Default global logging level.\n# This specifies which kinds of events are logged across\n# all loggers.  For any given facility this global level\n# can be overridden by a facility specific level.\n# Note that the ConsoleHandler also has a separate level\n# setting to limit messages printed to the console.\n.level = INFO\n\n############################################################\n# Handler specific properties.\n# Describes specific configuration information for Handlers.\n############################################################\n\n# default file output is in the tmp dir\njava.util.logging.FileHandler.pattern = snowflake_jdbc%u.log\njava.util.logging.FileHandler.limit = 5000000000000000\njava.util.logging.FileHandler.count = 10\njava.util.logging.FileHandler.level = ALL\njava.util.logging.FileHandler.formatter = net.snowflake.client.log.SFFormatter\n\n# Limit the messages that are printed on the console to INFO and above.\n#java.util.logging.ConsoleHandler.level = INFO\n#java.util.logging.ConsoleHandler.formatter = net.snowflake.client.log.SFFormatter\n\n# Example to customize the SimpleFormatter output format\n# to print one-line log message like this:\n#     <level>: <log message> [<date/time>]\n#\n# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n\n\n############################################################\n# Facility specific properties.\n# Provides extra control for each logger.\n############################################################\n\n# Snowflake JDBC logging level.\nnet.snowflake.level = ALL\nnet.snowflake.handler = java.util.logging.FileHandler\n"
  },
  {
    "path": "src/test/resources/net/snowflake/client/travis_it_logging.properties",
    "content": "###########################################################\n#   Default Logging Configuration File\n#\n# You can use a different file by specifying a filename\n# with the java.util.logging.config.file system property.\n# For example java -Djava.util.logging.config.file=myfile\n############################################################\n\n############################################################\n#   Global properties\n############################################################\n\n# \"handlers\" specifies a comma-separated list of log Handler\n# classes.  These handlers will be installed during VM startup.\n# Note that these classes must be on the system classpath.\n# ConsoleHandler and FileHandler are configured here such that\n# the logs are dumped into both a standard error and a file.\nhandlers = java.util.logging.FileHandler\n\n# Default global logging level.\n# This specifies which kinds of events are logged across\n# all loggers.  For any given facility this global level\n# can be overridden by a facility specific level.\n# Note that the ConsoleHandler also has a separate level\n# setting to limit messages printed to the console.\n.level = INFO\n\n############################################################\n# Handler specific properties.\n# Describes specific configuration information for Handlers.\n############################################################\n\n# default file output is in the tmp dir\njava.util.logging.FileHandler.pattern = snowflake_jdbc%u.log\njava.util.logging.FileHandler.limit = 5000000000000000\njava.util.logging.FileHandler.count = 10\njava.util.logging.FileHandler.level = ALL\njava.util.logging.FileHandler.formatter = net.snowflake.client.log.SFFormatter\n\n# Limit the messages that are printed on the console to INFO and above.\n#java.util.logging.ConsoleHandler.level = INFO\n#java.util.logging.ConsoleHandler.formatter = net.snowflake.client.log.SFFormatter\n\n# Example to customize the SimpleFormatter output format\n# to print one-line log message like this:\n#     <level>: <log message> [<date/time>]\n#\n# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n\n\n############################################################\n# Facility specific properties.\n# Provides extra control for each logger.\n############################################################\n\n# Snowflake JDBC logging level.\nnet.snowflake.level = ALL\nnet.snowflake.handler = java.util.logging.FileHandler\n"
  },
  {
    "path": "src/test/resources/orders_100.csv",
    "content": "1|36901|O|173665.47|1996-01-02|5-LOW|Clerk#000000951|0|nstructions sleep furiously among |\n2|78002|O|46929.18|1996-12-01|1-URGENT|Clerk#000000880|0| foxes. pending accounts at the pending, silent asymptot|\n3|123314|F|193846.25|1993-10-14|5-LOW|Clerk#000000955|0|sly final accounts boost. carefully regular ideas cajole carefully. depos|\n4|136777|O|32151.78|1995-10-11|5-LOW|Clerk#000000124|0|sits. slyly regular warthogs cajole. regular, regular theodolites acro|\n5|44485|F|144659.20|1994-07-30|5-LOW|Clerk#000000925|0|quickly. bold deposits sleep slyly. packages use slyly|\n6|55624|F|58749.59|1992-02-21|4-NOT SPECIFIED|Clerk#000000058|0|ggle. special, final requests are against the furiously specia|\n7|39136|O|252004.18|1996-01-10|2-HIGH|Clerk#000000470|0|ly special requests |\n32|130057|O|208660.75|1995-07-16|2-HIGH|Clerk#000000616|0|ise blithely bold, regular requests. quickly unusual dep|\n33|66958|F|163243.98|1993-10-27|3-MEDIUM|Clerk#000000409|0|uriously. furiously final request|\n34|61001|O|58949.67|1998-07-21|3-MEDIUM|Clerk#000000223|0|ly final packages. fluffily final deposits wake blithely ideas. spe|\n35|127588|O|253724.56|1995-10-23|4-NOT SPECIFIED|Clerk#000000259|0|zzle. carefully enticing deposits nag furio|\n36|115252|O|68289.96|1995-11-03|1-URGENT|Clerk#000000358|0| quick packages are blithely. slyly silent accounts wake qu|\n37|86116|F|206680.66|1992-06-03|3-MEDIUM|Clerk#000000456|0|kly regular pinto beans. carefully unusual waters cajole never|\n38|124828|O|82500.05|1996-08-21|4-NOT SPECIFIED|Clerk#000000604|0|haggle blithely. furiously express ideas haggle blithely furiously regular re|\n39|81763|O|341734.47|1996-09-20|3-MEDIUM|Clerk#000000659|0|ole express, ironic requests: ir|\n64|32113|F|39414.99|1994-07-16|3-MEDIUM|Clerk#000000661|0|wake fluffily. sometimes ironic pinto beans about the dolphin|\n65|16252|P|110643.60|1995-03-18|1-URGENT|Clerk#000000632|0|ular requests are blithely pending orbits-- even requests against the deposit|\n66|129200|F|103740.67|1994-01-20|5-LOW|Clerk#000000743|0|y pending requests integrate|\n67|56614|O|169405.01|1996-12-19|4-NOT SPECIFIED|Clerk#000000547|0|symptotes haggle slyly around the furiously iron|\n68|28547|O|330793.52|1998-04-18|3-MEDIUM|Clerk#000000440|0| pinto beans sleep carefully. blithely ironic deposits haggle furiously acro|\n69|84487|F|197689.49|1994-06-04|4-NOT SPECIFIED|Clerk#000000330|0| depths atop the slyly thin deposits detect among the furiously silent accou|\n70|64340|F|113534.42|1993-12-18|5-LOW|Clerk#000000322|0| carefully ironic request|\n71|3373|O|276992.74|1998-01-24|4-NOT SPECIFIED|Clerk#000000271|0| express deposits along the blithely regul|\n96|107779|F|68989.90|1994-04-17|2-HIGH|Clerk#000000395|0|oost furiously. pinto|\n97|21061|F|110512.84|1993-01-29|3-MEDIUM|Clerk#000000547|0|hang blithely along the regular accounts. furiously even ideas after the|\n98|104480|F|69168.33|1994-09-25|1-URGENT|Clerk#000000448|0|c asymptotes. quickly regular packages should have to nag re|\n99|88910|F|112126.95|1994-03-13|4-NOT SPECIFIED|Clerk#000000973|0|e carefully ironic packages. pending|\n100|147004|O|187782.63|1998-02-28|4-NOT SPECIFIED|Clerk#000000577|0|heodolites detect slyly alongside of the ent|\n"
  },
  {
    "path": "src/test/resources/orders_101.csv",
    "content": "353|1777|F|249710.43|1993-12-31|5-LOW|Clerk#000000449|0| quiet ideas sleep. even instructions cajole slyly. silently spe|\n354|138268|O|217160.72|1996-03-14|2-HIGH|Clerk#000000511|0|ly regular ideas wake across the slyly silent ideas. final deposits eat b|\n355|70007|F|99516.75|1994-06-14|5-LOW|Clerk#000000532|0|s. sometimes regular requests cajole. regular, pending accounts a|\n356|146809|F|209439.04|1994-06-30|4-NOT SPECIFIED|Clerk#000000944|0|as wake along the bold accounts. even, |\n357|60395|O|157411.61|1996-10-09|2-HIGH|Clerk#000000301|0|e blithely about the express, final accounts. quickl|\n358|2290|F|354132.39|1993-09-20|2-HIGH|Clerk#000000392|0|l, silent instructions are slyly. silently even de|\n359|77600|F|239998.53|1994-12-19|3-MEDIUM|Clerk#000000934|0|n dolphins. special courts above the carefully ironic requests use|\n384|113009|F|166753.71|1992-03-03|5-LOW|Clerk#000000206|0|, even accounts use furiously packages. slyly ironic pla|\n385|32947|O|54948.26|1996-03-22|5-LOW|Clerk#000000600|0|hless accounts unwind bold pain|\n386|60110|F|110216.57|1995-01-25|2-HIGH|Clerk#000000648|0| haggle quickly. stealthily bold asymptotes haggle among the furiously even re|\n387|3296|O|204546.39|1997-01-26|4-NOT SPECIFIED|Clerk#000000768|0| are carefully among the quickly even deposits. furiously silent req|\n388|44668|F|198800.71|1992-12-16|4-NOT SPECIFIED|Clerk#000000356|0|ar foxes above the furiously ironic deposits nag slyly final reque|\n389|126973|F|2519.40|1994-02-17|2-HIGH|Clerk#000000062|0|ing to the regular asymptotes. final, pending foxes about the blithely sil|\n390|102563|O|269761.09|1998-04-07|5-LOW|Clerk#000000404|0|xpress asymptotes use among the regular, final pinto b|\n391|110278|F|20890.17|1994-11-17|2-HIGH|Clerk#000000256|0|orges thrash fluffil|\n416|40130|F|105675.20|1993-09-27|5-LOW|Clerk#000000294|0| the accounts. fluffily bold depo|\n417|54583|F|125155.22|1994-02-06|3-MEDIUM|Clerk#000000468|0|ironic, even packages. thinly unusual accounts sleep along the slyly unusual |\n418|94834|P|53328.48|1995-04-13|4-NOT SPECIFIED|Clerk#000000643|0|. furiously ironic instruc|\n419|116261|O|165454.42|1996-10-01|3-MEDIUM|Clerk#000000376|0|osits. blithely pending theodolites boost carefully|\n420|90145|O|343254.06|1995-10-31|4-NOT SPECIFIED|Clerk#000000756|0|leep carefully final excuses. fluffily pending requests unwind carefully above|\n421|39149|F|1156.67|1992-02-22|5-LOW|Clerk#000000405|0|egular, even packages according to the final, un|\n422|73075|O|188124.81|1997-05-31|4-NOT SPECIFIED|Clerk#000000049|0|aggle carefully across the accounts. regular accounts eat fluffi|\n423|103396|O|50240.88|1996-06-01|1-URGENT|Clerk#000000674|0|quests. deposits cajole quickly. furiously bold accounts haggle q|\n448|149641|O|165954.35|1995-08-21|3-MEDIUM|Clerk#000000597|0| regular, express foxes use blithely. quic|\n449|95767|O|71120.82|1995-07-20|2-HIGH|Clerk#000000841|0|. furiously regular theodolites affix blithely |\n450|47380|P|228518.02|1995-03-05|4-NOT SPECIFIED|Clerk#000000293|0|d theodolites. boldly bold foxes since the pack|\n451|98758|O|141490.92|1998-05-25|5-LOW|Clerk#000000048|0|nic pinto beans. theodolites poach carefully; |\n452|59560|O|3270.20|1997-10-14|1-URGENT|Clerk#000000498|0|t, unusual instructions above the blithely bold pint|\n453|44030|O|329149.33|1997-05-26|5-LOW|Clerk#000000504|0|ss foxes. furiously regular ideas sleep according to t|\n454|48776|O|36743.83|1995-12-27|5-LOW|Clerk#000000890|0|dolites sleep carefully blithely regular deposits. quickly regul|\n455|12098|O|183606.42|1996-12-04|1-URGENT|Clerk#000000796|0| about the final platelets. dependen|\n480|71383|F|23699.64|1993-05-08|5-LOW|Clerk#000000004|0|ealthy pinto beans. fluffily regular requests along the special sheaves wake |\n481|30352|F|201254.08|1992-10-08|2-HIGH|Clerk#000000230|0|ly final ideas. packages haggle fluffily|\n482|125059|O|182312.78|1996-03-26|1-URGENT|Clerk#000000295|0|ts. deposits wake: final acco|\n483|34820|O|70146.28|1995-07-11|2-HIGH|Clerk#000000025|0|cross the carefully final e|\n484|54244|O|327889.57|1997-01-03|3-MEDIUM|Clerk#000000545|0|grouches use. furiously bold accounts maintain. bold, regular deposits|\n485|100561|O|192867.30|1997-03-26|2-HIGH|Clerk#000000105|0| regular ideas nag thinly furiously s|\n486|50861|O|284644.07|1996-03-11|4-NOT SPECIFIED|Clerk#000000803|0|riously dolphins. fluffily ironic requ|\n487|107825|F|90657.45|1992-08-18|1-URGENT|Clerk#000000086|0|ithely unusual courts eat accordi|\n512|63022|P|194834.40|1995-05-20|5-LOW|Clerk#000000814|0|ding requests. carefully express theodolites was quickly. furious|\n513|60569|O|105559.70|1995-05-01|2-HIGH|Clerk#000000522|0|regular packages. pinto beans cajole carefully against the even|\n514|74872|O|154735.68|1996-04-04|2-HIGH|Clerk#000000094|0| cajole furiously. slyly final excuses cajole. slyly special instructions |\n515|141829|F|244660.33|1993-08-29|4-NOT SPECIFIED|Clerk#000000700|0|eposits are furiously furiously silent pinto beans. pending pack|\n516|43903|O|21920.56|1998-04-21|2-HIGH|Clerk#000000305|0|lar, unusual platelets are carefully. even courts sleep bold, final pinto bea|\n517|9220|O|121396.01|1997-04-07|5-LOW|Clerk#000000359|0|slyly pending deposits cajole quickly packages. furiou|\n"
  },
  {
    "path": "src/test/resources/os-release-test",
    "content": "NAME=\"Test Linux\"\nPRETTY_NAME=\"Test Linux 1.0 LTS\"\nID=testlinux\nIMAGE_ID=testlinux-cloud\nIMAGE_VERSION=1.0.0-cloud\nBUILD_ID=20260130\nVERSION=\"1.0 LTS (Test Release)\"\nVERSION_ID=1.0.0\n"
  },
  {
    "path": "src/test/resources/revoked_certs.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIGoTCCBYmgAwIBAgIQAa8e+91erglSMgsk/mtVaDANBgkqhkiG9w0BAQsFADBN\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E\naWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTYwOTAyMDAwMDAwWhcN\nMTkwOTExMTIwMDAwWjBtMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p\nYTEVMBMGA1UEBxMMV2FsbnV0IENyZWVrMRUwEwYDVQQKEwxMdWNhcyBHYXJyb24x\nGzAZBgNVBAMTEnJldm9rZWQuYmFkc3NsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMcxZeRVz2mQn24f2GoTfnS/EzpUZA90JD3cYLinRQG3yGoD\nrGRKZfB8gYGDCtndMSCCSKYzY+4rdOq05sccsl7kKDp6PSAZA7cVP0/JJuy3y79I\nbl80cFbEhsfjUpohMy8QE/MlDB6UNS7o0NG1oHdAkS7puvj/TvX78noEp+bGzj8P\nEBgyyAa8FbO+aax1fUKgjC7DrOEgTx42nJouov15gLZi+MCyA6kpUMzVJYozXuB4\nExjAgBcJlb2i/pIVByB6gc7bDoEpidTI7LOzeQ7yziXn7r4hfa8ME5Qp3jWaHtiE\nGFpcGpSCzpph1p3s+O6tPwlbc+yim/rcYvFYH30CAwEAAaOCA1swggNXMB8GA1Ud\nIwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBT0SH0HRRoyB5CR\nrAW4n6kR8H4RNjAdBgNVHREEFjAUghJyZXZva2VkLmJhZHNzbC5jb20wDgYDVR0P\nAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBrBgNVHR8E\nZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1zaGEyLWc1\nLmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1n\nNS5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcCARYcaHR0\ncHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBAgMwfAYIKwYBBQUHAQEE\ncDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYB\nBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJT\nZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCCAX4GCisGAQQB1nkCBAIE\nggFuBIIBagFoAHUApLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BAAAAFW\n7KE32gAABAMARjBEAiA/bKj1xHwBTMNaKCdQR2PZrOG+Lb+HeMs6gJckdM0W9wIg\ncf+TorVUfn9TRX9ZWmAYIVyrfR8IslSgs8SIpYPSY1UAdwBo9pj4H2SCvjqM7rko\nHUz8cVFdZ5PURNEKZ6y7T0/7xAAAAVbsoTehAAAEAwBIMEYCIQD+WZciTGwPOQXZ\n5Mp+O9OzRxthcrY6T9byo1dJSE9qbQIhAI8UGzwbiaMdcOzU1xG8+Qs8YKyMhHMk\naw43blN/nX80AHYAVhQGmi/XwuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFW\n7KE4fwAABAMARzBFAiAOv1NZFwzsZgxeh7uPX7Z2hvJc/LyoucDfvBo77hHy0AIh\nAIclOeQymUjKIBsTlh3DLJhrG8DM5WcivZIU6WjNlYIyMA0GCSqGSIb3DQEBCwUA\nA4IBAQBaoEmIrWAfCFNM2bjc9UBBre/IewE7E3BEmfZcI0b3Osh9ySGtOklFgh5d\nOx6bago+YS32sZl0L5H51fGfrnQmizynjL4o/qw7cK4IVnGsVXxAiQItYSr9VHK/\nGlxwGZAVpHagf1YcwfCNXpk9g0FUaOViwVqiZIwBZHojuT+/Is8fwEeAH5TV8jCE\n+wcC+lugugkEmE7zJVZMxH7gJ9joMo+zPFqSS8B3LbDlrh+vHX8hnGUmvgy66A3B\n0me0uTPRSu78uK8DW8g+vPoJnQTOPqa1xHQ7MXrzLEKzx3PbqnUujYqeeTO+17YU\nmyare54Us1XmS7uGlBF0AjW0UnCb\n-----END CERTIFICATE-----\n\n-----BEGIN CERTIFICATE-----\nMIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\nQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\nMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\nU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\nnf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\nKpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\nkujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\nAQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\naHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\nLy9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\noDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\nQS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\nd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\nxtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\nCwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\nc+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\nj6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n-----END CERTIFICATE-----\n\n"
  },
  {
    "path": "src/test/resources/rsa_key.p8",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4CEi/SZ8oUjgQ\nLbyt2QAIAdy0ZU2ffiRAsua23ek3A211D2B2iAMKp/vbVV6zN0YTtLeSLhhDMeT6\nizBEAiEJXNU7FU6r7zQnLxKi8/25kW+RGV67b26fJ5WSYtGGGO//UmJUPvBedhu4\niRxpkRG93yHovJO/UDQOOqakiwAldXdYPnarlaUfMN4lg30s/IjEn0UAW/9f8a3C\nHm/ohIAp3PVca2VskK8jbccD3aNiTP0ZgCRznaTo2l7ZuMwrvvLpu0QB+9O5pAoH\nq1ZE/HW+/IWKyKFqg61QAXRx10Yfx+iKIN4p7AilcFPV+NrZ7ki7xpRzzKSwIr9u\n6HSP7TIhAgMBAAECggEAFZAq7fbzZicUGJkzVW9quIV3Vc/U445FMyuDKwQfcmm9\nZozxsZHIQXRjZ1S+buNYG7D+HWu3PhBXkx/B/QX+1vkApxadDDpP8xNp7JwIK7kl\nyGuflPi+jnl15hwcDslXPaRqjxJ82aSfJOUz5g6wdH3znDCUY0NDhjNLGVaMItdI\n0GPhazZ4EUiqD2HH+A41u0rH9L+N5oqLxJh+IzsBKywMjYC3AccomIDHodls/Ez5\nQjTb09Fz9nLsiQtUa3bDxDDedod0vA3JsE+DCIwqyOxGtIHMmYueF8nQFqQyfgJ1\nA17ScXFHPDzsCO1Cz+2DYBS3qHpxJ2NjmSSDO69D0QKBgQDckNCmHoBXicfQgOBu\nLt4A8UAYm4Qb2XvZfqsCDYeZln0NZw9v6EXDmG6dNB0DuExE/SBWSFelBZWVq3d/\n2ZsMtn1nVFuHJLWyFBg1rg9iDDF629ILew9rw42NNzA2RNua881afJfC2v+eEW2e\nVA1kJ131WGYMswSaHQqVuUmUdQKBgQDVmPdn1ZitcAw60Gojs2dQVcWLjio+mb92\nNpLopbWlD67+AnO2ObX0dJTPx2H7yQIZyMJqxFE93aNumnC44A10jbLsESROu1GQ\np9MoUmkU/t4fj2ApnBpKK2jwctEpt+MI0T1Iyz4MEOku7AF+bN1IRdytvFoJpHz1\nZVUKRVZBfQKBgQDP40ZlbFY3K8vDP9VIxK/GX5y0FNA9l4zeAD+aHbpzDp4rJbot\nFdW7d066qMW4Hdr1I0d5S5IgHdVRTl3BiQ5UuyQTYjUqeXUvtTTkU3x4Q1nXnJyq\n6YAtaBFgymWMoPYDZEtnQAuiQtuu1mplUn+UqG4ssvbsfUaXI0w4tn0diQKBgCyh\nn+CkcxiadxO7BNKc5BOXOIXmmNEn0yfiU2QlbAUnd8s/u+nxuSFxUr7vzHuODtBQ\ncL9achdZPpMX1kHrHyShjydglZOV9H8jci3UunxWnRc/IDrRkXS6CV1kwGvVLq1i\n+0lAnqBq0C6rxcwi90HoxynOV4n4QtwL5m8U+ecJAoGBALCt2oimQcvLmclwjOyn\njj/tlHh7sadb2pI3ZXpoCteJMuBVeCy5IpDL4WYoN7E8OuB2rS52m5Os9K+0f2gd\nnwAqZo73XBlq34zKqyxrOT4A2Bs4UzWcUr7HIm99IxjiDPkeSgfqAn1l4Ys2nmp9\nxuqeFm3+mtlt7pLKpY0CF2x2\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/rsa_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAuAhIv0mfKFI4EC28rdkACAHctGVNn34kQLLmtt3pNwNtdQ9g\ndogDCqf721VeszdGE7S3ki4YQzHk+oswRAIhCVzVOxVOq+80Jy8SovP9uZFvkRle\nu29unyeVkmLRhhjv/1JiVD7wXnYbuIkcaZERvd8h6LyTv1A0DjqmpIsAJXV3WD52\nq5WlHzDeJYN9LPyIxJ9FAFv/X/Gtwh5v6ISAKdz1XGtlbJCvI23HA92jYkz9GYAk\nc52k6Npe2bjMK77y6btEAfvTuaQKB6tWRPx1vvyFisihaoOtUAF0cddGH8foiiDe\nKewIpXBT1fja2e5Iu8aUc8yksCK/buh0j+0yIQIDAQABAoIBABWQKu3282YnFBiZ\nM1VvariFd1XP1OOORTMrgysEH3JpvWaM8bGRyEF0Y2dUvm7jWBuw/h1rtz4QV5Mf\nwf0F/tb5AKcWnQw6T/MTaeycCCu5Jchrn5T4vo55deYcHA7JVz2kao8SfNmknyTl\nM+YOsHR985wwlGNDQ4YzSxlWjCLXSNBj4Ws2eBFIqg9hx/gONbtKx/S/jeaKi8SY\nfiM7ASssDI2AtwHHKJiAx6HZbPxM+UI029PRc/Zy7IkLVGt2w8Qw3naHdLwNybBP\ngwiMKsjsRrSBzJmLnhfJ0BakMn4CdQNe0nFxRzw87AjtQs/tg2AUt6h6cSdjY5kk\ngzuvQ9ECgYEA3JDQph6AV4nH0IDgbi7eAPFAGJuEG9l72X6rAg2HmZZ9DWcPb+hF\nw5hunTQdA7hMRP0gVkhXpQWVlat3f9mbDLZ9Z1RbhyS1shQYNa4PYgwxetvSC3sP\na8ONjTcwNkTbmvPNWnyXwtr/nhFtnlQNZCdd9VhmDLMEmh0KlblJlHUCgYEA1Zj3\nZ9WYrXAMOtBqI7NnUFXFi44qPpm/djaS6KW1pQ+u/gJztjm19HSUz8dh+8kCGcjC\nasRRPd2jbppwuOANdI2y7BEkTrtRkKfTKFJpFP7eH49gKZwaSito8HLRKbfjCNE9\nSMs+DBDpLuwBfmzdSEXcrbxaCaR89WVVCkVWQX0CgYEAz+NGZWxWNyvLwz/VSMSv\nxl+ctBTQPZeM3gA/mh26cw6eKyW6LRXVu3dOuqjFuB3a9SNHeUuSIB3VUU5dwYkO\nVLskE2I1Knl1L7U05FN8eENZ15ycqumALWgRYMpljKD2A2RLZ0ALokLbrtZqZVJ/\nlKhuLLL27H1GlyNMOLZ9HYkCgYAsoZ/gpHMYmncTuwTSnOQTlziF5pjRJ9Mn4lNk\nJWwFJ3fLP7vp8bkhcVK+78x7jg7QUHC/WnIXWT6TF9ZB6x8koY8nYJWTlfR/I3It\n1Lp8Vp0XPyA60ZF0ugldZMBr1S6tYvtJQJ6gatAuq8XMIvdB6McpzleJ+ELcC+Zv\nFPnnCQKBgQCwrdqIpkHLy5nJcIzsp44/7ZR4e7GnW9qSN2V6aArXiTLgVXgsuSKQ\ny+FmKDexPDrgdq0udpuTrPSvtH9oHZ8AKmaO91wZat+Myqssazk+ANgbOFM1nFK+\nxyJvfSMY4gz5HkoH6gJ9ZeGLNp5qfcbqnhZt/prZbe6SyqWNAhdsdg==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/rsa_key.pub",
    "content": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuAhIv0mfKFI4EC28rdkA\nCAHctGVNn34kQLLmtt3pNwNtdQ9gdogDCqf721VeszdGE7S3ki4YQzHk+oswRAIh\nCVzVOxVOq+80Jy8SovP9uZFvkRleu29unyeVkmLRhhjv/1JiVD7wXnYbuIkcaZER\nvd8h6LyTv1A0DjqmpIsAJXV3WD52q5WlHzDeJYN9LPyIxJ9FAFv/X/Gtwh5v6ISA\nKdz1XGtlbJCvI23HA92jYkz9GYAkc52k6Npe2bjMK77y6btEAfvTuaQKB6tWRPx1\nvvyFisihaoOtUAF0cddGH8foiiDeKewIpXBT1fja2e5Iu8aUc8yksCK/buh0j+0y\nIQIDAQAB\n-----END PUBLIC KEY-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_root_ca1_chain.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDmTCCAoGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMCVVMx\nEDAOBgNVBAgMB0FyaXpvbmExEzARBgNVBAcMClNjb3R0c2RhbGUxHjAcBgNVBAoM\nFVNUIFRlY2hub2xvZ2llcywgSW5jLjE0MDIGA1UEAwwrU1QgU2VydmljZXMgUm9v\ndCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjAeFw0yNTA2MTcxMzE3MTVaFw0z\nNTA2MTUxMzE3MTVaMDMxCzAJBgNVBAYTAlVTMQwwCgYDVQQKDANBbXoxFjAUBgNV\nBAMMDUFteiBSb290IENBIDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQDTXnC2qCVnPT7IXm1bhgWcM2DRwNfPu17/mTyT5buG8AKq6PU9CrtlFU1yLdyM\nRqqSknuijN9IKNEhISaDKBftFqf5TsQqoh3B8kAJ7D+VXh6XyWxasMQ6evXqdhXj\nsbpIPwNxnqUt9O7m1Kctb1WiZ8RcN5hb28TSlnYRfvKM71H5AZb/c0sPqMly+f7f\nBCmENk9gp2WYMHGk/4FdH9kME4OklSxpJWrYBP7hEWENYjJw1KeoXqajXt77/Zmd\n8Pmtull7ja/LYJcXmivf3K6IbHdBCYy92btG4+36BaDR9gQRitUIAeADL4hsbO1S\njVKpmCGLFzLyWGm1xXRmswinAgMBAAGjYDBeMAwGA1UdEwQFMAMBAf8wDgYDVR0P\nAQH/BAQDAgGGMB0GA1UdDgQWBBRargAOlN3a5jmtZY88+1jS0lZ7aDAfBgNVHSME\nGDAWgBR2toMU+nqJMu7A9Y4HvYsrCWOYYTANBgkqhkiG9w0BAQsFAAOCAQEAQ13l\n1r6V/0C6Jsm0hBsQcl2k+FaZ17Sd+QR1hOKtfAn34qR994IjgE3e32F53FCBIOmm\nn+Sjpi7PwObt6xi9rQMoUiqRbPjgbx4m9OcMNv59wU9+jXG1YSFALpUNyzGBfGyq\nnwAfOj6Fvv6ZJTaTaPlQcuddBDSWB2h01RRA5GM1H+lnQdeObQ9wiaFdR+GvY1aV\nMrJd4GaM5XfiGFbHRq1KKOlAKI4d9DEgFOnL41r3dMX+bTKHw8/RqkGZhEmoIqU7\n9OAtzJrIn+d/5o5L0I4pLPsFuIyb5ZlmW7lxDMILOKXQjGBEGcFanu2uuwz0Boyh\nokEluJh50uUWPXNUVA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_root_ca1_chain.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICeDCCAWACAQAwMzELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0FtejEWMBQGA1UE\nAwwNQW16IFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANNecLaoJWc9PshebVuGBZwzYNHA18+7Xv+ZPJPlu4bwAqro9T0Ku2UVTXIt3IxG\nqpKSe6KM30go0SEhJoMoF+0Wp/lOxCqiHcHyQAnsP5VeHpfJbFqwxDp69ep2FeOx\nukg/A3GepS307ubUpy1vVaJnxFw3mFvbxNKWdhF+8ozvUfkBlv9zSw+oyXL5/t8E\nKYQ2T2CnZZgwcaT/gV0f2QwTg6SVLGklatgE/uERYQ1iMnDUp6hepqNe3vv9mZ3w\n+a26WXuNr8tglxeaK9/crohsd0EJjL3Zu0bj7foFoNH2BBGK1QgB4AMviGxs7VKN\nUqmYIYsXMvJYabXFdGazCKcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCDjqAs\ngv8DAhrXcCSBL8zVM5PbJWCtwL/RwnVURZZdTYZHjgjwYhtLepbPBo2mu9i2OSuW\nKrC2u18ZBGUxuuJaxB4e3XbbThnFROU2qgC4oXUhA91s2uSP7YB3c+qcEbMO3uo1\nncXIZhxefIDnxZA/rwIbun+nxEhogdGHbls2cq8mNJCfM5we6A5wSVN87PiNzyed\nlQ2Bo4iXRi4MKdAjflKinLAfbikyVHEPT6eRvghk2f1hXZXQ1HSqNshAKZEBV4Ke\ndmrRbvnJ/Ci5PJKSB5ZfEBsTHWd/dUpCEb5Ty+bGPZLzDyjL+sUqDVkR722N5zTy\nMx0JsC3qBVEHozcY\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_root_ca1_chain.srl",
    "content": "02\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_root_ca1_common.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTXnC2qCVnPT7I\nXm1bhgWcM2DRwNfPu17/mTyT5buG8AKq6PU9CrtlFU1yLdyMRqqSknuijN9IKNEh\nISaDKBftFqf5TsQqoh3B8kAJ7D+VXh6XyWxasMQ6evXqdhXjsbpIPwNxnqUt9O7m\n1Kctb1WiZ8RcN5hb28TSlnYRfvKM71H5AZb/c0sPqMly+f7fBCmENk9gp2WYMHGk\n/4FdH9kME4OklSxpJWrYBP7hEWENYjJw1KeoXqajXt77/Zmd8Pmtull7ja/LYJcX\nmivf3K6IbHdBCYy92btG4+36BaDR9gQRitUIAeADL4hsbO1SjVKpmCGLFzLyWGm1\nxXRmswinAgMBAAECggEAAQuWNV/AzckizbFlDQYp4uKs7qFOgdQQ41xuKxLhstUX\nSPPwShqhBBPf1npntyO9QauivEWEqGhcpx0dj6CdfZGARipYyrfLc+5w7ZdeEe21\nNmHoeHsJMoHZ/H0ehrnTWwF3pg1uxwQjAARZYt5+cHA3s/WF4/YwJLUSY2owHNoP\nc3O73vKYKUNf86sjE7uAfak5KV1U8C7spBVp579pRijogJkc+l4wuBSj4CZkuwfN\nDX02jcOnan8kPeZt85KhD9eRQu8GtzLhhOz6H+bzhhXUs6uI/FiqVSBR65F19djv\nRLJoLdoDeE9a7G69BLL87gp8R8EpyCsSMgPsKrQZMQKBgQDqZ/e14Cgio0czY8Nw\nNmcbfKU8JyU/qZZPQMJgHKssGkdodRhlTYakOIj/3osB91NAz+3pJo08FuOnQhS4\n0hj0FqBIGiIdeC1OgRGf2mW+0krb0pHz1D0AmJPJzwCWznMNcVvggAEHJBvRxGsx\nZziJKng+Mqt/B3MGHngQYkviDwKBgQDm1y7Kn/PzokgWRdBzbm44ztikkzgvdPC1\ndr4MtqCAu7VsiNa1NpYB/PohNFFqltV38ensK6EG5yRoyxAEJnR3s0XPPor4ca64\nM0nc+yWDtYZQMiHQC8+sUmwmsyOYmBA4oCUfG5ffvvdgi9ZrBnzxjDmISUMxyUym\n4pYYpq4n6QKBgQDS7qfLtGCpesBxkA6ohWwXIf0WuisctKCbxKylDGdEEom7h9Ni\nJXdJg85UYZA5PrReGD4Alj51mitVsXMwNW9bBplJCKs2VZR/lkmjzUXNhzLxyPSp\ns40JYkNrUKk5C6IgsKd6x/Uk1etqwhOrqzUGyX7WiYYlkzCAix6Dr9CVJQKBgQDM\n+8AdRjSG4FBLFuun1SQjZeqTGfgGjD1DppHVEEcuiYDtRAYFHNfQJCipW6AAmrgs\nrrEeew+e5Zo7RDaQuOUrdhJDmjIltC2GfGTWyl3hu0vt4taLvDFJwFcxdlnhowak\ngfD00rfmg9l/7i0VFMnWZMKh9wyoJSzf9M18TmbQIQKBgBr6629V68GSJiXjI2Z0\nWhi2H18A7rjYLvKR5f9rbIh8gStYTiAQHX0sNv/oSrURTo6zF3nHD6ZH9+rIWZOP\nlvs7ioZ4JLZb2OUpDYJKr08rfVxJTeF9HrVpCGDlAG0EQheq7FqI7W+9Y5UjNO4U\n+1+idGbwB0SnUHWTa2tINq2H\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_root_ca1_trust_store.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDMzCCAhugAwIBAgIUSkRVVuj/KLOZn8WrReXEZ+rg93swDQYJKoZIhvcNAQEL\nBQAwMzELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0FtejEWMBQGA1UEAwwNQW16IFJv\nb3QgQ0EgMTAeFw0yNTA2MTcxMzE3MTRaFw0zNTA2MTUxMzE3MTRaMDMxCzAJBgNV\nBAYTAlVTMQwwCgYDVQQKDANBbXoxFjAUBgNVBAMMDUFteiBSb290IENBIDEwggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTXnC2qCVnPT7IXm1bhgWcM2DR\nwNfPu17/mTyT5buG8AKq6PU9CrtlFU1yLdyMRqqSknuijN9IKNEhISaDKBftFqf5\nTsQqoh3B8kAJ7D+VXh6XyWxasMQ6evXqdhXjsbpIPwNxnqUt9O7m1Kctb1WiZ8Rc\nN5hb28TSlnYRfvKM71H5AZb/c0sPqMly+f7fBCmENk9gp2WYMHGk/4FdH9kME4Ok\nlSxpJWrYBP7hEWENYjJw1KeoXqajXt77/Zmd8Pmtull7ja/LYJcXmivf3K6IbHdB\nCYy92btG4+36BaDR9gQRitUIAeADL4hsbO1SjVKpmCGLFzLyWGm1xXRmswinAgMB\nAAGjPzA9MAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBRa\nrgAOlN3a5jmtZY88+1jS0lZ7aDANBgkqhkiG9w0BAQsFAAOCAQEAWWRwtyyRGW6q\nq/UHTMpDyNwNfPRoC3dd91yNZkcAlKgdr+UdeTYXHBf1rtewj4T5nPVWQkFoDOYJ\n/pTq+3YdHw/6yxnXGdaDN1YPf6wVMFHNHi1mubI+6rDfLcmEYtPqzInB9pf3SNGk\nsj3VvORshcLywSM5y+Bu3TjCyyagLBIZE84FEowioEmidy/66txKrD5/n8WLnXdD\nPnHWUcdKeH+YnTXBYRBVY6EJAGZ6avPY91kZXeB5bbFqnbhzXDI9pXtPeXRGdCnG\nFbe9ahk6DcRmUWtR4ZAPpUXL1vc4g1PHFatq+7iRMADzLmA0BjahBj2N66XehRFl\nQwcbrhc4fg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_root_ca1_trust_store.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICeDCCAWACAQAwMzELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0FtejEWMBQGA1UE\nAwwNQW16IFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANNecLaoJWc9PshebVuGBZwzYNHA18+7Xv+ZPJPlu4bwAqro9T0Ku2UVTXIt3IxG\nqpKSe6KM30go0SEhJoMoF+0Wp/lOxCqiHcHyQAnsP5VeHpfJbFqwxDp69ep2FeOx\nukg/A3GepS307ubUpy1vVaJnxFw3mFvbxNKWdhF+8ozvUfkBlv9zSw+oyXL5/t8E\nKYQ2T2CnZZgwcaT/gV0f2QwTg6SVLGklatgE/uERYQ1iMnDUp6hepqNe3vv9mZ3w\n+a26WXuNr8tglxeaK9/crohsd0EJjL3Zu0bj7foFoNH2BBGK1QgB4AMviGxs7VKN\nUqmYIYsXMvJYabXFdGazCKcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCDjqAs\ngv8DAhrXcCSBL8zVM5PbJWCtwL/RwnVURZZdTYZHjgjwYhtLepbPBo2mu9i2OSuW\nKrC2u18ZBGUxuuJaxB4e3XbbThnFROU2qgC4oXUhA91s2uSP7YB3c+qcEbMO3uo1\nncXIZhxefIDnxZA/rwIbun+nxEhogdGHbls2cq8mNJCfM5we6A5wSVN87PiNzyed\nlQ2Bo4iXRi4MKdAjflKinLAfbikyVHEPT6eRvghk2f1hXZXQ1HSqNshAKZEBV4Ke\ndmrRbvnJ/Ci5PJKSB5ZfEBsTHWd/dUpCEb5Ty+bGPZLzDyjL+sUqDVkR722N5zTy\nMx0JsC3qBVEHozcY\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_rsa_m02_intermediate.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDRDCCAiygAwIBAgIBAjANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJVUzEM\nMAoGA1UECgwDQW16MRYwFAYDVQQDDA1BbXogUm9vdCBDQSAxMB4XDTI1MDYxNzEz\nMTcxNVoXDTM1MDYxNTEzMTcxNVowNjELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0Ft\nejEZMBcGA1UEAwwQQW16IFJTQSAyMDQ4IE0wMjCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAN2zI7RCujotYqL6h7xUvSBK2YtguTV99YwEh9yfOvnwYVTs\ncZop1Wdie11Uh663DxSeQhyLi2rEcvrb7A/VW0VrTvQXlBw6X899wHXmKlWsPo3S\nRjT10AN27eIo5ZzjPXXTYgMmVbBhNa56wqI62OfVc+hDN5SfJRy03WgUcGpHwSLH\nuBqNOMubhQUs/FoWqODyqinG5g2GwcRvWM6HXt0SDtZOHyVSGXaEQU0Qk4Eb3YEs\nfF9qLT/1HYf4rcmbF6t0J3rwCLnZneXGvK0vR8Nmk7OEvlWrqibyKZvnFa+o3LcO\nEsY2aNmCi6gdBRT2WxYs2PSGaNgas7XClLEM9KMCAwEAAaNgMF4wDAYDVR0TBAUw\nAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFExmY2mRqPSwAE0DtCVJXPYz\n2LlMMB8GA1UdIwQYMBaAFFquAA6U3drmOa1ljzz7WNLSVntoMA0GCSqGSIb3DQEB\nCwUAA4IBAQAh9Wk080GOndzWeg0HB+QRGgTPI+0Kqc+eump2sgsKyx7LIt+6z3aE\nPFnEAIdqN02oLTnJP8o7lmMFodi/d3Q5nAhkI+6rP8yjJI+yenbC7w+XLqlZ13Yl\nsAK/jFv43ZwrVv7kf5Bbm/fNWo2IbxxWPm8wpOzAQNEPBlyoWeK9QSF05EqUGCij\nFeP5Ymcz4IUKI0ZssixBoqGsp6+bwhOGH0STmAIBXYikdEcCT4a2xXO+K2e5fIUT\nVm1EkDmmgoA1TcgMwR9VUrgpqVFS4LJg8DRa5SvY42Cg+v/LDegpqlZurUbFTa0/\nlR70dnolTCDp4l7ApMl+fYQkCE3Vsx0V\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_rsa_m02_intermediate.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICezCCAWMCAQAwNjELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0FtejEZMBcGA1UE\nAwwQQW16IFJTQSAyMDQ4IE0wMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBAN2zI7RCujotYqL6h7xUvSBK2YtguTV99YwEh9yfOvnwYVTscZop1Wdie11U\nh663DxSeQhyLi2rEcvrb7A/VW0VrTvQXlBw6X899wHXmKlWsPo3SRjT10AN27eIo\n5ZzjPXXTYgMmVbBhNa56wqI62OfVc+hDN5SfJRy03WgUcGpHwSLHuBqNOMubhQUs\n/FoWqODyqinG5g2GwcRvWM6HXt0SDtZOHyVSGXaEQU0Qk4Eb3YEsfF9qLT/1HYf4\nrcmbF6t0J3rwCLnZneXGvK0vR8Nmk7OEvlWrqibyKZvnFa+o3LcOEsY2aNmCi6gd\nBRT2WxYs2PSGaNgas7XClLEM9KMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQA1\n1t9F9z6x2Dm+QUD+83OPZphJPOzKy8kX88STnkf73vSRyJhZilGrH/uBhyyQyMGe\n1koGzI8DOid29AVsB/2b0nsXXuGNpisa/ucBXcTWorRQu1WG4iQkFvwff7vugcZT\n2XhnV81ifrBaTG11mqg+iic9ecH/V26R8uyDvEvledOkXsib6vI5T7/XUUdfEkyi\no+8RX0NI24qoKWSPnejPohL+5iAjsZCWmlKnLQ3wVIKIykbjtHAe1wYlMfPLNmWq\nfTqXVgbyW0cIlit4CCOehJ+IKQjlkxFd4wiUtL8fsv+fzFu1Iy22AFjUhVMCRTp+\nsJLLk2nbP5io5f+jzibB\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_rsa_m02_intermediate.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdsyO0Qro6LWKi\n+oe8VL0gStmLYLk1ffWMBIfcnzr58GFU7HGaKdVnYntdVIeutw8UnkIci4tqxHL6\n2+wP1VtFa070F5QcOl/PfcB15ipVrD6N0kY09dADdu3iKOWc4z1102IDJlWwYTWu\nesKiOtjn1XPoQzeUnyUctN1oFHBqR8Eix7gajTjLm4UFLPxaFqjg8qopxuYNhsHE\nb1jOh17dEg7WTh8lUhl2hEFNEJOBG92BLHxfai0/9R2H+K3JmxerdCd68Ai52Z3l\nxrytL0fDZpOzhL5Vq6om8imb5xWvqNy3DhLGNmjZgouoHQUU9lsWLNj0hmjYGrO1\nwpSxDPSjAgMBAAECggEADA/QnXoT6VGW0Dv5LpHqe3Tj3EvPrjPDX3IC2+OXfdZ9\naFvRqNi/asSJs0ydPsnO4QkYAVDGR3GXH91Fo21KoSUMqQZI+wNiB+C7m+G0SlaS\nTX0A1nJmD6PLOK0o8OqcHJ3HjPK4LsR0yAb9yxddV1LeeRob6AYdXfzcUh4dwtDS\noA6BessR+UltvnuUts3D8VufpcxYjZ6zXOECgpTt6j1eJhxoKRO5Qtpk2837NGRn\n92WjRlnTJsdt8KND+RCj849vrWgOfdFruzl8AG6oI1y3/Eh7R7eveiKXuQH8cu+o\nq0KcuSZnV0sCm8Wrl7u0OMtqD/ETGaFLc4JWTss+IQKBgQD7/CeznG+W1Yid0icG\n/j86CU/Rcv1PM1LreCrqEnyKeIlqwtkAJToNAXpZIV5nsUTj9gu6kQN0Fssb2tcf\ntw2fGQc/yBJyTljyCMyzZmSWJIYiLwE3hrrpwVwN9+Uv1v+jcaBg03OMKakt91wA\nFdxunL5LOE5piKTSEoOEcKxSrQKBgQDhO3OCBAVldCKYhBZxe5lRYdzJVV+ewKOK\nVoWcXpLr4xAqhIqN+nkuKRLF5RLLjFInKcVL8eBTc+CqFDPkJd13WfNdNpbjmLZh\nFyGb5HCXK07UAkTWAC5wqEuXvB+aeq9l3PGEEpZBT5ZBzkHIeKHwhEGawyxZSx/R\nyNrC+VSejwKBgDKlN4WOB4+VU2QE/PsW1rZfcL90ER19/0h8WoO5O3bJSzU9/Edf\nNA+xIFoRrtvAcUwwA6N3ye3nNuNeVKZ3MDGt4hsbpzl6Lb5XrmzQQ585sAvmzqPO\nXHrm9g2IqXO4DqFXERjk5vBg3zPx53fM7QT+U/DiXEAxyo6+UdionLnxAoGBAJI/\nAh938PERWa1ihxgkw0a+npf0YWQ5fXdD+vmvxwJ3q3WQHk9WRf0dGl15Ap08fdOE\nIeHQKnLFmSmWeKM5mvX2aDksSwl5jWK5nE8XI6xkqIis+HlkIMNx47OgqXyrENy5\ne/DgCMRboxsTjE9ZVP1RQy/0bwWD7CNRogpbzwAtAoGBAIDhjkUeHQbTI2G5DDVa\njZGV/z4QOFrqkDyG4FhNsGZten46cqzYdHuL+f9PHp8S5injddPGSEgXYoW3w1zG\nip+4ju0sFc7OEUESi76RYNlWwf7qJ5CTV4mEbeWqIu2LkTKRs/zt4GiHOIiREbHL\nZHggv+LFzgVnq/dxO0XVxuFx\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/amz_rsa_m02_intermediate.srl",
    "content": "02\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/leaf.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIID9zCCAt+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJVUzEM\nMAoGA1UECgwDQW16MRkwFwYDVQQDDBBBbXogUlNBIDIwNDggTTAyMB4XDTI1MDYx\nNzEzMTcxNVoXDTM1MDYxNTEzMTcxNVowLDEqMCgGA1UEAwwhKi5wcm9kMy51cy13\nZXN0LTIuZXhhbXBsZWRhdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEAod0IpTDutteqcBB0h5VeOr87cnjPKp6ercbtN3323NFHS87MNtmqR7Vk\n3pPPXavyPQ80tR3KT+2J52DWB/aBLad1qo1+CG+CbjbZcoFJdDIiNSP8JsPoK4kk\nSMMUTXzvmptCoOJR6KpYiAJcQWnGBh6qIwzOjy08fiNjLeh/1vutBFmuYQN0He2X\nOeDodaQ0OdahYfZUAwbaT28NWgrR1nXJpVQ79IZPZQYf/ei48AON7Uei2ilQdHJ+\nBFjj+qIwZPJLb4bEMc6WIDopwDVOpK3M2hiEudraZ31GGMfthVRyKfoI+rWcuDjD\nvYCnhJCn2E+1N/pq6KIcMF/uVmqr5QIDAQABo4IBGDCCARQwCQYDVR0TBAIwADAO\nBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwgaEGA1UdEQSBmTCB\nloIhKi5wcm9kMy51cy13ZXN0LTIuZXhhbXBsZWRhdGEuY29tghsqLnVzLXdlc3Qt\nMi5leGFtcGxlZGF0YS5jb22CGCouZ2xvYmFsLmV4YW1wbGVkYXRhLmNvbYIRKi5l\neGFtcGxlZGF0YS5jb22CJyoucHJvZDMudXMtd2VzdC0yLmNsb3VkLmV4YW1wbGVk\nYXRhLmNvbTAdBgNVHQ4EFgQUd2tp2r73dMdtQYn8faaUrhEUpu0wHwYDVR0jBBgw\nFoAUTGZjaZGo9LAATQO0JUlc9jPYuUwwDQYJKoZIhvcNAQELBQADggEBAIbHS92U\niXjTGhoOrIRVvXUtXIEsOA9l/nFw3vEQxpjCq7pvyMs0KNk2aggU6s0nIpyJIsQ9\nA72psN8sF+Sc5T5pHpwfIFDsRUSvQhRHkE2wTbzn41K/EuyJ5Q4lMFvEdqjYGrEM\nlSH4n15vp38rg/P8Ry3ZEtm1lj9iteRxaam8+DEO95OROrI9umK2bBJ1pYEzHddG\nUf0VMJFnukpWU8rMm41idQXg07mKUDAXJE98oS4/+z42KtzSwEKE5siH/zl39UhC\njVMvHDOkUXLV9VQG/60VwlJyXL4c+IhPJ1H9EPzwPS8djcNvx93Tk0X0LKg2nbqf\nuzqMxcBu98NYLY4=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/leaf.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICcTCCAVkCAQAwLDEqMCgGA1UEAwwhKi5wcm9kMy51cy13ZXN0LTIuZXhhbXBs\nZWRhdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAod0IpTDu\ntteqcBB0h5VeOr87cnjPKp6ercbtN3323NFHS87MNtmqR7Vk3pPPXavyPQ80tR3K\nT+2J52DWB/aBLad1qo1+CG+CbjbZcoFJdDIiNSP8JsPoK4kkSMMUTXzvmptCoOJR\n6KpYiAJcQWnGBh6qIwzOjy08fiNjLeh/1vutBFmuYQN0He2XOeDodaQ0OdahYfZU\nAwbaT28NWgrR1nXJpVQ79IZPZQYf/ei48AON7Uei2ilQdHJ+BFjj+qIwZPJLb4bE\nMc6WIDopwDVOpK3M2hiEudraZ31GGMfthVRyKfoI+rWcuDjDvYCnhJCn2E+1N/pq\n6KIcMF/uVmqr5QIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAAGPnavkM+IP4s/o\n3oSaCoJLJq+c2+2OUCEL0VkVl0sKMvydMe/jC0M9a+DKIRzv8CpG9Iatb1bWvst/\nNnn6jHz4Qr7DalZpNeI4nXacevpVv9R5aWOoPktAxQmZdFeB4oAMbLutP+M8wn9b\nlGenyqE0y9YXwiRUmkZNtdLwfHd8l+GsfLRczLJ7o1KOC1V4iSBKrNR7z4J0fdJ3\naPjMpSrtdm3sPdaHn/E8i4WQqlKLxtzUKj5mkf8CYa7WA0r3+woBrWqGvXQmoOZk\ns5v0fAFsg9o3/VN0l9uJEDZIQI8PsbV4Ja58NaFhV9YqA0wdeYQsn0icEujPcK8G\n0z+p4bE=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/leaf.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh3QilMO6216pw\nEHSHlV46vztyeM8qnp6txu03ffbc0UdLzsw22apHtWTek89dq/I9DzS1HcpP7Ynn\nYNYH9oEtp3WqjX4Ib4JuNtlygUl0MiI1I/wmw+griSRIwxRNfO+am0Kg4lHoqliI\nAlxBacYGHqojDM6PLTx+I2Mt6H/W+60EWa5hA3Qd7Zc54Oh1pDQ51qFh9lQDBtpP\nbw1aCtHWdcmlVDv0hk9lBh/96LjwA43tR6LaKVB0cn4EWOP6ojBk8ktvhsQxzpYg\nOinANU6krczaGIS52tpnfUYYx+2FVHIp+gj6tZy4OMO9gKeEkKfYT7U3+mroohww\nX+5WaqvlAgMBAAECggEAF7AYTsyNcta3C7VsyFbUtnY8BfGmJ8+U20usdZ2KniBG\nddKq0jAimmHyhBxPkZ7TUZpDFWpprC7QKrtiN0Ic/4htUOtqEWP4S5bdVcOT81xO\n40YD5cxv0sc8NyXNd9Wb3x1vr8nPQOSFFkp6I9yN7WFZds2uAQgWvUMChbpr0xNA\nD+wJBCWNI94IKa+Gt02fkOTrPxtUpLI9AhKX64wX/XysJOy30ZOH+BjfE21wkODG\nY6K4pIhtgLgkqnzYq7EcfFs1hpCE9ur3V3+eojLlHAGGcZDprT/9u+IhPnLrGHl0\nxYpxJEuLwCIjazoK/Kbm6SI/inalo863w597TZl7VQKBgQDgMSG6aQ5kLRwyU6xF\n4XRJt+lFkgC5seRVrVJdEtR4ayfj3quw2YLCH6zDu2rpVmzn8LKqeJeD+wS+IaHm\nOnVVWbmvUKkAh1rsfK0fwjIHzcEHIyu7a6G2Cc1Cww+QmOmdwo6zQSBxfdu9SSba\nv7aAEjPt5Yf0Je8oa81tWbvvHwKBgQC41BHyGGYzYEchYHY7htvk43IXuJ2Nt72o\neM3QmRofMGeci3W6sF8JUS2AVsNsTGMSqgxa0DRnTxQU9j1MsLn/Hlcsy6rE4ckY\nh8roJurD+Qvvq+RKuq4vf+/GrQY8G+MYjEZZQVhPgrNpVmPv4G16fY5mwlxA8B5l\nz/aL2TA4ewKBgGqILkTMQyBrJyjrBcO3HHEjSV/bvyh+Vcg4shi0J+6IzM3FM8S3\ntyDTuF7SIj166glVITFMWINItro6dRpX0QWbu1aJd6VerGAP5pxexTNcHEtt9Qv8\nWz2LbtQ4qJ7HasuidfjMC8SiT9akeigcTXaSL8bt+SqVFA4bG+TIbTt1AoGABd5u\nB3Lw49a9y3iFqJQ6rUTvnYM2NYbNFeloF6SU6MKft5/SCDCLg/8446ddozW1u6T8\nqXGNCG7BY30CK0o9TbWH7Rs2e/Pzb7z4G3EI6VoiH+UkZZ8R4t48HeHmncgA6Qmj\nPjhN9pBe5AxjNf25XDdaazbmuQUm4fK9kGlAF7sCgYEAq6ibgcEoS7f1sA27lT8Z\n1tCo6c1RCF5mA+BeQTXB+bnr5l1s7Tgcq7Ueo4Baxoh6M/0Ov+Oecdc8lVERabKA\nAM0OXDVavjjlmWSRSK1BJALEg3rocPG7aZv3LFJnMtROPA6xyCRJnciF2lievBy2\nySMItL6e4DhHdPO0PUY7YAs=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_class2_root.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDgTCCAmmgAwIBAgIUSwKblaL/F5H7WVcYZlcan0iW0pQwDQYJKoZIhvcNAQEL\nBQAwWjErMCkGA1UECwwiU1QgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\neTEeMBwGA1UECgwVU1QgVGVjaG5vbG9naWVzLCBJbmMuMQswCQYDVQQGEwJVUzAe\nFw0yNTA2MTcxMzE3MTRaFw0zNTA2MTUxMzE3MTRaMFoxKzApBgNVBAsMIlNUIENs\nYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHjAcBgNVBAoMFVNUIFRlY2hu\nb2xvZ2llcywgSW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IB\nDwAwggEKAoIBAQDHv6W8Gm9Kb8oeKbVzG5c26qeKwmfRuJS1e5i1+lm4GXutorYc\n6oC1/c0vUA2Ox3jIdIcyueDypql3ra9iZ7w6dmMkmhnrSKkAkzI+jQQqusbNmIrB\nGSfa6KxA2VF07z471OiGFglvLKOUxTe1hcXWPB2LBzmBqcdvFy/RhMOuzoOMweVC\nQC20k2XgRNBT0PLFWK7a2fm2gNmvqPSZvOzUUY/05lqzu1qzPfvOrHAhOxbRlTcx\n6s2SLLEXooyLouvfQ8FeKuB2WrJmxuTiYIeum+FJW9hS4D2EWZQIvLMROlgJrBfS\nR2HLmhPK82UTs9HamdUDUDTFut86EnpdMCfBAgMBAAGjPzA9MAwGA1UdEwQFMAMB\nAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS3CFxHvjlYpi2JRKmAMNYv0dwv\nsTANBgkqhkiG9w0BAQsFAAOCAQEAg3ut6Mj/3rUwNS0zy0R0YhT+53VyGWRIYpg8\nKkLNufdyxg33LT03T1LDIW6esOQJiZ6U07FgWWg7UkuFl6wHe9tIZ7jSSofCWCpR\nnFH2NYC3mUlcMPdux0BbqSN2xtlgf0Khg56oNkF/WfNTdTkKhc+fkSiZF30qxejr\nnFWZCoXA5euC4njvtOlRzZvk9o/4AKW0wRowWriL5gLo5wQRmR+RVIIvcPvtW3Qf\nUu9/EdoxAF62mqy0pPK+sepd0+6xbR3NATn95JNAK2Yu8J7usudHYp97Wiyrc5qS\nUbNpAaPx0X/OBzobXTIfRhG8bj2bQrQp+WE3q+TpSzlVwsqNpA==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_class2_root.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIICnzCCAYcCAQAwWjErMCkGA1UECwwiU1QgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9u\nIEF1dGhvcml0eTEeMBwGA1UECgwVU1QgVGVjaG5vbG9naWVzLCBJbmMuMQswCQYD\nVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMe/pbwab0pv\nyh4ptXMblzbqp4rCZ9G4lLV7mLX6WbgZe62ithzqgLX9zS9QDY7HeMh0hzK54PKm\nqXetr2JnvDp2YySaGetIqQCTMj6NBCq6xs2YisEZJ9rorEDZUXTvPjvU6IYWCW8s\no5TFN7WFxdY8HYsHOYGpx28XL9GEw67Og4zB5UJALbSTZeBE0FPQ8sVYrtrZ+baA\n2a+o9Jm87NRRj/TmWrO7WrM9+86scCE7FtGVNzHqzZIssReijIui699DwV4q4HZa\nsmbG5OJgh66b4Ulb2FLgPYRZlAi8sxE6WAmsF9JHYcuaE8rzZROz0dqZ1QNQNMW6\n3zoSel0wJ8ECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQALJq9k8ZxGhMt3ozXk\nCpNZCos3orr8EXprp4wXjpgOjfBvCD51InFecPTY0x8msrMEy6ayz3QeJz0ZUZ64\noPMOMBOSJ4fd2zr1Cndz98902UacgVd5vTeK0P2tXhYo0sTvkpyy0K9/LaUvkAqs\nf5soJkI1kijIhrSL3X/C1vBg8hG5AhZGyTfajhiIIUzgwpMczDpi2oXuy+kFMz+Y\nqG7gxzj9IhN0K3EyGp42qJAR04wi+QpyWFUnghanQ53Xqb2L76k11oPAdqjT4CfS\nh8Ql/eqc4yiZg/lgidr1aNDtQBaGUxaoU6oNyBzEW6zLTrhe9+RQcrHJU64fX3Zs\nfQh3\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_class2_root.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHv6W8Gm9Kb8oe\nKbVzG5c26qeKwmfRuJS1e5i1+lm4GXutorYc6oC1/c0vUA2Ox3jIdIcyueDypql3\nra9iZ7w6dmMkmhnrSKkAkzI+jQQqusbNmIrBGSfa6KxA2VF07z471OiGFglvLKOU\nxTe1hcXWPB2LBzmBqcdvFy/RhMOuzoOMweVCQC20k2XgRNBT0PLFWK7a2fm2gNmv\nqPSZvOzUUY/05lqzu1qzPfvOrHAhOxbRlTcx6s2SLLEXooyLouvfQ8FeKuB2WrJm\nxuTiYIeum+FJW9hS4D2EWZQIvLMROlgJrBfSR2HLmhPK82UTs9HamdUDUDTFut86\nEnpdMCfBAgMBAAECggEABnLTsymoT58hx40BqFxq6pEzRPnHN8BNfrllgfR52oVq\nFn4zAmZ4rsdpjR44vdQHPHN0nOjez/6c6HnxOExQiZOiWj9mWE/xcs0NuXK8gHOk\nsOT8j8TT4k+SJgrOBCgoGm7ibde+yLnIcyhVxtSxgRvSOVeLnfnA8D9vogQ2/Ajs\ni/rdwBZF9on3k9f7ko7L+YuMhLJa3FJpqCiJ8RS4Na1B0VhQ0KbLi/FTjF6XhR+c\nN6/t+8egfAGlcp7NL/Eqg+9snz32RhIMgBUuDZFbuxwEiUnH5tVDBUIvG5l/OGc6\n+9toW1xGRBV0DyRSHou1nbzW3i3ZAdP0wgc0FZ3XFQKBgQD7aU012t3vu4U84e9H\nVSSz/u33rnt0/HgvcvVhtbn1I9W+ksyrRlZBE53Qr7OXaAtmf1p5emPUlkocei6e\nWOMdy3cfNnYxX0fjKmE77Aw+54pJ7h0Mal2a54seo446NRefT2PCb9EknTb7JJct\nonqPYySc4a/ISt0/mGNeWO4fdQKBgQDLZPTDX8IQ3RVI7e6EHqmE0kWPygtD8QY3\npOL+HCpBqVlN2MhTUnWu2ExJxSUVnpTU200z5+msphUKvJRZy7zgPrM/T3k2PXs4\nusmG3xvFzUzcvdmW7YrKPJbBaGASkZt11flL4lcRIAW6xlFqjK4207lEeImAoo6p\nleQ2sCTJnQKBgC+pHsH+4+P2oKOSEVJsibC1u1cg9LaQxhf4qxwC32XhjTu7iKFP\njDp6BYjyRhGF3+NmcThmQ1ahxxru7reGEkgrskFwiaeCcJZYCmbZsNib/FNXmJop\n7+TV0EHWr7fJKNlCq57Io9VVGH8zyJUWIDXejapP/orx5k+QDAZuxXWhAoGBAMXS\n1XVJJ7m7Lj6WnIwIPpye32Vw/+/+9ysbxmOdBUSD8AYVORJBe5Cpc9m0U9NpQByf\nh8FaaR6xEno03J1R2czJbxG+vIgS2Exe8C+rxJloir+QL5fqRjlZIM/QMtEcqfW8\nPhKefEx9ttTomrtlRPSyjNmZ2zMv6E3tKeA/BijJAoGBANOsgV+tOH0o+McNbI2S\nFO9wWsg8eZzX+uGiOY9wHYZPVgyfZPGF6ONl6HqJ2HvEJrrjlYTrZV3/5nD0BwY9\nBta1vXYELCZIOQSdYz3Be73JUilvpmSQc+rgfjLTbZZ8yBoxKhSPfw6tu5UJZ2LH\na58i6I5/Jm1wJpTJZ/jEjt3C\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_class2_root.srl",
    "content": "02\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_g2_root.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDwDCCAqigAwIBAgIBAjANBgkqhkiG9w0BAQsFADBaMSswKQYDVQQLDCJTVCBD\nbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYDVQQKDBVTVCBUZWNo\nbm9sb2dpZXMsIEluYy4xCzAJBgNVBAYTAlVTMB4XDTI1MDYxNzEzMTcxNFoXDTM1\nMDYxNTEzMTcxNFowgYoxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMRMw\nEQYDVQQHDApTY290dHNkYWxlMR4wHAYDVQQKDBVTVCBUZWNobm9sb2dpZXMsIElu\nYy4xNDAyBgNVBAMMK1NUIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y\naXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyZBsVnnUI\nM463hyA9naMKe5TU8SkiwrQE5+ddjLuTV0tkvisMN2DlRxMEwDQdSFc2Ib/XJhXb\njp4/P5anmWMm5G0F9fe/0cyCahis7KshBw9JxQHLKYCR1uaPcMb/dcnIXA/H65dL\nNRjndclCRRQ1E6jrRrb0ZWH+cLyYlaqtp07OKxE+2H7XJ8QcjNh+Xvm6UNgKQewf\nLiT8FzQUaIyEXsGLKEFdVFQbWBnwtUZ4uHG/EFqzKG+872Vf59Z2YMGgPwAVkYKD\ng4u3/mtAGvUFALOjlfei2Cm8DqpLf52WZzRunGAIDRCnsJ3cWOejlF5oByulcV8C\neiEwXfPSv/qxAgMBAAGjYDBeMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQDAgGG\nMB0GA1UdDgQWBBR2toMU+nqJMu7A9Y4HvYsrCWOYYTAfBgNVHSMEGDAWgBS3CFxH\nvjlYpi2JRKmAMNYv0dwvsTANBgkqhkiG9w0BAQsFAAOCAQEADP8yzXGveZrwA5kP\nZLet357yMnwGaRm39/x2URlLBXxIIq7xRD4jdfVWI1ZlRW2JYrfzqpcR745xoWHC\nBGRfWnuHuivBINEyNIbUrzhEyDvSVexsiz+jfKXkTBKtc2kWpyByeTeeYO7Nvp23\nvVYWTxytMR7dodhYdZIcEsB6tmTihKDoABBhXZyn8SeGBMQ7zz71Vk8VVxiMgFpP\n/xcsKXkhj1zDM7/wF9ipRN1pBWRIkSwqPqIhJcwTs5nyHYxRiIhV/DlWDwcFre+7\nQprbxRKyg2NUJg7Z0o1vGZOpdACKp/4O2B592UhE9lIKTXMCgBD01uq1qS+BNg/4\nVdz3jQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_g2_root.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC0DCCAbgCAQAwgYoxCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdBcml6b25hMRMw\nEQYDVQQHDApTY290dHNkYWxlMR4wHAYDVQQKDBVTVCBUZWNobm9sb2dpZXMsIElu\nYy4xNDAyBgNVBAMMK1NUIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y\naXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyZBsVnnUI\nM463hyA9naMKe5TU8SkiwrQE5+ddjLuTV0tkvisMN2DlRxMEwDQdSFc2Ib/XJhXb\njp4/P5anmWMm5G0F9fe/0cyCahis7KshBw9JxQHLKYCR1uaPcMb/dcnIXA/H65dL\nNRjndclCRRQ1E6jrRrb0ZWH+cLyYlaqtp07OKxE+2H7XJ8QcjNh+Xvm6UNgKQewf\nLiT8FzQUaIyEXsGLKEFdVFQbWBnwtUZ4uHG/EFqzKG+872Vf59Z2YMGgPwAVkYKD\ng4u3/mtAGvUFALOjlfei2Cm8DqpLf52WZzRunGAIDRCnsJ3cWOejlF5oByulcV8C\neiEwXfPSv/qxAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAAYUYLUpsRDU3rwKn\nzKwEl4Mv1mrqrlLBnZli/VIOVwqxdkRTguU2y/DwaSuWRadlEWBG9e+PUvL3Fg86\nmp18y+KQNvj+j7phLPuQfZD+mbY6l2HFmTmzEbMme2GR/BMN0GNBiWjE23dMkLCF\n06dShi8IL3OhIvDrDQOQISmmgw/+KJ31eCB/DhPWenr7ft1yuxtZEiYEVQPrmzkU\nS9OF3hOMVNBnnX8MAK8njF1hfrUXXL/+/AjEyK2ounzDVmfszgwZ/PDPftnk3wOZ\nksYN95nc6ydLJbmXNy8Sf9ma4fyRySkJOfUMtMfkkO1DecOACuqhDHJ7mOz8No8q\nt5iQ3g==\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_g2_root.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCyZBsVnnUIM463\nhyA9naMKe5TU8SkiwrQE5+ddjLuTV0tkvisMN2DlRxMEwDQdSFc2Ib/XJhXbjp4/\nP5anmWMm5G0F9fe/0cyCahis7KshBw9JxQHLKYCR1uaPcMb/dcnIXA/H65dLNRjn\ndclCRRQ1E6jrRrb0ZWH+cLyYlaqtp07OKxE+2H7XJ8QcjNh+Xvm6UNgKQewfLiT8\nFzQUaIyEXsGLKEFdVFQbWBnwtUZ4uHG/EFqzKG+872Vf59Z2YMGgPwAVkYKDg4u3\n/mtAGvUFALOjlfei2Cm8DqpLf52WZzRunGAIDRCnsJ3cWOejlF5oByulcV8CeiEw\nXfPSv/qxAgMBAAECggEAAKCDv6f0RkB0ijZMn/YEbnAND7fx4QTy4CIjiOUXfLCK\ngiX9kNgTlfXrV6TITU9yVQbTcPFmk5Ehz8JmTs9mDvf8q4M+XDt9ZUdaFlnpNEWQ\nbj3/YCqUoKgzD8KBxI1le+smhNlrlioiqZ+bNgmRnsOJ4E45+UwI7X2RpofYikgC\nHExIZ8Vse6rd3wPXaZTLvhmpMKHL+mKfXShv7+1mlyD5zM4m8YAQaVGFNNObsb/0\ntj2e1Dayp9Us2tjVMtry7YJLiV9svZvlIws8Jwyr05H4RmuOOs2EP6TDsm0EO7jE\nseImZxBwWsYU3j89jo5bL0B9YC22XWOWXK5fti/qAQKBgQDZ9gWp475km5Cyrymy\nrry96BsaI3StbfSUozu5l6tW+77o80Y6JDpXshiOOBMTRqm6LSE8CzB4CStAHV9o\nG1nWCk5nwy97ZqqElHuquWHIxJnijCclsokXAYj2c2L+ZwDgPCZI1xMwbcQyF4oC\nolVR2pDCgNyxnoU2IaLkO/WEMQKBgQDRhjD1RWXykThWhKf3KiTm+UUlGTdeKdyL\nbFw75uL5tchvF2iA26DkJcD9o2P/HqoD+rLc04D51dm4NiexU+rELfKLd0RlNOyl\n2iMfyVKQ8xx4+PKgayxJSN87ftruRVjc8nP+nVenMyjjNRnLnUQBQ2tgrR+oxt1b\nOl3wA6y+gQKBgQCXMjjfjyKF+/w2V5gfYWOAgUA7s5i+reXaLhnCNtoerufSBOAU\neQkXyG2MSo3A82XNxnylgEWRHxsnkLicKUz6U4/sHtQTUDa9XZsj5RFWCjuNkhed\n6Ol8Ug5Gl+h1/dH3uwH7yWaAU8Sj0vxjs2RoMav1oRp3BDX4RQEQPh+kIQKBgD+b\ntZFAmEqju67IZxWqPbnBQzSYCy8TFllddELbPV0Byu+u85LrGlUpAQX/bIQvEv5S\nRaq0qxstyQMbrcEv8L3OFSwx8Rmoj49qQe+Mdvx6UdKIAWCiKLe23854HI7pjqjF\nEmHh8pJX/bm8jTA7dkHL7fVTmaDrfpgLiCktdGwBAoGALtly5oJ8dPsFp/1J7ujr\nHUsf45Mlc+qNMb/3KhFfQGaAsSTX0Xava4UCl7QVVCdBtY44qD1P9+1wmCmDTB6Q\nVctOjkDqQfWIZLwKCgWlJ2Y4GUbMxDhOdRnAHsHK1wkVomXM2ftwAayJNHz8lpYH\nOaNjVklPPiHXGXFsQAc+w3s=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "src/test/resources/ssl-tests/certs/st_g2_root.srl",
    "content": "02\n"
  },
  {
    "path": "src/test/resources/ssl-tests/generate_certs.sh",
    "content": "#!/bin/bash\nset -e # Exit immediately if a command exits with a non-zero status.\nset -o pipefail # Added for robustness with pipes\n\n# --- Script Configuration ---\n# Get the directory where the script is located, resolving symlinks and relative paths.\nSCRIPT_DIR=$(CDPATH= cd -- \"$(dirname -- \"$0\")\" && pwd -P)\n\n# Specific directory for certificates and keystores\nCERT_DIR=\"$SCRIPT_DIR/certs\"\n\necho \"DEBUG: Script is running from: '$PWD'\"\necho \"DEBUG: Resolved SCRIPT_DIR: '$SCRIPT_DIR'\"\necho \"DEBUG: Resolved CERT_BASE_DIR: '$CERT_BASE_DIR'\"\necho \"DEBUG: Resolved CERT_DIR: '$CERT_DIR'\"\n\n# Certificate validity period (in days)\nDAYS_VALID=3650 # ~10 years\n# Key bit length for RSA keys\nKEY_BITS=2048\n\n# Passwords for keystores\nCLIENT_KEYSTORE_PASSWORD=\"client_password\"\nTRUST_STORE_PASSWORD=\"changeit\"\n\n# Aliases for keystore entries\nCLIENT_KEY_ALIAS=\"client_leaf_key\"\nTRUST_STORE_ALIAS_ROOT_CA1_SELF_SIGNED=\"rootca1_self_signed_for_ts\"\n\n# --- Define Paths for ALL Certificates in this Scenario ---\nST_CLASS2_ROOT_KEY=\"$CERT_DIR/st_class2_root.key\"\nST_CLASS2_ROOT_CRT=\"$CERT_DIR/st_class2_root.crt\"\nST_CLASS2_ROOT_CSR=\"$CERT_DIR/st_class2_root.csr\"\nST_CLASS2_ROOT_SRL=\"$CERT_DIR/st_class2_root.srl\"\nST_CLASS2_ROOT_SUBJ=\"/OU=ST Class 2 Certification Authority/O=ST Technologies, Inc./C=US\"\n\nST_G2_ROOT_KEY=\"$CERT_DIR/st_g2_root.key\"\nST_G2_ROOT_CRT=\"$CERT_DIR/st_g2_root.crt\"\nST_G2_ROOT_CSR=\"$CERT_DIR/st_g2_root.csr\"\nST_G2_ROOT_SRL=\"$CERT_DIR/st_g2_root.srl\"\nST_G2_ROOT_SUBJ=\"/C=US/ST=Arizona/L=Scottsdale/O=ST Technologies, Inc./CN=ST Services Root Certificate Authority - G2\"\n\nAMZ_ROOT_CA1_COMMON_KEY=\"$CERT_DIR/amz_root_ca1_common.key\"\nAMZ_ROOT_CA1_SUBJ=\"/C=US/O=Amz/CN=Amz Root CA 1\"\n\nAMZ_ROOT_CA1_CHAIN_CRT=\"$CERT_DIR/amz_root_ca1_chain.crt\"\nAMZ_ROOT_CA1_CHAIN_CSR=\"$CERT_DIR/amz_root_ca1_chain.csr\"\nAMZ_ROOT_CA1_CHAIN_SRL=\"$CERT_DIR/amz_root_ca1_chain.srl\"\nAMZ_ROOT_CA1_CHAIN_KEY=\"$AMZ_ROOT_CA1_COMMON_KEY\" # This variable's definition is fine, but we'll bypass its usage for CAkey\n\nAMZ_RSA_M02_INTERMEDIATE_KEY=\"$CERT_DIR/amz_rsa_m02_intermediate.key\"\nAMZ_RSA_M02_INTERMEDIATE_CSR=\"$CERT_DIR/amz_rsa_m02_intermediate.csr\"\nAMZ_RSA_M02_INTERMEDIATE_CRT=\"$CERT_DIR/amz_rsa_m02_intermediate.crt\"\nAMZ_RSA_M02_INTERMEDIATE_SRL=\"$CERT_DIR/amz_rsa_m02_intermediate.srl\"\nAMZ_RSA_M02_SUBJ=\"/C=US/O=Amz/CN=Amz RSA 2048 M02\"\n\nLEAF_KEY=\"$CERT_DIR/leaf.key\"\nLEAF_CSR=\"$CERT_DIR/leaf.csr\"\nLEAF_CRT=\"$CERT_DIR/leaf.crt\"\nLEAF_SUBJ=\"/CN=*.prod3.us-west-2.exampledata.com\"\nLEAF_SANS=\"DNS:*.prod3.us-west-2.exampledata.com,DNS:*.us-west-2.exampledata.com,DNS:*.global.exampledata.com,DNS:*.exampledata.com,DNS:*.prod3.us-west-2.cloud.exampledata.com\"\n\nAMZ_ROOT_CA1_TRUST_STORE_CRT=\"$CERT_DIR/amz_root_ca1_trust_store.crt\"\nAMZ_ROOT_CA1_TRUST_STORE_CSR=\"$CERT_DIR/amz_root_ca1_trust_store.csr\"\nAMZ_ROOT_CA1_TRUST_STORE_SRL=\"$CERT_DIR/amz_root_ca1_trust_store.srl\"\n\nCLIENT_KEYSTORE_FILE=\"$CERT_DIR/client_keystore.p12\"\nTRUST_STORE_FILE=\"$CERT_DIR/truststore.jks\"\n\nEXT_CONF_CA=\"$CERT_DIR/ext_config_ca.cnf\"\nLEAF_EXT_CONF=\"$CERT_DIR/leaf_ext_conf.cnf\"\n\n\n# --- Cleanup Previous Run ---\necho \"--- Cleaning up existing test certificates and keystores in '$CERT_BASE_DIR'...\"\nrm -rf \"$CERT_BASE_DIR\"\nmkdir -p \"$CERT_DIR\"\necho \"--- Current CERT_DIR after creation: '$CERT_DIR'\"\necho \"--- Clean up complete.\"\n\n\n# --- 1. Generate ST Class 2 Root (Root of the Chain) ---\necho \"--- Starting generation of ST Class 2 Root: $ST_CLASS2_ROOT_CRT\"\necho \"--- Generating private key for ST Class 2 Root at: '$ST_CLASS2_ROOT_KEY'\"\nopenssl genrsa -out \"$ST_CLASS2_ROOT_KEY\" \"$KEY_BITS\"\necho \"--- Finished generating private key for ST Class 2 Root.\"\ncat > \"$EXT_CONF_CA\" << EOF\n[ v3_ca ]\nbasicConstraints=CA:TRUE\nkeyUsage=critical,digitalSignature,cRLSign,keyCertSign\nEOF\nopenssl req -new -key \"$ST_CLASS2_ROOT_KEY\" -out \"$ST_CLASS2_ROOT_CSR\" \\\n    -subj \"$ST_CLASS2_ROOT_SUBJ\"\necho \"--- Signing self-signed ST Class 2 Root using key: '$ST_CLASS2_ROOT_KEY'\"\nopenssl x509 -req -in \"$ST_CLASS2_ROOT_CSR\" -signkey \"$ST_CLASS2_ROOT_KEY\" -out \"$ST_CLASS2_ROOT_CRT\" -days \"$DAYS_VALID\" \\\n    -extfile \"$EXT_CONF_CA\" -extensions v3_ca\necho \"--- Finished generating ST Class 2 Root.\"\n\n\n# --- 2. Generate ST G2 Root (Cert 3 in chain) ---\necho \"--- Starting generation of ST G2 Root: $ST_G2_ROOT_CRT\"\necho \"--- Generating private key for ST G2 Root at: '$ST_G2_ROOT_KEY'\"\nopenssl genrsa -out \"$ST_G2_ROOT_KEY\" \"$KEY_BITS\"\nopenssl req -new -key \"$ST_G2_ROOT_KEY\" -out \"$ST_G2_ROOT_CSR\" \\\n    -subj \"$ST_G2_ROOT_SUBJ\"\necho \"--- Signing ST G2 Root using CA private key: '$ST_CLASS2_ROOT_KEY'\"\necho 01 > \"$ST_CLASS2_ROOT_SRL\"\nopenssl x509 -req -in \"$ST_G2_ROOT_CSR\" -CA \"$ST_CLASS2_ROOT_CRT\" -CAkey \"$ST_CLASS2_ROOT_KEY\" -CAcreateserial \\\n    -out \"$ST_G2_ROOT_CRT\" -days \"$DAYS_VALID\" -sha256 \\\n    -extfile \"$EXT_CONF_CA\" -extensions v3_ca\necho \"--- Finished generating ST G2 Root.\"\n\n\n# --- 3. Generate COMMON Private Key for Amz Root CA 1 Entity ---\necho \"--- Generating COMMON Private Key for Amz Root CA 1 Entity at: '$AMZ_ROOT_CA1_COMMON_KEY'\"\nopenssl genrsa -out \"$AMZ_ROOT_CA1_COMMON_KEY\" \"$KEY_BITS\"\necho \"--- Finished generating common private key for Amz Root CA 1.\"\n\n\n# --- 4. Generate Self-Signed Amz Root CA 1 (FOR TRUST STORE) ---\necho \"--- Starting generation of Self-Signed Amz Root CA 1 (for Trust Store): $AMZ_ROOT_CA1_TRUST_STORE_CRT\"\ncat > \"$EXT_CONF_CA\" << EOF\n[ v3_ca ]\nbasicConstraints=CA:TRUE\nkeyUsage=critical,digitalSignature,cRLSign,keyCertSign\nEOF\nopenssl req -new -key \"$AMZ_ROOT_CA1_COMMON_KEY\" -out \"$AMZ_ROOT_CA1_TRUST_STORE_CSR\" \\\n    -subj \"$AMZ_ROOT_CA1_SUBJ\"\necho \"--- Signing self-signed Amz Root CA 1 (Trust Store) using key: '$AMZ_ROOT_CA1_COMMON_KEY'\"\nopenssl x509 -req -in \"$AMZ_ROOT_CA1_TRUST_STORE_CSR\" -signkey \"$AMZ_ROOT_CA1_COMMON_KEY\" -out \"$AMZ_ROOT_CA1_TRUST_STORE_CRT\" -days \"$DAYS_VALID\" \\\n    -extfile \"$EXT_CONF_CA\" -extensions v3_ca\necho \"--- Finished generating self-signed Amz Root CA 1 (for Trust Store).\"\n\n\n# --- 5. Generate Amz Root CA 1 (FOR CHAIN - signed by ST G2, uses COMMON_KEY) ---\necho \"--- Starting generation of Amz Root CA 1 (FOR CHAIN): $AMZ_ROOT_CA1_CHAIN_CRT\"\nopenssl req -new -key \"$AMZ_ROOT_CA1_COMMON_KEY\" -out \"$AMZ_ROOT_CA1_CHAIN_CSR\" \\\n    -subj \"$AMZ_ROOT_CA1_SUBJ\"\necho \"--- Signing Amz Root CA 1 (Chain) using CA private key: '$ST_G2_ROOT_KEY'\"\necho 01 > \"$ST_G2_ROOT_SRL\"\nopenssl x509 -req -in \"$AMZ_ROOT_CA1_CHAIN_CSR\" -CA \"$ST_G2_ROOT_CRT\" -CAkey \"$ST_G2_ROOT_KEY\" -CAcreateserial \\\n    -out \"$AMZ_ROOT_CA1_CHAIN_CRT\" -days \"$DAYS_VALID\" -sha256 \\\n    -extfile \"$EXT_CONF_CA\" -extensions v3_ca\necho \"--- Finished generating Amz Root CA 1 (chain version).\"\n\n\n# --- 6. Generate Amz RSA 2048 M02 (Intermediate) ---\necho \"--- Starting generation of Amz RSA 2048 M02: $AMZ_RSA_M02_INTERMEDIATE_CRT\"\necho \"--- Generating private key for Amz RSA M02 at: '$AMZ_RSA_M02_INTERMEDIATE_KEY'\"\nopenssl genrsa -out \"$AMZ_RSA_M02_INTERMEDIATE_KEY\" \"$KEY_BITS\"\nopenssl req -new -key \"$AMZ_RSA_M02_INTERMEDIATE_KEY\" -out \"$AMZ_RSA_M02_INTERMEDIATE_CSR\" \\\n    -subj \"$AMZ_RSA_M02_SUBJ\"\n# >>> FIX: Directly use the expanded path for the CA private key here <<<\necho \"--- Signing Amz RSA M02 using CA private key: '$AMZ_ROOT_CA1_COMMON_KEY' (bypassing variable issue)\" # Debugging\necho 01 > \"$AMZ_ROOT_CA1_CHAIN_SRL\"\nopenssl x509 -req -in \"$AMZ_RSA_M02_INTERMEDIATE_CSR\" -CA \"$AMZ_ROOT_CA1_CHAIN_CRT\" -CAkey \"$AMZ_ROOT_CA1_COMMON_KEY\" -CAcreateserial \\\n    -out \"$AMZ_RSA_M02_INTERMEDIATE_CRT\" -days \"$DAYS_VALID\" -sha256 \\\n    -extfile \"$EXT_CONF_CA\" -extensions v3_ca\necho \"--- Finished generating Amz RSA 2048 M02.\"\n\n\n# --- 7. Generate Leaf ---\necho \"--- Starting generation of Leaf: $LEAF_CRT\"\necho \"--- Generating private key for Leaf at: '$LEAF_KEY'\"\nopenssl genrsa -out \"$LEAF_KEY\" \"$KEY_BITS\"\nopenssl req -new -key \"$LEAF_KEY\" -out \"$LEAF_CSR\" \\\n    -subj \"$LEAF_SUBJ\"\n\ncat > \"$LEAF_EXT_CONF\" << EOF\n[ v3_leaf_cert ]\nbasicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature,keyEncipherment # Corrected KeyUsage\nextendedKeyUsage=serverAuth\nsubjectAltName=${LEAF_SANS}\nEOF\necho 01 > \"$AMZ_RSA_M02_INTERMEDIATE_SRL\"\nopenssl x509 -req -in \"$LEAF_CSR\" -CA \"$AMZ_RSA_M02_INTERMEDIATE_CRT\" -CAkey \"$AMZ_RSA_M02_INTERMEDIATE_KEY\" -CAcreateserial \\\n    -out \"$LEAF_CRT\" -days \"$DAYS_VALID\" -sha256 \\\n    -extfile \"$LEAF_EXT_CONF\" -extensions v3_leaf_cert\necho \"--- Finished generating Leaf.\"\n\n\n# --- 8. Create Client Keystore (PKCS12) ---\necho \"--- Creating client keystore: $CLIENT_KEYSTORE_FILE\"\nopenssl pkcs12 -export \\\n    -in \"$LEAF_CRT\" \\\n    -inkey \"$LEAF_KEY\" \\\n    -name \"$CLIENT_KEY_ALIAS\" \\\n    -out \"$CLIENT_KEYSTORE_FILE\" \\\n    -passout pass:\"$CLIENT_KEYSTORE_PASSWORD\" \\\n    -chain \\\n    -CAfile <(cat \"$AMZ_RSA_M02_INTERMEDIATE_CRT\" \"$AMZ_ROOT_CA1_CHAIN_CRT\" \"$ST_G2_ROOT_CRT\" \"$ST_CLASS2_ROOT_CRT\")\necho \"--- Client keystore created.\"\n\n\n# --- 9. Create Trust Store (JKS) ---\necho \"--- Creating trust store: $TRUST_STORE_FILE\"\nkeytool -import -trustcacerts -alias \"$TRUST_STORE_ALIAS_ROOT_CA1_SELF_SIGNED\" -file \"$AMZ_ROOT_CA1_TRUST_STORE_CRT\" \\\n    -keystore \"$TRUST_STORE_FILE\" -storepass \"$TRUST_STORE_PASSWORD\" -noprompt\necho \"--- Trust store created: Contains ONLY Self-Signed Amz Root CA 1 (for trust store).\"\n\n\n# --- Final Verification ---\necho \"--- Generation Complete ---\"\necho \"All certificates and keystores are located in a '$CERT_DIR' directory under your project's test resources.\"\necho \"\"\necho \"Verifying client keystore content:\"\nkeytool -list -v -keystore \"$CLIENT_KEYSTORE_FILE\" -storetype PKCS12 -storepass \"$CLIENT_KEYSTORE_PASSWORD\" | grep \"Alias name\"\necho \"\"\necho \"Verifying trust store content:\"\nkeytool -list -v -keystore \"$TRUST_STORE_FILE\" -storepass \"$TRUST_STORE_PASSWORD\" | grep \"Alias name\"\n\n\n# --- Cleanup Temporary Files ---\necho \"--- Cleaning up temporary OpenSSL config files...\"\nrm \"$EXT_CONF_CA\" \"$LEAF_EXT_CONF\"\necho \"--- Cleanup of temporary files complete.\"\n\necho \"You can now run your Java tests.\""
  },
  {
    "path": "src/test/resources/test_encryption_256.csv",
    "content": "1,hello\n2,world\n3,test\n"
  },
  {
    "path": "src/test/resources/test_file",
    "content": "I am a file without extension"
  },
  {
    "path": "src/test/resources/test_file.csv",
    "content": "I am a file with extension"
  },
  {
    "path": "src/test/resources/wiremock/mappings/connection/session_context_switches.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Logged in\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"Logged in\",\n      \"newScenarioState\": \"DB switched to SECOND_DB\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [],\n            \"rowset\": [],\n            \"total\": 0,\n            \"returned\": 0,\n            \"queryId\": \"ctx-test-0001\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"SECOND_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 17153,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"json\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"DB switched to SECOND_DB\",\n      \"newScenarioState\": \"Schema switched to SECOND_SCHEMA\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [],\n            \"rowset\": [],\n            \"total\": 0,\n            \"returned\": 0,\n            \"queryId\": \"ctx-test-0002\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"SECOND_DB\",\n            \"finalSchemaName\": \"SECOND_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 17154,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"json\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"Schema switched to SECOND_SCHEMA\",\n      \"newScenarioState\": \"Role switched to PUBLIC\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [],\n            \"rowset\": [],\n            \"total\": 0,\n            \"returned\": 0,\n            \"queryId\": \"ctx-test-0003\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"SECOND_DB\",\n            \"finalSchemaName\": \"SECOND_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"PUBLIC\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 17152,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"json\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"Role switched to PUBLIC\",\n      \"newScenarioState\": \"Role switched back to ANALYST\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [],\n            \"rowset\": [],\n            \"total\": 0,\n            \"returned\": 0,\n            \"queryId\": \"ctx-test-0004\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"SECOND_DB\",\n            \"finalSchemaName\": \"SECOND_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 17152,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"json\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"Role switched back to ANALYST\",\n      \"newScenarioState\": \"DB switched back to TEST_DB\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [],\n            \"rowset\": [],\n            \"total\": 0,\n            \"returned\": 0,\n            \"queryId\": \"ctx-test-0005\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"SECOND_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 17153,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"json\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Session context switches\",\n      \"requiredScenarioState\": \"DB switched back to TEST_DB\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [],\n            \"rowset\": [],\n            \"total\": 0,\n            \"returned\": 0,\n            \"queryId\": \"ctx-test-0006\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 17154,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"json\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"request\": {\n        \"urlPathPattern\": \"/session.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": null,\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/minicore/minicore_telemetry.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Login with minicore telemetry\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticated\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"bodyPatterns\": [\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.ISA\",\n              \"matches\": \"^(x86_64|aarch64|ppc64|x86|unknown)$\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.CORE_VERSION\",\n              \"matches\": \"^0\\\\.0\\\\.1.*\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.CORE_FILE_NAME\",\n              \"matches\": \"^libsf_mini_core_.+\\\\.(so|dylib|dll|a)$\"\n            }\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"healthCheckInterval\": 45,\n            \"sessionId\": 1234567890,\n            \"parameters\": [],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"TEST_ROLE\"\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/browser_timeout_authorization_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Browser Authorization timeout\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"fixedDelayMilliseconds\": 5000\n      }\n    }\n  ]\n}\n "
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/dpop_nonce_error_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow with DPoP nonce error\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize\",\n        \"queryParameters\": {\n          \"response_type\": {\n            \"equalTo\": \"code\"\n          },\n          \"scope\": {\n            \"equalTo\": \"session:role:ANALYST\"\n          },\n          \"code_challenge_method\": {\n            \"equalTo\": \"S256\"\n          },\n          \"dpop_jkt\": {\n            \"matches\": \".*\"\n          },\n          \"redirect_uri\": {\n            \"equalTo\": \"http://localhost:8013/snowflake/oauth-redirect\"\n          },\n          \"code_challenge\": {\n            \"matches\": \".*\"\n          },\n          \"state\": {\n            \"matches\": \".*\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"123\"\n          }\n        },\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8013/snowflake/oauth-redirect?code=123&state=abc123\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow with DPoP nonce error\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Got use_dpop_nonce error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8013%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 400,\n        \"jsonBody\": {\n          \"error\": \"use_dpop_nonce\"\n        },\n        \"headers\": {\n          \"DPoP-Nonce\": \"some-nonce-value\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow with DPoP nonce error\",\n      \"requiredScenarioState\": \"Got use_dpop_nonce error\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8013%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"DPoP\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/external_idp_custom_urls.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Custom urls OAuth authorization code flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/authorization\",\n        \"method\": \"GET\",\n        \"queryParameters\": {\n          \"response_type\": {\n            \"equalTo\": \"code\"\n          },\n          \"scope\": {\n            \"equalTo\": \"session:role:ANALYST\"\n          },\n          \"code_challenge_method\": {\n            \"equalTo\": \"S256\"\n          },\n          \"redirect_uri\": {\n            \"equalTo\": \"http://localhost:8007/snowflake/oauth-redirect\"\n          },\n          \"code_challenge\": {\n            \"matches\": \".*\"\n          },\n          \"state\": {\n            \"matches\": \".*\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"123\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8007/snowflake/oauth-redirect?code=123&state=abc123\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Custom urls OAuth authorization code flow\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/tokenrequest.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8007%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/invalid_scope_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Invalid scope authorization error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8002/snowflake/oauth-redirect?error=invalid_scope&error_description=One+or+more+scopes+are+not+configured+for+the+authorization+server+resource.\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/invalid_state_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Invalid scope authorization error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8010/snowflake/oauth-redirect?code=123&state=invalidstate\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/successful_dpop_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow with DPoP\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize\",\n        \"queryParameters\": {\n          \"response_type\": {\n            \"equalTo\": \"code\"\n          },\n          \"scope\": {\n            \"equalTo\": \"session:role:ANALYST\"\n          },\n          \"code_challenge_method\": {\n            \"equalTo\": \"S256\"\n          },\n          \"dpop_jkt\": {\n            \"matches\": \".*\"\n          },\n          \"redirect_uri\": {\n            \"equalTo\": \"http://localhost:8012/snowflake/oauth-redirect\"\n          },\n          \"code_challenge\": {\n            \"matches\": \".*\"\n          },\n          \"state\": {\n            \"matches\": \".*\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"123\"\n          }\n        },\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8012/snowflake/oauth-redirect?code=123&state=abc123\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow with DPoP\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8012%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"DPoP\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/successful_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize\",\n        \"queryParameters\": {\n          \"response_type\": {\n            \"equalTo\": \"code\"\n          },\n          \"scope\": {\n            \"equalTo\": \"session:role:ANALYST\"\n          },\n          \"code_challenge_method\": {\n            \"equalTo\": \"S256\"\n          },\n          \"redirect_uri\": {\n            \"equalTo\": \"http://localhost:8009/snowflake/oauth-redirect\"\n          },\n          \"code_challenge\": {\n            \"matches\": \".*\"\n          },\n          \"state\": {\n            \"matches\": \".*\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"123\"\n          }\n        },\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8009/snowflake/oauth-redirect?code=123&state=abc123\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8009%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/successful_flow_with_single_use_refresh_tokens.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize\",\n        \"queryParameters\": {\n          \"response_type\": {\n            \"equalTo\": \"code\"\n          },\n          \"scope\": {\n            \"equalTo\": \"session:role:ANALYST\"\n          },\n          \"code_challenge_method\": {\n            \"equalTo\": \"S256\"\n          },\n          \"redirect_uri\": {\n            \"equalTo\": \"http://localhost:8009/snowflake/oauth-redirect\"\n          },\n          \"code_challenge\": {\n            \"matches\": \".*\"\n          },\n          \"state\": {\n            \"matches\": \".*\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"123\"\n          }\n        },\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8009/snowflake/oauth-redirect?code=123&state=abc123\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful OAuth authorization code flow\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8009%2Fsnowflake%2Foauth-redirect\"\n          },\n          {\n            \"contains\": \"&enable_single_use_refresh_tokens=true\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"refresh-token-123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/authorization_code/token_request_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"OAuth token request error\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"http://localhost:8003/snowflake/oauth-redirect?code=123&state=abc123\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"OAuth token request error\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Token request error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123&redirect_uri=http%3A%2F%2Flocalhost%3A8003%2Fsnowflake%2Foauth-redirect&code_verifier=\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 400\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/client_credentials/dpop_nonce_error_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth client credentials flow with DPoP nonce error\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Got use_dpop_nonce error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=client_credentials&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 400,\n        \"jsonBody\": {\n          \"error\": \"use_dpop_nonce\"\n        },\n        \"headers\": {\n          \"DPoP-Nonce\": \"some-nonce-value\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful OAuth client credentials flow with DPoP nonce error\",\n      \"requiredScenarioState\": \"Got use_dpop_nonce error\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=client_credentials&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"DPoP\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/client_credentials/successful_dpop_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth client credentials flow with DPoP\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=client_credentials&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/client_credentials/successful_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful OAuth client credentials flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=client_credentials&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/client_credentials/token_request_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"OAuth client credentials flow with token request error\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=client_credentials&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 400\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/legacy_oauth/token_expired.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Throwing expired access token exception in legacy OAuth flow\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/caching_refreshed_access_token_and_new_refresh_token.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Caching refreshed access token and new refresh token\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Removed expired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Caching refreshed access token and new refresh token\",\n      \"requiredScenarioState\": \"Removed expired access token\",\n      \"newScenarioState\": \"Acquired new access token and new refresh token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\" : \"new-refreshed-access-token-123\",\n          \"refresh_token\": \"new-refresh-token-123\",\n          \"token_type\" : \"Bearer\",\n          \"expires_in\" : 599,\n          \"idpInitiated\" : false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Caching refreshed access token and new refresh token\",\n      \"requiredScenarioState\": \"Acquired new access token and new refresh token\",\n      \"newScenarioState\": \"Established session with newly acquired access token and cache both tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"new-refreshed-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/caching_tokens_after_connecting.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful writing access token and refresh token to empty cache\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"transformers\": [\"response-template\"],\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"{{request.query.redirect_uri}}?code=123&state={{request.query.state}}\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful writing access token and refresh token to empty cache\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Acquired tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"refresh-token-123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }      }\n    },\n    {\n      \"scenarioName\": \"Successful writing access token and refresh token to empty cache\",\n      \"requiredScenarioState\": \"Acquired tokens\",\n      \"newScenarioState\": \"Cached tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\" : {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\" : true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/caching_tokens_and_dpop_key_after_connecting.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful writing access token and refresh token to empty cache\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"transformers\": [\"response-template\"],\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"{{request.query.redirect_uri}}?code=123&state={{request.query.state}}\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful writing access token and refresh token to empty cache\",\n      \"requiredScenarioState\": \"Authorized\",\n      \"newScenarioState\": \"Acquired tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"refresh-token-123\",\n          \"token_type\": \"DPoP\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Successful writing access token and refresh token to empty cache\",\n      \"requiredScenarioState\": \"Acquired tokens\",\n      \"newScenarioState\": \"Cached tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\" : {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\" : true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/not_caching_after_client_credentials_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Not caching tokens for client credentials flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Acquired tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=client_credentials&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"access-token-123\",\n          \"refresh_token\": \"refresh-token-123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }      }\n    },\n    {\n      \"scenarioName\": \"Not caching tokens for client credentials flow\",\n      \"requiredScenarioState\": \"Acquired tokens\",\n      \"newScenarioState\": \"Cached tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\" : {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_CLIENT_CREDENTIALS\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\" : true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/refreshing_expired_access_token.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Removed expired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Removed expired access token\",\n      \"newScenarioState\": \"Acquired new access token using refresh token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\" : \"new-refreshed-access-token-123\",\n          \"token_type\" : \"Bearer\",\n          \"expires_in\" : 599,\n          \"idpInitiated\" : false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Acquired new access token using refresh token\",\n      \"newScenarioState\": \"Established session with newly acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"new-refreshed-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/refreshing_expired_access_token_dpop.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Removed expired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Removed expired access token\",\n      \"newScenarioState\": \"Acquired new access token using refresh token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\" : \"new-refreshed-access-token-123\",\n          \"token_type\" : \"DPoP\",\n          \"expires_in\" : 599,\n          \"idpInitiated\" : false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Acquired new access token using refresh token\",\n      \"newScenarioState\": \"Established session with newly acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"new-refreshed-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/refreshing_expired_access_token_dpop_nonce_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Removed expired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Removed expired access token\",\n      \"newScenarioState\": \"Received use_dpop_nonce error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 400,\n        \"jsonBody\": {\n          \"error\": \"use_dpop_nonce\"\n        },\n        \"headers\": {\n          \"DPoP-Nonce\": \"some-nonce-value\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Received use_dpop_nonce error\",\n      \"newScenarioState\": \"Acquired new access token using refresh token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\" : \"new-refreshed-access-token-123\",\n          \"token_type\" : \"DPoP\",\n          \"expires_in\" : 599,\n          \"idpInitiated\" : false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Refreshing expired access token from cache\",\n      \"requiredScenarioState\": \"Acquired new access token using refresh token\",\n      \"newScenarioState\": \"Established session with newly acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          },\n          \"DPoP\": {\n            \"matches\": \".*\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"new-refreshed-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_expiration_and_no_refresh_token.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Restarting full flow on access token expiration but no refresh token\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Removed expired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token expiration but no refresh token\",\n      \"requiredScenarioState\": \"Removed expired access token\",\n      \"newScenarioState\": \"Started full flow and authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"transformers\": [\"response-template\"],\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"{{request.query.redirect_uri}}?code=123&state={{request.query.state}}\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token expiration but no refresh token\",\n      \"requiredScenarioState\": \"Started full flow and authorized\",\n      \"newScenarioState\": \"Acquired new access token (no refresh token in cache)\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"newly-obtained-access-token-123\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"idpInitiated\": false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token expiration but no refresh token\",\n      \"requiredScenarioState\": \"Acquired new access token (no refresh token in cache)\",\n      \"newScenarioState\": \"Established session with newly acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"newly-obtained-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/restarting_full_flow_on_refresh_token_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Restarting full flow on access token refreshing error\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Removed expired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"expired-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"OAUTH\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"390318\",\n          \"message\": \"OAuth access token expired. [1172527951366]\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token refreshing error\",\n      \"requiredScenarioState\": \"Removed expired access token\",\n      \"newScenarioState\": \"Refreshing token error\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=refresh_token&refresh_token=some-refresh-token-123&scope=session%3Arole%3AANALYST\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 400,\n        \"jsonBody\": {\n          \"error\": \"Invalid refresh token\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token refreshing error\",\n      \"requiredScenarioState\": \"Refreshing token error\",\n      \"newScenarioState\": \"Started full flow and authorized\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/authorize.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"transformers\": [\"response-template\"],\n        \"status\": 302,\n        \"headers\": {\n          \"Location\": \"{{request.query.redirect_uri}}?code=123&state={{request.query.state}}\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token refreshing error\",\n      \"requiredScenarioState\": \"Started full flow and authorized\",\n      \"newScenarioState\": \"Acquired new access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/oauth/token-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"Authorization\": {\n            \"contains\": \"Basic\"\n          },\n          \"Content-Type\": {\n            \"contains\": \"application/x-www-form-urlencoded; charset=UTF-8\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"contains\": \"grant_type=authorization_code&code=123\"\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"newly-obtained-access-token-123\",\n          \"refresh_token\": \"newly-obtained-refresh-token\",\n          \"token_type\": \"Bearer\",\n          \"username\": \"user\",\n          \"scope\": \"refresh_token session:role:ANALYST\",\n          \"expires_in\": 600,\n          \"refresh_token_expires_in\": 86399,\n          \"idpInitiated\": false\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Restarting full flow on access token refreshing error\",\n      \"requiredScenarioState\": \"Acquired new access token\",\n      \"newScenarioState\": \"Established session with newly acquired access token\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"newly-obtained-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/oauth/token_caching/reusing_cached_access_token_to_authenticate.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Reusing access token and refresh token from cache\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Reusing cached tokens\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\" : {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\",\n                  \"OAUTH_TYPE\": \"OAUTH_AUTHORIZATION_CODE\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"reused-access-token-123\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"OAUTH\"\n              }\n            },\n            \"ignoreExtraElements\" : true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/pat/invalid_pat_token.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful PAT authentication flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticated\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\" : {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"MOCK_TOKEN\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"PROGRAMMATIC_ACCESS_TOKEN\"\n              }\n            },\n            \"ignoreExtraElements\" : true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"nextAction\": \"RETRY_LOGIN\",\n            \"authnMethod\": \"PAT\",\n            \"signInOptions\": {}\n          },\n          \"code\": \"394400\",\n          \"message\": \"Programmatic access token is invalid.\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/pat/successful_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful PAT authentication flow\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticated\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"CLIENT_APP_ID\": {\n            \"equalTo\": \"MOCK_APP_ID\"\n          },\n          \"CLIENT_APP_VERSION\": {\n            \"equalTo\": \"MOCK_APP_VERSION\"\n          },\n          \"Authorization\": {\n            \"equalTo\": \"Basic\"\n          },\n          \"accept\": {\n            \"equalTo\": \"application/json\"\n          }\n        },\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\" : {\n              \"data\": {\n                \"ACCOUNT_NAME\": \"MOCK_ACCOUNT_NAME\",\n                \"CLIENT_APP_ID\": \"MOCK_APP_ID\",\n                \"CLIENT_ENVIRONMENT\": {\n                  \"tracing\": \"INFO\",\n                  \"OCSP_MODE\": \"FAIL_OPEN\"\n                },\n                \"CLIENT_APP_VERSION\": \"MOCK_APP_VERSION\",\n                \"TOKEN\": \"MOCK_TOKEN\",\n                \"LOGIN_NAME\": \"MOCK_USERNAME\",\n                \"AUTHENTICATOR\": \"PROGRAMMATIC_ACCESS_TOKEN\"\n              }\n            },\n            \"ignoreExtraElements\" : true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/aws_ec2_instance_successful.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/latest/dynamic/instance-identity/document\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"X-aws-ec2-metadata-token\": {\n            \"equalTo\": \"mock-token-12345\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"devpayProductCodes\": null,\n          \"marketplaceProductCodes\": null,\n          \"availabilityZone\": \"us-west-2a\",\n          \"privateIp\": \"10.0.0.1\",\n          \"version\": \"2017-09-30\",\n          \"instanceId\": \"i-1234567890abcdef0\",\n          \"billingProducts\": null,\n          \"instanceType\": \"t3.micro\",\n          \"accountId\": \"123456789012\",\n          \"imageId\": \"ami-12345678\",\n          \"pendingTime\": \"2021-10-01T00:00:00Z\",\n          \"architecture\": \"x86_64\",\n          \"kernelId\": null,\n          \"ramdiskId\": null,\n          \"region\": \"us-west-2\"\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/aws_ec2_token_successful.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/latest/api/token\",\n        \"method\": \"PUT\",\n        \"headers\": {\n          \"X-aws-ec2-metadata-token-ttl-seconds\": {\n            \"equalTo\": \"21600\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"mock-token-12345\"\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/azure_managed_identity_successful.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...\",\n          \"expires_in\": \"3599\",\n          \"expires_on\": \"1635337800\",\n          \"not_before\": \"1635334200\",\n          \"resource\": \"https://management.azure.com/\",\n          \"token_type\": \"Bearer\"\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/azure_metadata_unavailable.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/metadata/instance\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2021-02-01\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 404,\n        \"headers\": {\n          \"Content-Type\": \"text/plain\"\n        },\n        \"body\": \"Azure metadata instance unavailable\"\n      }\n    },\n    {\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/metadata/identity/oauth2/token\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"https://management.azure.com\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 404,\n        \"headers\": {\n          \"Content-Type\": \"text/plain\"\n        },\n        \"body\": \"Azure managed identity unavailable\"\n      }\n    }\n  ]\n}\n\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/azure_vm_successful.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/metadata/instance?api-version=2021-02-01\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"compute\": {\n            \"azEnvironment\": \"AzurePublicCloud\",\n            \"customData\": \"\",\n            \"isHostCompatibilityLayerVm\": \"false\",\n            \"licenseType\": \"\",\n            \"location\": \"westus2\",\n            \"name\": \"test-vm\",\n            \"offer\": \"0001-com-ubuntu-server-focal\",\n            \"osProfile\": {\n              \"adminUsername\": \"testuser\",\n              \"computerName\": \"test-vm\",\n              \"disablePasswordAuthentication\": \"true\"\n            },\n            \"osType\": \"Linux\",\n            \"placementGroupId\": \"\",\n            \"plan\": {\n              \"name\": \"\",\n              \"product\": \"\",\n              \"publisher\": \"\"\n            },\n            \"platformFaultDomain\": \"0\",\n            \"platformUpdateDomain\": \"0\",\n            \"publicKeys\": [\n              {\n                \"keyData\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC...\",\n                \"path\": \"/home/testuser/.ssh/authorized_keys\"\n              }\n            ],\n            \"publisher\": \"canonical\",\n            \"resourceGroupName\": \"test-rg\",\n            \"resourceId\": \"/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachines/test-vm\",\n            \"securityProfile\": {\n              \"secureBootEnabled\": \"false\",\n              \"virtualTpmEnabled\": \"false\"\n            },\n            \"sku\": \"20_04-lts-gen2\",\n            \"storageProfile\": {\n              \"dataDisks\": [],\n              \"imageReference\": {\n                \"id\": \"\",\n                \"offer\": \"0001-com-ubuntu-server-focal\",\n                \"publisher\": \"canonical\",\n                \"sku\": \"20_04-lts-gen2\",\n                \"version\": \"latest\"\n              },\n              \"osDisk\": {\n                \"caching\": \"ReadWrite\",\n                \"createOption\": \"FromImage\",\n                \"diskSizeGB\": \"30\",\n                \"encryptionSettings\": {\n                  \"enabled\": \"false\"\n                },\n                \"image\": {\n                  \"uri\": \"\"\n                },\n                \"managedDisk\": {\n                  \"id\": \"/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/test-rg/providers/Microsoft.Compute/disks/test-vm_OsDisk_1_abcd1234\",\n                  \"storageAccountType\": \"Premium_LRS\"\n                },\n                \"name\": \"test-vm_OsDisk_1_abcd1234\",\n                \"osType\": \"Linux\",\n                \"vhd\": {\n                  \"uri\": \"\"\n                },\n                \"writeAcceleratorEnabled\": \"false\"\n              }\n            },\n            \"subscriptionId\": \"12345678-1234-1234-1234-123456789012\",\n            \"tags\": \"Environment:Test;Application:TestApp\",\n            \"tagsList\": [\n              {\n                \"name\": \"Environment\",\n                \"value\": \"Test\"\n              },\n              {\n                \"name\": \"Application\",\n                \"value\": \"TestApp\"\n              }\n            ],\n            \"version\": \"20.04.202212120\",\n            \"vmId\": \"abcd1234-5678-90ab-cdef-1234567890ab\",\n            \"vmScaleSetName\": \"\",\n            \"vmSize\": \"Standard_D2s_v3\",\n            \"zone\": \"1\"\n          },\n          \"network\": {\n            \"interface\": [\n              {\n                \"ipv4\": {\n                  \"ipAddress\": [\n                    {\n                      \"privateIpAddress\": \"10.0.0.4\",\n                      \"publicIpAddress\": \"1.2.3.4\"\n                    }\n                  ],\n                  \"subnet\": [\n                    {\n                      \"address\": \"10.0.0.0\",\n                      \"prefix\": \"24\"\n                    }\n                  ]\n                },\n                \"ipv6\": {\n                  \"ipAddress\": []\n                },\n                \"macAddress\": \"000D3AF806EC\"\n              }\n            ]\n          }\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/ec2_successful_imdsv1.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/latest/api/token\",\n        \"method\": \"PUT\",\n        \"headers\": {\n          \"X-aws-ec2-metadata-token-ttl-seconds\": {\n            \"equalTo\": \"21600\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 404\n      }\n    },\n    {\n      \"request\": {\n        \"url\": \"/latest/dynamic/instance-identity/document\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"devpayProductCodes\": null,\n          \"marketplaceProductCodes\": null,\n          \"availabilityZone\": \"us-west-2a\",\n          \"privateIp\": \"10.0.0.1\",\n          \"version\": \"2017-09-30\",\n          \"instanceId\": \"i-1234567890abcdef0\",\n          \"billingProducts\": null,\n          \"instanceType\": \"t2.micro\",\n          \"accountId\": \"123456789012\",\n          \"imageId\": \"ami-12345678\",\n          \"pendingTime\": \"2023-01-01T00:00:00Z\",\n          \"architecture\": \"x86_64\",\n          \"kernelId\": null,\n          \"ramdiskId\": null,\n          \"region\": \"us-west-2\"\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/ec2_successful_imdsv2.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/latest/api/token\",\n        \"method\": \"PUT\",\n        \"headers\": {\n          \"X-aws-ec2-metadata-token-ttl-seconds\": {\n            \"equalTo\": \"21600\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"mock-token-12345\"\n      }\n    },\n    {\n      \"request\": {\n        \"url\": \"/latest/dynamic/instance-identity/document\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"X-aws-ec2-metadata-token\": {\n            \"equalTo\": \"mock-token-12345\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"devpayProductCodes\": null,\n          \"marketplaceProductCodes\": null,\n          \"availabilityZone\": \"us-west-2a\",\n          \"privateIp\": \"10.0.0.1\",\n          \"version\": \"2017-09-30\",\n          \"instanceId\": \"i-1234567890abcdef0\",\n          \"billingProducts\": null,\n          \"instanceType\": \"t2.micro\",\n          \"accountId\": \"123456789012\",\n          \"imageId\": \"ami-12345678\",\n          \"pendingTime\": \"2023-01-01T00:00:00Z\",\n          \"architecture\": \"x86_64\",\n          \"kernelId\": null,\n          \"ramdiskId\": null,\n          \"region\": \"us-west-2\"\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/gcp_identity_successful.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/computeMetadata/v1/instance/service-accounts/default/email\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"test-service-account@test-project.iam.gserviceaccount.com\"\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/gcp_metadata_unavailable.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"method\": \"GET\",\n        \"url\": \"/\"\n      },\n      \"response\": {\n        \"status\": 404,\n        \"headers\": {\n          \"Content-Type\": \"text/plain\"\n        },\n        \"body\": \"GCP metadata service unavailable\"\n      }\n    },\n    {\n      \"request\": {\n        \"method\": \"GET\",\n        \"url\": \"/computeMetadata/v1/instance/service-accounts/default/email\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 404,\n        \"headers\": {\n          \"Content-Type\": \"text/plain\"\n        },\n        \"body\": \"GCP metadata service unavailable\"\n      }\n    }\n  ]\n}\n\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/gcp_vm_slow_response.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"0.1/\\ncomputeMetadata/\\n\",\n        \"fixedDelayMilliseconds\": 1000\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/gcp_vm_successful.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"0.1/\\ncomputeMetadata/\\n\"\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/slow_response_timeout.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/.*\",\n        \"method\": \"GET\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"This response is too slow\",\n        \"fixedDelayMilliseconds\": 5000\n      }\n    },\n    {\n      \"request\": {\n        \"urlPattern\": \"/.*\",\n        \"method\": \"PUT\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"This PUT response is too slow\",\n        \"fixedDelayMilliseconds\": 5000\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/platform-detection/timeout_scenarios.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"url\": \"/latest/api/token\",\n        \"method\": \"PUT\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"mock-token\",\n        \"fixedDelayMilliseconds\": 2000\n      }\n    },\n    {\n      \"request\": {\n        \"url\": \"/metadata/instance?api-version=2021-02-01\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\"test\": \"response\"},\n        \"fixedDelayMilliseconds\": 2000\n      }\n    },\n    {\n      \"request\": {\n        \"url\": \"/computeMetadata/v1/instance/service-accounts/default/email\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"test@example.com\",\n        \"fixedDelayMilliseconds\": 2000\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/querycontext/qcc-merge-on-failed-query.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"QCC merge on failed query\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Logged in\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"QCC merge on failed query\",\n      \"requiredScenarioState\": \"Logged in\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"errorCode\": \"200001\",\n            \"age\": 0,\n            \"sqlState\": \"22000\",\n            \"queryId\": \"01c37557-c810-44d1-0000-011109f5513e\",\n            \"queryContext\": {\n              \"entries\": [\n                {\n                  \"id\": 0,\n                  \"timestamp\": 1775206502642407,\n                  \"priority\": 0,\n                  \"context\": \"CMamhAI=\"\n                },\n                {\n                  \"id\": 69924,\n                  \"timestamp\": 1775206502558790,\n                  \"priority\": 1\n                }\n              ]\n            }\n          },\n          \"code\": \"200001\",\n          \"message\": \"A primary key already exists.\",\n          \"success\": false,\n          \"headers\": null\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/certificate_revoked.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful login\",\n      \"requiredScenarioState\": \"Started\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/correct_response.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful login\",\n      \"requiredScenarioState\": \"Started\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Cache-Control\": \"no-cache, no-store\",\n          \"Content-Type\": \"application/json\",\n          \"Vary\": \"Accept-Encoding, User-Agent\",\n          \"Server\": \"SF-LB\",\n          \"X-Envoy-Attempt-Count\": \"1\",\n          \"X-Envoy-Upstream-Service-Time\": \"183\",\n          \"X-Content-Type-Options\": \"nosniff\",\n          \"X-Xss-Protection\": \"1; mode=block\",\n          \"Expect-Ct\": \"enforce, max-age=1800\",\n          \"Strict-Transport-Security\": \"max-age=31536000\",\n          \"X-Snowflake-Fe-Instance\": \"envoy-ingress-awsuswest2qa6-48vvh\",\n          \"X-Snowflake-Fe-Config\": \"9a1a3d85_1738289930_9b94f583_1738793826170_0_0_1738793824803\",\n          \"X-Frame-Options\": \"deny\",\n          \"Content-Length\": \"4180\",\n          \"Transfer\": \"4180\",\n          \"Connection\": \"close\",\n          \"transfer-encoding\": \"chunked\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              },\n              {\n                \"name\": \"TIMESTAMP_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\"\n              },\n              {\n                \"name\": \"TIME_OUTPUT_FORMAT\",\n                \"value\": \"HH24:MI:SS\"\n              },\n              {\n                \"name\": \"TIMESTAMP_TZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_CHUNK_SIZE\",\n                \"value\": 16\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_RS_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_HIDE_INTERNAL_ALIAS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP\",\n                \"value\": 64\n              },\n              {\n                \"name\": \"QUERY_CONTEXT_CACHE_SIZE\",\n                \"value\": 5\n              },\n              {\n                \"name\": \"CLIENT_METADATA_USE_SESSION_DATABASE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_ENABLE_COMBINED_DESCRIBE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_DECIMAL_AS_INT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_THREADS\",\n                \"value\": 1\n              },\n              {\n                \"name\": \"TIMESTAMP_NTZ_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3\"\n              },\n              {\n                \"name\": \"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_MEMORY_LIMIT\",\n                \"value\": 1536\n              },\n              {\n                \"name\": \"CLIENT_TIMESTAMP_TYPE_MAPPING\",\n                \"value\": \"TIMESTAMP_LTZ\"\n              },\n              {\n                \"name\": \"JDBC_EFFICIENT_CHUNK_STORAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"TIMEZONE\",\n                \"value\": \"America/Los_Angeles\"\n              },\n              {\n                \"name\": \"SNOWPARK_REQUEST_TIMEOUT_IN_SECONDS\",\n                \"value\": 86400\n              },\n              {\n                \"name\": \"PYTHON_SNOWPARK_USE_AST\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SERVICE_NAME\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_SLOTS\",\n                \"value\": 2\n              },\n              {\n                \"name\": \"CLIENT_DISABLE_INCIDENTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_ENABLE_PUT_GET\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"BINARY_OUTPUT_FORMAT\",\n                \"value\": \"HEX\"\n              },\n              {\n                \"name\": \"CSV_TIMESTAMP_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_SESSIONLESS_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"DATE_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD\"\n              },\n              {\n                \"name\": \"JDBC_FORMAT_DATE_WITH_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"SNOWPARK_LAZY_ANALYSIS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_USE_JSON_PARSER\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\",\n                \"value\": 3600\n              },\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_CLONE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"TIMESTAMP_LTZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"JDBC_USE_SESSION_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_EXECUTE_RETURN_COUNT_FOR_DML\",\n                \"value\": false\n              },\n              {\n                \"name\": \"ENABLE_FIX_1247059\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_USE_SCOPED_TEMP_OBJECTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_USE_V1_QUERY_API\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\",\n                \"value\": 65280\n              }\n            ],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\":\"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409a\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_PMOTACKI\",\n            \"finalSchemaName\": \"TEST_NODEJS\",\n            \"finalWarehouseName\": \"TEST1\",\n            \"finalRoleName\": \"ACCOUNTADMIN\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\",\n            \"queryContext\": {\n              \"entries\": [\n                {\n                  \"id\": 0,\n                  \"timestamp\": 1738794687263091,\n                  \"priority\": 0,\n                  \"context\": \"CPb8fA==\"\n                }\n              ]\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/http_307_retry.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"HTTP 307 retry\",\n      \"newScenarioState\": \"Successful login\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"HTTP 307 retry\",\n      \"requiredScenarioState\": \"Successful login\",\n      \"newScenarioState\": \"Query attempt with HTTP 307 response\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 307,\n        \"headers\": {\n          \"Location\": \"/temp-redirect-1\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"HTTP 307 retry\",\n      \"requiredScenarioState\": \"Query attempt with HTTP 307 response\",\n      \"newScenarioState\": \"307 redirect followed and times out\",\n        \"request\": {\n            \"urlPathPattern\": \"/temp-redirect-1\",\n            \"method\": \"POST\"\n        },\n        \"response\": {\n          \"fixedDelayMilliseconds\": 65000\n        }\n    },\n    {\n      \"scenarioName\": \"HTTP 307 retry\",\n      \"requiredScenarioState\": \"307 redirect followed and times out\",\n      \"newScenarioState\": \"Retry attempt successful\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Cache-Control\": \"no-cache, no-store\",\n          \"Content-Type\": \"application/json\",\n          \"Vary\": \"Accept-Encoding, User-Agent\",\n          \"Server\": \"SF-LB\",\n          \"X-Envoy-Attempt-Count\": \"1\",\n          \"X-Envoy-Upstream-Service-Time\": \"183\",\n          \"X-Content-Type-Options\": \"nosniff\",\n          \"X-Xss-Protection\": \"1; mode=block\",\n          \"Expect-Ct\": \"enforce, max-age=1800\",\n          \"Strict-Transport-Security\": \"max-age=31536000\",\n          \"X-Snowflake-Fe-Instance\": \"envoy-ingress-awsuswest2qa6-48vvh\",\n          \"X-Snowflake-Fe-Config\": \"9a1a3d85_1738289930_9b94f583_1738793826170_0_0_1738793824803\",\n          \"X-Frame-Options\": \"deny\",\n          \"Content-Length\": \"4180\",\n          \"Transfer\": \"4180\",\n          \"Connection\": \"close\",\n          \"transfer-encoding\": \"chunked\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              },\n              {\n                \"name\": \"TIMESTAMP_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\"\n              },\n              {\n                \"name\": \"TIME_OUTPUT_FORMAT\",\n                \"value\": \"HH24:MI:SS\"\n              },\n              {\n                \"name\": \"TIMESTAMP_TZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_CHUNK_SIZE\",\n                \"value\": 16\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_RS_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_HIDE_INTERNAL_ALIAS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP\",\n                \"value\": 64\n              },\n              {\n                \"name\": \"QUERY_CONTEXT_CACHE_SIZE\",\n                \"value\": 5\n              },\n              {\n                \"name\": \"CLIENT_METADATA_USE_SESSION_DATABASE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_ENABLE_COMBINED_DESCRIBE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_DECIMAL_AS_INT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_THREADS\",\n                \"value\": 1\n              },\n              {\n                \"name\": \"TIMESTAMP_NTZ_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3\"\n              },\n              {\n                \"name\": \"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_MEMORY_LIMIT\",\n                \"value\": 1536\n              },\n              {\n                \"name\": \"CLIENT_TIMESTAMP_TYPE_MAPPING\",\n                \"value\": \"TIMESTAMP_LTZ\"\n              },\n              {\n                \"name\": \"JDBC_EFFICIENT_CHUNK_STORAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"TIMEZONE\",\n                \"value\": \"America/Los_Angeles\"\n              },\n              {\n                \"name\": \"SNOWPARK_REQUEST_TIMEOUT_IN_SECONDS\",\n                \"value\": 86400\n              },\n              {\n                \"name\": \"PYTHON_SNOWPARK_USE_AST\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SERVICE_NAME\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_SLOTS\",\n                \"value\": 2\n              },\n              {\n                \"name\": \"CLIENT_DISABLE_INCIDENTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_ENABLE_PUT_GET\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"BINARY_OUTPUT_FORMAT\",\n                \"value\": \"HEX\"\n              },\n              {\n                \"name\": \"CSV_TIMESTAMP_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_SESSIONLESS_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"DATE_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD\"\n              },\n              {\n                \"name\": \"JDBC_FORMAT_DATE_WITH_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"SNOWPARK_LAZY_ANALYSIS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_USE_JSON_PARSER\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\",\n                \"value\": 3600\n              },\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_CLONE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"TIMESTAMP_LTZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"JDBC_USE_SESSION_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_EXECUTE_RETURN_COUNT_FOR_DML\",\n                \"value\": false\n              },\n              {\n                \"name\": \"ENABLE_FIX_1247059\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_USE_SCOPED_TEMP_OBJECTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_USE_V1_QUERY_API\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\",\n                \"value\": 65280\n              }\n            ],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\":\"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409a\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_PMOTACKI\",\n            \"finalSchemaName\": \"TEST_NODEJS\",\n            \"finalWarehouseName\": \"TEST1\",\n            \"finalRoleName\": \"ACCOUNTADMIN\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\",\n            \"queryContext\": {\n              \"entries\": [\n                {\n                  \"id\": 0,\n                  \"timestamp\": 1738794687263091,\n                  \"priority\": 0,\n                  \"context\": \"CPb8fA==\"\n                }\n              ]\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/http_308_retry.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"HTTP 308 retry\",\n      \"newScenarioState\": \"Successful login\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"HTTP 308 retry\",\n      \"requiredScenarioState\": \"Successful login\",\n      \"newScenarioState\": \"Query attempt with HTTP 308 response\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 308,\n        \"headers\": {\n          \"Location\": \"/temp-redirect-1\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"HTTP 308 retry\",\n      \"requiredScenarioState\": \"Query attempt with HTTP 308 response\",\n      \"newScenarioState\": \"308 redirect followed and times out\",\n        \"request\": {\n            \"urlPathPattern\": \"/temp-redirect-1\",\n            \"method\": \"POST\"\n        },\n        \"response\": {\n          \"fixedDelayMilliseconds\": 65000\n        }\n    },\n    {\n      \"scenarioName\": \"HTTP 308 retry\",\n      \"requiredScenarioState\": \"308 redirect followed and times out\",\n      \"newScenarioState\": \"Retry attempt successful\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Cache-Control\": \"no-cache, no-store\",\n          \"Content-Type\": \"application/json\",\n          \"Vary\": \"Accept-Encoding, User-Agent\",\n          \"Server\": \"SF-LB\",\n          \"X-Envoy-Attempt-Count\": \"1\",\n          \"X-Envoy-Upstream-Service-Time\": \"183\",\n          \"X-Content-Type-Options\": \"nosniff\",\n          \"X-Xss-Protection\": \"1; mode=block\",\n          \"Expect-Ct\": \"enforce, max-age=1800\",\n          \"Strict-Transport-Security\": \"max-age=31536000\",\n          \"X-Snowflake-Fe-Instance\": \"envoy-ingress-awsuswest2qa6-48vvh\",\n          \"X-Snowflake-Fe-Config\": \"9a1a3d85_1738289930_9b94f583_1738793826170_0_0_1738793824803\",\n          \"X-Frame-Options\": \"deny\",\n          \"Content-Length\": \"4180\",\n          \"Transfer\": \"4180\",\n          \"Connection\": \"close\",\n          \"transfer-encoding\": \"chunked\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              },\n              {\n                \"name\": \"TIMESTAMP_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\"\n              },\n              {\n                \"name\": \"TIME_OUTPUT_FORMAT\",\n                \"value\": \"HH24:MI:SS\"\n              },\n              {\n                \"name\": \"TIMESTAMP_TZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_CHUNK_SIZE\",\n                \"value\": 16\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_RS_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_HIDE_INTERNAL_ALIAS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP\",\n                \"value\": 64\n              },\n              {\n                \"name\": \"QUERY_CONTEXT_CACHE_SIZE\",\n                \"value\": 5\n              },\n              {\n                \"name\": \"CLIENT_METADATA_USE_SESSION_DATABASE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_ENABLE_COMBINED_DESCRIBE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_DECIMAL_AS_INT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_THREADS\",\n                \"value\": 1\n              },\n              {\n                \"name\": \"TIMESTAMP_NTZ_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3\"\n              },\n              {\n                \"name\": \"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_MEMORY_LIMIT\",\n                \"value\": 1536\n              },\n              {\n                \"name\": \"CLIENT_TIMESTAMP_TYPE_MAPPING\",\n                \"value\": \"TIMESTAMP_LTZ\"\n              },\n              {\n                \"name\": \"JDBC_EFFICIENT_CHUNK_STORAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"TIMEZONE\",\n                \"value\": \"America/Los_Angeles\"\n              },\n              {\n                \"name\": \"SNOWPARK_REQUEST_TIMEOUT_IN_SECONDS\",\n                \"value\": 86400\n              },\n              {\n                \"name\": \"PYTHON_SNOWPARK_USE_AST\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SERVICE_NAME\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_SLOTS\",\n                \"value\": 2\n              },\n              {\n                \"name\": \"CLIENT_DISABLE_INCIDENTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_ENABLE_PUT_GET\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"BINARY_OUTPUT_FORMAT\",\n                \"value\": \"HEX\"\n              },\n              {\n                \"name\": \"CSV_TIMESTAMP_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_SESSIONLESS_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"DATE_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD\"\n              },\n              {\n                \"name\": \"JDBC_FORMAT_DATE_WITH_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"SNOWPARK_LAZY_ANALYSIS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_USE_JSON_PARSER\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\",\n                \"value\": 3600\n              },\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_CLONE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"TIMESTAMP_LTZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"JDBC_USE_SESSION_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_EXECUTE_RETURN_COUNT_FOR_DML\",\n                \"value\": false\n              },\n              {\n                \"name\": \"ENABLE_FIX_1247059\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_USE_SCOPED_TEMP_OBJECTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_USE_V1_QUERY_API\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\",\n                \"value\": 65280\n              }\n            ],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\":\"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409a\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_PMOTACKI\",\n            \"finalSchemaName\": \"TEST_NODEJS\",\n            \"finalWarehouseName\": \"TEST1\",\n            \"finalRoleName\": \"ACCOUNTADMIN\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\",\n            \"queryContext\": {\n              \"entries\": [\n                {\n                  \"id\": 0,\n                  \"timestamp\": 1738794687263091,\n                  \"priority\": 0,\n                  \"context\": \"CPb8fA==\"\n                }\n              ]\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/response503.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful login\",\n      \"requiredScenarioState\": \"Started\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 503\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/six_malformed_and_correct.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Successful login\",\n      \"requiredScenarioState\": \"Started\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"OAUTH_TEST_AUTH_CODE\",\n            \"serverVersion\": \"8.48.0 b2024121104444034239f05\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              },\n              {\n                \"name\": \"TIMESTAMP_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\"\n              },\n              {\n                \"name\": \"TIME_OUTPUT_FORMAT\",\n                \"value\": \"HH24:MI:SS\"\n              },\n              {\n                \"name\": \"TIMESTAMP_TZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_CHUNK_SIZE\",\n                \"value\": 16\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_RS_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_HIDE_INTERNAL_ALIAS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP\",\n                \"value\": 64\n              },\n              {\n                \"name\": \"QUERY_CONTEXT_CACHE_SIZE\",\n                \"value\": 5\n              },\n              {\n                \"name\": \"CLIENT_METADATA_USE_SESSION_DATABASE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_ENABLE_COMBINED_DESCRIBE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_DECIMAL_AS_INT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_THREADS\",\n                \"value\": 1\n              },\n              {\n                \"name\": \"TIMESTAMP_NTZ_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3\"\n              },\n              {\n                \"name\": \"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_MEMORY_LIMIT\",\n                \"value\": 1536\n              },\n              {\n                \"name\": \"CLIENT_TIMESTAMP_TYPE_MAPPING\",\n                \"value\": \"TIMESTAMP_LTZ\"\n              },\n              {\n                \"name\": \"JDBC_EFFICIENT_CHUNK_STORAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"TIMEZONE\",\n                \"value\": \"America/Los_Angeles\"\n              },\n              {\n                \"name\": \"SNOWPARK_REQUEST_TIMEOUT_IN_SECONDS\",\n                \"value\": 86400\n              },\n              {\n                \"name\": \"PYTHON_SNOWPARK_USE_AST\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SERVICE_NAME\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_SLOTS\",\n                \"value\": 2\n              },\n              {\n                \"name\": \"CLIENT_DISABLE_INCIDENTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_ENABLE_PUT_GET\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"BINARY_OUTPUT_FORMAT\",\n                \"value\": \"HEX\"\n              },\n              {\n                \"name\": \"CSV_TIMESTAMP_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_SESSIONLESS_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"DATE_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD\"\n              },\n              {\n                \"name\": \"JDBC_FORMAT_DATE_WITH_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"SNOWPARK_LAZY_ANALYSIS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_USE_JSON_PARSER\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\",\n                \"value\": 3600\n              },\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_CLONE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"TIMESTAMP_LTZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"VARIANT_MAX_SIZE_IN_RESULT\",\n                \"value\": 134217728\n              },\n              {\n                \"name\": \"JDBC_USE_SESSION_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_EXECUTE_RETURN_COUNT_FOR_DML\",\n                \"value\": false\n              },\n              {\n                \"name\": \"ENABLE_FIX_1247059\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\",\n                \"value\": false\n              },\n              {\n                \"name\": \"VARCHAR_AND_BINARY_MAX_SIZE_IN_RESULT\",\n                \"value\": 134217728\n              },\n              {\n                \"name\": \"SNOWPARK_USE_SCOPED_TEMP_OBJECTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_USE_V1_QUERY_API\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\",\n                \"value\": 65280\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DHEYMAN\",\n              \"schemaName\": \"TEST_JDBC\",\n              \"warehouseName\": \"TEST_XSMALL\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"MALFORMED_RETRY_1\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"fault\": \"MALFORMED_RESPONSE_CHUNK\"\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"MALFORMED_RETRY_1\",\n      \"newScenarioState\": \"MALFORMED_RETRY_2\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"fault\": \"MALFORMED_RESPONSE_CHUNK\"\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"MALFORMED_RETRY_2\",\n      \"newScenarioState\": \"MALFORMED_RETRY_3\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"fault\": \"MALFORMED_RESPONSE_CHUNK\"\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"MALFORMED_RETRY_3\",\n      \"newScenarioState\": \"MALFORMED_RETRY_4\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"fault\": \"MALFORMED_RESPONSE_CHUNK\"\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"MALFORMED_RETRY_4\",\n      \"newScenarioState\": \"MALFORMED_RETRY_5\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"fault\": \"MALFORMED_RESPONSE_CHUNK\"\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"MALFORMED_RETRY_5\",\n      \"newScenarioState\": \"CORRECT\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"fault\": \"MALFORMED_RESPONSE_CHUNK\"\n      }\n    },\n    {\n      \"scenarioName\": \"malformed_response_retry\",\n      \"requiredScenarioState\": \"CORRECT\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Cache-Control\": \"no-cache, no-store\",\n          \"Content-Type\": \"application/json\",\n          \"Vary\": \"Accept-Encoding, User-Agent\",\n          \"Server\": \"SF-LB\",\n          \"X-Envoy-Attempt-Count\": \"1\",\n          \"X-Envoy-Upstream-Service-Time\": \"183\",\n          \"X-Content-Type-Options\": \"nosniff\",\n          \"X-Xss-Protection\": \"1; mode=block\",\n          \"Expect-Ct\": \"enforce, max-age=1800\",\n          \"Strict-Transport-Security\": \"max-age=31536000\",\n          \"X-Snowflake-Fe-Instance\": \"envoy-ingress-awsuswest2qa6-48vvh\",\n          \"X-Snowflake-Fe-Config\": \"9a1a3d85_1738289930_9b94f583_1738793826170_0_0_1738793824803\",\n          \"X-Frame-Options\": \"deny\",\n          \"Content-Length\": \"4180\",\n          \"Transfer\": \"4180\",\n          \"Connection\": \"close\",\n          \"transfer-encoding\": \"chunked\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [\n              {\n                \"name\": \"CLIENT_PREFETCH_THREADS\",\n                \"value\": 4\n              },\n              {\n                \"name\": \"TIMESTAMP_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3 TZHTZM\"\n              },\n              {\n                \"name\": \"TIME_OUTPUT_FORMAT\",\n                \"value\": \"HH24:MI:SS\"\n              },\n              {\n                \"name\": \"TIMESTAMP_TZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_CHUNK_SIZE\",\n                \"value\": 16\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_RS_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_HIDE_INTERNAL_ALIAS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_CONSERVATIVE_MEMORY_ADJUST_STEP\",\n                \"value\": 64\n              },\n              {\n                \"name\": \"QUERY_CONTEXT_CACHE_SIZE\",\n                \"value\": 5\n              },\n              {\n                \"name\": \"CLIENT_METADATA_USE_SESSION_DATABASE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_ENABLE_COMBINED_DESCRIBE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"ENABLE_STAGE_S3_PRIVATELINK_FOR_US_EAST_1\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_DECIMAL_AS_INT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_THREADS\",\n                \"value\": 1\n              },\n              {\n                \"name\": \"TIMESTAMP_NTZ_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD HH24:MI:SS.FF3\"\n              },\n              {\n                \"name\": \"CLIENT_METADATA_REQUEST_USE_CONNECTION_CTX\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_HONOR_CLIENT_TZ_FOR_TIMESTAMP_NTZ\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_MEMORY_LIMIT\",\n                \"value\": 1536\n              },\n              {\n                \"name\": \"CLIENT_TIMESTAMP_TYPE_MAPPING\",\n                \"value\": \"TIMESTAMP_LTZ\"\n              },\n              {\n                \"name\": \"JDBC_EFFICIENT_CHUNK_STORAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"TIMEZONE\",\n                \"value\": \"America/Los_Angeles\"\n              },\n              {\n                \"name\": \"SNOWPARK_REQUEST_TIMEOUT_IN_SECONDS\",\n                \"value\": 86400\n              },\n              {\n                \"name\": \"PYTHON_SNOWPARK_USE_AST\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SERVICE_NAME\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_RESULT_PREFETCH_SLOTS\",\n                \"value\": 2\n              },\n              {\n                \"name\": \"CLIENT_DISABLE_INCIDENTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_ENABLE_PUT_GET\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_CONSERVATIVE_MEMORY_USAGE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"BINARY_OUTPUT_FORMAT\",\n                \"value\": \"HEX\"\n              },\n              {\n                \"name\": \"CSV_TIMESTAMP_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_SESSIONLESS_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"DATE_OUTPUT_FORMAT\",\n                \"value\": \"YYYY-MM-DD\"\n              },\n              {\n                \"name\": \"JDBC_FORMAT_DATE_WITH_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"SNOWPARK_LAZY_ANALYSIS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_USE_JSON_PARSER\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY\",\n                \"value\": 3600\n              },\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_SESSION_CLONE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"TIMESTAMP_LTZ_OUTPUT_FORMAT\",\n                \"value\": \"\"\n              },\n              {\n                \"name\": \"JDBC_USE_SESSION_TIMEZONE\",\n                \"value\": true\n              },\n              {\n                \"name\": \"JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC\",\n                \"value\": false\n              },\n              {\n                \"name\": \"JDBC_EXECUTE_RETURN_COUNT_FOR_DML\",\n                \"value\": false\n              },\n              {\n                \"name\": \"ENABLE_FIX_1247059\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_OUT_OF_BAND_TELEMETRY_ENABLED\",\n                \"value\": false\n              },\n              {\n                \"name\": \"SNOWPARK_USE_SCOPED_TEMP_OBJECTS\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_TELEMETRY_ENABLED\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_USE_V1_QUERY_API\",\n                \"value\": true\n              },\n              {\n                \"name\": \"CLIENT_RESULT_COLUMN_CASE_INSENSITIVE\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_ENABLE_LOG_INFO_STATEMENT_PARAMETERS\",\n                \"value\": false\n              },\n              {\n                \"name\": \"CLIENT_STAGE_ARRAY_BINDING_THRESHOLD\",\n                \"value\": 65280\n              }\n            ],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\":\"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409a\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_PMOTACKI\",\n            \"finalSchemaName\": \"TEST_NODEJS\",\n            \"finalWarehouseName\": \"TEST1\",\n            \"finalRoleName\": \"ACCOUNTADMIN\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\",\n            \"queryContext\": {\n              \"entries\": [\n                {\n                  \"id\": 0,\n                  \"timestamp\": 1738794687263091,\n                  \"priority\": 0,\n                  \"context\": \"CPb8fA==\"\n                }\n              ]\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/sticky_header_from_first_query.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Sticky header from first query\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Logged in without sticky header\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Sticky header from first query\",\n      \"requiredScenarioState\": \"Logged in without sticky header\",\n      \"newScenarioState\": \"First query executed\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"x-snowflake-session\": \"session-67890\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\": \"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409b\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Sticky header from first query\",\n      \"requiredScenarioState\": \"First query executed\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"x-snowflake-session\": {\n            \"equalTo\": \"session-67890\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\": \"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409c\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/sticky_header_from_login.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Sticky header from login\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Logged in\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"x-snowflake-session\": \"session-12345\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Sticky header from login\",\n      \"requiredScenarioState\": \"Logged in\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"x-snowflake-session\": {\n            \"equalTo\": \"session-12345\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\": \"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409a\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/restrequest/sticky_header_updated.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Sticky header updated\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Logged in\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"x-snowflake-session\": \"session-ABC\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"remMeToken\": null,\n            \"remMeValidityInSeconds\": 0,\n            \"healthCheckInterval\": 45,\n            \"newClientForUpgrade\": \"3.12.3\",\n            \"sessionId\": 1172562260498,\n            \"parameters\": [\n              {\n                \"name\": \"AUTOCOMMIT\",\n                \"value\": true\n              }\n            ],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"ANALYST\"\n            },\n            \"idToken\": null,\n            \"idTokenValidityInSeconds\": 0,\n            \"responseData\": null,\n            \"mfaToken\": null,\n            \"mfaTokenValidityInSeconds\": 0\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Sticky header updated\",\n      \"requiredScenarioState\": \"Logged in\",\n      \"newScenarioState\": \"First query done\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"x-snowflake-session\": {\n            \"equalTo\": \"session-ABC\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\": \"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409a\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Sticky header updated\",\n      \"requiredScenarioState\": \"First query done\",\n      \"newScenarioState\": \"Header updated\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"x-snowflake-session\": {\n            \"equalTo\": \"session-ABC\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"x-snowflake-session\": \"session-XYZ\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\": \"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409b\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Sticky header updated\",\n      \"requiredScenarioState\": \"Header updated\",\n      \"request\": {\n        \"urlPathPattern\": \"/queries/v1/query-request.*\",\n        \"method\": \"POST\",\n        \"headers\": {\n          \"x-snowflake-session\": {\n            \"equalTo\": \"session-XYZ\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"parameters\": [],\n            \"rowtype\": [\n              {\n                \"name\": \"1\",\n                \"database\": \"\",\n                \"schema\": \"\",\n                \"table\": \"\",\n                \"nullable\": false,\n                \"byteLength\": null,\n                \"length\": null,\n                \"type\": \"fixed\",\n                \"scale\": 0,\n                \"precision\": 1,\n                \"collation\": null\n              }\n            ],\n            \"rowsetBase64\": \"/////0ABAAAQAAAAAAAKAA4ABgANAAgACgAAAAAABAAQAAAAAAEKAAwAAAAIAAQACgAAAAgAAAAIAAAAAAAAAAEAAAAYAAAAAAASABgAFAATABIADAAAAAgABAASAAAAFAAAAMAAAADIAAAAAAACAcwAAAAEAAAAhAAAAFgAAAAsAAAABAAAAJT///8IAAAADAAAAAEAAAAxAAAACQAAAHByZWNpc2lvbgAAALj///8IAAAAEAAAAAUAAABGSVhFRAAAAAsAAABsb2dpY2FsVHlwZQDg////CAAAAAwAAAABAAAAMAAAAAUAAABzY2FsZQAAAAgADAAIAAQACAAAAAgAAAAMAAAAAwAAAFNCMQAMAAAAcGh5c2ljYWxUeXBlAAAAAAAAAAAIAAwACAAHAAgAAAAAAAABCAAAAAEAAAAxAAAAAAAAAP////+IAAAAFAAAAAAAAAAMABYADgAVABAABAAMAAAAEAAAAAAAAAAAAAQAEAAAAAADCgAYAAwACAAEAAoAAAAUAAAAOAAAAAEAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAAAAAAACAAAAAAAAAABAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD/////AAAAAA==\",\n            \"total\": 1,\n            \"returned\": 1,\n            \"queryId\": \"01ba32c7-0107-cf9d-0000-01110304409c\",\n            \"databaseProvider\": null,\n            \"finalDatabaseName\": \"TEST_DB\",\n            \"finalSchemaName\": \"TEST_SCHEMA\",\n            \"finalWarehouseName\": \"TEST_WH\",\n            \"finalRoleName\": \"ANALYST\",\n            \"numberOfBinds\": 0,\n            \"arrayBindSupported\": false,\n            \"statementTypeId\": 4096,\n            \"version\": 1,\n            \"sendResultTime\": 1738794687270,\n            \"queryResultFormat\": \"arrow\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n} "
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-always-429-in-federated-step-3.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Always 429 in federated step 3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/authenticator-request\",\n        \"queryParameters\": {\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"tokenUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn\",\n            \"ssoUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n            \"proofKey\": null\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Always 429 in federated step 3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-always-429-in-federated-step-4.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Always 429 in federated step 4\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/authenticator-request\",\n        \"queryParameters\": {\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"tokenUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn\",\n            \"ssoUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n            \"proofKey\": null\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Always 429 in federated step 4\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"TokenRetrieved\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"test-session-token-1\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Always 429 in federated step 4\",\n      \"requiredScenarioState\": \"TokenRetrieved\",\n      \"newScenarioState\": \"RateLimited\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n        \"queryParameters\": {\n          \"RelayState\": {\n            \"matches\": \".*\"\n          },\n          \"onetimetoken\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Always 429 in federated step 4\",\n      \"requiredScenarioState\": \"RateLimited\",\n      \"newScenarioState\": \"TokenRetrieved\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"test-session-token-N\"\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-libc-details-in-login-request.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Login with libc details\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticated\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"bodyPatterns\": [\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.LIBC_FAMILY\",\n              \"equalTo\": \"glibc\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.LIBC_VERSION\",\n              \"equalTo\": \"2.34\"\n            }\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"healthCheckInterval\": 45,\n            \"sessionId\": 1234567890,\n            \"parameters\": [],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"TEST_ROLE\"\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-multiple-429-from-okta-in-login-request-to-sf.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticator Requested\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/authenticator-request\",\n        \"queryParameters\": {\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"tokenUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn\",\n            \"ssoUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n            \"proofKey\": null\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"Authenticator Requested\",\n      \"newScenarioState\": \"FirstFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"FirstFailed\",\n      \"newScenarioState\": \"SecondFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"SecondFailed\",\n      \"newScenarioState\": \"ThirdFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"ThirdFailed\",\n      \"newScenarioState\": \"FourthFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"FourthFailed\",\n      \"newScenarioState\": \"Successful\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"testsessiontoken\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n        \"queryParameters\": {\n          \"RelayState\": {\n            \"matches\": \".*\"\n          },\n          \"onetimetoken\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"text/html\"\n        },\n        \"body\": \"<body><form action=\\\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/\\\"></form></body>\"\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/v1/login-request\",\n        \"queryParameters\": {\n          \"requestId\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"some\": \"data-that-will-be-ignored-in-current-testcase-but-should-be-replaced-with-correct-response-for-further-tests\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-multiple-429-in-federated-step-3.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticator Requested\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/authenticator-request\",\n        \"queryParameters\": {\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"tokenUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn\",\n            \"ssoUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n            \"proofKey\": null\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"requiredScenarioState\": \"Authenticator Requested\",\n      \"newScenarioState\": \"FirstFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"requiredScenarioState\": \"FirstFailed\",\n      \"newScenarioState\": \"SecondFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"requiredScenarioState\": \"SecondFailed\",\n      \"newScenarioState\": \"ThirdFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"requiredScenarioState\": \"ThirdFailed\",\n      \"newScenarioState\": \"FourthFailed\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"requiredScenarioState\": \"FourthFailed\",\n      \"newScenarioState\": \"Successful\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"testsessiontoken\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n        \"queryParameters\": {\n          \"RelayState\": {\n            \"matches\": \".*\"\n          },\n          \"onetimetoken\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"text/html\"\n        },\n        \"body\": \"<body><form action=\\\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/\\\"></form></body>\"\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/v1/login-request\",\n        \"queryParameters\": {\n          \"requestId\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"some\": \"data-that-will-be-ignored-in-current-testcase-but-should-be-replaced-with-correct-response-for-further-tests\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-multiple-429-in-federated-step-4.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/authenticator-request\",\n        \"queryParameters\": {\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"tokenUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn\",\n            \"ssoUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n            \"proofKey\": null\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"TokenRetrieved\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"test-session-token-1\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"TokenRetrieved\",\n      \"newScenarioState\": \"FirstRateLimitedInStep4\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n        \"queryParameters\": {\n          \"RelayState\": {\n            \"matches\": \".*\"\n          },\n          \"onetimetoken\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"FirstRateLimitedInStep4\",\n      \"newScenarioState\": \"FirstRateLimitedRetriedStep3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"FirstRateLimitedRetriedStep3\",\n      \"newScenarioState\": \"WorksAfterFirstRateLimitedRetriedStep3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"test-session-token-2\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"WorksAfterFirstRateLimitedRetriedStep3\",\n      \"newScenarioState\": \"SecondRateLimited\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"SecondRateLimited\",\n      \"newScenarioState\": \"SecondRateLimitedRetriedStep3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 429,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"message\": \"Too Many Requests\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"SecondRateLimitedRetriedStep3\",\n      \"newScenarioState\": \"WorksAfterSecondRateLimitedRetriedStep3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"expiresAt\": \"2023-10-13T19:18:09.000Z\",\n          \"status\": \"SUCCESS\",\n          \"sessionToken\": \"test-session-token-3\"\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Multiple 429 in federated step 4\",\n      \"requiredScenarioState\": \"WorksAfterSecondRateLimitedRetriedStep3\",\n      \"newScenarioState\": \"SuccessfulSAML\",\n      \"request\": {\n        \"method\": \"GET\",\n        \"urlPath\": \"/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"text/html\"\n        },\n        \"body\": \"<body><form action=\\\"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/\\\"></form></body>\"\n      }\n    },\n    {\n      \"scenarioName\": \"Mock SF Login Request\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/v1/login-request\",\n        \"queryParameters\": {\n          \"requestId\": {\n            \"matches\": \".*\"\n          },\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"some\": \"data-that-will-be-ignored-in-current-testcase-but-should-be-replaced-with-correct-response-for-further-tests\"\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-os-details-in-login-request.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Login with OS details\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticated\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"bodyPatterns\": [\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.NAME\",\n              \"equalTo\": \"Test Linux\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.PRETTY_NAME\",\n              \"equalTo\": \"Test Linux 1.0.0 LTS\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.ID\",\n              \"equalTo\": \"testlinux\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.IMAGE_ID\",\n              \"equalTo\": \"testlinux-cloud\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.IMAGE_VERSION\",\n              \"equalTo\": \"1.0.0-cloud\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.BUILD_ID\",\n              \"equalTo\": \"20260130\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.VERSION\",\n              \"equalTo\": \"1.0.0 LTS (Test Release)\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.data.CLIENT_ENVIRONMENT.OS_DETAILS.VERSION_ID\",\n              \"equalTo\": \"1.0.0\"\n            }\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"healthCheckInterval\": 45,\n            \"sessionId\": 1234567890,\n            \"parameters\": [],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"TEST_ROLE\"\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-spcs-token-in-login-request.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Login with SPCS token\",\n      \"requiredScenarioState\": \"Started\",\n      \"newScenarioState\": \"Authenticated\",\n      \"request\": {\n        \"urlPathPattern\": \"/session/v1/login-request.*\",\n        \"method\": \"POST\",\n        \"bodyPatterns\": [\n          {\n            \"equalToJson\": {\n              \"data\": {\n                \"SPCS_TOKEN\": \"test-spcs-token\"\n              }\n            },\n            \"ignoreExtraElements\": true\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"data\": {\n            \"masterToken\": \"master token\",\n            \"token\": \"session token\",\n            \"validityInSeconds\": 3600,\n            \"masterValidityInSeconds\": 14400,\n            \"displayUserName\": \"TEST_USER\",\n            \"serverVersion\": \"8.48.0\",\n            \"firstLogin\": false,\n            \"healthCheckInterval\": 45,\n            \"sessionId\": 1234567890,\n            \"parameters\": [],\n            \"sessionInfo\": {\n              \"databaseName\": \"TEST_DB\",\n              \"schemaName\": \"TEST_SCHEMA\",\n              \"warehouseName\": \"TEST_WH\",\n              \"roleName\": \"TEST_ROLE\"\n            }\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/session/session-util-wiremock-it-unsupported-mfa-in-federated-step-3.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"scenarioName\": \"Unsupported MFA in federated step 3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/session/authenticator-request\",\n        \"queryParameters\": {\n          \"request_guid\": {\n            \"matches\": \".*\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"data\": {\n            \"tokenUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/api/v1/authn\",\n            \"ssoUrl\": \"{{WIREMOCK_HOST_WITH_HTTPS_AND_PORT}}/okta-stub/vanity-url/app/snowflake/tokenlikepartofurl/sso/saml\",\n            \"proofKey\": null\n          },\n          \"code\": null,\n          \"message\": null,\n          \"success\": true\n        }\n      }\n    },\n    {\n      \"scenarioName\": \"Unsupported MFA in federated step 3\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"urlPath\": \"/okta-stub/vanity-url/api/v1/authn\"\n      },\n      \"response\": {\n        \"status\": 200,\n        \"headers\": {\n          \"Content-Type\": \"application/json\"\n        },\n        \"jsonBody\": {\n          \"status\": \"MFA_REQUIRED\",\n          \"sessionToken\": null,\n          \"cookieToken\": null\n        }\n      }\n    }\n  ],\n  \"importOptions\": {\n    \"duplicatePolicy\": \"IGNORE\",\n    \"deleteAllNotInImport\": true\n  }\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/http_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 400\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/invalid_issuer_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwiaXNzIjoiaHR0cHM6Ly9ub3QuYXp1cmUuc3RzLmlzc3Vlci5jb20iLCJzdWIiOiI3NzIxM0UzMC1FOENCLTQ1OTUtQjFCNi01RjA1MEU4MzA4RkQiLCJleHAiOjE3NDQ3MTYwNTEsImlhdCI6MTc0NDcxMjQ1MSwianRpIjoiODcxMzM3NzAwOTQxNmZiYWE0MzQyYWQyMzFkZTAwMGQifQ.oB0Nq-qhcYlYBtWg_bhqIW0eym4uCyUohDrd7FlDMIgFtMdMzNMAVzm6kP_nKB3pWlEDtSOgbp8dEeECwUlNDA\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/missing_issuer_claim.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIiwiZXhwIjoxNzQ0NzE2MDUxLCJpYXQiOjE3NDQ3MTI0NTEsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIn0.xv_rY9IUnnoC0SeBsoXbF2UZo5wmeYNuumLJuTa7cwq0P6OHa2R5DkrHVMu4Zgz3eipQ_O9wln66BQPr_VG1iQ\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/missing_sub_claim.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZmExNWQ2OTItZTljNy00NDYwLWE3NDMtMjlmMjk1MjIyMjkvIiwiZXhwIjoxNzQ0NzE2MDUxLCJpYXQiOjE3NDQ3MTI0NTEsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIn0.KfVQlyouRS2EoGZTvzTN77pTviXdyPl27WrC9rPsr9AiTwnsXnOxIj-CDahyeFksWGNuhRcyzN_nI_ewBS7fVw\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/non_json_response.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"not a JSON format\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/successful_flow_azure_functions.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/endpoint/from/env.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2019-08-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"managed-client-id-from-env\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"X-IDENTITY-HEADER\": {\n            \"equalTo\": \"some-identity-header-from-env\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZmExNWQ2OTItZTljNy00NDYwLWE3NDMtMjlmMjk1MjIyMjkvIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIiwiZXhwIjoxNzQ0NzE2MDUxLCJpYXQiOjE3NDQ3MTI0NTEsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIn0.C5jTYoybRs5YF5GvPgoDq4WK5U9-gDzh_N3IPaqEBI0IifdYSWpKQ72v3UISnVpp7Fc46C-ZC8kijUGe3IU9zA\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/successful_flow_azure_functions_custom_entra_resource.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/endpoint/from/env.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2019-08-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://1111111-2222-3333-44444-55555555\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"managed-client-id-from-env\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"X-IDENTITY-HEADER\": {\n            \"equalTo\": \"some-identity-header-from-env\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZmExNWQ2OTItZTljNy00NDYwLWE3NDMtMjlmMjk1MjIyMjkvIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIiwiZXhwIjoxNzQ0NzE2MDUxLCJpYXQiOjE3NDQ3MTI0NTEsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIn0.C5jTYoybRs5YF5GvPgoDq4WK5U9-gDzh_N3IPaqEBI0IifdYSWpKQ72v3UISnVpp7Fc46C-ZC8kijUGe3IU9zA\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/successful_flow_azure_functions_no_client_id.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/endpoint/from/env.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2019-08-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"X-IDENTITY-HEADER\": {\n            \"equalTo\": \"some-identity-header-from-env\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZmExNWQ2OTItZTljNy00NDYwLWE3NDMtMjlmMjk1MjIyMjkvIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIiwiZXhwIjoxNzQ0NzE2MDUxLCJpYXQiOjE3NDQ3MTI0NTEsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIn0.C5jTYoybRs5YF5GvPgoDq4WK5U9-gDzh_N3IPaqEBI0IifdYSWpKQ72v3UISnVpp7Fc46C-ZC8kijUGe3IU9zA\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/successful_flow_azure_functions_v2_issuer.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/endpoint/from/env.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2019-08-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          },\n          \"client_id\": {\n            \"equalTo\": \"managed-client-id-from-env\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"X-IDENTITY-HEADER\": {\n            \"equalTo\": \"some-identity-header-from-env\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhcGk6Ly9mZDNmNzUzYi1lZWQzLTQ2MmMtYjZhNy1hNGI1YmI2NTBhYWQiLCJleHAiOjE3NDQ3MTYwNTEsImlhdCI6MTc0NDcxMjQ1MSwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5taWNyb3NvZnRvbmxpbmUuY29tL2ZhMTVkNjkyLWU5YzctNDQ2MC1hNzQzLTI5ZjI5NTIyMjI5LyIsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIn0.5mAlEPkzHLR7YbllpKgk-8ZEd88XfzA15DUK8u1rLWs\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/successful_flow_basic.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk0ZGI4N2NiMjdmNjdjZDA1Zjk5OTlkZjMwNjg1NmQ4In0.eyJhdWQiOiJhcGkxIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZmExNWQ2OTItZTljNy00NDYwLWE3NDMtMjlmMjk1MjIyMjkvIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIiwiZXhwIjoxNzQ0NzE2MDUxLCJpYXQiOjE3NDQ3MTI0NTEsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIn0.C5jTYoybRs5YF5GvPgoDq4WK5U9-gDzh_N3IPaqEBI0IifdYSWpKQ72v3UISnVpp7Fc46C-ZC8kijUGe3IU9zA\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/successful_flow_v2_issuer.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhcGk6Ly9mZDNmNzUzYi1lZWQzLTQ2MmMtYjZhNy1hNGI1YmI2NTBhYWQiLCJleHAiOjE3NDQ3MTYwNTEsImlhdCI6MTc0NDcxMjQ1MSwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5taWNyb3NvZnRvbmxpbmUuY29tL2ZhMTVkNjkyLWU5YzctNDQ2MC1hNzQzLTI5ZjI5NTIyMjI5LyIsImp0aSI6Ijg3MTMzNzcwMDk0MTZmYmFhNDM0MmFkMjMxZGUwMDBkIiwic3ViIjoiNzcyMTNFMzAtRThDQi00NTk1LUIxQjYtNUYwNTBFODMwOEZEIn0.5mAlEPkzHLR7YbllpKgk-8ZEd88XfzA15DUK8u1rLWs\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/azure/unparsable_token.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/metadata/identity/oauth2/token.*\",\n        \"queryParameters\": {\n          \"api-version\": {\n            \"equalTo\": \"2018-02-01\"\n          },\n          \"resource\": {\n            \"equalTo\": \"api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata\": {\n            \"equalTo\": \"True\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\n          \"access_token\": \"unparsable.token\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/http_error.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/identity.*\",\n        \"queryParameters\": {\n          \"audience\": {\n            \"equalTo\": \"snowflakecomputing.com\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 400\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/invalid_issuer_claim.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/identity.*\",\n        \"queryParameters\": {\n          \"audience\": {\n            \"equalTo\": \"snowflakecomputing.com\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImU2M2I5NzA1OTRiY2NmZTAxMDlkOTg4OWM2MDk3OWEwIn0.eyJpc3MiOiJodHRwczovL25vdC5nb29nbGUuY29tIiwiaWF0IjoxNzQzNzYxMjEzLCJleHAiOjE3NDM3NjQ4MTMsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6InNvbWUtc3ViamVjdCJ9.lQjch0MLams6AprSVOdZfVnHxqySB1sgDPW8sV839QPFUutMPj3SNkxGvrkIJj2QPDeCh02bdAzXe4N75tLORg\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/missing_issuer_claim.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/identity.*\",\n        \"queryParameters\": {\n          \"audience\": {\n            \"equalTo\": \"snowflakecomputing.com\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImU2M2I5NzA1OTRiY2NmZTAxMDlkOTg4OWM2MDk3OWEwIn0.eyJzdWIiOiJzb21lLXN1YmplY3QiLCJpYXQiOjE3NDM3NjEyMTMsImV4cCI6MTc0Mzc2NDgxMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIn0.H6sN6kjA82EuijFcv-yCJTqau5qvVTCsk0ZQ4gvFQMkB7c71XPs4lkwTa7ZlNNlx9e6TpN1CVGnpCIRDDAZaDw\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/missing_sub_claim.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/identity.*\",\n        \"queryParameters\": {\n          \"audience\": {\n            \"equalTo\": \"snowflakecomputing.com\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImU2M2I5NzA1OTRiY2NmZTAxMDlkOTg4OWM2MDk3OWEwIn0.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJpYXQiOjE3NDM3NjEyMTMsImV4cCI6MTc0Mzc2NDgxMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIn0.w0njdpfWFETVK8Ktq9GdvuKRQJjvhOplcSyvQ_zHHwBUSMapqO1bjEWBx5VhGkdECZIGS1VY7db_IOqT45yOMA\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/successful_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/identity.*\",\n        \"queryParameters\": {\n          \"audience\": {\n            \"equalTo\": \"snowflakecomputing.com\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJpYXQiOjE3NDM2OTIwMTcsImV4cCI6MTc3NTIyODAxNCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoic29tZS1zdWJqZWN0In0.k7018udXQjw-sgVY8sTLTnNrnJoGwVpjE6HozZN-h0w\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/successful_impersonation_flow.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/token\",\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\"access_token\":\"randomToken123\",\"expires_in\":3599,\"token_type\":\"Bearer\"}\n      }\n    },\n    {\n      \"request\": {\n        \"urlPattern\": \"/v1/projects/-/serviceAccounts/targetServiceAccount:generateIdToken\",\n        \"method\": \"POST\",\n        \"bodyPatterns\": [\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.delegates\",\n              \"equalToJson\": \"[\\\"projects/-/serviceAccounts/delegate1\\\", \\\"projects/-/serviceAccounts/delegate2\\\"]\"\n            }\n          },\n          {\n            \"matchesJsonPath\": {\n              \"expression\": \"$.audience\",\n              \"equalTo\": \"snowflakecomputing.com\"\n            }\n          }\n        ]\n      },\n      \"response\": {\n        \"status\": 200,\n        \"jsonBody\": {\"token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJpYXQiOjE3NDM2OTIwMTcsImV4cCI6MTc3NTIyODAxNCwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoic29tZS1zdWJqZWN0In0.k7018udXQjw-sgVY8sTLTnNrnJoGwVpjE6HozZN-h0w\"}\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/wiremock/mappings/wif/gcp/unparsable_token.json",
    "content": "{\n  \"mappings\": [\n    {\n      \"request\": {\n        \"urlPattern\": \"/computeMetadata/v1/instance/service-accounts/default/identity.*\",\n        \"queryParameters\": {\n          \"audience\": {\n            \"equalTo\": \"snowflakecomputing.com\"\n          }\n        },\n        \"method\": \"GET\",\n        \"headers\": {\n          \"Metadata-Flavor\": {\n            \"equalTo\": \"Google\"\n          }\n        }\n      },\n      \"response\": {\n        \"status\": 200,\n        \"body\": \"unparsable.token\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "thin_public_pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n     xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>net.snowflake</groupId>\n  <artifactId>snowflake-jdbc-thin</artifactId>\n  <version>1.0-SNAPSHOT</version>\n  <packaging>jar</packaging>\n  <name>Snowflake JDBC Driver Thin</name>\n  <description>Snowflake JDBC Driver Thin</description>\n  <url>https://www.snowflake.net/</url>\n\n  <licenses>\n    <license>\n      <name>The Apache Software License, Version 2.0</name>\n      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n    </license>\n  </licenses>\n\n  <developers>\n    <developer>\n      <name>Snowflake Support Team</name>\n      <email>snowflake-java@snowflake.net</email>\n      <organization>Snowflake Computing</organization>\n      <organizationUrl>https://www.snowflake.net</organizationUrl>\n    </developer>\n  </developers>\n\n  <scm>\n    <connection>scm:git:git://github.com/snowflakedb/snowflake-jdbc</connection>\n    <url>http://github.com/snowflakedb/snowflake-jdbc/tree/master</url>\n  </scm>\n\n  <properties>\n    <animal.sniffer.annotations.version>1.27</animal.sniffer.annotations.version>\n    <apache.httpclient.version>4.5.14</apache.httpclient.version>\n    <apache.httpcore.version>4.4.16</apache.httpcore.version>\n    <awssdk.version>2.37.5</awssdk.version>\n    <azure.core.version>1.57.0</azure.core.version>\n    <azure.storage.blob.version>12.32.0</azure.storage.blob.version>\n    <azure.storage.common.version>12.31.0</azure.storage.common.version>\n    <awssdk.encryption.s3.version>3.4.0</awssdk.encryption.s3.version>\n    <bouncycastle.version>1.84</bouncycastle.version>\n    <commons.codec.version>1.17.0</commons.codec.version>\n    <commons.io.version>2.17.0</commons.io.version>\n    <commons.logging.version>1.2</commons.logging.version>\n    <google.auth.library.oauth2.http.version>1.29.0</google.auth.library.oauth2.http.version>\n    <google.cloud.core.version>2.47.0</google.cloud.core.version>\n    <google.cloud.storage.version>2.44.1</google.cloud.storage.version>\n    <google.flatbuffers.version>24.3.25</google.flatbuffers.version>\n    <google.gax.version>2.57.0</google.gax.version>\n    <google.guava.version>33.3.1-jre</google.guava.version>\n    <google.http.client.version>1.45.0</google.http.client.version>\n    <google.jsr305.version>3.0.2</google.jsr305.version>\n    <google.protobuf.java.version>4.28.2</google.protobuf.java.version>\n    <grpc.version>1.81.0</grpc.version>\n    <jackson.version>2.18.4.1</jackson.version>\n    <javax.servlet.version>3.1.0</javax.servlet.version>\n    <jna.version>5.13.0</jna.version>\n    <json.smart.version>2.5.2</json.smart.version>\n    <jsoup.version>1.15.3</jsoup.version>\n    <metrics.version>2.2.0</metrics.version>\n    <netty.version>4.1.133.Final</netty.version>\n    <nimbusds.version>10.6</nimbusds.version>\n    <nimbusds.oauth2.version>11.30.1</nimbusds.oauth2.version>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n    <slf4j.version>2.0.13</slf4j.version>\n    <zstd-jni.version>1.5.6-5</zstd-jni.version>\n  </properties>\n\n  <dependencyManagement>\n    <dependencies>\n      <dependency>\n        <groupId>software.amazon.awssdk</groupId>\n        <artifactId>bom</artifactId>\n        <version>${awssdk.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.fasterxml.jackson</groupId>\n        <artifactId>jackson-bom</artifactId>\n        <version>${jackson.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.google.http-client</groupId>\n        <artifactId>google-http-client-bom</artifactId>\n        <version>${google.http.client.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>com.google.protobuf</groupId>\n        <artifactId>protobuf-bom</artifactId>\n        <version>${google.protobuf.java.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>io.grpc</groupId>\n        <artifactId>grpc-bom</artifactId>\n        <version>${grpc.version}</version>\n        <type>pom</type>\n        <scope>import</scope>\n      </dependency>\n      <dependency>\n        <groupId>org.codehaus.mojo</groupId>\n        <artifactId>animal-sniffer-annotations</artifactId>\n        <version>${animal.sniffer.annotations.version}</version>\n      </dependency>\n    </dependencies>\n  </dependencyManagement>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcpkix-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcprov-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.bouncycastle</groupId>\n      <artifactId>bcutil-jdk18on</artifactId>\n      <version>${bouncycastle.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>s3</artifactId>\n      <exclusions>\n        <!-- aws-crt cannot be shaded https://github.com/awslabs/aws-crt-java/issues/166 -->\n        <!-- We are not using it indirectly or directly so we can exclude this -->\n        <exclusion>\n          <groupId>software.amazon.awssdk.crt</groupId>\n          <artifactId>aws-crt</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>s3-transfer-manager</artifactId>\n      <exclusions>\n        <!-- aws-crt cannot be shaded https://github.com/awslabs/aws-crt-java/issues/166 -->\n        <!-- We are not using it indirectly or directly so we can exclude this -->\n        <exclusion>\n          <groupId>software.amazon.awssdk.crt</groupId>\n          <artifactId>aws-crt</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>sdk-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>netty-nio-client</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>http-client-spi</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>identity-spi</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>aws-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>auth</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>http-auth-aws</artifactId>\n      <exclusions>\n        <exclusion>\n          <groupId>software.amazon.awssdk.crt</groupId>\n          <artifactId>aws-crt</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>http-auth-spi</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>regions</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>software.amazon.awssdk</groupId>\n      <artifactId>sts</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-annotations</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-core</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.core</groupId>\n      <artifactId>jackson-databind</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.dataformat</groupId>\n      <artifactId>jackson-dataformat-toml</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.google.api</groupId>\n      <artifactId>gax</artifactId>\n      <version>${google.gax.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.auth</groupId>\n      <artifactId>google-auth-library-oauth2-http</artifactId>\n      <version>${google.auth.library.oauth2.http.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-core</artifactId>\n      <version>${google.cloud.core.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.cloud</groupId>\n      <artifactId>google-cloud-storage</artifactId>\n      <version>${google.cloud.storage.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.code.findbugs</groupId>\n      <artifactId>jsr305</artifactId>\n      <version>${google.jsr305.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.guava</groupId>\n      <artifactId>guava</artifactId>\n      <version>${google.guava.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.google.http-client</groupId>\n      <artifactId>google-http-client</artifactId>\n    </dependency>\n    <dependency>\n      <groupId>com.azure</groupId>\n      <artifactId>azure-core</artifactId>\n      <version>${azure.core.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.azure</groupId>\n      <artifactId>azure-storage-common</artifactId>\n      <version>${azure.storage.common.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.azure</groupId>\n      <artifactId>azure-storage-blob</artifactId>\n      <version>${azure.storage.blob.version}</version>\n      <exclusions>\n        <exclusion>\n          <groupId>io.netty</groupId>\n          <artifactId>netty-tcnative-boringssl-static</artifactId>\n        </exclusion>\n      </exclusions>\n    </dependency>\n    <dependency>\n      <groupId>com.nimbusds</groupId>\n      <artifactId>nimbus-jose-jwt</artifactId>\n      <version>${nimbusds.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.nimbusds</groupId>\n      <artifactId>oauth2-oidc-sdk</artifactId>\n      <version>${nimbusds.oauth2.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-codec</groupId>\n      <artifactId>commons-codec</artifactId>\n      <version>${commons.codec.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n      <version>${commons.io.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-logging</groupId>\n      <artifactId>commons-logging</artifactId>\n      <version>${commons.logging.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>javax.servlet</groupId>\n      <artifactId>javax.servlet-api</artifactId>\n      <version>${javax.servlet.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>net.java.dev.jna</groupId>\n      <artifactId>jna</artifactId>\n      <version>${jna.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>net.java.dev.jna</groupId>\n      <artifactId>jna-platform</artifactId>\n      <version>${jna.version}</version>\n      <optional>true</optional>\n    </dependency>\n    <dependency>\n      <groupId>net.minidev</groupId>\n      <artifactId>json-smart</artifactId>\n      <version>${json.smart.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents</groupId>\n      <artifactId>httpclient</artifactId>\n      <version>${apache.httpclient.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.httpcomponents</groupId>\n      <artifactId>httpcore</artifactId>\n      <version>${apache.httpcore.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.jsoup</groupId>\n      <artifactId>jsoup</artifactId>\n      <version>${jsoup.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.github.luben</groupId>\n      <artifactId>zstd-jni</artifactId>\n      <version>${zstd-jni.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>org.slf4j</groupId>\n      <artifactId>slf4j-api</artifactId>\n      <version>${slf4j.version}</version>\n    </dependency>\n    <dependency>\n      <groupId>com.fasterxml.jackson.datatype</groupId>\n      <artifactId>jackson-datatype-jsr310</artifactId>\n      <scope>runtime</scope>\n    </dependency>\n    <dependency>\n      <!-- used by apache arrow -->\n      <groupId>com.google.flatbuffers</groupId>\n      <artifactId>flatbuffers-java</artifactId>\n      <version>${google.flatbuffers.version}</version>\n    </dependency>\n  </dependencies>\n\n</project>\n"
  }
]