├── ballerina ├── tests │ ├── resources │ │ └── files │ │ │ ├── clobValue.txt │ │ │ ├── blobValue.txt │ │ │ └── byteValue.txt │ ├── init-test-client.bal │ ├── utils.bal │ ├── batch-execute-query-test.bal │ ├── call-procedure-test.bal │ ├── complex-params-query-test.bal │ ├── execute-basic-test.bal │ ├── simple-params-query-test.bal │ └── simple-params-query-with-key-based-client-test.bal ├── icon.png ├── Ballerina.toml ├── build.gradle ├── Dependencies.toml ├── advanced_client.bal ├── README.md └── client.bal ├── examples ├── employees-db │ ├── setup │ │ ├── Ballerina.toml │ │ └── setup.bal │ ├── service │ │ ├── Ballerina.toml │ │ └── service.bal │ └── README.md └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── codecov.yml ├── docs ├── setup │ └── resources │ │ ├── snokeflakes_user_profile.png │ │ ├── snowflakes_create_database.png │ │ ├── snowflakes_create_warehouse.png │ │ ├── snowflakes_create_warehouse_2.png │ │ └── snowflakes_set_default_warehouse.png └── spec │ └── spec.md ├── .github ├── CODEOWNERS └── workflows │ ├── trivy-scan.yml │ ├── daily-build.yml │ ├── pull-request.yml │ ├── release.yml │ ├── ci.yml │ ├── build-with-bal-test-native.yml │ └── dev-stg-release.yml ├── changelog.md ├── native ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── native-image │ │ │ └── io.ballerina.stdlib │ │ │ └── snowflake-native │ │ │ ├── resource-config.json │ │ │ └── native-image.properties │ │ └── java │ │ ├── module-info.java │ │ └── io │ │ └── ballerina │ │ └── lib │ │ └── snowflake │ │ ├── nativeimpl │ │ ├── CallProcessor.java │ │ ├── ExecuteProcessor.java │ │ ├── QueryProcessor.java │ │ └── ClientProcessor.java │ │ └── Constants.java └── build.gradle ├── spotbugs-exclude.xml ├── issue_template.md ├── .gitignore ├── gradle.properties ├── settings.gradle ├── pull_request_template.md ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /ballerina/tests/resources/files/clobValue.txt: -------------------------------------------------------------------------------- 1 | very long text -------------------------------------------------------------------------------- /ballerina/tests/resources/files/blobValue.txt: -------------------------------------------------------------------------------- 1 | wso2 ballerina blob test. -------------------------------------------------------------------------------- /ballerina/tests/resources/files/byteValue.txt: -------------------------------------------------------------------------------- 1 | wso2 ballerina binary test. -------------------------------------------------------------------------------- /ballerina/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/ballerina/icon.png -------------------------------------------------------------------------------- /examples/employees-db/setup/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "wso2" 3 | name = "employees_example_setup" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /examples/employees-db/service/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "wso2" 3 | name = "employees_example_service" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "60...80" 5 | status: 6 | project: 7 | default: 8 | target: 80 9 | -------------------------------------------------------------------------------- /docs/setup/resources/snokeflakes_user_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/docs/setup/resources/snokeflakes_user_profile.png -------------------------------------------------------------------------------- /docs/setup/resources/snowflakes_create_database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/docs/setup/resources/snowflakes_create_database.png -------------------------------------------------------------------------------- /docs/setup/resources/snowflakes_create_warehouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/docs/setup/resources/snowflakes_create_warehouse.png -------------------------------------------------------------------------------- /docs/setup/resources/snowflakes_create_warehouse_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/docs/setup/resources/snowflakes_create_warehouse_2.png -------------------------------------------------------------------------------- /docs/setup/resources/snowflakes_set_default_warehouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/HEAD/docs/setup/resources/snowflakes_set_default_warehouse.png -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # See: https://help.github.com/articles/about-codeowners/ 5 | 6 | # These owners will be the default owners for everything in the repo. 7 | * @daneshk 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | This file contains all the notable changes done to the Ballerina Persist SQL package through the releases. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ### Added 10 | - [Added Snowflake Key-pair authentication](https://github.com/ballerina-platform/ballerina-library/issues/7685) -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yml: -------------------------------------------------------------------------------- 1 | name: Trivy 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "30 20 * * *" 7 | 8 | jobs: 9 | call_workflow: 10 | name: Run Trivy Scan Workflow 11 | if: ${{ github.repository_owner == 'ballerina-platform' }} 12 | uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main 13 | secrets: inherit 14 | with: 15 | additional-build-flags: "-x :snowflake-examples:build" 16 | -------------------------------------------------------------------------------- /.github/workflows/daily-build.yml: -------------------------------------------------------------------------------- 1 | name: Daily build 2 | 3 | on: 4 | schedule: 5 | - cron: "30 2 * * *" 6 | 7 | jobs: 8 | call_workflow: 9 | name: Run Daily Build Workflow 10 | if: ${{ github.repository_owner == 'ballerina-platform' }} 11 | uses: ballerina-platform/ballerina-library/.github/workflows/daily-build-connector-template.yml@main 12 | secrets: inherit 13 | with: 14 | repo-name: module-ballerinax-snowflake 15 | additional-test-flags: "-x test" 16 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 5 | cancel-in-progress: true 6 | 7 | on: pull_request 8 | 9 | jobs: 10 | call_workflow: 11 | name: Run PR Build Workflow 12 | if: ${{ github.repository_owner == 'ballerina-platform' }} 13 | uses: ballerina-platform/ballerina-library/.github/workflows/pr-build-connector-template.yml@main 14 | secrets: inherit 15 | with: 16 | additional-test-flags: "-x test" 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: [ stdlib-release-pipeline ] 7 | 8 | jobs: 9 | call_workflow: 10 | name: Run Release Workflow 11 | if: ${{ github.repository_owner == 'ballerina-platform' }} 12 | uses: ballerina-platform/ballerina-library/.github/workflows/release-package-connector-template.yml@main 13 | secrets: inherit 14 | with: 15 | package-name: snowflake 16 | package-org: ballerinax 17 | additional-release-flags: "-x test" 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 2201.[0-9]+.x 8 | repository_dispatch: 9 | types: check_connector_for_breaking_changes 10 | 11 | jobs: 12 | call_workflow: 13 | name: Run Connector Build Workflow 14 | if: ${{ github.repository_owner == 'ballerina-platform' }} 15 | uses: ballerina-platform/ballerina-library/.github/workflows/build-connector-template.yml@main 16 | secrets: inherit 17 | with: 18 | repo-name: module-ballerinax-snowflake 19 | additional-test-flags: "-x test" 20 | -------------------------------------------------------------------------------- /.github/workflows/build-with-bal-test-native.yml: -------------------------------------------------------------------------------- 1 | name: GraalVM Check 2 | 3 | on: 4 | schedule: 5 | - cron: "30 18 * * *" 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | call_workflow_template: 14 | name: Run Workflow Template 15 | if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} 16 | uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-connector-template.yml@main 17 | secrets: inherit 18 | -------------------------------------------------------------------------------- /native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/snowflake-native/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources": { 3 | "includes": [ 4 | { 5 | "pattern": "\\Qmozilla/public-suffix-list.txt\\E" 6 | } 7 | ] 8 | }, 9 | "bundles": [ 10 | { 11 | "name": "net.snowflake.client.jdbc.internal.snowflake.common.version", 12 | "locales": [ 13 | "" 14 | ] 15 | }, 16 | { 17 | "name": "net.snowflake.client.jdbc.version", 18 | "locales": [ 19 | "" 20 | ] 21 | }, 22 | { 23 | "name": "net.snowflake.client.jdbc.jdbc_error_messages", 24 | "locales": [ 25 | "en_LK" 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.github/workflows/dev-stg-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to the Ballerina Dev\Stage Central 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | environment: 7 | type: choice 8 | description: Select Environment 9 | required: true 10 | options: 11 | - DEV CENTRAL 12 | - STAGE CENTRAL 13 | 14 | jobs: 15 | call_workflow: 16 | name: Run Dev\Stage Central Publish Workflow 17 | if: ${{ github.repository_owner == 'ballerina-platform' }} 18 | uses: ballerina-platform/ballerina-library/.github/workflows/dev-stage-central-publish-connector-template.yml@main 19 | secrets: inherit 20 | with: 21 | environment: ${{ github.event.inputs.environment }} 22 | additional-publish-flags: "-x test" 23 | -------------------------------------------------------------------------------- /spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ballerina/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "ballerinax" 3 | name = "snowflake" 4 | version = "2.2.1" 5 | authors = ["Ballerina"] 6 | keywords = ["IT Operations/Cloud Services", "Cost/Paid", "Vendor/Snowflake", "Area/Database", "Type/Connector"] 7 | repository = "https://github.com/ballerina-platform/module-ballerinax-snowflake" 8 | icon = "icon.png" 9 | license = ["Apache-2.0"] 10 | distribution = "2201.11.0-20241218-101200-109f6cc7" 11 | 12 | [platform.java21] 13 | graalvmCompatible = true 14 | 15 | [[platform.java21.dependency]] 16 | groupId = "io.ballerina.lib" 17 | artifactId = "snowflake-native" 18 | version = "2.2.1" 19 | path = "../native/build/libs/snowflake-native-2.2.1.jar" 20 | 21 | [[platform.java21.dependency]] 22 | groupId = "io.ballerina.stdlib" 23 | artifactId = "sql-native" 24 | version = "1.15.0" 25 | path = "./lib/sql-native-1.15.0.jar" 26 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | **Description:** 2 | 3 | 4 | **Suggested Labels:** 5 | 6 | 7 | **Suggested Assignees:** 8 | 9 | 10 | **Affected Product Version:** 11 | 12 | **OS, DB, other environment details and versions:** 13 | 14 | **Steps to reproduce:** 15 | 16 | 17 | **Related Issues:** 18 | -------------------------------------------------------------------------------- /native/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 3 | // 4 | // WSO2 LLC. licenses this file to you under the Apache License, 5 | // Version 2.0 (the "License"); you may not use this file except 6 | // in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | */ 18 | 19 | module io.ballerina.lib.snowflake { 20 | requires io.ballerina.runtime; 21 | requires io.ballerina.stdlib.sql; 22 | exports io.ballerina.lib.snowflake; 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | !gradle-wrapper/*.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | 26 | # .DS_Store files 27 | *.DS_Store 28 | 29 | # Ballerina configuartion file 30 | ballerina.conf 31 | Config.toml 32 | 33 | # Target folder of ballerina project 34 | target 35 | 36 | # json files created inside resources folder 37 | /resources/*.json 38 | 39 | # idea files 40 | *.idea 41 | *.iml 42 | *.ipr 43 | *.iws 44 | 45 | # Ignore Gradle project-specific cache directory 46 | .gradle 47 | .vscode 48 | 49 | # Ignore Gradle build output directory 50 | build 51 | 52 | # Ignore lib 53 | ballerina/lib/*.jar 54 | 55 | # Ignore bin directory 56 | native/bin 57 | 58 | # Ignore deb file 59 | *.deb 60 | -------------------------------------------------------------------------------- /native/src/main/resources/META-INF/native-image/io.ballerina.stdlib/snowflake-native/native-image.properties: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. 2 | # 3 | # WSO2 LLC. licenses this file to you under the Apache License, 4 | # Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, 11 | # software distributed under the License is distributed on an 12 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | # KIND, either express or implied. See the License for the 14 | # specific language governing permissions and limitations 15 | # under the License. 16 | 17 | Args = --initialize-at-build-time=net.snowflake.client.jdbc.internal.apache.tika.mime.MediaType \ 18 | --initialize-at-build-time=net.snowflake.client.jdbc.internal.apache.tika.config.ServiceLoader \ 19 | --initialize-at-build-time=net.snowflake.client.jdbc.internal.apache.tika.mime.MimeTypesReader 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=io.ballerina.lib 2 | version=2.2.2-SNAPSHOT 3 | 4 | checkstylePluginVersion=10.12.0 5 | spotbugsPluginVersion=6.0.18 6 | shadowJarPluginVersion=8.1.1 7 | downloadPluginVersion=5.4.0 8 | releasePluginVersion=2.8.0 9 | testngVersion=7.6.1 10 | ballerinaGradlePluginVersion=2.3.0 11 | 12 | ballerinaLangVersion=2201.11.0 13 | stdlibSqlVersion=1.15.0 14 | 15 | # Direct Dependencies 16 | # Level 01 17 | stdlibIoVersion=1.7.0 18 | stdlibTimeVersion=2.6.0 19 | 20 | # Level 02 21 | stdlibLogVersion=2.11.0 22 | stdlibOsVersion=1.9.0 23 | 24 | # Level 03 25 | stdlibFileVersion=1.11.0 26 | 27 | # Ballerinax Observer 28 | observeVersion=1.4.0 29 | observeInternalVersion=1.4.0 30 | 31 | # Transitive Dependencies 32 | # Level 01 33 | stdlibConstraintVersion=1.6.0 34 | stdlibUrlVersion=2.5.0 35 | 36 | # Level 02 37 | stdlibCryptoVersion=2.8.0 38 | stdlibTaskVersion=2.6.0 39 | 40 | # Level 03 41 | stdlibCacheVersion=3.9.0 42 | stdlibMimeVersion=2.11.0 43 | stdlibUuidVersion=1.9.0 44 | 45 | # Level 04 46 | stdlibAuthVersion=2.13.0 47 | stdlibJwtVersion=2.14.0 48 | stdlibOAuth2Version=2.13.0 49 | stdlibDataJsondataVersion=1.0.0 50 | 51 | # Level 05 52 | stdlibHttpVersion=2.13.0 53 | 54 | # Level 06 55 | stdlibTransactionVersion=1.11.0 56 | 57 | # Driver library 58 | snowflakeDriverVersion=2.8.0 59 | -------------------------------------------------------------------------------- /ballerina/tests/init-test-client.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | // Connection Configurations 18 | configurable string accountIdentifier = ?; 19 | configurable string user = ?; 20 | configurable string password = ?; 21 | configurable string privateKeyPath = ?; 22 | configurable string privateKeyPassphrase = ?; 23 | 24 | Options options = { 25 | properties: { 26 | "JDBC_QUERY_RESULT_FORMAT": "JSON" 27 | } 28 | }; 29 | 30 | // Initialize the Snowflake client 31 | Client snowflakeClient = check new (accountIdentifier, user, password, options); 32 | AuthConfig authConfig = { 33 | user, 34 | privateKeyPath, 35 | privateKeyPassphrase 36 | }; 37 | AdvancedClient snowflakeKeyBasedClient = check new (accountIdentifier, authConfig, options); 38 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/lib/snowflake/nativeimpl/CallProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 3 | // 4 | // WSO2 LLC. licenses this file to you under the Apache License, 5 | // Version 2.0 (the "License"); you may not use this file except 6 | // in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | */ 18 | 19 | package io.ballerina.lib.snowflake.nativeimpl; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.values.BArray; 23 | import io.ballerina.runtime.api.values.BObject; 24 | import io.ballerina.stdlib.sql.parameterprocessor.DefaultResultParameterProcessor; 25 | import io.ballerina.stdlib.sql.parameterprocessor.DefaultStatementParameterProcessor; 26 | 27 | /** 28 | * This class holds the utility methods involved with executing the call statements. 29 | * 30 | * @since 1.0.0 31 | */ 32 | public class CallProcessor { 33 | private CallProcessor() { 34 | } 35 | 36 | public static Object nativeCall(Environment env, BObject client, BObject paramSQLString, BArray recordTypes) { 37 | return io.ballerina.stdlib.sql.nativeimpl.CallProcessor.nativeCall(env, client, paramSQLString, 38 | recordTypes, DefaultStatementParameterProcessor.getInstance(), 39 | DefaultResultParameterProcessor.getInstance()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ballerina/tests/utils.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/io; 18 | 19 | isolated function getByteColumnChannel() returns io:ReadableByteChannel|error { 20 | io:ReadableByteChannel byteChannel = check io:openReadableFile("./tests/resources/files/byteValue.txt"); 21 | return byteChannel; 22 | } 23 | 24 | isolated function getBlobColumnChannel() returns io:ReadableByteChannel|error { 25 | io:ReadableByteChannel byteChannel = check io:openReadableFile("./tests/resources/files/blobValue.txt"); 26 | return byteChannel; 27 | } 28 | 29 | isolated function getClobColumnChannel() returns io:ReadableCharacterChannel|error { 30 | io:ReadableByteChannel byteChannel = check io:openReadableFile("./tests/resources/files/clobValue.txt"); 31 | io:ReadableCharacterChannel sourceChannel = new (byteChannel, "UTF-8"); 32 | return sourceChannel; 33 | } 34 | 35 | isolated function getTextColumnChannel() returns io:ReadableCharacterChannel|error { 36 | io:ReadableByteChannel byteChannel = check io:openReadableFile("./tests/resources/files/clobValue.txt"); 37 | io:ReadableCharacterChannel sourceChannel = new (byteChannel, "UTF-8"); 38 | return sourceChannel; 39 | } 40 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/lib/snowflake/nativeimpl/ExecuteProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 3 | // 4 | // WSO2 LLC. licenses this file to you under the Apache License, 5 | // Version 2.0 (the "License"); you may not use this file except 6 | // in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | */ 18 | 19 | package io.ballerina.lib.snowflake.nativeimpl; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.values.BArray; 23 | import io.ballerina.runtime.api.values.BObject; 24 | import io.ballerina.stdlib.sql.parameterprocessor.DefaultStatementParameterProcessor; 25 | 26 | /** 27 | * This class contains methods for executing SQL queries. 28 | * 29 | * @since 1.0.0 30 | */ 31 | public class ExecuteProcessor { 32 | private ExecuteProcessor() { 33 | } 34 | 35 | public static Object nativeExecute(Environment env, BObject client, BObject paramSQLString) { 36 | return io.ballerina.stdlib.sql.nativeimpl.ExecuteProcessor.nativeExecute(env, client, paramSQLString, 37 | DefaultStatementParameterProcessor.getInstance()); 38 | } 39 | 40 | public static Object nativeBatchExecute(Environment env, BObject client, BArray paramSQLStrings) { 41 | return io.ballerina.stdlib.sql.nativeimpl.ExecuteProcessor.nativeBatchExecute(env, client, paramSQLStrings, 42 | DefaultStatementParameterProcessor.getInstance()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.3/userguide/multi_project_builds.html 8 | */ 9 | 10 | pluginManagement { 11 | plugins { 12 | id "com.github.spotbugs" version "${spotbugsPluginVersion}" 13 | id "com.github.johnrengelman.shadow" version "${shadowJarPluginVersion}" 14 | id "de.undercouch.download" version "${downloadPluginVersion}" 15 | id "net.researchgate.release" version "${releasePluginVersion}" 16 | id "io.ballerina.plugin" version "${ballerinaGradlePluginVersion}" 17 | } 18 | 19 | repositories { 20 | gradlePluginPortal() 21 | maven { 22 | url = 'https://maven.pkg.github.com/ballerina-platform/*' 23 | credentials { 24 | username System.getenv("packageUser") 25 | password System.getenv("packagePAT") 26 | } 27 | } 28 | } 29 | } 30 | 31 | plugins { 32 | id "com.gradle.enterprise" version "3.13.2" 33 | } 34 | 35 | def projectName = 'snowflake' 36 | rootProject.name = "ballerinax-${projectName}" 37 | 38 | include ":checkstyle" 39 | include ":${projectName}-native" 40 | include ":${projectName}-ballerina" 41 | include ":${projectName}-examples" 42 | 43 | project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") 44 | project(":${projectName}-native").projectDir = file('native') 45 | project(":${projectName}-ballerina").projectDir = file('ballerina') 46 | project(":${projectName}-examples").projectDir = file('examples') 47 | 48 | gradleEnterprise { 49 | buildScan { 50 | termsOfServiceUrl = 'https://gradle.com/terms-of-service' 51 | termsOfServiceAgree = 'yes' 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/lib/snowflake/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 3 | // 4 | // WSO2 LLC. licenses this file to you under the Apache License, 5 | // Version 2.0 (the "License"); you may not use this file except 6 | // in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | */ 18 | 19 | package io.ballerina.lib.snowflake; 20 | 21 | import io.ballerina.runtime.api.utils.StringUtils; 22 | import io.ballerina.runtime.api.values.BString; 23 | 24 | /** 25 | * Constants for JDBC client. 26 | * 27 | * @since 1.0.0 28 | */ 29 | public final class Constants { 30 | /** 31 | * Constants for Endpoint Configs. 32 | */ 33 | public static final class ClientConfiguration { 34 | public static final BString URL = StringUtils.fromString("url"); 35 | public static final BString USER = StringUtils.fromString("user"); 36 | public static final BString PASSWORD = StringUtils.fromString("password"); 37 | public static final BString DATASOURCE_NAME = StringUtils.fromString("datasourceName"); 38 | public static final BString REQUEST_GENERATED_KEYS = StringUtils.fromString("requestGeneratedKeys"); 39 | public static final BString CONNECTION_POOL_OPTIONS = StringUtils.fromString("connectionPool"); 40 | public static final BString OPTIONS = StringUtils.fromString("options"); 41 | public static final BString AUTH_CONFIG = StringUtils.fromString("authConfig"); 42 | public static final String BASIC_AUTH_TYPE = "BasicAuth"; 43 | public static final String KEY_BASED_AUTH_TYPE = "KeyBasedAuth"; 44 | public static final BString PROPERTIES = StringUtils.fromString("properties"); 45 | 46 | public static final String CONFIG_PRIVATE_KEY_PATH = "privateKeyPath"; 47 | public static final String CONFIG_PRIVATE_KEY_PASSPHRASE = "privateKeyPassphrase"; 48 | public static final String PROPERTY_PRIVATE_KEY_FILE = "private_key_file"; 49 | public static final String PROPERTY_PRIVATE_KEY_FILE_PWD = "private_key_file_pwd"; 50 | } 51 | 52 | public static final String CONNECT_TIMEOUT = ".*(connect).*(timeout).*"; 53 | public static final String POOL_CONNECTION_TIMEOUT = "ConnectionTimeout"; 54 | } 55 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/lib/snowflake/nativeimpl/QueryProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 3 | // 4 | // WSO2 LLC. licenses this file to you under the Apache License, 5 | // Version 2.0 (the "License"); you may not use this file except 6 | // in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | */ 18 | 19 | package io.ballerina.lib.snowflake.nativeimpl; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.values.BObject; 23 | import io.ballerina.runtime.api.values.BStream; 24 | import io.ballerina.runtime.api.values.BTypedesc; 25 | import io.ballerina.stdlib.sql.parameterprocessor.DefaultResultParameterProcessor; 26 | import io.ballerina.stdlib.sql.parameterprocessor.DefaultStatementParameterProcessor; 27 | 28 | /** 29 | * This class provides the query processing implementation which executes sql queries. 30 | * 31 | * @since 1.0.0 32 | */ 33 | public class QueryProcessor { 34 | 35 | private QueryProcessor() { 36 | } 37 | 38 | public static BStream nativeQuery(Environment env, BObject client, BObject paramSQLString, 39 | BTypedesc recordType) { 40 | DefaultStatementParameterProcessor statementParametersProcessor = DefaultStatementParameterProcessor 41 | .getInstance(); 42 | DefaultResultParameterProcessor resultParametersProcessor = DefaultResultParameterProcessor 43 | .getInstance(); 44 | return io.ballerina.stdlib.sql.nativeimpl.QueryProcessor.nativeQuery(env, client, paramSQLString, recordType, 45 | statementParametersProcessor, resultParametersProcessor); 46 | } 47 | 48 | public static Object nativeQueryRow(Environment env, BObject client, BObject paramSQLString, BTypedesc recordType) { 49 | DefaultStatementParameterProcessor statementParametersProcessor = DefaultStatementParameterProcessor 50 | .getInstance(); 51 | DefaultResultParameterProcessor resultParametersProcessor = DefaultResultParameterProcessor 52 | .getInstance(); 53 | return io.ballerina.stdlib.sql.nativeimpl.QueryProcessor.nativeQueryRow(env, client, paramSQLString, recordType, 54 | statementParametersProcessor, resultParametersProcessor); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | > Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc. 3 | 4 | ## Goals 5 | > Describe the solutions that this feature/fix will introduce to resolve the problems described above 6 | 7 | ## Approach 8 | > Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here. 9 | 10 | ## User stories 11 | > Summary of user stories addressed by this change> 12 | 13 | ## Release note 14 | > Brief description of the new feature or bug fix as it will appear in the release notes 15 | 16 | ## Documentation 17 | > Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact 18 | 19 | ## Training 20 | > Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable 21 | 22 | ## Certification 23 | > Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why. 24 | 25 | ## Marketing 26 | > Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable 27 | 28 | ## Automation tests 29 | - Unit tests 30 | > Code coverage information 31 | - Integration tests 32 | > Details about the test cases and coverage 33 | 34 | ## Security checks 35 | - Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes/no 36 | - Ran FindSecurityBugs plugin and verified report? yes/no 37 | - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes/no 38 | 39 | ## Samples 40 | > Provide high-level details about the samples related to this feature 41 | 42 | ## Related PRs 43 | > List any other related PRs 44 | 45 | ## Migrations (if applicable) 46 | > Describe migration steps and platforms on which migration has been tested 47 | 48 | ## Test environment 49 | > List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested 50 | 51 | ## Learning 52 | > Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem. -------------------------------------------------------------------------------- /examples/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 3 | * 4 | * WSO2 LLC. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | import org.apache.tools.ant.taskdefs.condition.Os 19 | 20 | apply plugin: 'java' 21 | 22 | description = 'Ballerina - Snowflake Examples' 23 | 24 | def ballerinaDist = "${project.rootDir}/target/ballerina-runtime" 25 | def examples = ["employees-db/setup", "employees-db/service"] 26 | 27 | clean { 28 | examples.forEach { example -> 29 | delete "${projectDir}/${example}/target" 30 | delete "${projectDir}/${example}/Dependencies.toml" 31 | } 32 | } 33 | 34 | def graalvmFlag = "" 35 | 36 | task testExamples { 37 | if (project.hasProperty('balGraalVMTest')) { 38 | graalvmFlag = '--graalvm' 39 | } 40 | 41 | doLast { 42 | examples.each { example -> 43 | try { 44 | exec { 45 | workingDir "${project.projectDir}/${example}" 46 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 47 | commandLine 'cmd', '/c', "${ballerinaDist}/bin/bal.bat test --offline ${graalvmFlag} && exit %%ERRORLEVEL%%" 48 | } else { 49 | commandLine 'sh', '-c', "${ballerinaDist}/bin/bal test --offline ${graalvmFlag}" 50 | } 51 | } 52 | } catch (Exception e) { 53 | println("Example '${example}' Build failed: " + e.message) 54 | throw e 55 | } 56 | } 57 | } 58 | } 59 | 60 | task buildExamples { 61 | gradle.taskGraph.whenReady { graph -> 62 | if (graph.hasTask(":snowflake-examples:test")) { 63 | buildExamples.enabled = false 64 | } 65 | } 66 | doLast { 67 | examples.each { example -> 68 | try { 69 | exec { 70 | workingDir "${project.projectDir}/${example}" 71 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 72 | commandLine 'cmd', '/c', "${ballerinaDist}/bin/bal.bat pack --offline && exit %%ERRORLEVEL%%" 73 | } else { 74 | commandLine 'sh', '-c', "${ballerinaDist}/bin/bal pack" 75 | } 76 | } 77 | } catch (Exception e) { 78 | println("Example '${example}' Build failed: " + e.message) 79 | throw e 80 | } 81 | } 82 | } 83 | } 84 | 85 | testExamples.dependsOn ":snowflake-ballerina:build" 86 | //test.dependsOn testExamples 87 | buildExamples.dependsOn ":snowflake-ballerina:build" 88 | //build.dependsOn buildExamples 89 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /ballerina/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import org.apache.tools.ant.taskdefs.condition.Os 19 | 20 | plugins { 21 | id 'io.ballerina.plugin' 22 | } 23 | 24 | description = 'Ballerina - Snowflake Ballerina Connector' 25 | 26 | def packageName = 'snowflake' 27 | def packageOrg = 'ballerinax' 28 | def tomlVersion = stripBallerinaExtensionVersion("${project.version}") 29 | 30 | def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") 31 | def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") 32 | 33 | def stripBallerinaExtensionVersion(String extVersion) { 34 | if (extVersion.matches(project.ext.timestampedVersionRegex)) { 35 | def splitVersion = extVersion.split('-') 36 | if (splitVersion.length > 3) { 37 | def strippedValues = splitVersion[0..-4] 38 | return strippedValues.join('-') 39 | } else { 40 | return extVersion 41 | } 42 | } else { 43 | return extVersion.replace("${project.ext.snapshotVersion}", '') 44 | } 45 | } 46 | 47 | ballerina { 48 | packageOrganization = packageOrg 49 | module = packageName 50 | langVersion = ballerinaLangVersion 51 | } 52 | 53 | dependencies { 54 | externalJars(group: 'io.ballerina.stdlib', name: 'sql-native', version: "${stdlibSqlVersion}") { 55 | transitive = false 56 | } 57 | } 58 | 59 | task updateTomlFiles { 60 | doLast { 61 | def stdlibDependentSQLVersion = stripBallerinaExtensionVersion(project.stdlibSqlVersion) 62 | 63 | def newConfig = ballerinaTomlFilePlaceHolder.text.replace('@project.version@', project.version.toString()) 64 | newConfig = newConfig.replace('@toml.version@', tomlVersion) 65 | newConfig = newConfig.replace('@sql.version@', stdlibDependentSQLVersion) 66 | newConfig = newConfig.replace('@sql.native.version@', project.stdlibSqlVersion) 67 | ballerinaTomlFile.text = newConfig 68 | } 69 | } 70 | 71 | task commitTomlFiles { 72 | doLast { 73 | project.exec { 74 | ignoreExitValue true 75 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 76 | commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the native jar versions\" Ballerina.toml Dependencies.toml" 77 | } else { 78 | commandLine 'sh', '-c', "git commit -m '[Automated] Update the native jar versions' Ballerina.toml Dependencies.toml" 79 | } 80 | } 81 | } 82 | } 83 | 84 | updateTomlFiles.dependsOn copyStdlibs 85 | build.dependsOn ":${packageName}-native:build" 86 | test.dependsOn ":${packageName}-native:build" 87 | -------------------------------------------------------------------------------- /ballerina/Dependencies.toml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED FILE. DO NOT MODIFY. 2 | 3 | # This file is auto-generated by Ballerina for managing dependency versions. 4 | # It should not be modified by hand. 5 | 6 | [ballerina] 7 | dependencies-toml-version = "2" 8 | distribution-version = "2201.11.0" 9 | 10 | [[package]] 11 | org = "ballerina" 12 | name = "io" 13 | version = "1.7.0" 14 | dependencies = [ 15 | {org = "ballerina", name = "jballerina.java"}, 16 | {org = "ballerina", name = "lang.value"} 17 | ] 18 | modules = [ 19 | {org = "ballerina", packageName = "io", moduleName = "io"} 20 | ] 21 | 22 | [[package]] 23 | org = "ballerina" 24 | name = "jballerina.java" 25 | version = "0.0.0" 26 | modules = [ 27 | {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} 28 | ] 29 | 30 | [[package]] 31 | org = "ballerina" 32 | name = "lang.__internal" 33 | version = "0.0.0" 34 | scope = "testOnly" 35 | dependencies = [ 36 | {org = "ballerina", name = "jballerina.java"}, 37 | {org = "ballerina", name = "lang.object"} 38 | ] 39 | 40 | [[package]] 41 | org = "ballerina" 42 | name = "lang.array" 43 | version = "0.0.0" 44 | scope = "testOnly" 45 | dependencies = [ 46 | {org = "ballerina", name = "jballerina.java"}, 47 | {org = "ballerina", name = "lang.__internal"} 48 | ] 49 | 50 | [[package]] 51 | org = "ballerina" 52 | name = "lang.error" 53 | version = "0.0.0" 54 | scope = "testOnly" 55 | dependencies = [ 56 | {org = "ballerina", name = "jballerina.java"} 57 | ] 58 | 59 | [[package]] 60 | org = "ballerina" 61 | name = "lang.object" 62 | version = "0.0.0" 63 | 64 | [[package]] 65 | org = "ballerina" 66 | name = "lang.value" 67 | version = "0.0.0" 68 | dependencies = [ 69 | {org = "ballerina", name = "jballerina.java"} 70 | ] 71 | 72 | [[package]] 73 | org = "ballerina" 74 | name = "sql" 75 | version = "1.15.0" 76 | dependencies = [ 77 | {org = "ballerina", name = "io"}, 78 | {org = "ballerina", name = "jballerina.java"}, 79 | {org = "ballerina", name = "lang.object"}, 80 | {org = "ballerina", name = "time"} 81 | ] 82 | modules = [ 83 | {org = "ballerina", packageName = "sql", moduleName = "sql"} 84 | ] 85 | 86 | [[package]] 87 | org = "ballerina" 88 | name = "test" 89 | version = "0.0.0" 90 | scope = "testOnly" 91 | dependencies = [ 92 | {org = "ballerina", name = "jballerina.java"}, 93 | {org = "ballerina", name = "lang.array"}, 94 | {org = "ballerina", name = "lang.error"} 95 | ] 96 | modules = [ 97 | {org = "ballerina", packageName = "test", moduleName = "test"} 98 | ] 99 | 100 | [[package]] 101 | org = "ballerina" 102 | name = "time" 103 | version = "2.6.0" 104 | dependencies = [ 105 | {org = "ballerina", name = "jballerina.java"} 106 | ] 107 | 108 | [[package]] 109 | org = "ballerinax" 110 | name = "snowflake" 111 | version = "2.2.1" 112 | dependencies = [ 113 | {org = "ballerina", name = "io"}, 114 | {org = "ballerina", name = "jballerina.java"}, 115 | {org = "ballerina", name = "sql"}, 116 | {org = "ballerina", name = "test"}, 117 | {org = "ballerinax", name = "snowflake.driver"} 118 | ] 119 | modules = [ 120 | {org = "ballerinax", packageName = "snowflake", moduleName = "snowflake"} 121 | ] 122 | 123 | [[package]] 124 | org = "ballerinax" 125 | name = "snowflake.driver" 126 | version = "2.8.0" 127 | scope = "testOnly" 128 | dependencies = [ 129 | {org = "ballerina", name = "jballerina.java"} 130 | ] 131 | modules = [ 132 | {org = "ballerinax", packageName = "snowflake.driver", moduleName = "snowflake.driver"} 133 | ] 134 | 135 | -------------------------------------------------------------------------------- /examples/employees-db/service/service.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/time; 18 | import ballerina/http; 19 | import ballerina/sql; 20 | import ballerinax/snowflake; 21 | import ballerinax/snowflake.driver as _; 22 | 23 | // Connection Configurations 24 | configurable string accountIdentifier = ?; 25 | configurable string user = ?; 26 | configurable string password = ?; 27 | 28 | snowflake:Options options = { 29 | properties: { 30 | "JDBC_QUERY_RESULT_FORMAT": "JSON" 31 | } 32 | }; 33 | 34 | public type Employee record { 35 | int employee_id?; 36 | string first_name; 37 | string last_name; 38 | string email; 39 | string phone; 40 | time:Date hire_date; 41 | int? manager_id; 42 | string job_title; 43 | }; 44 | 45 | // Initialize the Snowflake client 46 | final snowflake:Client snowflakeClient = check new (accountIdentifier, user, password, options); 47 | 48 | isolated service /employees on new http:Listener(8080) { 49 | 50 | isolated resource function get .() returns Employee[]|error? { 51 | stream resultStream = snowflakeClient->query(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEE`); 52 | return check from Employee employee in resultStream 53 | select employee; 54 | } 55 | 56 | isolated resource function get [int id]() returns Employee|error? { 57 | return check snowflakeClient->queryRow(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEE WHERE employee_id = ${id}`); 58 | } 59 | 60 | isolated resource function post .(@http:Payload Employee emp) returns string|int|error? { 61 | _ = check snowflakeClient->execute(` 62 | INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEE (first_name, last_name, email, phone, hire_date, manager_id, job_title) 63 | VALUES (${emp.first_name}, ${emp.last_name}, ${emp.email}, ${emp.phone}, ${emp.hire_date}, 64 | ${emp.manager_id}, ${emp.job_title}) 65 | `); 66 | 67 | return check snowflakeClient->queryRow(`select max(employee_id) from COMPANY_DB.PUBLIC.EMPLOYEE AT(statement=>last_query_id())`); 68 | } 69 | 70 | isolated resource function put [int id](@http:Payload Employee emp) returns int|error? { 71 | sql:ExecutionResult result = check snowflakeClient->execute(` 72 | UPDATE COMPANY_DB.PUBLIC.EMPLOYEE 73 | SET first_name = ${emp.first_name}, last_name = ${emp.last_name}, email = ${emp.email}, 74 | phone = ${emp.phone}, hire_date = ${emp.hire_date}, manager_id = ${emp.manager_id}, 75 | job_title = ${emp.job_title} 76 | WHERE employee_id = ${id} 77 | `); 78 | return result.affectedRowCount; 79 | } 80 | 81 | isolated resource function delete [int id]() returns int|error? { 82 | sql:ExecutionResult result = check snowflakeClient->execute(`DELETE FROM COMPANY_DB.PUBLIC.EMPLOYEE WHERE employee_id = ${id}`); 83 | return result.affectedRowCount; 84 | } 85 | 86 | isolated resource function get count() returns int|error? { 87 | return check snowflakeClient->queryRow(`SELECT COUNT(*) FROM COMPANY_DB.PUBLIC.EMPLOYEE`); 88 | } 89 | 90 | isolated resource function get subordinates/[int id]() returns Employee[]|error? { 91 | stream resultStream = snowflakeClient->query(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEE WHERE manager_id = ${id}`); 92 | return check from Employee employee in resultStream 93 | select employee; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /native/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | plugins { 19 | id 'java' 20 | id 'com.github.spotbugs' 21 | id 'checkstyle' 22 | id 'jacoco' 23 | id 'application' 24 | } 25 | 26 | description = 'Ballerina - Snowflake Native' 27 | 28 | applicationDefaultJvmArgs = ['JDK_JAVA_OPTIONS', '--add-opens java.base/java.nio=ALL-UNNAMED'] 29 | 30 | configurations { 31 | jacocoRuntime 32 | } 33 | 34 | dependencies { 35 | jacocoRuntime "org.jacoco:org.jacoco.agent:${jacoco.toolVersion}:runtime" 36 | 37 | checkstyle project(":checkstyle") 38 | checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" 39 | implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" 40 | implementation group: 'io.ballerina.stdlib', name: 'sql-native', version: "${stdlibSqlVersion}" 41 | 42 | } 43 | 44 | tasks.withType(JavaCompile) { 45 | options.encoding = 'UTF-8' 46 | } 47 | 48 | sourceCompatibility = JavaVersion.VERSION_21 49 | 50 | jacoco { 51 | toolVersion = "0.8.10" 52 | } 53 | 54 | test { 55 | testLogging { 56 | showStackTraces = true 57 | showStandardStreams = true 58 | events "failed" 59 | exceptionFormat "full" 60 | } 61 | jacoco { 62 | enabled = true 63 | destinationFile = file("$buildDir/coverage-reports/jacoco.exec") 64 | includeNoLocationClasses = true 65 | } 66 | } 67 | 68 | spotbugsMain { 69 | def classLoader = plugins["com.github.spotbugs"].class.classLoader 70 | def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") 71 | def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") 72 | effort = SpotBugsEffort.MAX 73 | reportLevel = SpotBugsConfidence.LOW 74 | ignoreFailures = true 75 | reportsDir = file("$project.buildDir/reports/spotbugs") 76 | def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") 77 | if (excludeFile.exists()) { 78 | it.excludeFilter = excludeFile 79 | } 80 | reports { 81 | text.enabled = true 82 | } 83 | } 84 | 85 | spotbugsTest { 86 | enabled = false 87 | } 88 | 89 | task validateSpotbugs() { 90 | doLast { 91 | if (spotbugsMain.reports.size() > 0 && 92 | spotbugsMain.reports[0].destination.exists() && 93 | spotbugsMain.reports[0].destination.text.readLines().size() > 0) { 94 | spotbugsMain.reports[0].destination?.eachLine { 95 | println 'Failure: ' + it 96 | } 97 | throw new GradleException("Spotbugs rule violations were found."); 98 | } 99 | } 100 | } 101 | 102 | checkstyle { 103 | toolVersion "${checkstylePluginVersion}" 104 | configFile file("${rootDir}/build-config/checkstyle/build/checkstyle.xml") 105 | configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] 106 | } 107 | 108 | tasks.withType(Checkstyle) { 109 | exclude '**/module-info.java' 110 | } 111 | 112 | spotbugsMain.finalizedBy validateSpotbugs 113 | checkstyleMain.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' 114 | 115 | compileJava { 116 | doFirst { 117 | options.compilerArgs = [ 118 | '--module-path', classpath.asPath, 119 | ] 120 | classpath = files() 121 | } 122 | } 123 | 124 | jar { 125 | manifest { 126 | attributes "Add-Opens": "java.base/java.nio=ALL-UNNAMED" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /examples/employees-db/setup/setup.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/time; 18 | import ballerina/sql; 19 | import ballerinax/snowflake; 20 | import ballerinax/snowflake.driver as _; 21 | 22 | // Connection Configurations 23 | configurable string accountIdentifier = ?; 24 | configurable string user = ?; 25 | configurable string password = ?; 26 | 27 | snowflake:Options options = { 28 | properties: { 29 | "JDBC_QUERY_RESULT_FORMAT": "JSON" 30 | } 31 | }; 32 | 33 | public type Employee record { 34 | int employee_id?; 35 | string first_name; 36 | string last_name; 37 | string email; 38 | string phone; 39 | time:Date hire_date; 40 | int? manager_id; 41 | string job_title; 42 | }; 43 | 44 | // Initialize the Snowflake client 45 | snowflake:Client snowflakeClient = check new (accountIdentifier, user, password, options); 46 | 47 | public function main() returns error? { 48 | check createDatabase(); 49 | check createAndPopulateEmployeesTable(); 50 | } 51 | 52 | function createDatabase() returns error? { 53 | _ = check snowflakeClient->execute(`DROP DATABASE IF EXISTS COMPANY_DB`); 54 | _ = check snowflakeClient->execute(`CREATE DATABASE COMPANY_DB`); 55 | } 56 | 57 | function createAndPopulateEmployeesTable() returns error? { 58 | _ = check snowflakeClient->execute(`DROP TABLE IF EXISTS COMPANY_DB.PUBLIC.EMPLOYEE`); 59 | 60 | _ = check snowflakeClient->execute(` 61 | CREATE TABLE COMPANY_DB.PUBLIC.EMPLOYEE ( 62 | employee_id INTEGER AUTOINCREMENT PRIMARY KEY, 63 | first_name VARCHAR(255) NOT NULL, 64 | last_name VARCHAR(255) NOT NULL, 65 | email VARCHAR(255) NOT NULL, 66 | phone VARCHAR(50) NOT NULL , 67 | hire_date DATE NOT NULL, 68 | manager_id INTEGER REFERENCES COMPANY_DB.PUBLIC.EMPLOYEE(employee_id), 69 | job_title VARCHAR(255) NOT NULL 70 | ) 71 | `); 72 | 73 | Employee[] employees = [ 74 | { 75 | first_name: "Michael", 76 | last_name: "Scott", 77 | email: "michael.scott@example.com", 78 | phone: "737 299 2772", 79 | hire_date: {year: 1994, month: 2, day: 29}, 80 | manager_id: (), 81 | job_title: "CEO" 82 | }, 83 | { 84 | first_name: "Jane", 85 | last_name: "McIntyre", 86 | email: "jane.mcintyre@example.com", 87 | phone: "737 299 1111", 88 | hire_date: {year: 1996, month: 12, day: 15}, 89 | manager_id: 1, 90 | job_title: "Vice President - Marketing" 91 | }, 92 | { 93 | first_name: "Tom", 94 | last_name: "Scott", 95 | email: "tom.scott@example.com", 96 | phone: "439 882 099", 97 | hire_date: {year: 1998, month: 3, day: 23}, 98 | manager_id: 1, 99 | job_title: "Vice President - Sales" 100 | }, 101 | { 102 | first_name: "Elizabeth", 103 | last_name: "Queen", 104 | email: "elizabeth.queen@example.com", 105 | phone: "881 299 1123", 106 | hire_date: {year: 1978, month: 8, day: 19}, 107 | manager_id: 2, 108 | job_title: "Marketing Executive" 109 | }, 110 | { 111 | first_name: "Sam", 112 | last_name: "Smith", 113 | email: "sam.smith@example.com", 114 | phone: "752 479 2991", 115 | hire_date: {year: 2001, month: 5, day: 29}, 116 | manager_id: 3, 117 | job_title: "Sales Intern" 118 | } 119 | ]; 120 | 121 | sql:ParameterizedQuery[] insertQueries = 122 | from var emp in employees 123 | select ` 124 | INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEE 125 | (first_name, last_name, email, phone, hire_date, manager_id, job_title) 126 | VALUES 127 | (${emp.first_name}, ${emp.last_name}, ${emp.email}, ${emp.phone}, ${emp.hire_date}, 128 | ${emp.manager_id}, ${emp.job_title}) 129 | `; 130 | 131 | _ = check snowflakeClient->batchExecute(insertQueries); 132 | } 133 | -------------------------------------------------------------------------------- /ballerina/tests/batch-execute-query-test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/sql; 18 | import ballerina/test; 19 | import ballerinax/snowflake.driver as _; 20 | 21 | @test:BeforeSuite 22 | function setupBatchTable() returns error? { 23 | _ = check snowflakeClient->execute(`CREATE DATABASE IF NOT EXISTS TEST_DB`); 24 | _ = check snowflakeClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS TEST_DB.PUBLIC.BATCH_TABLE ( 25 | id INT AUTOINCREMENT, 26 | int_type INTEGER NOT NULL, 27 | long_type BIGINT, 28 | float_type FLOAT, 29 | UNIQUE (int_type), 30 | PRIMARY KEY (id) 31 | )`); 32 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.BATCH_TABLE (int_type, long_type, float_type) 33 | VALUES(1, 9223372036854774807, 123.34)`); 34 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.BATCH_TABLE (int_type, long_type, float_type) 35 | VALUES(2, 9372036854774807, 124.34)`); 36 | return (); 37 | } 38 | 39 | @test:Config { 40 | groups: ["batch-execute"] 41 | } 42 | function batchInsertIntoDataTable() returns error? { 43 | var data = [ 44 | {intVal: 3, longVal: 9223372036854774807, floatVal: 123.34}, 45 | {intVal: 4, longVal: 9223372036854774807, floatVal: 123.34}, 46 | {intVal: 5, longVal: 9223372036854774807, floatVal: 123.34} 47 | ]; 48 | sql:ParameterizedQuery[] sqlQueries = 49 | from var row in data 50 | select `INSERT INTO TEST_DB.PUBLIC.BATCH_TABLE (int_type, long_type, float_type) VALUES (${row.intVal}, ${row.longVal}, ${row.floatVal})`; 51 | sql:ExecutionResult[] result = check snowflakeClient->batchExecute(sqlQueries); 52 | validateBatchExecutionResult(result, [1, 1, 1], [2,3,4]); 53 | } 54 | 55 | @test:Config { 56 | groups: ["batch-execute"], 57 | dependsOn: [batchInsertIntoDataTable] 58 | } 59 | function batchInsertIntoDataTable2() returns error? { 60 | int intType = 6; 61 | sql:ParameterizedQuery sqlQuery = `INSERT INTO TEST_DB.PUBLIC.BATCH_TABLE (int_type) VALUES(${intType})`; 62 | sql:ParameterizedQuery[] sqlQueries = [sqlQuery]; 63 | 64 | sql:ExecutionResult[] result = check snowflakeClient->batchExecute(sqlQueries); 65 | validateBatchExecutionResult(result, [1], [5]); 66 | } 67 | 68 | @test:Config { 69 | groups: ["batch-execute"], 70 | dependsOn: [batchInsertIntoDataTable2] 71 | } 72 | function batchInsertIntoDataTableFailure() { 73 | var data = [ 74 | {intVal: 7, longVal: 9223372036854774807, floatVal: 123.34}, 75 | {intVal: (), longVal: 9223372036854774807, floatVal: 123.34}, 76 | {intVal: 9, longVal: 9223372036854774807, floatVal: 123.34} 77 | ]; 78 | sql:ParameterizedQuery[] sqlQueries = 79 | from var row in data 80 | select `INSERT INTO TEST_DB.PUBLIC.BATCH_TABLE (int_type, long_type, float_type) VALUES (${row.intVal}, ${row.longVal}, ${row.floatVal})`; 81 | sql:ExecutionResult[]|error result = snowflakeClient->batchExecute(sqlQueries); 82 | test:assertTrue(result is sql:DatabaseError); 83 | 84 | if result is sql:DatabaseError { 85 | test:assertTrue(result.message().endsWith("NULL result in a non-nullable column."), 86 | "Invalid error message received."); 87 | } else { 88 | test:assertFail("Database Error expected."); 89 | } 90 | } 91 | 92 | isolated function validateBatchExecutionResult(sql:ExecutionResult[] results, int[] rowCount, int[] lastId) { 93 | test:assertEquals(results.length(), rowCount.length()); 94 | 95 | int i = 0; 96 | while i < results.length() { 97 | test:assertEquals(results[i].affectedRowCount, rowCount[i]); 98 | int|string? lastInsertIdVal = results[i].lastInsertId; 99 | if lastId[i] == -1 { 100 | test:assertNotEquals(lastInsertIdVal, ()); 101 | } else if lastInsertIdVal is int { 102 | test:assertTrue(lastInsertIdVal > 1, "Last Insert Id is nil."); 103 | } 104 | i = i + 1; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ballerina/advanced_client.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/jballerina.java; 18 | import ballerina/sql; 19 | 20 | # Represents a Snowflake database client. 21 | @display {label: "Snowflake", iconPath: "icon.png"} 22 | public isolated client class AdvancedClient { 23 | *sql:Client; 24 | 25 | # Initializes the Snowflake Advanced Client. This client aliows to authenticate using basic authentication and private key based authentication. 26 | # The client must be kept open throughout the application lifetime. 27 | # 28 | # + account_identifier - The Snowflake account identifier 29 | # + authConfig - The authentication configuration for the Snowflake account 30 | # + options - The Snowflake client properties 31 | # + connectionPool - The `sql:ConnectionPool` to be used for the connection. If there is no 32 | # `connectionPool` provided, the global connection pool (shared by all clients) will be used 33 | # + return - An `sql:Error` if the client creation fails 34 | public isolated function init(string accountIdentifier, AuthConfig authConfig, Options? options = (), 35 | sql:ConnectionPool? connectionPool = ()) returns sql:Error? { 36 | string url = string `jdbc:snowflake://${accountIdentifier}.snowflakecomputing.com/`; 37 | ClientConfiguration clientConf = {url, authConfig, options, connectionPool}; 38 | return createClient(self, clientConf, sql:getGlobalConnectionPool()); 39 | } 40 | 41 | # Executes the query, which may return multiple results. 42 | # When processing the stream, make sure to consume all fetched data or close the stream. 43 | # 44 | # + sqlQuery - The SQL query such as `` `SELECT * from Album WHERE name=${albumName}` `` 45 | # + rowType - The `typedesc` of the record to which the result needs to be returned 46 | # + return - Stream of records in the `rowType` type 47 | remote isolated function query(sql:ParameterizedQuery sqlQuery, typedesc rowType = <>) 48 | returns stream = @java:Method { 49 | 'class: "io.ballerina.lib.snowflake.nativeimpl.QueryProcessor", 50 | name: "nativeQuery" 51 | } external; 52 | 53 | # Executes the query, which is expected to return at most one row of the result. 54 | # If the query does not return any results, `sql:NoRowsError` is returned. 55 | # 56 | # + sqlQuery - The SQL query such as `` `SELECT * from Album WHERE name=${albumName}` `` 57 | # + returnType - The `typedesc` of the record to which the result needs to be returned. 58 | # It can be a basic type if the query result contains only one column 59 | # + return - Result in the `returnType` type or an `sql:Error` 60 | remote isolated function queryRow(sql:ParameterizedQuery sqlQuery, typedesc returnType = <>) 61 | returns returnType|sql:Error = @java:Method { 62 | 'class: "io.ballerina.lib.snowflake.nativeimpl.QueryProcessor", 63 | name: "nativeQueryRow" 64 | } external; 65 | 66 | # Executes the SQL query. Only the metadata of the execution is returned (not the results from the query). 67 | # 68 | # + sqlQuery - The SQL query such as `` `DELETE FROM Album WHERE artist=${artistName}` `` 69 | # + return - Metadata of the query execution as an `sql:ExecutionResult` or an `sql:Error` 70 | remote isolated function execute(sql:ParameterizedQuery sqlQuery) 71 | returns sql:ExecutionResult|sql:Error = @java:Method { 72 | 'class: "io.ballerina.lib.snowflake.nativeimpl.ExecuteProcessor", 73 | name: "nativeExecute" 74 | } external; 75 | 76 | # Executes the SQL query with multiple sets of parameters in a batch. 77 | # Only the metadata of the execution is returned (not results from the query). 78 | # If one of the commands in the batch fails, this will return an `sql:BatchExecuteError`. However, the driver may 79 | # or may not continue to process the remaining commands in the batch after a failure. 80 | # 81 | # + sqlQueries - The SQL query with multiple sets of parameters 82 | # + return - Metadata of the query execution as an `sql:ExecutionResult[]` or an `sql:Error` 83 | remote isolated function batchExecute(sql:ParameterizedQuery[] sqlQueries) returns sql:ExecutionResult[]|sql:Error { 84 | if sqlQueries.length() == 0 { 85 | return error sql:ApplicationError(" Parameter 'sqlQueries' cannot be empty array"); 86 | } 87 | return nativeBatchExecute(self, sqlQueries); 88 | } 89 | 90 | # Executes a SQL query, which calls a stored procedure. This may or may not return results. 91 | # 92 | # + sqlQuery - The SQL query such as `` `CALL sp_GetAlbums();` `` 93 | # + rowTypes - `typedesc` array of the records to which the results need to be returned 94 | # + return - Summary of the execution and results are returned in an `sql:ProcedureCallResult`, or an `sql:Error` 95 | remote isolated function call(sql:ParameterizedCallQuery sqlQuery, typedesc[] rowTypes = []) 96 | returns sql:ProcedureCallResult|sql:Error = @java:Method { 97 | 'class: "io.ballerina.lib.snowflake.nativeimpl.CallProcessor", 98 | name: "nativeCall" 99 | } external; 100 | 101 | # Closes the SQL client and shuts down the connection pool. 102 | # 103 | # + return - Possible error when closing the client 104 | public isolated function close() returns sql:Error? = @java:Method { 105 | 'class: "io.ballerina.lib.snowflake.nativeimpl.ClientProcessor", 106 | name: "close" 107 | } external; 108 | } 109 | -------------------------------------------------------------------------------- /examples/employees-db/README.md: -------------------------------------------------------------------------------- 1 | # Employees Data Management Example 2 | 3 | ## Overview 4 | This example demonstrates how to use the Ballerina `Snowflake` module to execute statements and query a Snowflake database. 5 | 6 | Here, a sample database is used to demonstrate the functionalities of the module. This sample database models a 7 | company's employees management system. The database contains a single table `Employee` which contains information 8 | regarding an employee such as their employee ID, name, contact details, hire date, employee ID of their manager, and ' 9 | their job title. 10 | 11 | This consists of two separate examples, and covers the following features: 12 | * Connection 13 | * Query (`SELECT`) 14 | * Query row 15 | * Execution (`INSERT`, `UPDATE`, `DELETE`) 16 | * Batch Execution 17 | 18 | ### 1. Setup Example 19 | This example shows how to establish a connection to a Snowflake database with the required configurations and connection 20 | parameters, create a database & table, and finally populate the table. 21 | 22 | ### 2. Service Example 23 | This example shows how an HTTP RESTful service can be created to insert and retrieve data from the Snowflake database. 24 | 25 | ## Prerequisites 26 | 27 | ### 1. Setting the configuration variables 28 | In the `Config.toml` file, set the configuration variables to correspond to your Snowflake server. 29 | * `accountIdentifier`: The account identifier of your Snowflake server 30 | * `user`: The username of your Snowflake account 31 | * `password`: The password of your Snowflake account 32 | 33 | *NOTE:* Snowflake driver fails with Java 16 and above because starting with JDK 16, strong encapsulation was turned on by default and one of the driver dependencies have employed the use of sun.misc.Unsafe along with reflection. This is not allowed in Java 16 and above. Therefore, to run this example, you need to use Java 15 or below. For more information, see [here](https://community.snowflake.com/s/article/JDBC-Driver-Compatibility-Issue-With-JDK-16-and-Later). If you are using Java 16 or above, you can use the following workaround to run this example: 34 | 35 | * Export the following environment variable: 36 | ```shell 37 | export JDK_JAVA_OPTIONS="--add-opens java.base/java.nio=ALL-UNNAMED" 38 | ``` 39 | * Set Snowflake property `JDBC_QUERY_RESULT_FORMAT` to `JSON` as follows: 40 | ```ballerina 41 | snowflake:Options options = { 42 | properties: { 43 | "JDBC_QUERY_RESULT_FORMAT": "JSON" 44 | } 45 | }; 46 | ``` 47 | 48 | ### 2. Establishing the connection 49 | * The following can be used to connect to a MySQL server using Ballerina 50 | ```ballerina 51 | snowflake:Client snowflakeClient = check new (accountIdentifier, user, password, options); 52 | ``` 53 | 54 | After establishing the connection, queries may be executed using the `snowflakeClient` as usual. 55 | ```ballerina 56 | _ = check snowflakeClient->execute(` 57 | INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEE (employee_id, first_name, last_name, email, phone, hire_date, manager_id) 58 | VALUES (10, 'John', 'Smith', 'john@smith.com', '483 299 111', '2021-08-20', 1, "Software Engineer"); 59 | `); 60 | 61 | stream streamData = snowflakeClient->query("SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEE"); 62 | check from Employee emp in streamData 63 | do { 64 | io:println(emp); 65 | }; 66 | ``` 67 | 68 | ## Running the Example 69 | 70 | ### 1. Setup 71 | This example illustrates the following 72 | * How to establish a connection to your MySQL server 73 | * How to create a database and table 74 | * Populating the table 75 | 76 | This example can be run by executing the command `bal run setup`. 77 | 78 | ### 2. Service 79 | This example creates an HTTP service with the endpoint `/employees` on port 8080 that can be used to interact with the 80 | database 81 | 82 | #### 2.1 Get all employee details - method:`GET` 83 | * This would query the Employees table and fetch details of all the employees present in it. 84 | * Example CURL request: 85 | ```shell 86 | curl 'localhost:8080/employees' 87 | ``` 88 | 89 | #### 2.2 Get details on a single employee - method:`GET` 90 | * This would retrieve the details of a single employee with the given employee ID. 91 | * Example CURL request: 92 | ```shell 93 | curl 'localhost:8080/employees/3' 94 | ``` 95 | 96 | #### 2.3 Add a new employee - method:`POST` 97 | * This would add a new employee to the table. 98 | * Example CURL request: 99 | ```shell 100 | curl -X POST 'localhost:8080/employees/' \ 101 | --header 'Content-Type: application/json' \ 102 | --data-raw '{ 103 | "first_name": "test", 104 | "last_name": "test", 105 | "email": "test@test.com", 106 | "phone": "882 771 110", 107 | "hire_date": { 108 | "year": 2021, 109 | "month": 12, 110 | "day": 16 111 | }, 112 | "manager_id": 1, 113 | "job_title": "Sales Intern" 114 | }' 115 | ``` 116 | 117 | #### 2.4 Update an employee's information - method:`PUT` 118 | * This would update the details of a provided employee on the table. 119 | * Example CURL request: 120 | ```shell 121 | curl -X PUT 'localhost:8080/employees/2' \ 122 | --header 'Content-Type: application/json' \ 123 | --data-raw '{ 124 | "first_name": "test", 125 | "last_name": "test", 126 | "email": "test@test.com", 127 | "phone": "882 771 110", 128 | "hire_date": { 129 | "year": 2021, 130 | "month": 12, 131 | "day": 16 132 | }, 133 | "manager_id": 1, 134 | "job_title": "Sales Intern" 135 | }' 136 | ``` 137 | 138 | #### 2.5 Delete an employee - method:`DELETE` 139 | * This would delete the details of the employee with the provided ID from the table. 140 | * Example CURL request: 141 | ```shell 142 | curl -X DELETE 'localhost:8080/employees/6' 143 | ``` 144 | 145 | ### 2.6 Count - method: `GET` 146 | * This would retrieve the total number of employees that are present in the table. 147 | * Example CURL request: 148 | ```shell 149 | curl 'localhost:8080/employees/count' 150 | ``` 151 | 152 | ### 2.7 Get subordinates - method: `GET` 153 | * This would retrieve the list of employees that another is responsible for managing. 154 | * Example CURL request: 155 | ```shell 156 | curl 'localhost:8080/employees/subordinates/1' 157 | ``` 158 | 159 | This example can be run by executing the command `bal run service`. 160 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/lib/snowflake/nativeimpl/ClientProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 3 | // 4 | // WSO2 LLC. licenses this file to you under the Apache License, 5 | // Version 2.0 (the "License"); you may not use this file except 6 | // in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, 12 | // software distributed under the License is distributed on an 13 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | // KIND, either express or implied. See the License for the 15 | // specific language governing permissions and limitations 16 | // under the License. 17 | */ 18 | 19 | package io.ballerina.lib.snowflake.nativeimpl; 20 | 21 | import io.ballerina.lib.snowflake.Constants; 22 | import io.ballerina.runtime.api.creators.ValueCreator; 23 | import io.ballerina.runtime.api.utils.StringUtils; 24 | import io.ballerina.runtime.api.utils.TypeUtils; 25 | import io.ballerina.runtime.api.values.BMap; 26 | import io.ballerina.runtime.api.values.BObject; 27 | import io.ballerina.runtime.api.values.BString; 28 | import io.ballerina.stdlib.sql.datasource.SQLDatasource; 29 | import io.ballerina.stdlib.sql.utils.ErrorGenerator; 30 | 31 | import java.util.Locale; 32 | import java.util.Properties; 33 | 34 | import static io.ballerina.lib.snowflake.Constants.ClientConfiguration.BASIC_AUTH_TYPE; 35 | import static io.ballerina.lib.snowflake.Constants.ClientConfiguration.CONFIG_PRIVATE_KEY_PASSPHRASE; 36 | import static io.ballerina.lib.snowflake.Constants.ClientConfiguration.CONFIG_PRIVATE_KEY_PATH; 37 | import static io.ballerina.lib.snowflake.Constants.ClientConfiguration.KEY_BASED_AUTH_TYPE; 38 | import static io.ballerina.lib.snowflake.Constants.ClientConfiguration.PROPERTY_PRIVATE_KEY_FILE; 39 | import static io.ballerina.lib.snowflake.Constants.ClientConfiguration.PROPERTY_PRIVATE_KEY_FILE_PWD; 40 | 41 | /** 42 | * This class will include the native method implementation for the JDBC client. 43 | * 44 | * @since 1.0.0 45 | */ 46 | public class ClientProcessor { 47 | 48 | public static Object createClient(BObject client, BMap clientConfig, 49 | BMap globalPool) { 50 | String url = clientConfig.getStringValue(Constants.ClientConfiguration.URL).getValue(); 51 | if (!isJdbcUrlValid(url)) { 52 | return ErrorGenerator.getSQLApplicationError("Invalid JDBC URL: " + url); 53 | } 54 | 55 | BMap options = clientConfig.getMapValue(Constants.ClientConfiguration.OPTIONS); 56 | BMap properties = ValueCreator.createMapValue(); 57 | Properties poolProperties = null; 58 | 59 | String datasourceName = null; 60 | if (options != null) { 61 | BMap optionProperties = options.getMapValue(Constants.ClientConfiguration.PROPERTIES); 62 | BString dataSourceNamVal = options.getStringValue(Constants.ClientConfiguration.DATASOURCE_NAME); 63 | datasourceName = dataSourceNamVal == null ? null : dataSourceNamVal.getValue(); 64 | if (optionProperties != null) { 65 | for (BString propKey : optionProperties.getKeys()) { 66 | if (propKey.getValue().toLowerCase(Locale.ENGLISH).matches(Constants.CONNECT_TIMEOUT)) { 67 | poolProperties = new Properties(); 68 | poolProperties.setProperty(Constants.POOL_CONNECTION_TIMEOUT, 69 | optionProperties.getStringValue(propKey).getValue()); 70 | } 71 | } 72 | properties = optionProperties; 73 | } 74 | } 75 | 76 | BMap connectionPool = clientConfig.getMapValue(Constants.ClientConfiguration.CONNECTION_POOL_OPTIONS); 77 | 78 | BMap authConfigs = clientConfig.getMapValue(Constants.ClientConfiguration.AUTH_CONFIG); 79 | String authType = TypeUtils.getType(authConfigs).getName(); 80 | 81 | BString userVal = authConfigs.getStringValue(Constants.ClientConfiguration.USER); 82 | String user = userVal == null ? null : userVal.getValue(); 83 | SQLDatasource.SQLDatasourceParams sqlDatasourceParams = new SQLDatasource.SQLDatasourceParams() 84 | .setUrl(url) 85 | .setDatasourceName(datasourceName) 86 | .setOptions(properties) 87 | .setPoolProperties(poolProperties) 88 | .setConnectionPool(connectionPool, globalPool); 89 | if (BASIC_AUTH_TYPE.equals(authType)) { 90 | BString passwordVal = authConfigs.getStringValue(Constants.ClientConfiguration.PASSWORD); 91 | String password = passwordVal == null ? null : passwordVal.getValue(); 92 | 93 | sqlDatasourceParams = sqlDatasourceParams 94 | .setUser(user) 95 | .setPassword(password); 96 | } else if (KEY_BASED_AUTH_TYPE.equals((authType))) { 97 | BString privateKeyPathValue = authConfigs.getStringValue( 98 | StringUtils.fromString(CONFIG_PRIVATE_KEY_PATH)); 99 | BString keyPassphraseValue = authConfigs.getStringValue( 100 | StringUtils.fromString(CONFIG_PRIVATE_KEY_PASSPHRASE)); 101 | properties.put(StringUtils.fromString(PROPERTY_PRIVATE_KEY_FILE), privateKeyPathValue); 102 | properties.put(StringUtils.fromString(PROPERTY_PRIVATE_KEY_FILE_PWD), keyPassphraseValue); 103 | sqlDatasourceParams = sqlDatasourceParams 104 | .setUser(user) 105 | .setOptions(properties); 106 | } else { 107 | return ErrorGenerator.getSQLApplicationError("Invalid Auth Type: " + authType); 108 | } 109 | 110 | boolean executeGKFlag = false; 111 | boolean batchExecuteGKFlag = false; 112 | 113 | return io.ballerina.stdlib.sql.nativeimpl.ClientProcessor.createClient(client, sqlDatasourceParams, 114 | executeGKFlag, batchExecuteGKFlag); 115 | } 116 | 117 | // Unable to perform a complete validation since URL differs based on the database. 118 | private static boolean isJdbcUrlValid(String jdbcUrl) { 119 | return !jdbcUrl.isEmpty() && jdbcUrl.trim().startsWith("jdbc:"); 120 | } 121 | 122 | public static Object close(BObject client) { 123 | return io.ballerina.stdlib.sql.nativeimpl.ClientProcessor.close(client); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /docs/spec/spec.md: -------------------------------------------------------------------------------- 1 | # Specification: Ballerina Snowflake Library 2 | 3 | _Authors_: @daneshk 4 | _Reviewers_: @bhashinee, @sahanhe 5 | _Created_: 2024/01/08 6 | _Updated_: 2024/01/08 7 | _Edition_: Swan Lake 8 | 9 | ## Introduction 10 | 11 | This is the specification for the Snowflake standard library of [Ballerina language](https://ballerina.io/), which the functionality required to access and manipulate data stored in a Snowflake database. 12 | 13 | The Snowflake library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag. 14 | 15 | If you have any feedback or suggestions about the library, start a discussion via a [GitHub issue](https://github.com/ballerina-platform/ballerina-standard-library/issues) or in the [Discord server](https://discord.gg/ballerinalang). Based on the outcome of the discussion, the specification and implementation can be updated. Community feedback is always welcome. Any accepted proposal, which affects the specification is stored under `/docs/proposals`. Proposals under discussion can be found with the label `type/proposal` in GitHub. 16 | 17 | The conforming implementation of the specification is released to Ballerina Central. Any deviation from the specification is considered a bug. 18 | 19 | ## Contents 20 | 21 | 1. [Overview](#1-overview) 22 | 2. [Client](#2-client) 23 | 2.1. [Handle connection pools](#21-handle-connection-pools) 24 | 2.2. [Close the client](#22-close-the-client) 25 | 3. [Queries and values](#3-queries-and-values) 26 | 4. [Database operations](#4-database-operations) 27 | 28 | # 1. Overview 29 | 30 | This specification elaborates on the usage of the Snowflake `Client` interface to interface with a Snowflake database. 31 | 32 | `Client` supports five database operations as follows, 33 | 1. Executes the query, which may return multiple results. 34 | 2. Executes the query, which is expected to return at most one row of the result. 35 | 3. Executes the SQL query. Only the metadata of the execution is returned. 36 | 4. Executes the SQL query with multiple sets of parameters in a batch. Only the metadata of the execution is returned. 37 | 5. Executes a SQL query, which calls a stored procedure. This can either return results or nil. 38 | 39 | All the above operations make use of `sql:ParameterizedQuery` object, backtick surrounded string template to pass 40 | SQL statements to the database. 41 | 42 | # 2. Client 43 | 44 | Each client represents a pool of connections to the database. The pool of connections is maintained throughout the lifetime of the client. 45 | 46 | **Initialization of the Client:** 47 | 48 | The Snowflake default client is for the basic authentication method. The client definition is as follows: 49 | ```ballerina 50 | # Initializes the Snowflake Client. The client must be kept open throughout the application lifetime. 51 | # 52 | # + account_identifier - The Snowflake account identifier 53 | # + user - The username of the Snowflake account 54 | # + password - The password of the Snowflake account 55 | # + options - The Snowflake client properties 56 | # + connectionPool - The `sql:ConnectionPool` to be used for the connection. If there is no 57 | # `connectionPool` provided, the global connection pool (shared by all clients) will be used 58 | # + return - An `sql:Error` if the client creation fails 59 | public isolated function init(string account_identifier, string user, string password, Options? options = (), sql:ConnectionPool? connectionPool = ()) returns sql:Error?; 60 | ``` 61 | 62 | Additionally, the Snowflake connector has an Advanced client, which allows the user to use other authentication methods such as public/private key authentication, etc. Right now, the Advanced client supports only Basic authentication and Key based authentication, but it can be extended to support other authentication methods in the future. 63 | The Advanced client definition is as follows: 64 | ```ballerina 65 | # Initializes the Snowflake Advanced Client. This client aliows to authenticate using basic authentication and private key based authentication. 66 | # The client must be kept open throughout the application lifetime. 67 | # 68 | # + account_identifier - The Snowflake account identifier 69 | # + authConfig - The authentication configuration for the Snowflake account 70 | # + options - The Snowflake client properties 71 | # + connectionPool - The `sql:ConnectionPool` to be used for the connection. If there is no 72 | # `connectionPool` provided, the global connection pool (shared by all clients) will be used 73 | # + return - An `sql:Error` if the client creation fails 74 | public isolated function init(string accountIdentifier, AuthConfig authConfig, Options? options = (), 75 | sql:ConnectionPool? connectionPool = ()) returns sql:Error?; 76 | 77 | # Represents the authentication configuration for the Snowflake client. 78 | type AuthConfig BasicAuth|KeyBasedAuth; 79 | 80 | # Represents the basic authentication configuration for the Snowflake client. 81 | type BasicAuth record { 82 | # The username of the Snowflake account 83 | string user; 84 | # The password of the Snowflake account 85 | string password; 86 | }; 87 | 88 | # Represents the key-based authentication configuration for the Snowflake client. 89 | type KeyBasedAuth record { 90 | # The username of the Snowflake account 91 | string user; 92 | # The path to the private key file. The private key file must be in the PKCS#8 format. 93 | # Use forward slashes as file path separators on all operating systems, including Windows. The JDBC driver replaces forward slashes with the appropriate path separator for the platform. 94 | string privateKeyPath; 95 | # The passphrase for the private key file. If the private key file is encrypted, provide the passphrase to decrypt the file. 96 | string privateKeyPassphrase?; 97 | }; 98 | ``` 99 | 100 | **Additional configurations available for initializing the Snowflake client:** 101 | * Connection properties: 102 | ```ballerina 103 | # An additional set of configurations related to a database connection. 104 | public type Options record {| 105 | # The driver class name to be used to get the connection 106 | string? datasourceName = (); 107 | # The database properties, which should be applied when getting the connection 108 | map? properties = (); 109 | |}; 110 | ``` 111 | 112 | ## 2.1. Handle connection pools 113 | 114 | Connection pool handling is generic and implemented through `sql` module. For more information, see the 115 | [SQL specification](https://github.com/ballerina-platform/module-ballerina-sql/blob/master/docs/spec/spec.md#21-connection-pool-handling) 116 | 117 | ## 2.2. Close the client 118 | 119 | Once all the database operations are performed, the client can be closed by invoking the `close()` 120 | operation. This will close the corresponding connection pool if it is not shared by any other database clients. 121 | 122 | ```ballerina 123 | # Closes the Snowflake client and shuts down the connection pool. 124 | # 125 | # + return - Possible error when closing the client 126 | public isolated function close() returns Error?; 127 | ``` 128 | 129 | # 3. Queries and values 130 | 131 | All the generic `sql` Queries and Values are supported. For more information, see the 132 | [SQL specification](https://github.com/ballerina-platform/module-ballerina-sql/blob/master/docs/spec/spec.md#3-queries-and-values) 133 | 134 | # 4. Database operations 135 | 136 | The `Client` supports five database operations as follows, 137 | 1. Executes the query, which may return multiple results. 138 | 2. Executes the query, which is expected to return at most one row of the result. 139 | 3. Executes the SQL query. Only the metadata of the execution is returned. 140 | 4. Executes the SQL query with multiple sets of parameters in a batch. Only the metadata of the execution is returned. 141 | 5. Executes a SQL query, which calls a stored procedure. This can either return results or nil. 142 | 143 | For more information on database operations, see the [SQL specification](https://github.com/ballerina-platform/module-ballerina-sql/blob/master/docs/spec/spec.md#4-database-operations) 144 | -------------------------------------------------------------------------------- /ballerina/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | The [Snowflake](https://www.snowflake.com/) is a cloud-based data platform that provides a data warehouse as a service designed for the cloud, providing a single integrated platform with a single SQL-based data warehouse for all data workloads. 3 | The Snowflake data warehouse uses a new SQL database engine with a unique architecture designed for the cloud. It provides operations to execute a wide range of standard DDL Commands, SQL Commands, and SQL Functions for querying data sources. 4 | You can find reference information for all the Snowflake SQL commands (DDL, DML, and query syntax) [here](https://docs.snowflake.com/en/sql-reference-commands.html). 5 | 6 | The `ballerinax/snowflake` package allows you to access the Snowflake database via the Ballerina SQL APIs and manage data persistent in the Snowflake database. 7 | 8 | ## Setup guide 9 | 10 | To use the Snowflake connector, you must have a valid Snowflake account. If you do not have an account, you can sign up for a account [here](https://signup.snowflake.com/). 11 | 12 | ### Create a warehouse and database 13 | 14 | 1. Log in to your Snowflake account. 15 | 2. Go to the **Warehouses** tab under the **Admin** section, as shown below. 16 | ![Snowflake Warehouse](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/main/docs/setup/resources/snowflakes_create_warehouse.png) 17 | 18 | 3. Click **+ Warehouse** and select a name and type for a new warehouse, as shown below. 19 | ![Snowflake Create Warehouse](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/main/docs/setup/resources/snowflakes_create_warehouse_2.png) 20 | 21 | 4. Optional - You can set the created warehouse as the default warehouse for the account by editing the profile settings, as shown below. 22 | ![Snowflake Edit Profile](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/main/docs/setup/resources/snokeflakes_user_profile.png) 23 | ![Snowflake set default warehouse](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/main/docs/setup/resources/snowflakes_set_default_warehouse.png) 24 | 25 | *NOTE* If you do not set a default warehouse, you must specify the warehouse name when you create a connection to the Snowflake database. 26 | 27 | 5. Go to the **Databases** tab under the **Data** section and click **+ Database** to create a new database, as shown below. 28 | ![Snowflake Database](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-snowflake/main/docs/setup/resources/snowflakes_create_database.png) 29 | 30 | *NOTE* Create a database can either be created using the Snowflake web interface or using the SQL command with the Snowflake connector. 31 | 32 | ### Configure key-pair authentication 33 | 34 | To use the Snowflake connector with key-pair authentication, you must have a public/private key pair. You can generate a key pair using the following command: 35 | 36 | 1. Generate the private key using the following command: 37 | ```shell 38 | openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 aes256 -inform PEM -out key-aes256.p8 39 | ``` 40 | *NOTE* 3DES is not supported by the Ballerina Snowflake connector. Therefore, you must use the `-v2 aes256` option to generate the private key. 41 | 42 | 2. Generate the public key using the following command: 43 | ```shell 44 | openssl rsa -in key-aes256.p8 -pubout -out key-aes256.pub 45 | ``` 46 | 47 | 3. Copy the public and private key files to a local directory for storage. Record the path to the files. Note that the private key is stored using the PKCS#8 (Public Key Cryptography Standards) format and is encrypted using the passphrase you specified in the previous step. 48 | 49 | 4. Assign the public key to a Snowflake user by executing the following an ALTER USER command: 50 | ```sql 51 | ALTER USER example_user SET RSA_PUBLIC_KEY='MIIBIjANBgkqh...'; 52 | ``` 53 | 54 | ## Quickstart 55 | 56 | To use the snowflake connector in your Ballerina application, modify the .bal file as follows: 57 | 58 | ### Step 1: Import the connector 59 | 60 | Import the `ballerinax/snowflake` package into your Ballerina project. 61 | ```ballerina 62 | import ballerinax/snowflake; 63 | ``` 64 | 65 | ### Step 2: Import the Snowflake driver into your Ballerina project 66 | 67 | ```ballerina 68 | import ballerinax/snowflake.driver as _; 69 | ``` 70 | 71 | ### Step 3: Instantiate a new connector 72 | 73 | *NOTE:* Snowflake driver fails with Java 16 and above because starting with JDK 16, strong encapsulation was turned on by default and one of the driver dependencies have employed the use of sun.misc.Unsafe along with reflection. This is not allowed in Java 16 and above. Therefore, to run this example, you need to use Java 15 or below. For more information, see [here](https://community.snowflake.com/s/article/JDBC-Driver-Compatibility-Issue-With-JDK-16-and-Later). If you are using Java 16 or above, you can use the following workaround to work with the Snowflake connector: 74 | 75 | * Export the following environment variable: 76 | ```shell 77 | export JDK_JAVA_OPTIONS="--add-opens java.base/java.nio=ALL-UNNAMED" 78 | ``` 79 | * Set Snowflake property `JDBC_QUERY_RESULT_FORMAT` to `JSON` as follows: 80 | ```ballerina 81 | snowflake:Options options = { 82 | properties: { 83 | "JDBC_QUERY_RESULT_FORMAT": "JSON" 84 | } 85 | }; 86 | ``` 87 | 88 | Create a Snowflake client endpoint by giving authentication details in the Snowflake configuration. 89 | ```ballerina 90 | snowflake:Client snowflakeClient = check new(accountIdentifier, user, password); 91 | ``` 92 | 93 | If you are using key-pair authentication, you can create a Snowflake client endpoint as follows: 94 | ```ballerina 95 | AuthConfig authConfig = { 96 | user: "ballerina" 97 | privateKeyPath: "path/to/privatekey.p8" 98 | privateKeyPassphrase: "ballerina" 99 | }; 100 | snowflake:AdvancedClient snowflakeKeyBasedClient = check new (accountIdentifier, authConfig, options); 101 | ``` 102 | 103 | ### Step 4: Invoke the connector operation 104 | Now, utilize the available connector operations. 105 | 106 | #### Execute a DDL command 107 | ```ballerina 108 | sql:ExecutionResult result = check snowflakeClient->execute(`CREATE TABLE COMPANY_DB.PUBLIC.EMPLOYEES ( 109 | ID INT NOT NULL AUTOINCREMENT, 110 | FirstName VARCHAR(255), 111 | LastName VARCHAR(255), 112 | BusinessUnit VARCHAR(255), 113 | PRIMARY KEY (ID) 114 | )`); 115 | ``` 116 | 117 | #### Execute a DML command 118 | ```ballerina 119 | sql:ExecutionResult result = check snowflakeClient->execute(`INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEES (FirstName, 120 | LastName, BusinessUnit) VALUES ('Shawn', 'Jerome', 'Integration')`); 121 | ``` 122 | 123 | #### Execute a query 124 | ```ballerina 125 | type Employee record { 126 | int id; 127 | string firstName; 128 | string lastName; 129 | string businessUnit; 130 | }; 131 | ... 132 | 133 | stream resultStream = check snowflakeClient->query(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEES`); 134 | ``` 135 | 136 | #### Execute a query returning a single row 137 | ```ballerina 138 | type Employee record { 139 | int id; 140 | string firstName; 141 | string lastName; 142 | string businessUnit; 143 | }; 144 | ... 145 | 146 | Employee|error result = check snowflakeClient->queryRow(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEES WHERE ID = 1`); 147 | ``` 148 | 149 | #### Execute batch DML commands 150 | ```ballerina 151 | sql:ExecutionResult[] result = check snowflakeClient->batchExecute([ 152 | `INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEES (FirstName, LastName, BusinessUnit) VALUES ('Shawn', 'Jerome', 'Integration')`, 153 | `INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEES (FirstName, LastName, BusinessUnit) VALUES ('John', 'Doe', 'Integration')` 154 | ]); 155 | ``` 156 | 157 | #### Call a stored procedure 158 | ```ballerina 159 | sql:ProcedureCallResult ret = check snowflakeClient->call(`{call PROCEDURES_DB.PUBLIC.SELECT_EMPLOYEE_DATA(1)}`, [Employee]); 160 | stream? qResult = ret.queryResult; 161 | ``` 162 | 163 | ### Examples 164 | 165 | The following example shows how to use the Snowflake connector to create a table, insert data, and query data from the Snowflake database. 166 | 167 | [Employees Data Management Example](https://github.com/ballerina-platform/module-ballerinax-snowflake/tree/master/examples/employees-db) - Manages employee data in a Snowflake database and exposes an HTTP service to interact with the database. 168 | -------------------------------------------------------------------------------- /ballerina/client.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/jballerina.java; 18 | import ballerina/sql; 19 | 20 | # Represents a Snowflake database client. 21 | @display {label: "Snowflake", iconPath: "icon.png"} 22 | public isolated client class Client { 23 | *sql:Client; 24 | 25 | # Initializes the Snowflake Client. The client must be kept open throughout the application lifetime. 26 | # 27 | # + account_identifier - The Snowflake account identifier 28 | # + user - The username of the Snowflake account 29 | # + password - The password of the Snowflake account 30 | # + options - The Snowflake client properties 31 | # + connectionPool - The `sql:ConnectionPool` to be used for the connection. If there is no 32 | # `connectionPool` provided, the global connection pool (shared by all clients) will be used 33 | # + return - An `sql:Error` if the client creation fails 34 | public isolated function init(string account_identifier, string user, string password, 35 | Options? options = (), sql:ConnectionPool? connectionPool = ()) returns sql:Error? { 36 | string url = string `jdbc:snowflake://${account_identifier}.snowflakecomputing.com/`; 37 | ClientConfiguration clientConf = {url, authConfig: { user, password }, options, connectionPool}; 38 | return createClient(self, clientConf, sql:getGlobalConnectionPool()); 39 | } 40 | 41 | # Executes the query, which may return multiple results. 42 | # When processing the stream, make sure to consume all fetched data or close the stream. 43 | # 44 | # + sqlQuery - The SQL query such as `` `SELECT * from Album WHERE name=${albumName}` `` 45 | # + rowType - The `typedesc` of the record to which the result needs to be returned 46 | # + return - Stream of records in the `rowType` type 47 | remote isolated function query(sql:ParameterizedQuery sqlQuery, typedesc rowType = <>) 48 | returns stream = @java:Method { 49 | 'class: "io.ballerina.lib.snowflake.nativeimpl.QueryProcessor", 50 | name: "nativeQuery" 51 | } external; 52 | 53 | # Executes the query, which is expected to return at most one row of the result. 54 | # If the query does not return any results, `sql:NoRowsError` is returned. 55 | # 56 | # + sqlQuery - The SQL query such as `` `SELECT * from Album WHERE name=${albumName}` `` 57 | # + returnType - The `typedesc` of the record to which the result needs to be returned. 58 | # It can be a basic type if the query result contains only one column 59 | # + return - Result in the `returnType` type or an `sql:Error` 60 | remote isolated function queryRow(sql:ParameterizedQuery sqlQuery, typedesc returnType = <>) 61 | returns returnType|sql:Error = @java:Method { 62 | 'class: "io.ballerina.lib.snowflake.nativeimpl.QueryProcessor", 63 | name: "nativeQueryRow" 64 | } external; 65 | 66 | # Executes the SQL query. Only the metadata of the execution is returned (not the results from the query). 67 | # 68 | # + sqlQuery - The SQL query such as `` `DELETE FROM Album WHERE artist=${artistName}` `` 69 | # + return - Metadata of the query execution as an `sql:ExecutionResult` or an `sql:Error` 70 | remote isolated function execute(sql:ParameterizedQuery sqlQuery) 71 | returns sql:ExecutionResult|sql:Error = @java:Method { 72 | 'class: "io.ballerina.lib.snowflake.nativeimpl.ExecuteProcessor", 73 | name: "nativeExecute" 74 | } external; 75 | 76 | # Executes the SQL query with multiple sets of parameters in a batch. 77 | # Only the metadata of the execution is returned (not results from the query). 78 | # If one of the commands in the batch fails, this will return an `sql:BatchExecuteError`. However, the driver may 79 | # or may not continue to process the remaining commands in the batch after a failure. 80 | # 81 | # + sqlQueries - The SQL query with multiple sets of parameters 82 | # + return - Metadata of the query execution as an `sql:ExecutionResult[]` or an `sql:Error` 83 | remote isolated function batchExecute(sql:ParameterizedQuery[] sqlQueries) returns sql:ExecutionResult[]|sql:Error { 84 | if sqlQueries.length() == 0 { 85 | return error sql:ApplicationError(" Parameter 'sqlQueries' cannot be empty array"); 86 | } 87 | return nativeBatchExecute(self, sqlQueries); 88 | } 89 | 90 | # Executes a SQL query, which calls a stored procedure. This may or may not return results. 91 | # 92 | # + sqlQuery - The SQL query such as `` `CALL sp_GetAlbums();` `` 93 | # + rowTypes - `typedesc` array of the records to which the results need to be returned 94 | # + return - Summary of the execution and results are returned in an `sql:ProcedureCallResult`, or an `sql:Error` 95 | remote isolated function call(sql:ParameterizedCallQuery sqlQuery, typedesc[] rowTypes = []) 96 | returns sql:ProcedureCallResult|sql:Error = @java:Method { 97 | 'class: "io.ballerina.lib.snowflake.nativeimpl.CallProcessor", 98 | name: "nativeCall" 99 | } external; 100 | 101 | # Closes the SQL client and shuts down the connection pool. 102 | # 103 | # + return - Possible error when closing the client 104 | public isolated function close() returns sql:Error? = @java:Method { 105 | 'class: "io.ballerina.lib.snowflake.nativeimpl.ClientProcessor", 106 | name: "close" 107 | } external; 108 | } 109 | 110 | # An additional set of configurations related to a database connection. 111 | public type Options record {| 112 | # The driver class name to be used to get the connection 113 | string? datasourceName = (); 114 | # The database properties, which should be applied when getting the connection 115 | map? properties = (); 116 | |}; 117 | 118 | # An additional set of configurations for the JDBC Client to be passed internally within the module. 119 | type ClientConfiguration record {| 120 | # The JDBC URL to be used for the database connection. 121 | string? url; 122 | # The authentication configuration for the Snowflake client. 123 | AuthConfig authConfig; 124 | # The JDBC client properties 125 | Options? options; 126 | # The `sql:ConnectionPool` to be used for the connection. If there is no `connectionPool` provided, the global connection pool (shared by all clients) will be used 127 | sql:ConnectionPool? connectionPool; 128 | |}; 129 | 130 | # Represents the authentication configuration for the Snowflake client. 131 | public type AuthConfig BasicAuth|KeyBasedAuth; 132 | 133 | # Represents the basic authentication configuration for the Snowflake client. 134 | public type BasicAuth record {| 135 | # The username of the Snowflake account 136 | string user; 137 | # The password of the Snowflake account 138 | string password; 139 | |}; 140 | 141 | # Represents the key-based authentication configuration for the Snowflake client. 142 | public type KeyBasedAuth record {| 143 | # The username of the Snowflake account 144 | string user; 145 | # The path to the private key file. The private key file must be in the PKCS#8 format. 146 | # Use forward slashes as file path separators on all operating systems, including Windows. The JDBC driver replaces forward slashes with the appropriate path separator for the platform. 147 | string privateKeyPath; 148 | # The passphrase for the private key file. If the private key file is encrypted, provide the passphrase to decrypt the file. 149 | string privateKeyPassphrase?; 150 | |}; 151 | 152 | isolated function createClient(Client jdbcClient, ClientConfiguration clientConf, 153 | sql:ConnectionPool globalConnPool) returns sql:Error? = @java:Method { 154 | 'class: "io.ballerina.lib.snowflake.nativeimpl.ClientProcessor" 155 | } external; 156 | 157 | isolated function nativeBatchExecute(Client sqlClient, string[]|sql:ParameterizedQuery[] sqlQueries) 158 | returns sql:ExecutionResult[]|sql:Error = @java:Method { 159 | 'class: "io.ballerina.lib.snowflake.nativeimpl.ExecuteProcessor" 160 | } external; 161 | -------------------------------------------------------------------------------- /ballerina/tests/call-procedure-test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/sql; 18 | import ballerina/test; 19 | import ballerinax/snowflake.driver as _; 20 | 21 | @test:BeforeSuite 22 | function setupProcedureDB() returns error? { 23 | _ = check snowflakeClient->execute(`CREATE DATABASE IF NOT EXISTS PROCEDURES_DB`); 24 | 25 | _ = check snowflakeClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS PROCEDURES_DB.PUBLIC.STRING_TYPES ( 26 | ID INTEGER, 27 | VARCHAR_TYPE VARCHAR(255), 28 | CHARMAX_TYPE CHAR(10), 29 | CHAR_TYPE CHAR, 30 | CHARACTERMAX_TYPE CHARACTER(10), 31 | CHARACTER_TYPE CHARACTER, 32 | NVARCHARMAX_TYPE NVARCHAR(255), 33 | PRIMARY KEY (ID) 34 | )`); 35 | _ = check snowflakeClient->execute(`INSERT INTO PROCEDURES_DB.PUBLIC.STRING_TYPES (ID, VARCHAR_TYPE, CHARMAX_TYPE, CHAR_TYPE, CHARACTERMAX_TYPE, CHARACTER_TYPE, NVARCHARMAX_TYPE) 36 | VALUES (1, 'test0', 'test1', 'a', 'test2', 'b', 'test3')`); 37 | 38 | _ = check snowflakeClient->execute(`CREATE OR REPLACE PROCEDURE PROCEDURES_DB.PUBLIC.INSERT_STRING_DATA (ID INTEGER, 39 | VARCHAR_TYPE VARCHAR(255), 40 | CHARMAX_TYPE CHAR(10), 41 | CHAR_TYPE CHAR, 42 | CHARACTERMAX_TYPE CHARACTER(10), 43 | CHARACTER_TYPE CHARACTER, 44 | NVARCHARMAX_TYPE NVARCHAR(255)) 45 | RETURNS VARCHAR(255) 46 | LANGUAGE SQL 47 | AS 48 | $$ 49 | BEGIN 50 | INSERT INTO PROCEDURES_DB.PUBLIC.STRING_TYPES (ID, VARCHAR_TYPE, CHARMAX_TYPE, CHAR_TYPE, CHARACTERMAX_TYPE, CHARACTER_TYPE, NVARCHARMAX_TYPE) 51 | VALUES (:ID, :VARCHAR_TYPE, :CHARMAX_TYPE, :CHAR_TYPE, :CHARACTERMAX_TYPE, :CHARACTER_TYPE, :NVARCHARMAX_TYPE); 52 | RETURN 'SUCCESS'; 53 | END; 54 | $$;`); 55 | 56 | _ = check snowflakeClient->execute(`CREATE OR REPLACE PROCEDURE PROCEDURES_DB.PUBLIC.SELECT_STRING_DATA (p_id INTEGER) 57 | RETURNS TABLE (varchar_type VARCHAR(255), 58 | charmax_type CHAR(10), 59 | char_type CHAR, 60 | charactermax_type CHARACTER(10), 61 | character_type CHARACTER, 62 | nvarcharmax_type NVARCHAR(255)) 63 | LANGUAGE SQL 64 | AS 65 | $$ 66 | DECLARE 67 | select_statement VARCHAR; 68 | res RESULTSET; 69 | BEGIN 70 | select_statement := 'SELECT varchar_type, charmax_type, char_type, charactermax_type, character_type, 71 | nvarcharmax_type FROM PROCEDURES_DB.PUBLIC.STRING_TYPES WHERE ID = ' || :p_id; 72 | res := (EXECUTE IMMEDIATE :select_statement); 73 | RETURN TABLE(res); 74 | END; 75 | $$;`); 76 | } 77 | 78 | type StringDataForCall record { 79 | string varchar_type; 80 | string charmax_type; 81 | string char_type; 82 | string charactermax_type; 83 | string character_type; 84 | string nvarcharmax_type; 85 | }; 86 | 87 | type StringDataSingle record { 88 | string varchar_type; 89 | }; 90 | 91 | @test:Config { 92 | groups: ["procedures"], 93 | enable: true 94 | } 95 | function testCallWithStringTypes() returns error? { 96 | sql:ProcedureCallResult procResult = check snowflakeClient->call(`{CALL PROCEDURES_DB.PUBLIC.INSERT_STRING_DATA (ID => 2, VARCHAR_TYPE => 'test1', CHARMAX_TYPE => 'test2', CHAR_TYPE => 'c', CHARACTERMAX_TYPE => 'test3', CHARACTER_TYPE => 'd', NVARCHARMAX_TYPE => 'test4')}`); 97 | 98 | stream? qResult = procResult.queryResult; 99 | if qResult is () { 100 | test:assertFail("Empty result set returned."); 101 | } else { 102 | record {|record {} value;|}? data = check qResult.next(); 103 | if data is () { 104 | test:assertFail("Empty result set returned."); 105 | } else { 106 | record {} value = data.value; 107 | test:assertEquals(value["INSERT_STRING_DATA"], "SUCCESS", "Call procedure insert and query did not match."); 108 | } 109 | check qResult.close(); 110 | } 111 | check procResult.close(); 112 | 113 | sql:ParameterizedQuery sqlQuery = `SELECT varchar_type, charmax_type, char_type, charactermax_type, character_type, 114 | nvarcharmax_type from PROCEDURES_DB.PUBLIC.STRING_TYPES where ID = 2`; 115 | 116 | StringDataForCall expectedDataRow = { 117 | varchar_type: "test1", 118 | charmax_type: "test2", 119 | char_type: "c", 120 | charactermax_type: "test3", 121 | character_type: "d", 122 | nvarcharmax_type: "test4" 123 | }; 124 | 125 | StringDataForCall storedData = check snowflakeClient->queryRow(sqlQuery); 126 | test:assertEquals(storedData, expectedDataRow, "Call procedure insert and query did not match."); 127 | } 128 | 129 | @test:Config { 130 | groups: ["procedures"] 131 | } 132 | function testCallWithStringTypesReturnsData() returns error? { 133 | sql:ProcedureCallResult ret = check snowflakeClient->call(`{call PROCEDURES_DB.PUBLIC.SELECT_STRING_DATA(1)}`, [StringDataForCall]); 134 | stream? qResult = ret.queryResult; 135 | if qResult is () { 136 | test:assertFail("Empty result set returned."); 137 | } else { 138 | record {|record {} value;|}? data = check qResult.next(); 139 | record {}? value = data?.value; 140 | StringDataForCall expectedDataRow = { 141 | varchar_type: "test0", 142 | charmax_type: "test1", 143 | char_type: "a", 144 | charactermax_type: "test2", 145 | character_type: "b", 146 | nvarcharmax_type: "test3" 147 | }; 148 | test:assertEquals(value, expectedDataRow, "Call procedure insert and query did not match."); 149 | check qResult.close(); 150 | check ret.close(); 151 | } 152 | } 153 | 154 | @test:AfterSuite 155 | function cleanupProcedureDB() returns error? { 156 | _ = check snowflakeClient->execute(`DROP DATABASE IF EXISTS PROCEDURES_DB`); 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ballerina Snowflake Connector 2 | =================== 3 | 4 | [![Build](https://github.com/ballerina-platform/module-ballerinax-snowflake/workflows/CI/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions?query=workflow%3ACI) 5 | [![Trivy](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions/workflows/trivy-scan.yml) 6 | [![codecov](https://codecov.io/gh/ballerina-platform/module-ballerinax-snowflake/branch/main/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/module-ballerinax-snowflake) 7 | [![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-snowflake.svg)](https://github.com/ballerina-platform/module-ballerinax-snowflake/commits/main) 8 | [![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions/workflows/build-with-bal-test-native.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions/workflows/build-with-bal-test-native.yml) 9 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 10 | 11 | ## Package overview 12 | 13 | The [Snowflake](https://www.snowflake.com/) is a cloud-based data platform that provides a data warehouse as a service designed for the cloud, providing a single integrated platform with a single SQL-based data warehouse for all data workloads. 14 | The Snowflake data warehouse uses a new SQL database engine with a unique architecture designed for the cloud. It provides operations to execute a wide range of standard DDL Commands, SQL Commands, and SQL Functions for querying data sources. 15 | You can find reference information for all the Snowflake SQL commands (DDL, DML, and query syntax) [here](https://docs.snowflake.com/en/sql-reference-commands.html). 16 | 17 | The `ballerinax/snowflake` package allows you to access the Snowflake database via the Ballerina SQL APIs and manage data persistent in the Snowflake database. 18 | 19 | ## Setup guide 20 | 21 | To use the Snowflake connector, you must have a valid Snowflake account. If you do not have an account, you can sign up for a account [here](https://signup.snowflake.com/). 22 | 23 | ### Create a warehouse and database 24 | 25 | 1. Log in to your Snowflake account. 26 | 2. Go to the **Warehouses** tab under the **Admin** section, as shown below. 27 | Snowflake Warehouse 28 | 29 | 3. Click **+ Warehouse** and select a name and type for a new warehouse, as shown below. 30 | Snowflake Create Warehouse 31 | 32 | 4. Optional - You can set the created warehouse as the default warehouse for the account by editing the profile settings, as shown below. 33 | Snowflake Edit Profile 34 | Snowflake set default warehouse 35 | 36 | *NOTE* If you do not set a default warehouse, you must specify the warehouse name when you create a connection to the Snowflake database. 37 | 38 | 5. Go to the **Databases** tab under the **Data** section and click **+ Database** to create a new database, as shown below. 39 | Snowflake Database 40 | 41 | *NOTE* Create a database can either be created using the Snowflake web interface or using the SQL command with the Snowflake connector. 42 | 43 | ## Quickstart 44 | 45 | To use the snowflake connector in your Ballerina application, modify the .bal file as follows: 46 | 47 | ### Step 1: Import the connector 48 | 49 | Import the `ballerinax/snowflake` package into your Ballerina project. 50 | ```ballerina 51 | import ballerinax/snowflake; 52 | ``` 53 | 54 | ### Step 2: Import the Snowflake driver into your Ballerina project 55 | 56 | ```ballerina 57 | import ballerinax/snowflake.driver as _; 58 | ``` 59 | 60 | ### Step 3: Instantiate a new connector 61 | 62 | Create a Snowflake client endpoint by giving authentication details in the Snowflake configuration. 63 | ```ballerina 64 | snowflake:Client snowflakeClient = check new(accountIdentifier, user, password); 65 | ``` 66 | 67 | ### Step 4: Invoke the connector operation 68 | Now, utilize the available connector operations. 69 | 70 | #### Execute a DDL command 71 | ```ballerina 72 | sql:ExecutionResult result = check snowflakeClient->execute(`CREATE TABLE COMPANY_DB.PUBLIC.EMPLOYEES ( 73 | ID INT NOT NULL AUTOINCREMENT, 74 | FirstName VARCHAR(255), 75 | LastName VARCHAR(255), 76 | BusinessUnit VARCHAR(255), 77 | PRIMARY KEY (ID) 78 | )`); 79 | ``` 80 | 81 | #### Execute a DML command 82 | ```ballerina 83 | sql:ExecutionResult result = check snowflakeClient->execute(`INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEES (FirstName, 84 | LastName, BusinessUnit) VALUES ('Shawn', 'Jerome', 'Integration')`); 85 | ``` 86 | 87 | #### Execute a query 88 | ```ballerina 89 | type Employee record { 90 | int id; 91 | string firstName; 92 | string lastName; 93 | string businessUnit; 94 | }; 95 | ... 96 | 97 | stream resultStream = check snowflakeClient->query(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEES`); 98 | ``` 99 | 100 | #### Execute a query returning a single row 101 | ```ballerina 102 | type Employee record { 103 | int id; 104 | string firstName; 105 | string lastName; 106 | string businessUnit; 107 | }; 108 | ... 109 | 110 | Employee|error result = check snowflakeClient->queryRow(`SELECT * FROM COMPANY_DB.PUBLIC.EMPLOYEES WHERE ID = 1`); 111 | ``` 112 | 113 | #### Execute batch DML commands 114 | ```ballerina 115 | sql:ExecutionResult[] result = check snowflakeClient->batchExecute([ 116 | `INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEES (FirstName, LastName, BusinessUnit) VALUES ('Shawn', 'Jerome', 'Integration')`, 117 | `INSERT INTO COMPANY_DB.PUBLIC.EMPLOYEES (FirstName, LastName, BusinessUnit) VALUES ('John', 'Doe', 'Integration')` 118 | ]); 119 | ``` 120 | 121 | #### Call a stored procedure 122 | ```ballerina 123 | sql:ProcedureCallResult ret = check snowflakeClient->call(`{call PROCEDURES_DB.PUBLIC.SELECT_EMPLOYEE_DATA(1)}`, [Employee]); 124 | stream? qResult = ret.queryResult; 125 | ``` 126 | 127 | ### Examples 128 | 129 | The following example shows how to use the Snowflake connector to create a table, insert data, and query data from the Snowflake database. 130 | 131 | [Employees Data Management Example](https://github.com/ballerina-platform/module-ballerinax-snowflake/tree/master/examples/employees-db) - Manages employee data in a Snowflake database and exposes an HTTP service to interact with the database. 132 | 133 | ## Issues and projects 134 | 135 | The **Issues** and **Projects** tabs are disabled for this repository as this is part of the Ballerina library. To report bugs, request new features, start new discussions, view project boards, etc., visit the Ballerina library [parent repository](https://github.com/ballerina-platform/ballerina-library). 136 | 137 | This repository only contains the source code for the package. 138 | 139 | ## Building from the source 140 | 141 | ### Setting up the prerequisites 142 | 143 | 1. Download and install Java SE Development Kit (JDK) version 21. You can install either [OpenJDK](https://adoptopenjdk.net/) or [Oracle](https://www.oracle.com/java/technologies/downloads/). 144 | 145 | > **Note:** Set the JAVA_HOME environment variable to the path name of the directory into which you installed JDK. 146 | 147 | 2. Download and install [Ballerina Swan Lake](https://ballerina.io/). 148 | 149 | ### Building the source 150 | 151 | Execute the commands below to build from the source. 152 | 153 | - To build the library: 154 | ```shell 155 | ./gradlew clean build 156 | ``` 157 | - To run the integration tests: 158 | ```shell 159 | ./gradlew clean test 160 | ``` 161 | 162 | ## Contributing to Ballerina 163 | 164 | As an open source project, Ballerina welcomes contributions from the community. 165 | 166 | For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). 167 | 168 | ## Code of conduct 169 | 170 | All the contributors are encouraged to read the [Ballerina Code of Conduct](https://ballerina.io/code-of-conduct). 171 | 172 | ## Useful links 173 | 174 | * Discuss the code changes of the Ballerina project in [ballerina-dev@googlegroups.com](mailto:ballerina-dev@googlegroups.com). 175 | * Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 176 | * Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. 177 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ballerina/tests/complex-params-query-test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/io; 18 | import ballerina/sql; 19 | import ballerina/test; 20 | import ballerinax/snowflake.driver as _; 21 | 22 | @test:BeforeSuite 23 | function setupComplexTypesTable() returns error? { 24 | _ = check snowflakeClient->execute(`CREATE DATABASE IF NOT EXISTS TEST_DB`); 25 | _ = check snowflakeClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS TEST_DB.PUBLIC.COMPLEX_TYPES ( 26 | row_id INTEGER NOT NULL, 27 | tinyblob_type BINARY(255), 28 | blob_type BINARY, 29 | mediumblob_type BINARY, 30 | longblob_type BINARY, 31 | tinytext_type STRING, 32 | text_type STRING, 33 | mediumtext_type TEXT, 34 | longtext_type TEXT, 35 | binary_type BINARY(27), 36 | var_binary_type VARBINARY(27), 37 | PRIMARY KEY (row_id) 38 | )`); 39 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.COMPLEX_TYPES (row_id, tinyblob_type, blob_type, mediumblob_type, longblob_type, tinytext_type, text_type, 40 | mediumtext_type, longtext_type, binary_type, var_binary_type) VALUES 41 | (1, X'77736F322062616C6C6572696E6120626C6F6220746573742E', X'77736F322062616C6C6572696E6120626C6F6220746573742E', 42 | X'77736F322062616C6C6572696E6120626C6F6220746573742E', X'77736F322062616C6C6572696E6120626C6F6220746573742E', 43 | 'very long text', 'very long text','very long text','very long text', 44 | X'77736F322062616C6C6572696E612062696E61727920746573742E', X'77736F322062616C6C6572696E612062696E61727920746573742E')`); 45 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.COMPLEX_TYPES (row_id, tinyblob_type, blob_type, mediumblob_type, longblob_type, tinytext_type, text_type, 46 | mediumtext_type, longtext_type, binary_type, var_binary_type) VALUES 47 | (2, null, null, null, null, null, null, null, null, null, null)`); 48 | return (); 49 | } 50 | 51 | @test:Config { 52 | groups: ["query", "query-simple-params"] 53 | } 54 | function queryByteArrayParam() returns error? { 55 | sql:ParameterizedQuery sqlQuery = `Select * from TEST_DB.PUBLIC.COMPLEX_TYPES where row_id = 1`; 56 | stream resultStream = snowflakeClient->query(sqlQuery); 57 | ComplexTypes[] data = check from var result in resultStream 58 | select result; 59 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 60 | byte[] binaryData = data[0].binary_type; 61 | 62 | sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE binary_type = ${binaryData}`; 63 | resultStream = snowflakeClient->query(sqlQuery); 64 | data = check from var result in resultStream 65 | select result; 66 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 67 | validateComplexTableResult(data[0]); 68 | } 69 | 70 | @test:Config { 71 | groups: ["query", "query-simple-params"] 72 | } 73 | function queryTypeBinaryByteParam() returns error? { 74 | sql:ParameterizedQuery sqlQuery = `Select * from TEST_DB.PUBLIC.COMPLEX_TYPES where row_id = 1`; 75 | stream resultStream = snowflakeClient->query(sqlQuery); 76 | ComplexTypes[] data = check from var result in resultStream 77 | select result; 78 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 79 | byte[] binaryData = data[0].binary_type; 80 | 81 | sql:BinaryValue typeVal = new (binaryData); 82 | sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE binary_type = ${typeVal}`; 83 | resultStream = snowflakeClient->query(sqlQuery); 84 | data = check from var result in resultStream 85 | select result; 86 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 87 | validateComplexTableResult(data[0]); 88 | } 89 | 90 | @test:Config { 91 | groups: ["query", "query-simple-params"], 92 | enable: false 93 | } 94 | function queryTypeBinaryReadableByteChannelParam() returns error? { 95 | io:ReadableByteChannel byteChannel = check getByteColumnChannel(); 96 | sql:BinaryValue typeVal = new (byteChannel); 97 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE binary_type = ${typeVal}`; 98 | stream resultStream = snowflakeClient->query(sqlQuery); 99 | ComplexTypes[] data = check from var result in resultStream 100 | select result; 101 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 102 | validateComplexTableResult(data[0]); 103 | } 104 | 105 | @test:Config { 106 | groups: ["query", "query-simple-params"], 107 | enable: false 108 | } 109 | function queryTypeVarBinaryReadableByteChannelParam() returns error? { 110 | io:ReadableByteChannel byteChannel = check getByteColumnChannel(); 111 | sql:VarBinaryValue typeVal = new (byteChannel); 112 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE binary_type = ${typeVal}`; 113 | stream resultStream = snowflakeClient->query(sqlQuery); 114 | ComplexTypes[] data = check from var result in resultStream 115 | select result; 116 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 117 | validateComplexTableResult(data[0]); 118 | } 119 | 120 | @test:Config { 121 | groups: ["query", "query-simple-params"] 122 | } 123 | function queryTypeTinyBlobByteParam() returns error? { 124 | sql:ParameterizedQuery sqlQuery = `Select * from TEST_DB.PUBLIC.COMPLEX_TYPES where row_id = 1`; 125 | stream resultStream = snowflakeClient->query(sqlQuery); 126 | ComplexTypes[] data = check from var result in resultStream 127 | select result; 128 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 129 | byte[] binaryData = data[0].tinyblob_type; 130 | 131 | sql:BinaryValue typeVal = new (binaryData); 132 | sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE tinyblob_type = ${typeVal}`; 133 | resultStream = snowflakeClient->query(sqlQuery); 134 | data = check from var result in resultStream 135 | select result; 136 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 137 | validateComplexTableResult(data[0]); 138 | } 139 | 140 | @test:Config { 141 | groups: ["query", "query-simple-params"] 142 | } 143 | function queryTypeBlobByteParam() returns error? { 144 | sql:ParameterizedQuery sqlQuery = `Select * from TEST_DB.PUBLIC.COMPLEX_TYPES where row_id = 1`; 145 | stream resultStream = snowflakeClient->query(sqlQuery); 146 | ComplexTypes[] data = check from var result in resultStream 147 | select result; 148 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 149 | byte[] binaryData = data[0].blob_type; 150 | 151 | sql:BlobValue typeVal = new (binaryData); 152 | sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE blob_type = ${typeVal}`; 153 | resultStream = snowflakeClient->query(sqlQuery); 154 | data = check from var result in resultStream 155 | select result; 156 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 157 | validateComplexTableResult(data[0]); 158 | } 159 | 160 | @test:Config { 161 | groups: ["query", "query-simple-params"] 162 | } 163 | function queryTypeMediumBlobByteParam() returns error? { 164 | sql:ParameterizedQuery sqlQuery = `Select * from TEST_DB.PUBLIC.COMPLEX_TYPES where row_id = 1`; 165 | stream resultStream = snowflakeClient->query(sqlQuery); 166 | ComplexTypes[] data = check from var result in resultStream 167 | select result; 168 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 169 | byte[] binaryData = data[0].mediumblob_type; 170 | 171 | sql:BlobValue typeVal = new (binaryData); 172 | sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE mediumblob_type = ${typeVal}`; 173 | resultStream = snowflakeClient->query(sqlQuery); 174 | data = check from var result in resultStream 175 | select result; 176 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 177 | validateComplexTableResult(data[0]); 178 | } 179 | 180 | @test:Config { 181 | groups: ["query", "query-simple-params"] 182 | } 183 | function queryTypeLongBlobByteParam() returns error? { 184 | sql:ParameterizedQuery sqlQuery = `Select * from TEST_DB.PUBLIC.COMPLEX_TYPES where row_id = 1`; 185 | stream resultStream = snowflakeClient->query(sqlQuery); 186 | ComplexTypes[] data = check from var result in resultStream 187 | select result; 188 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 189 | byte[] binaryData = data[0].longblob_type; 190 | 191 | sql:BlobValue typeVal = new (binaryData); 192 | sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE longblob_type = ${typeVal}`; 193 | resultStream = snowflakeClient->query(sqlQuery); 194 | data = check from var result in resultStream 195 | select result; 196 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 197 | validateComplexTableResult(data[0]); 198 | } 199 | 200 | @test:Config { 201 | groups: ["query", "query-simple-params"], 202 | enable: false 203 | } 204 | function queryTypeBlobReadableByteChannelParam() returns error? { 205 | io:ReadableByteChannel byteChannel = check getBlobColumnChannel(); 206 | sql:BlobValue typeVal = new (byteChannel); 207 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE blob_type = ${typeVal}`; 208 | stream resultStream = snowflakeClient->query(sqlQuery); 209 | ComplexTypes[] data = check from var result in resultStream 210 | select result; 211 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 212 | validateComplexTableResult(data[0]); 213 | } 214 | 215 | @test:Config { 216 | groups: ["query", "query-simple-params"] 217 | } 218 | function queryTypeTinyTextStringParam() returns error? { 219 | sql:TextValue typeVal = new ("very long text"); 220 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE tinytext_type = ${typeVal}`; 221 | stream resultStream = snowflakeClient->query(sqlQuery); 222 | ComplexTypes[] data = check from var result in resultStream 223 | select result; 224 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 225 | validateComplexTableResult(data[0]); 226 | } 227 | 228 | @test:Config { 229 | groups: ["query", "query-simple-params"] 230 | } 231 | function queryTypeTextStringParam() returns error? { 232 | sql:TextValue typeVal = new ("very long text"); 233 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE text_type = ${typeVal}`; 234 | stream resultStream = snowflakeClient->query(sqlQuery); 235 | ComplexTypes[] data = check from var result in resultStream 236 | select result; 237 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 238 | validateComplexTableResult(data[0]); 239 | } 240 | 241 | @test:Config { 242 | groups: ["query", "query-simple-params"] 243 | } 244 | function queryTypeMediumTextStringParam() returns error? { 245 | sql:TextValue typeVal = new ("very long text"); 246 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE mediumtext_type = ${typeVal}`; 247 | stream resultStream = snowflakeClient->query(sqlQuery); 248 | ComplexTypes[] data = check from var result in resultStream 249 | select result; 250 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 251 | validateComplexTableResult(data[0]); 252 | } 253 | 254 | @test:Config { 255 | groups: ["query", "query-simple-params"] 256 | } 257 | function queryTypeLongTextStringParam() returns error? { 258 | sql:TextValue typeVal = new ("very long text"); 259 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE longtext_type = ${typeVal}`; 260 | stream resultStream = snowflakeClient->query(sqlQuery); 261 | ComplexTypes[] data = check from var result in resultStream 262 | select result; 263 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 264 | validateComplexTableResult(data[0]); 265 | } 266 | 267 | @test:Config { 268 | groups: ["query", "query-simple-params"], 269 | enable: false 270 | } 271 | function queryTypeTextReadableCharChannelParam() returns error? { 272 | io:ReadableCharacterChannel clobChannel = check getTextColumnChannel(); 273 | sql:ClobValue typeVal = new (clobChannel); 274 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE text_type = ${typeVal}`; 275 | stream resultStream = snowflakeClient->query(sqlQuery); 276 | ComplexTypes[] data = check from var result in resultStream 277 | select result; 278 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 279 | validateComplexTableResult(data[0]); 280 | } 281 | 282 | @test:Config { 283 | groups: ["query", "query-simple-params"], 284 | enable: false 285 | } 286 | function queryTypeNTextReadableCharChannelParam() returns error? { 287 | io:ReadableCharacterChannel clobChannel = check getTextColumnChannel(); 288 | sql:NClobValue typeVal = new (clobChannel); 289 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.COMPLEX_TYPES WHERE text_type = ${typeVal}`; 290 | stream resultStream = snowflakeClient->query(sqlQuery); 291 | ComplexTypes[] data = check from var result in resultStream 292 | select result; 293 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 294 | validateComplexTableResult(data[0]); 295 | } 296 | 297 | type ComplexTypes record { 298 | int row_id; 299 | byte[] tinyblob_type; 300 | byte[] blob_type; 301 | byte[] mediumblob_type; 302 | byte[] longblob_type; 303 | string tinytext_type; 304 | string text_type; 305 | string mediumtext_type; 306 | string longtext_type; 307 | byte[] binary_type; 308 | byte[] var_binary_type; 309 | }; 310 | 311 | isolated function validateComplexTableResult(ComplexTypes returnData) { 312 | test:assertEquals(returnData.length(), 11); 313 | test:assertEquals(returnData.row_id, 1); 314 | test:assertEquals(returnData.text_type, "very long text"); 315 | } 316 | -------------------------------------------------------------------------------- /ballerina/tests/execute-basic-test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/sql; 18 | import ballerina/test; 19 | import ballerinax/snowflake.driver as _; 20 | 21 | @test:BeforeSuite 22 | function setupExecuteTable() returns error? { 23 | _ = check snowflakeClient->execute(`CREATE DATABASE IF NOT EXISTS EXECUTE_DB`); 24 | _ = check snowflakeClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS EXECUTE_DB.PUBLIC.NUMERIC_TYPES ( 25 | id INT AUTOINCREMENT, 26 | int_type INT, 27 | bigint_type BIGINT, 28 | smallint_type SMALLINT, 29 | tinyint_type TINYINT, 30 | bit_type BOOLEAN, 31 | decimal_type DECIMAL(10,2), 32 | numeric_type NUMERIC(10,2), 33 | float_type FLOAT, 34 | real_type REAL, 35 | PRIMARY KEY (id) 36 | )`); 37 | _ = check snowflakeClient->execute(`INSERT INTO EXECUTE_DB.PUBLIC.NUMERIC_TYPES (int_type) VALUES (10)`); 38 | _ = check snowflakeClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS EXECUTE_DB.PUBLIC.STRING_TYPES ( 39 | id INT, 40 | varchar_type VARCHAR(255), 41 | charmax_type CHAR(10), 42 | char_type CHAR, 43 | charactermax_type CHARACTER(10), 44 | character_type CHARACTER, 45 | nvarcharmax_type NVARCHAR(255), 46 | longvarchar_type VARCHAR(511), 47 | clob_type TEXT, 48 | PRIMARY KEY (id) 49 | )`); 50 | } 51 | 52 | @test:Config { 53 | groups: ["execute", "execute-basic"] 54 | } 55 | function testCreateTable() returns error? { 56 | sql:ExecutionResult result = check snowflakeClient->execute(`CREATE TABLE EXECUTE_DB.PUBLIC.CREATE_TABLE(studentID int, LastName 57 | varchar(255))`); 58 | test:assertExactEquals(result.affectedRowCount, 0, "Affected row count is different."); 59 | test:assertExactEquals(result.lastInsertId, (), "Last Insert Id is not nil."); 60 | } 61 | 62 | @test:Config { 63 | groups: ["execute", "execute-basic"], 64 | dependsOn: [testCreateTable] 65 | } 66 | function testInsertTable() returns error? { 67 | sql:ExecutionResult result = check snowflakeClient->execute(`Insert into EXECUTE_DB.PUBLIC.NUMERIC_TYPES (int_type) values (20)`); 68 | 69 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 70 | } 71 | 72 | @test:Config { 73 | groups: ["execute", "execute-basic"], 74 | dependsOn: [testInsertTable] 75 | } 76 | function testInsertTableWithoutGeneratedKeys() returns error? { 77 | sql:ExecutionResult result = check snowflakeClient->execute(`Insert into EXECUTE_DB.PUBLIC.STRING_TYPES (id, varchar_type) 78 | values (20, 'test')`); 79 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 80 | test:assertEquals(result.lastInsertId, (), "Last Insert Id is nil."); 81 | } 82 | 83 | @test:Config { 84 | groups: ["execute", "execute-basic"], 85 | dependsOn: [testInsertTableWithoutGeneratedKeys] 86 | } 87 | function testInsertTableWithGeneratedKeys() returns error? { 88 | sql:ExecutionResult result = check snowflakeClient->execute(`insert into EXECUTE_DB.PUBLIC.NUMERIC_TYPES (int_type) values (21)`); 89 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 90 | 91 | int insertedId = check snowflakeClient->queryRow(`select max(id) from EXECUTE_DB.PUBLIC.NUMERIC_TYPES AT(statement=>last_query_id())`); 92 | test:assertTrue(insertedId > 1, "Last Insert Id is nil."); 93 | } 94 | 95 | type NumericType record { 96 | int id; 97 | int? int_type; 98 | int? bigint_type; 99 | int? smallint_type; 100 | int? tinyint_type; 101 | boolean? bit_type; 102 | decimal? decimal_type; 103 | decimal? numeric_type; 104 | float? float_type; 105 | float? real_type; 106 | }; 107 | 108 | @test:Config { 109 | groups: ["execute", "execute-basic"], 110 | dependsOn: [testInsertTableWithGeneratedKeys] 111 | } 112 | function testInsertAndSelectTableWithGeneratedKeys() returns error? { 113 | sql:ExecutionResult result = check snowflakeClient->execute(`insert into EXECUTE_DB.PUBLIC.NUMERIC_TYPES (int_type) values (31)`); 114 | 115 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 116 | 117 | int insertedId = check snowflakeClient->queryRow(`select max(id) from EXECUTE_DB.PUBLIC.NUMERIC_TYPES AT(statement=>last_query_id())`); 118 | sql:ParameterizedQuery query = `SELECT * from EXECUTE_DB.PUBLIC.NUMERIC_TYPES where id = ${insertedId}`; 119 | stream streamData = snowflakeClient->query(query); 120 | record {|NumericType value;|}? data = check streamData.next(); 121 | check streamData.close(); 122 | test:assertNotExactEquals(data?.value, (), "Incorrect InsetId returned."); 123 | } 124 | 125 | @test:Config { 126 | groups: ["execute", "execute-basic"], 127 | dependsOn: [testInsertAndSelectTableWithGeneratedKeys] 128 | } 129 | function testInsertWithAllNilAndSelectTableWithGeneratedKeys() returns error? { 130 | sql:ExecutionResult result = check snowflakeClient->execute(`insert into EXECUTE_DB.PUBLIC.NUMERIC_TYPES (int_type, bigint_type, 131 | smallint_type, tinyint_type, bit_type, decimal_type, numeric_type, float_type, real_type) 132 | values (null,null,null,null,null,null,null,null,null)`); 133 | 134 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 135 | 136 | int insertedId = check snowflakeClient->queryRow(`select max(id) from EXECUTE_DB.PUBLIC.NUMERIC_TYPES AT(statement=>last_query_id())`); 137 | sql:ParameterizedQuery query = `SELECT * from EXECUTE_DB.PUBLIC.NUMERIC_TYPES where id = ${insertedId}`; 138 | stream streamData = snowflakeClient->query(query); 139 | record {|NumericType value;|}? data = check streamData.next(); 140 | check streamData.close(); 141 | test:assertNotExactEquals(data?.value, (), "Incorrect InsetId returned."); 142 | } 143 | 144 | type StringData record { 145 | int id; 146 | string varchar_type; 147 | string charmax_type; 148 | string char_type; 149 | string charactermax_type; 150 | string character_type; 151 | string nvarcharmax_type; 152 | string longvarchar_type; 153 | string clob_type; 154 | }; 155 | 156 | @test:Config { 157 | groups: ["execute", "execute-basic"], 158 | dependsOn: [testInsertWithAllNilAndSelectTableWithGeneratedKeys] 159 | } 160 | function testInsertWithStringAndSelectTable() returns error? { 161 | string intIDVal = "25"; 162 | sql:ParameterizedQuery insertQuery = `Insert into EXECUTE_DB.PUBLIC.STRING_TYPES (id, varchar_type, charmax_type, char_type, charactermax_type, 163 | character_type, nvarcharmax_type, longvarchar_type, clob_type) values ( ${intIDVal} 164 | ,'str1','str2','s','str4','s','str6','str7','str8')`; 165 | sql:ExecutionResult result = check snowflakeClient->execute(insertQuery); 166 | 167 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 168 | 169 | sql:ParameterizedQuery query = `SELECT * from EXECUTE_DB.PUBLIC.STRING_TYPES where id = ${intIDVal}`; 170 | stream streamData = snowflakeClient->query(query); 171 | record {|StringData value;|}? data = check streamData.next(); 172 | check streamData.close(); 173 | 174 | StringData expectedInsertRow = { 175 | id: 25, 176 | varchar_type: "str1", 177 | charmax_type: "str2", 178 | char_type: "s", 179 | charactermax_type: "str4", 180 | character_type: "s", 181 | nvarcharmax_type: "str6", 182 | longvarchar_type: "str7", 183 | clob_type: "str8" 184 | }; 185 | test:assertEquals(data?.value, expectedInsertRow, "Incorrect InsetId returned."); 186 | } 187 | 188 | @test:Config { 189 | groups: ["execute", "execute-basic"], 190 | dependsOn: [testInsertWithStringAndSelectTable] 191 | } 192 | function testInsertWithEmptyStringAndSelectTable() returns error? { 193 | string intIDVal = "35"; 194 | sql:ParameterizedQuery insertQuery = `Insert into EXECUTE_DB.PUBLIC.STRING_TYPES (id, varchar_type, charmax_type, char_type, charactermax_type, 195 | character_type, nvarcharmax_type, longvarchar_type, clob_type) values ( ${intIDVal},'','','','','','','','')`; 196 | sql:ExecutionResult result = check snowflakeClient->execute(insertQuery); 197 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 198 | 199 | sql:ParameterizedQuery query = `SELECT * from EXECUTE_DB.PUBLIC.STRING_TYPES where id = ${intIDVal}`; 200 | stream streamData = snowflakeClient->query(query); 201 | record {|StringData value;|}? data = check streamData.next(); 202 | check streamData.close(); 203 | 204 | StringData expectedInsertRow = { 205 | id: 35, 206 | varchar_type: "", 207 | charmax_type: "", 208 | char_type: "", 209 | charactermax_type: "", 210 | character_type: "", 211 | nvarcharmax_type: "", 212 | longvarchar_type: "", 213 | clob_type: "" 214 | }; 215 | test:assertEquals(data?.value, expectedInsertRow, "Incorrect InsetId returned."); 216 | } 217 | 218 | type StringNilData record { 219 | int id; 220 | string? varchar_type; 221 | string? charmax_type; 222 | string? char_type; 223 | string? charactermax_type; 224 | string? character_type; 225 | string? nvarcharmax_type; 226 | string? longvarchar_type; 227 | string? clob_type; 228 | }; 229 | 230 | @test:Config { 231 | groups: ["execute", "execute-basic"], 232 | dependsOn: [testInsertWithEmptyStringAndSelectTable] 233 | } 234 | function testInsertWithNilStringAndSelectTable() returns error? { 235 | string intIDVal = "45"; 236 | sql:ParameterizedQuery insertQuery = `Insert into EXECUTE_DB.PUBLIC.STRING_TYPES (id, varchar_type, charmax_type, char_type, charactermax_type, 237 | character_type, nvarcharmax_type, longvarchar_type, clob_type) values (${intIDVal},null,null,null,null,null,null,null,null)`; 238 | sql:ExecutionResult result = check snowflakeClient->execute(insertQuery); 239 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 240 | 241 | sql:ParameterizedQuery query = `SELECT * from EXECUTE_DB.PUBLIC.STRING_TYPES where id = ${intIDVal}`; 242 | stream streamData = snowflakeClient->query(query); 243 | record {|StringNilData value;|}? data = check streamData.next(); 244 | check streamData.close(); 245 | 246 | StringNilData expectedInsertRow = { 247 | id: 45, 248 | varchar_type: (), 249 | charmax_type: (), 250 | char_type: (), 251 | charactermax_type: (), 252 | character_type: (), 253 | nvarcharmax_type: (), 254 | longvarchar_type: (), 255 | clob_type: () 256 | }; 257 | test:assertEquals(data?.value, expectedInsertRow, "Incorrect InsetId returned."); 258 | } 259 | 260 | @test:Config { 261 | groups: ["execute", "execute-basic"], 262 | dependsOn: [testInsertWithNilStringAndSelectTable] 263 | } 264 | function testInsertTableWithDatabaseError() returns error? { 265 | sql:ExecutionResult|sql:Error result = snowflakeClient->execute(`Insert into EXECUTE_DB.PUBLIC.NumericTypesNonExistTable (int_type) values (20)`); 266 | 267 | if result is sql:DatabaseError { 268 | test:assertTrue(result.message().includes("EXECUTE_DB.PUBLIC.NUMERICTYPESNONEXISTTABLE' does not exist or not authorized.."), 269 | "Error message does not match, actual :'" + result.message() + "'"); 270 | sql:DatabaseErrorDetail errorDetails = result.detail(); 271 | test:assertEquals(errorDetails.errorCode, 2003, "SQL Error code does not match"); 272 | test:assertEquals(errorDetails.sqlState, "42S02", "SQL Error state does not match"); 273 | } else { 274 | test:assertFail("Database Error expected."); 275 | } 276 | } 277 | 278 | @test:Config { 279 | groups: ["execute", "execute-basic"], 280 | dependsOn: [testInsertTableWithDatabaseError] 281 | } 282 | function testInsertTableWithDataTypeError() returns error? { 283 | sql:ExecutionResult|sql:Error result = snowflakeClient->execute(`Insert into EXECUTE_DB.PUBLIC.NUMERIC_TYPES (int_type) values ('This is wrong type')`); 284 | 285 | if result is sql:DatabaseError { 286 | test:assertTrue(result.message().startsWith("Error while executing SQL query: Insert into EXECUTE_DB.PUBLIC.NUMERIC_TYPES" + 287 | " (int_type) values ('This is wrong type'). Numeric value 'This is wrong type' is not recognized."), 288 | "Error message does not match, actual :'" + result.message() + "'"); 289 | sql:DatabaseErrorDetail errorDetails = result.detail(); 290 | test:assertEquals(errorDetails.errorCode, 100038, "SQL Error code does not match"); 291 | test:assertEquals(errorDetails.sqlState, "22018", "SQL Error state does not match"); 292 | } else { 293 | test:assertFail("Database Error expected."); 294 | } 295 | } 296 | 297 | type ResultCount record { 298 | int countVal; 299 | }; 300 | 301 | @test:Config { 302 | groups: ["execute", "execute-basic"], 303 | dependsOn: [testInsertTableWithDataTypeError] 304 | } 305 | function testUpdateData() returns error? { 306 | sql:ExecutionResult result = check snowflakeClient->execute(`Update EXECUTE_DB.PUBLIC.NUMERIC_TYPES set int_type = 11 where int_type = 10`); 307 | test:assertExactEquals(result.affectedRowCount, 1, "Affected row count is different."); 308 | 309 | stream streamData = snowflakeClient->query(`SELECT count(*) as countval from EXECUTE_DB.PUBLIC.NUMERIC_TYPES 310 | where int_type = 11`); 311 | record {|ResultCount value;|}? data = check streamData.next(); 312 | check streamData.close(); 313 | test:assertEquals(data?.value?.countVal, 1, "Update command was not successful."); 314 | } 315 | 316 | @test:AfterSuite 317 | function cleanUpExecuteDB() returns error? { 318 | _ = check snowflakeClient->execute(`DROP DATABASE IF EXISTS EXECUTE_DB`); 319 | } 320 | -------------------------------------------------------------------------------- /ballerina/tests/simple-params-query-test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/sql; 18 | import ballerina/test; 19 | import ballerinax/snowflake.driver as _; 20 | 21 | @test:BeforeSuite 22 | function setupDataTable() returns error? { 23 | _ = check snowflakeClient->execute(`CREATE DATABASE IF NOT EXISTS TEST_DB`); 24 | _ = check snowflakeClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS TEST_DB.PUBLIC.DATA_TABLE(row_id INTEGER, int_type INTEGER, long_type BIGINT, 25 | float_type FLOAT, 26 | double_type DOUBLE, 27 | boolean_type BOOLEAN, 28 | string_type VARCHAR(50), 29 | decimal_type DECIMAL(20, 2), 30 | PRIMARY KEY (row_id) 31 | );`); 32 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.DATA_TABLE (row_id, int_type, long_type, float_type, double_type, boolean_type, string_type, decimal_type) 33 | VALUES(1, 1, 9223372036854774807, 123.34, 2139095039, TRUE, 'Hello', 23.45)`); 34 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.DATA_TABLE (row_id, int_type, long_type, float_type, double_type, boolean_type, string_type, decimal_type) 35 | VALUES(3, 1, 9372036854774807, 124.34, 29095039, false, '1', 25.45)`); 36 | _ = check snowflakeClient->execute(`INSERT INTO TEST_DB.PUBLIC.DATA_TABLE (row_id) VALUES (2)`); 37 | return (); 38 | } 39 | 40 | @test:Config { 41 | groups: ["query", "query-simple-params"] 42 | } 43 | function querySingleIntParam() returns error? { 44 | int rowId = 1; 45 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 46 | stream resultStream = snowflakeClient->query(sqlQuery); 47 | DataTable[] data = check from var result in resultStream 48 | select result; 49 | 50 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 51 | validateDataTableResult(data[0]); 52 | } 53 | 54 | @test:Config { 55 | groups: ["query", "query-simple-params"] 56 | } 57 | function queryDoubleIntParam() returns error? { 58 | int rowId = 1; 59 | int intType = 1; 60 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId} AND int_type = ${intType}`; 61 | stream resultStream = snowflakeClient->query(sqlQuery); 62 | DataTable[] data = check from var result in resultStream 63 | select result; 64 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 65 | validateDataTableResult(data[0]); 66 | } 67 | 68 | @test:Config { 69 | groups: ["query", "query-simple-params"] 70 | } 71 | function queryIntAndLongParam() returns error? { 72 | int rowId = 1; 73 | int longType = 9223372036854774807; 74 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId} AND long_type = ${longType}`; 75 | stream resultStream = snowflakeClient->query(sqlQuery); 76 | DataTable[] data = check from var result in resultStream 77 | select result; 78 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 79 | validateDataTableResult(data[0]); 80 | } 81 | 82 | @test:Config { 83 | groups: ["query", "query-simple-params"] 84 | } 85 | function queryStringParam() returns error? { 86 | string stringType = "Hello"; 87 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${stringType}`; 88 | stream resultStream = snowflakeClient->query(sqlQuery); 89 | DataTable[] data = check from var result in resultStream 90 | select result; 91 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 92 | validateDataTableResult(data[0]); 93 | } 94 | 95 | @test:Config { 96 | groups: ["query", "query-simple-params"] 97 | } 98 | function queryIntAndStringParam() returns error? { 99 | string stringType = "Hello"; 100 | int rowId = 1; 101 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${stringType} AND row_id = ${rowId}`; 102 | stream resultStream = snowflakeClient->query(sqlQuery); 103 | DataTable[] data = check from var result in resultStream 104 | select result; 105 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 106 | validateDataTableResult(data[0]); 107 | } 108 | 109 | @test:Config { 110 | groups: ["query", "query-simple-params"] 111 | } 112 | function queryDoubleParam() returns error? { 113 | float doubleType = 2139095039.0; 114 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE double_type = ${doubleType}`; 115 | stream resultStream = snowflakeClient->query(sqlQuery); 116 | DataTable[] data = check from var result in resultStream 117 | select result; 118 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 119 | validateDataTableResult(data[0]); 120 | } 121 | 122 | @test:Config { 123 | groups: ["query", "query-simple-params"] 124 | } 125 | function queryFloatParam() returns error? { 126 | float floatType = 123.34; 127 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE float_type = ${floatType}`; 128 | stream resultStream = snowflakeClient->query(sqlQuery); 129 | DataTable[] data = check from var result in resultStream 130 | select result; 131 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 132 | validateDataTableResult(data[0]); 133 | } 134 | 135 | @test:Config { 136 | groups: ["query", "query-simple-params"] 137 | } 138 | function queryDoubleAndFloatParam() returns error? { 139 | float floatType = 123.34; 140 | float doubleType = 2139095039.0; 141 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE float_type = ${floatType} 142 | and double_type = ${doubleType}`; 143 | stream resultStream = snowflakeClient->query(sqlQuery); 144 | DataTable[] data = check from var result in resultStream 145 | select result; 146 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 147 | validateDataTableResult(data[0]); 148 | } 149 | 150 | @test:Config { 151 | groups: ["query", "query-simple-params"] 152 | } 153 | function queryDecimalParam() returns error? { 154 | decimal decimalValue = 23.45; 155 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE decimal_type = ${decimalValue}`; 156 | stream resultStream = snowflakeClient->query(sqlQuery); 157 | DataTable[] data = check from var result in resultStream 158 | select result; 159 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 160 | validateDataTableResult(data[0]); 161 | } 162 | 163 | @test:Config { 164 | groups: ["query", "query-simple-params"] 165 | } 166 | function queryDecimalAnFloatParam() returns error? { 167 | decimal decimalValue = 23.45; 168 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE decimal_type = ${decimalValue} 169 | and double_type = 2139095039.0`; 170 | stream resultStream = snowflakeClient->query(sqlQuery); 171 | DataTable[] data = check from var result in resultStream 172 | select result; 173 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 174 | validateDataTableResult(data[0]); 175 | } 176 | 177 | @test:Config { 178 | groups: ["query", "query-simple-params"] 179 | } 180 | function queryTypeVarcharStringParam() returns error? { 181 | sql:VarcharValue typeVal = new ("Hello"); 182 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 183 | stream resultStream = snowflakeClient->query(sqlQuery); 184 | DataTable[] data = check from var result in resultStream 185 | select result; 186 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 187 | validateDataTableResult(data[0]); 188 | } 189 | 190 | @test:Config { 191 | groups: ["query", "query-simple-params"] 192 | } 193 | function queryTypeCharStringParam() returns error? { 194 | sql:CharValue typeVal = new ("Hello"); 195 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 196 | stream resultStream = snowflakeClient->query(sqlQuery); 197 | DataTable[] data = check from var result in resultStream 198 | select result; 199 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 200 | validateDataTableResult(data[0]); 201 | } 202 | 203 | @test:Config { 204 | groups: ["query", "query-simple-params"] 205 | } 206 | function queryTypeNCharStringParam() returns error? { 207 | sql:CharValue typeVal = new ("Hello"); 208 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 209 | stream resultStream = snowflakeClient->query(sqlQuery); 210 | DataTable[] data = check from var result in resultStream 211 | select result; 212 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 213 | validateDataTableResult(data[0]); 214 | } 215 | 216 | @test:Config { 217 | groups: ["query", "query-simple-params"] 218 | } 219 | function queryTypeNVarCharStringParam() returns error? { 220 | sql:VarcharValue typeVal = new ("Hello"); 221 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 222 | stream resultStream = snowflakeClient->query(sqlQuery); 223 | DataTable[] data = check from var result in resultStream 224 | select result; 225 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 226 | validateDataTableResult(data[0]); 227 | } 228 | 229 | @test:Config { 230 | groups: ["query", "query-simple-params"] 231 | } 232 | function queryTypeVarCharIntegerParam() returns error? { 233 | sql:VarcharValue typeVal = new ("1"); 234 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 235 | 236 | decimal decimalVal = 25.45; 237 | stream resultStream = snowflakeClient->query(sqlQuery); 238 | DataTable[] data = check from var result in resultStream 239 | select result; 240 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 241 | test:assertEquals(data[0].int_type, 1); 242 | test:assertEquals(data[0].long_type, 9372036854774807); 243 | test:assertEquals(data[0].double_type, 29095039); 244 | test:assertEquals(data[0].boolean_type, false); 245 | test:assertEquals(data[0].decimal_type, decimalVal); 246 | test:assertEquals(data[0].string_type, "1"); 247 | test:assertEquals(data[0].row_id, 3); 248 | } 249 | 250 | @test:Config { 251 | groups: ["query", "query-simple-params"] 252 | } 253 | function queryTypBooleanBooleanParam() returns error? { 254 | sql:BooleanValue typeVal = new (true); 255 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 256 | stream resultStream = snowflakeClient->query(sqlQuery); 257 | DataTable[] data = check from var result in resultStream 258 | select result; 259 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 260 | validateDataTableResult(data[0]); 261 | } 262 | 263 | @test:Config { 264 | groups: ["query", "query-simple-params"] 265 | } 266 | function queryTypBitIntParam() returns error? { 267 | sql:BitValue typeVal = new (1); 268 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 269 | stream resultStream = snowflakeClient->query(sqlQuery); 270 | DataTable[] data = check from var result in resultStream 271 | select result; 272 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 273 | validateDataTableResult(data[0]); 274 | } 275 | 276 | @test:Config { 277 | groups: ["query", "query-simple-params"] 278 | } 279 | function queryTypBitStringParam() returns error? { 280 | sql:BitValue typeVal = new (true); 281 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 282 | stream resultStream = snowflakeClient->query(sqlQuery); 283 | DataTable[] data = check from var result in resultStream 284 | select result; 285 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 286 | validateDataTableResult(data[0]); 287 | } 288 | 289 | @test:Config { 290 | groups: ["query", "query-simple-params"] 291 | } 292 | function queryTypBitInvalidIntParam() returns error? { 293 | sql:BitValue typeVal = new (12); 294 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 295 | stream resultStream = snowflakeClient->query(sqlQuery); 296 | DataTable[]|error returnVal = from var result in resultStream 297 | select result; 298 | test:assertTrue(returnVal is error); 299 | error dbError = returnVal; 300 | test:assertEquals(dbError.message(), "Only 1 or 0 can be passed for BitValue SQL Type, but found :12"); 301 | } 302 | 303 | type DataTable record { 304 | int row_id; 305 | int int_type; 306 | int long_type; 307 | float float_type; 308 | float double_type; 309 | boolean boolean_type; 310 | string string_type; 311 | decimal decimal_type; 312 | }; 313 | 314 | isolated function validateDataTableResult(DataTable returnData) { 315 | decimal decimalVal = 23.45; 316 | test:assertEquals(returnData.row_id, 1); 317 | test:assertEquals(returnData.int_type, 1); 318 | test:assertEquals(returnData.long_type, 9223372036854774807); 319 | test:assertEquals(returnData.double_type, 2139095039); 320 | test:assertEquals(returnData.boolean_type, true); 321 | test:assertEquals(returnData.decimal_type, decimalVal); 322 | test:assertEquals(returnData.string_type, "Hello"); 323 | } 324 | 325 | @test:Config { 326 | groups: ["query", "query-simple-params"] 327 | } 328 | function queryRecord() returns sql:Error? { 329 | int rowId = 1; 330 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 331 | DataTable queryResult = check snowflakeClient->queryRow(sqlQuery); 332 | validateDataTableResult(queryResult); 333 | } 334 | 335 | @test:Config { 336 | groups: ["query", "query-simple-params"] 337 | } 338 | function queryRecordNegative() returns sql:Error? { 339 | int rowId = 999; 340 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 341 | DataTable|sql:Error queryResult = snowflakeClient->queryRow(sqlQuery); 342 | if queryResult is sql:Error { 343 | test:assertTrue(queryResult is sql:NoRowsError); 344 | test:assertTrue(queryResult.message().endsWith("Query did not retrieve any rows."), "Incorrect error message"); 345 | } else { 346 | test:assertFail("Expected no rows error with empty query result."); 347 | } 348 | } 349 | 350 | @test:Config { 351 | groups: ["query", "query-simple-params"] 352 | } 353 | function queryRecordNegative3() returns error? { 354 | int rowId = 1; 355 | sql:ParameterizedQuery sqlQuery = `SELECT row_id, invalid_column_name from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 356 | DataTable|error queryResult = snowflakeClient->queryRow(sqlQuery); 357 | if queryResult is error { 358 | test:assertTrue(queryResult.message().endsWith("invalid identifier 'INVALID_COLUMN_NAME'."), 359 | "Incorrect error message"); 360 | } else { 361 | test:assertFail("Expected error when querying with invalid column name."); 362 | } 363 | } 364 | 365 | @test:Config { 366 | groups: ["query", "query-simple-params"] 367 | } 368 | function queryValue() returns error? { 369 | int count = check snowflakeClient->queryRow(`SELECT COUNT(*) FROM TEST_DB.PUBLIC.DATA_TABLE`); 370 | test:assertEquals(count, 3); 371 | } 372 | 373 | @test:Config { 374 | groups: ["query", "query-simple-params"] 375 | } 376 | function queryValueNegative1() returns error? { 377 | int rowId = 1; 378 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 379 | int|error queryResult = snowflakeClient->queryRow(sqlQuery); 380 | if queryResult is error { 381 | test:assertTrue(queryResult is sql:TypeMismatchError, "Incorrect error type"); 382 | test:assertEquals(queryResult.message(), "Expected type to be 'int' but found 'record{}'."); 383 | } else { 384 | test:assertFail("Expected error when query result contains multiple columns."); 385 | } 386 | } 387 | 388 | @test:Config { 389 | groups: ["query", "query-simple-params"] 390 | } 391 | function queryValueNegative2() returns error? { 392 | int rowId = 1; 393 | sql:ParameterizedQuery sqlQuery = `SELECT string_type from TEST_DB.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 394 | int|error queryResult = snowflakeClient->queryRow(sqlQuery); 395 | if queryResult is error { 396 | test:assertEquals(queryResult.message(), 397 | "SQL Type 'VARCHAR' cannot be converted to ballerina type 'int'.", 398 | "Incorrect error message"); 399 | } else { 400 | test:assertFail("Expected error when query returns unexpected result type."); 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /ballerina/tests/simple-params-query-with-key-based-client-test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/sql; 18 | import ballerina/test; 19 | import ballerinax/snowflake.driver as _; 20 | 21 | @test:BeforeSuite 22 | function setupDataTableWithKeyBasedClient() returns error? { 23 | _ = check snowflakeKeyBasedClient->execute(`CREATE DATABASE IF NOT EXISTS TEST_DB_1`); 24 | _ = check snowflakeKeyBasedClient->execute(`CREATE TEMPORARY TABLE IF NOT EXISTS TEST_DB_1.PUBLIC.DATA_TABLE(row_id INTEGER, int_type INTEGER, long_type BIGINT, 25 | float_type FLOAT, 26 | double_type DOUBLE, 27 | boolean_type BOOLEAN, 28 | string_type VARCHAR(50), 29 | decimal_type DECIMAL(20, 2), 30 | PRIMARY KEY (row_id) 31 | );`); 32 | _ = check snowflakeKeyBasedClient->execute(`INSERT INTO TEST_DB_1.PUBLIC.DATA_TABLE (row_id, int_type, long_type, float_type, double_type, boolean_type, string_type, decimal_type) 33 | VALUES(1, 1, 9223372036854774807, 123.34, 2139095039, TRUE, 'Hello', 23.45)`); 34 | _ = check snowflakeKeyBasedClient->execute(`INSERT INTO TEST_DB_1.PUBLIC.DATA_TABLE (row_id, int_type, long_type, float_type, double_type, boolean_type, string_type, decimal_type) 35 | VALUES(3, 1, 9372036854774807, 124.34, 29095039, false, '1', 25.45)`); 36 | _ = check snowflakeKeyBasedClient->execute(`INSERT INTO TEST_DB_1.PUBLIC.DATA_TABLE (row_id) VALUES (2)`); 37 | return (); 38 | } 39 | 40 | @test:Config { 41 | groups: ["query", "query-simple-params"] 42 | } 43 | function querySingleIntParamWithKeyBasedClient() returns error? { 44 | int rowId = 1; 45 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 46 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 47 | DataTable[] data = check from var result in resultStream 48 | select result; 49 | 50 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 51 | validateDataTableResult(data[0]); 52 | } 53 | 54 | @test:Config { 55 | groups: ["query", "query-simple-params"] 56 | } 57 | function queryDoubleIntParamWithKeyBasedClient() returns error? { 58 | int rowId = 1; 59 | int intType = 1; 60 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId} AND int_type = ${intType}`; 61 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 62 | DataTable[] data = check from var result in resultStream 63 | select result; 64 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 65 | validateDataTableResult(data[0]); 66 | } 67 | 68 | @test:Config { 69 | groups: ["query", "query-simple-params"] 70 | } 71 | function queryIntAndLongParamWithKeyBasedClient() returns error? { 72 | int rowId = 1; 73 | int longType = 9223372036854774807; 74 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId} AND long_type = ${longType}`; 75 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 76 | DataTable[] data = check from var result in resultStream 77 | select result; 78 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 79 | validateDataTableResult(data[0]); 80 | } 81 | 82 | @test:Config { 83 | groups: ["query", "query-simple-params"] 84 | } 85 | function queryStringParamWithKeyBasedClient() returns error? { 86 | string stringType = "Hello"; 87 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${stringType}`; 88 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 89 | DataTable[] data = check from var result in resultStream 90 | select result; 91 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 92 | validateDataTableResult(data[0]); 93 | } 94 | 95 | @test:Config { 96 | groups: ["query", "query-simple-params"] 97 | } 98 | function queryIntAndStringParamWithKeyBasedClient() returns error? { 99 | string stringType = "Hello"; 100 | int rowId = 1; 101 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${stringType} AND row_id = ${rowId}`; 102 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 103 | DataTable[] data = check from var result in resultStream 104 | select result; 105 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 106 | validateDataTableResult(data[0]); 107 | } 108 | 109 | @test:Config { 110 | groups: ["query", "query-simple-params"] 111 | } 112 | function queryDoubleParamWithKeyBasedClient() returns error? { 113 | float doubleType = 2139095039.0; 114 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE double_type = ${doubleType}`; 115 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 116 | DataTable[] data = check from var result in resultStream 117 | select result; 118 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 119 | validateDataTableResult(data[0]); 120 | } 121 | 122 | @test:Config { 123 | groups: ["query", "query-simple-params"] 124 | } 125 | function queryFloatParamWithKeyBasedClient() returns error? { 126 | float floatType = 123.34; 127 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE float_type = ${floatType}`; 128 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 129 | DataTable[] data = check from var result in resultStream 130 | select result; 131 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 132 | validateDataTableResult(data[0]); 133 | } 134 | 135 | @test:Config { 136 | groups: ["query", "query-simple-params"] 137 | } 138 | function queryDoubleAndFloatParamWithKeyBasedClient() returns error? { 139 | float floatType = 123.34; 140 | float doubleType = 2139095039.0; 141 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE float_type = ${floatType} 142 | and double_type = ${doubleType}`; 143 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 144 | DataTable[] data = check from var result in resultStream 145 | select result; 146 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 147 | validateDataTableResult(data[0]); 148 | } 149 | 150 | @test:Config { 151 | groups: ["query", "query-simple-params"] 152 | } 153 | function queryDecimalParamWithKeyBasedClient() returns error? { 154 | decimal decimalValue = 23.45; 155 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE decimal_type = ${decimalValue}`; 156 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 157 | DataTable[] data = check from var result in resultStream 158 | select result; 159 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 160 | validateDataTableResult(data[0]); 161 | } 162 | 163 | @test:Config { 164 | groups: ["query", "query-simple-params"] 165 | } 166 | function queryDecimalAnFloatParamWithKeyBasedClient() returns error? { 167 | decimal decimalValue = 23.45; 168 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE decimal_type = ${decimalValue} 169 | and double_type = 2139095039.0`; 170 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 171 | DataTable[] data = check from var result in resultStream 172 | select result; 173 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 174 | validateDataTableResult(data[0]); 175 | } 176 | 177 | @test:Config { 178 | groups: ["query", "query-simple-params"] 179 | } 180 | function queryTypeVarcharStringParamWithKeyBasedClient() returns error? { 181 | sql:VarcharValue typeVal = new ("Hello"); 182 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 183 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 184 | DataTable[] data = check from var result in resultStream 185 | select result; 186 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 187 | validateDataTableResult(data[0]); 188 | } 189 | 190 | @test:Config { 191 | groups: ["query", "query-simple-params"] 192 | } 193 | function queryTypeCharStringParamWithKeyBasedClient() returns error? { 194 | sql:CharValue typeVal = new ("Hello"); 195 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 196 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 197 | DataTable[] data = check from var result in resultStream 198 | select result; 199 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 200 | validateDataTableResult(data[0]); 201 | } 202 | 203 | @test:Config { 204 | groups: ["query", "query-simple-params"] 205 | } 206 | function queryTypeNCharStringParamWithKeyBasedClient() returns error? { 207 | sql:CharValue typeVal = new ("Hello"); 208 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 209 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 210 | DataTable[] data = check from var result in resultStream 211 | select result; 212 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 213 | validateDataTableResult(data[0]); 214 | } 215 | 216 | @test:Config { 217 | groups: ["query", "query-simple-params"] 218 | } 219 | function queryTypeNVarCharStringParamWithKeyBasedClient() returns error? { 220 | sql:VarcharValue typeVal = new ("Hello"); 221 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 222 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 223 | DataTable[] data = check from var result in resultStream 224 | select result; 225 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 226 | validateDataTableResult(data[0]); 227 | } 228 | 229 | @test:Config { 230 | groups: ["query", "query-simple-params"] 231 | } 232 | function queryTypeVarCharIntegerParamWithKeyBasedClient() returns error? { 233 | sql:VarcharValue typeVal = new ("1"); 234 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE string_type = ${typeVal}`; 235 | 236 | decimal decimalVal = 25.45; 237 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 238 | DataTable[] data = check from var result in resultStream 239 | select result; 240 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 241 | test:assertEquals(data[0].int_type, 1); 242 | test:assertEquals(data[0].long_type, 9372036854774807); 243 | test:assertEquals(data[0].double_type, 29095039); 244 | test:assertEquals(data[0].boolean_type, false); 245 | test:assertEquals(data[0].decimal_type, decimalVal); 246 | test:assertEquals(data[0].string_type, "1"); 247 | test:assertEquals(data[0].row_id, 3); 248 | } 249 | 250 | @test:Config { 251 | groups: ["query", "query-simple-params"] 252 | } 253 | function queryTypBooleanBooleanParamWithKeyBasedClient() returns error? { 254 | sql:BooleanValue typeVal = new (true); 255 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 256 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 257 | DataTable[] data = check from var result in resultStream 258 | select result; 259 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 260 | validateDataTableResult(data[0]); 261 | } 262 | 263 | @test:Config { 264 | groups: ["query", "query-simple-params"] 265 | } 266 | function queryTypBitIntParamWithKeyBasedClient() returns error? { 267 | sql:BitValue typeVal = new (1); 268 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 269 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 270 | DataTable[] data = check from var result in resultStream 271 | select result; 272 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 273 | validateDataTableResult(data[0]); 274 | } 275 | 276 | @test:Config { 277 | groups: ["query", "query-simple-params"] 278 | } 279 | function queryTypBitStringParamWithKeyBasedClient() returns error? { 280 | sql:BitValue typeVal = new (true); 281 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 282 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 283 | DataTable[] data = check from var result in resultStream 284 | select result; 285 | test:assertEquals(data.length(), 1, "Invalid number of records returned"); 286 | validateDataTableResult(data[0]); 287 | } 288 | 289 | @test:Config { 290 | groups: ["query", "query-simple-params"] 291 | } 292 | function queryTypBitInvalidIntParamWithKeyBasedClient() returns error? { 293 | sql:BitValue typeVal = new (12); 294 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE boolean_type = ${typeVal}`; 295 | stream resultStream = snowflakeKeyBasedClient->query(sqlQuery); 296 | DataTable[]|error returnVal = from var result in resultStream 297 | select result; 298 | test:assertTrue(returnVal is error); 299 | error dbError = returnVal; 300 | test:assertEquals(dbError.message(), "Only 1 or 0 can be passed for BitValue SQL Type, but found :12"); 301 | } 302 | 303 | isolated function validateDataTableResultWithKeyBasedClient(DataTable returnData) { 304 | decimal decimalVal = 23.45; 305 | test:assertEquals(returnData.row_id, 1); 306 | test:assertEquals(returnData.int_type, 1); 307 | test:assertEquals(returnData.long_type, 9223372036854774807); 308 | test:assertEquals(returnData.double_type, 2139095039); 309 | test:assertEquals(returnData.boolean_type, true); 310 | test:assertEquals(returnData.decimal_type, decimalVal); 311 | test:assertEquals(returnData.string_type, "Hello"); 312 | } 313 | 314 | @test:Config { 315 | groups: ["query", "query-simple-params"] 316 | } 317 | function queryRecordWithKeyBasedClient() returns sql:Error? { 318 | int rowId = 1; 319 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 320 | DataTable queryResult = check snowflakeKeyBasedClient->queryRow(sqlQuery); 321 | validateDataTableResult(queryResult); 322 | } 323 | 324 | @test:Config { 325 | groups: ["query", "query-simple-params"] 326 | } 327 | function queryRecordNegativeWithKeyBasedClient() returns sql:Error? { 328 | int rowId = 999; 329 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 330 | DataTable|sql:Error queryResult = snowflakeKeyBasedClient->queryRow(sqlQuery); 331 | if queryResult is sql:Error { 332 | test:assertTrue(queryResult is sql:NoRowsError); 333 | test:assertTrue(queryResult.message().endsWith("Query did not retrieve any rows."), "Incorrect error message"); 334 | } else { 335 | test:assertFail("Expected no rows error with empty query result."); 336 | } 337 | } 338 | 339 | @test:Config { 340 | groups: ["query", "query-simple-params"] 341 | } 342 | function queryRecordNegative3WithKeyBasedClient() returns error? { 343 | int rowId = 1; 344 | sql:ParameterizedQuery sqlQuery = `SELECT row_id, invalid_column_name from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 345 | DataTable|error queryResult = snowflakeKeyBasedClient->queryRow(sqlQuery); 346 | if queryResult is error { 347 | test:assertTrue(queryResult.message().endsWith("invalid identifier 'INVALID_COLUMN_NAME'."), 348 | "Incorrect error message"); 349 | } else { 350 | test:assertFail("Expected error when querying with invalid column name."); 351 | } 352 | } 353 | 354 | @test:Config { 355 | groups: ["query", "query-simple-params"] 356 | } 357 | function queryValueWithKeyBasedClient() returns error? { 358 | int count = check snowflakeKeyBasedClient->queryRow(`SELECT COUNT(*) FROM TEST_DB_1.PUBLIC.DATA_TABLE`); 359 | test:assertEquals(count, 3); 360 | } 361 | 362 | @test:Config { 363 | groups: ["query", "query-simple-params"] 364 | } 365 | function queryValueNegative1WithKeyBasedClient() returns error? { 366 | int rowId = 1; 367 | sql:ParameterizedQuery sqlQuery = `SELECT * from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 368 | int|error queryResult = snowflakeKeyBasedClient->queryRow(sqlQuery); 369 | if queryResult is error { 370 | test:assertTrue(queryResult is sql:TypeMismatchError, "Incorrect error type"); 371 | test:assertEquals(queryResult.message(), "Expected type to be 'int' but found 'record{}'."); 372 | } else { 373 | test:assertFail("Expected error when query result contains multiple columns."); 374 | } 375 | } 376 | 377 | @test:Config { 378 | groups: ["query", "query-simple-params"] 379 | } 380 | function queryValueNegative2WithKeyBasedClient() returns error? { 381 | int rowId = 1; 382 | sql:ParameterizedQuery sqlQuery = `SELECT string_type from TEST_DB_1.PUBLIC.DATA_TABLE WHERE row_id = ${rowId}`; 383 | int|error queryResult = snowflakeKeyBasedClient->queryRow(sqlQuery); 384 | if queryResult is error { 385 | test:assertEquals(queryResult.message(), 386 | "SQL Type 'VARCHAR' cannot be converted to ballerina type 'int'.", 387 | "Incorrect error message"); 388 | } else { 389 | test:assertFail("Expected error when query returns unexpected result type."); 390 | } 391 | } 392 | --------------------------------------------------------------------------------