├── 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 | 
17 |
18 | 3. Click **+ Warehouse** and select a name and type for a new warehouse, as shown below.
19 | 
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 | 
23 | 
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 | 
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 | [](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions?query=workflow%3ACI)
5 | [](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions/workflows/trivy-scan.yml)
6 | [](https://codecov.io/gh/ballerina-platform/module-ballerinax-snowflake)
7 | [](https://github.com/ballerina-platform/module-ballerinax-snowflake/commits/main)
8 | [](https://github.com/ballerina-platform/module-ballerinax-snowflake/actions/workflows/build-with-bal-test-native.yml)
9 | [](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 |
28 |
29 | 3. Click **+ Warehouse** and select a name and type for a new warehouse, as shown below.
30 |
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 |
34 |
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 |
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 |
--------------------------------------------------------------------------------