├── .gitattributes ├── ballerina ├── icon.png ├── tests │ ├── Config.toml │ ├── persist │ │ ├── test_entities.bal │ │ └── rainier.bal │ ├── test_entities_generated_types.bal │ ├── gsheets-all_data_types-tests.bal │ ├── rainier_generated_types.bal │ ├── gsheets-department-tests.bal │ ├── gsheets-building-tests.bal │ ├── googlesheets_negative_test_client.bal │ ├── gsheets-workspace-tests.bal │ ├── gsheets-employee-tests.bal │ ├── googlesheets_all_data_types_generated_client.bal │ ├── gsheets-composite-key-tests.bal │ ├── gsheets-associations-tests.bal │ └── init-tests.bal ├── Ballerina.toml ├── init.bal ├── utils.bal ├── field_types.bal ├── metadata_types.bal ├── stream_types.bal ├── Module.md ├── Package.md ├── build.gradle └── Dependencies.toml ├── codecov.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── docs └── spec │ └── spec.md ├── .github ├── pull_request_template.md ├── CODEOWNERS └── workflows │ ├── trivy-scan.yml │ ├── publish-snapshot-nexus.yml │ ├── build-with-bal-test-graalvm.yml │ ├── pull-request.yml │ ├── build-timestamped-master.yml │ ├── central-publish.yml │ └── publish-release.yml ├── issue_template.md ├── issue_template copy.md ├── .gitignore ├── native ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── io │ │ └── ballerina │ │ └── stdlib │ │ └── persist │ │ └── googlesheets │ │ ├── Constants.java │ │ ├── ModuleUtils.java │ │ ├── Utils.java │ │ └── datastore │ │ └── GoogleSheetsProcessor.java └── build.gradle ├── changelog.md ├── gradle.properties ├── settings.gradle ├── pull_request_template.md ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure all Java files use LF. 2 | *.java eol=lf 3 | -------------------------------------------------------------------------------- /ballerina/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-persist.googlesheets/HEAD/ballerina/icon.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "60...80" 5 | status: 6 | project: 7 | default: 8 | target: 80 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-persist.googlesheets/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/spec/spec.md: -------------------------------------------------------------------------------- 1 | # Specification: Ballerina Persist Library 2 | 3 | _Owners_: @daneshk @sahanhe 4 | _Reviewers_: 5 | _Created_: 6 | _Updated_: 7 | _Edition_: Swan Lake 8 | 9 | -------------------------------------------------------------------------------- /ballerina/tests/Config.toml: -------------------------------------------------------------------------------- 1 | spreadsheetId="16x1E_wl7kHgd41BTLgIWxWyBPFsNJDqDQ-1aKGMEQb8" 2 | 3 | [ballerina.http.traceLogAdvancedConfig] 4 | # Enable printing trace logs on the Console. The default value is `false`. 5 | console = false 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | Fixes: 4 | 5 | ## Examples 6 | 7 | ## Checklist 8 | - [ ] Linked to an issue 9 | - [ ] Updated the specification 10 | - [ ] Updated the changelog 11 | - [ ] Added tests 12 | - [ ] Checked native-image compatibility 13 | -------------------------------------------------------------------------------- /.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 @lnash94 @shafreenAnfar 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /ballerina/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "ballerinax" 3 | name = "persist.googlesheets" 4 | version = "1.6.0" 5 | authors = ["Ballerina"] 6 | keywords = ["persist", "googlesheets", "experimental"] 7 | repository = "https://github.com/ballerina-platform/module-ballerina-persist" 8 | icon = "icon.png" 9 | license = ["Apache-2.0"] 10 | distribution = "2201.12.0" 11 | 12 | [platform.java21] 13 | graalvmCompatible = true 14 | 15 | [[platform.java21.dependency]] 16 | groupId = "io.ballerina.stdlib" 17 | artifactId = "persist.googlesheets-native" 18 | version = "1.6.0" 19 | path = "../native/build/libs/persist.googlesheets-native-1.6.0.jar" 20 | 21 | [[platform.java21.dependency]] 22 | groupId = "io.ballerina.stdlib" 23 | artifactId = "persist-native" 24 | version = "1.6.0" 25 | path = "./lib/persist-native-1.6.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 | -------------------------------------------------------------------------------- /issue_template copy.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.nar 19 | *.ear 20 | *.zip 21 | *.tar.gz 22 | *.rar 23 | 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Ballerina 28 | Ballerina.lock 29 | 30 | #Idea files 31 | .idea 32 | *.iml 33 | *.ipr 34 | *.iws 35 | 36 | # generated files 37 | target 38 | .ballerina 39 | /compiler-plugin-tests/bin/ 40 | 41 | # gradle 42 | .gradle 43 | build/ 44 | gradle-app.setting 45 | !gradle-wrapper.jar 46 | .gradletasknamecache 47 | 48 | # mac 49 | .DS_Store 50 | 51 | .classpath 52 | .project 53 | .settings 54 | .vscode 55 | 56 | # Examples 57 | examples/**/Dependencies.toml 58 | /compiler-plugin-test/bin/ 59 | ballerina/tests/Config.toml 60 | -------------------------------------------------------------------------------- /native/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 WSO2 LLC. (http://www.wso2.org) 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.stdlib.persist.googlesheets { 20 | requires io.ballerina.runtime; 21 | requires io.ballerina.lang; 22 | requires io.ballerina.stdlib.persist; 23 | } 24 | -------------------------------------------------------------------------------- /ballerina/init.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/jballerina.java; 18 | 19 | isolated function init() { 20 | setModule(); 21 | } 22 | 23 | isolated function setModule() = @java:Method { 24 | 'class: "io.ballerina.stdlib.persist.googlesheets.ModuleUtils" 25 | } external; 26 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | This file contains all the notable changes done to the Ballerina Persist 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 | 11 | ### Changed 12 | - [Updated error message in cases of uninitialized spreadsheets](https://github.com/ballerina-platform/ballerina-standard-library/issues/4505) 13 | - [Update code to preserve local time when converting civil time to string](https://github.com/ballerina-platform/ballerina-standard-library/issues/4575) 14 | - [Updated error messages to be consistent across all data sources](https://github.com/ballerina-platform/ballerina-standard-library/issues/4360) 15 | 16 | ## [1.0.0] - 2023-06-01 17 | 18 | ### Added 19 | - [Initial release](https://github.com/ballerina-platform/ballerina-standard-library/issues/4488) 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot-nexus.yml: -------------------------------------------------------------------------------- 1 | name: Publish Snapshot to Nexus 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | if: github.repository_owner == 'ballerina-platform' 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@v3 13 | 14 | - name: Set up JDK 21 15 | uses: actions/setup-java@v3 16 | with: 17 | distribution: "temurin" 18 | java-version: 21.0.3 19 | 20 | - name: Build with Gradle 21 | env: 22 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 23 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 24 | nexusUser: ${{ secrets.NEXUS_USERNAME }} 25 | nexusPassword: ${{ secrets.NEXUS_PASSWORD }} 26 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 27 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 28 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 29 | run: | 30 | git config --global user.name ${{ secrets.BALLERINA_BOT_USERNAME }} 31 | git config --global user.email ${{ secrets.BALLERINA_BOT_EMAIL }} 32 | ./gradlew build publishMavenJavaPublicationToWSO2NexusRepository --scan --no-daemon 33 | -------------------------------------------------------------------------------- /ballerina/tests/persist/test_entities.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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 | import ballerina/time; 17 | 18 | public enum OrderType { 19 | ONLINE, 20 | INSTORE 21 | } 22 | 23 | type OrderItemExtended record {| 24 | readonly string orderId; 25 | readonly string itemId; 26 | int CustomerId; 27 | boolean paid; 28 | float ammountPaid; 29 | decimal ammountPaidDecimal; 30 | time:Civil arivalTimeCivil; 31 | time:Utc arivalTimeUtc; 32 | time:Date arivalTimeDate; 33 | time:TimeOfDay arivalTimeTimeOfDay; 34 | OrderType orderType; 35 | |}; 36 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/persist/googlesheets/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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.stdlib.persist.googlesheets; 20 | 21 | /** 22 | * Constants for Persist module. 23 | * 24 | * @since 0.1.0 25 | */ 26 | public final class Constants { 27 | private Constants() { 28 | } 29 | public static final String PERSIST_GOOGLE_SHEETS_STREAM = "PersistGoogleSheetsStream"; 30 | public static final String RUN_READ_TABLE_AS_STREAM_METHOD = "readTableAsStream"; 31 | public static final String PERSIST = "persist"; 32 | public static final String ERROR = "Error"; 33 | } 34 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/persist/googlesheets/ModuleUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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.stdlib.persist.googlesheets; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.Module; 23 | 24 | /** 25 | * Utility functions relevant to module operations. 26 | * 27 | * @since 0.1.1 28 | */ 29 | public class ModuleUtils { 30 | private static Module module; 31 | private ModuleUtils() { 32 | } 33 | public static void setModule(Environment env) { 34 | module = env.getCurrentModule(); 35 | } 36 | public static Module getModule() { 37 | return module; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ballerina/utils.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/persist; 18 | import ballerinax/googleapis.sheets; 19 | 20 | public isolated function getSheetIds(sheets:Client googleSheetClient, record {|SheetMetadata...;|} metadata, string spreadsheetId) returns map|persist:Error { 21 | map sheetIds = {}; 22 | sheets:Sheet|error sheet; 23 | foreach string key in metadata.keys() { 24 | sheet = googleSheetClient->getSheetByName(spreadsheetId, metadata.get(key).tableName); 25 | if sheet is error { 26 | return error(sheet.message()); 27 | } 28 | sheetIds[key] = sheet.properties.sheetId; 29 | } 30 | return sheetIds; 31 | } 32 | -------------------------------------------------------------------------------- /ballerina/field_types.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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 | import ballerina/time; 17 | 18 | # Generic type that can used to store any of the types supported by google sheets 19 | # 20 | public type SheetFieldType string|int|decimal|boolean|float|time:Date|time:TimeOfDay|time:Civil|time:Utc; 21 | 22 | # Generic type that can used to store any of time types supported by sheets 23 | # 24 | public type SheetTimeType time:Date|time:TimeOfDay|time:Civil|time:Utc; 25 | 26 | # Generic type that can used to store any of basic numeric types supported by sheets 27 | # 28 | public type SheetNumericType int|decimal|boolean|float; 29 | 30 | # Generic type that can used to store any of basic types supported by sheets 31 | # 32 | public type SheetBasicType int|string|decimal|boolean|float; 33 | -------------------------------------------------------------------------------- /.github/workflows/build-with-bal-test-graalvm.yml: -------------------------------------------------------------------------------- 1 | name: GraalVM Check 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | lang_tag: 7 | description: Branch/Release Tag of the Ballerina Lang 8 | required: true 9 | default: master 10 | lang_version: 11 | description: Ballerina Lang Version (If given ballerina lang buid will be skipped) 12 | required: false 13 | default: '' 14 | native_image_options: 15 | description: Default native-image options 16 | required: false 17 | default: '' 18 | schedule: 19 | - cron: '30 18 * * *' 20 | pull_request: 21 | branches: 22 | - main 23 | types: [ opened, synchronize, reopened, labeled, unlabeled ] 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | call_stdlib_workflow: 31 | name: Run StdLib Workflow 32 | if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} 33 | uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main 34 | with: 35 | lang_tag: ${{ inputs.lang_tag }} 36 | lang_version: ${{ inputs.lang_version }} 37 | native_image_options: '-J-Xmx7G ${{ inputs.native_image_options }}' 38 | additional_windows_build_flags: '-x test' 39 | additional_ubuntu_build_flags: '-x test' 40 | secrets: inherit 41 | 42 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=io.ballerina.stdlib 2 | version=1.6.1-SNAPSHOT 3 | ballerinaLangVersion=2201.12.0 4 | 5 | checkstylePluginVersion=10.12.1 6 | spotbugsPluginVersion=6.0.18 7 | downloadPluginVersion=5.4.0 8 | shadowJarPluginVersion=8.1.1 9 | releasePluginVersion=2.8.0 10 | ballerinaGradlePluginVersion=2.3.0 11 | testngVersion=7.6.1 12 | gsonVersion=2.10 13 | 14 | # Direct Dependencies 15 | 16 | # Level 01 17 | stdlibIoVersion=1.8.0 18 | stdlibTimeVersion=2.7.0 19 | stdlibUrlVersion=2.6.0 20 | 21 | # Level 02 22 | stdlibLogVersion=2.12.0 23 | stdlibOsVersion=1.10.0 24 | 25 | # Level 03 26 | stdlibFileVersion=1.12.0 27 | 28 | # Level 05 29 | stdlibHttpVersion=2.14.0 30 | 31 | # Level 08 32 | stdlibPersistVersion=1.6.0 33 | 34 | # Ballerinax Observer 35 | observeVersion=1.5.0 36 | observeInternalVersion=1.5.0 37 | 38 | # Transitive Dependencies 39 | # Level 01 40 | stdlibConstraintVersion=1.7.0 41 | 42 | #Level 02 43 | stdlibCryptoVersion=2.9.0 44 | stdlibTaskVersion=2.7.0 45 | 46 | # Level 03 47 | stdlibCacheVersion=3.10.0 48 | stdlibMimeVersion=2.12.0 49 | stdlibUuidVersion=1.10.0 50 | 51 | # Level 04 52 | stdlibAuthVersion=2.14.0 53 | stdlibJwtVersion=2.15.0 54 | stdlibOAuth2Version=2.14.0 55 | stdlibDataJsonDataVersion=1.1.0 56 | # Level 06 57 | stdlibTransactionVersion=1.12.0 58 | 59 | # Connectors 60 | googleSheetsConnector=3.5.0 61 | 62 | # Enabled publishing insecure checksums, due to fail to publish to maven central 63 | # Refer https://github.com/gradle/gradle/issues/11308 64 | systemProp.org.gradle.internal.publish.checksums.insecure=true 65 | -------------------------------------------------------------------------------- /ballerina/metadata_types.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/persist; 18 | 19 | public type SheetMetadata record {| 20 | string entityName; 21 | string tableName; 22 | map fieldMetadata; 23 | string[] keyFields; 24 | string range; 25 | map dataTypes; 26 | isolated function (string[]) returns stream|persist:Error query; 27 | isolated function (anydata) returns record {}|persist:Error queryOne; 28 | map associationsMethods; 29 | |}; 30 | 31 | # Represents the metadata associated with a field in the entity record. 32 | # 33 | # + columnName - The name of the spreadsheet column to which the field is mapped 34 | # + columnId - The alphabetical Id of the column 35 | public type SheetFieldMetadata record {| 36 | string columnName; 37 | string columnId; 38 | |}; 39 | 40 | -------------------------------------------------------------------------------- /ballerina/tests/persist/rainier.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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 | 19 | enum Gender { 20 | MALE, 21 | FEMALE 22 | } 23 | 24 | type Employee record {| 25 | readonly string empNo; 26 | string firstName; 27 | string lastName; 28 | time:Date birthDate; 29 | Gender gender; 30 | time:Date hireDate; 31 | 32 | Department department; 33 | Workspace workspace; 34 | |}; 35 | 36 | type Workspace record {| 37 | readonly string workspaceId; 38 | string workspaceType; 39 | 40 | Building location; 41 | Employee[] employees; 42 | |}; 43 | 44 | type Building record {| 45 | readonly string buildingCode; 46 | string city; 47 | string state; 48 | string country; 49 | string postalCode; 50 | string 'type; 51 | 52 | Workspace[] workspaces; 53 | |}; 54 | 55 | type Department record {| 56 | readonly string deptNo; 57 | string deptName; 58 | 59 | Employee[] employees; 60 | |}; 61 | 62 | type OrderItem record {| 63 | readonly string orderId; 64 | readonly string itemId; 65 | int quantity; 66 | string notes; 67 | |}; 68 | -------------------------------------------------------------------------------- /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-base" 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 | rootProject.name = 'ballerinax-persist.googlesheets' 36 | 37 | include ':checkstyle' 38 | include ':persist.googlesheets-native' 39 | include ':persist.googlesheets-ballerina' 40 | 41 | project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") 42 | project(':persist.googlesheets-native').projectDir = file('native') 43 | project(':persist.googlesheets-ballerina').projectDir = file('ballerina') 44 | 45 | gradleEnterprise { 46 | buildScan { 47 | termsOfServiceUrl = 'https://gradle.com/terms-of-service' 48 | termsOfServiceAgree = 'yes' 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ballerina/tests/test_entities_generated_types.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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 | 19 | public enum OrderType { 20 | ONLINE, 21 | INSTORE 22 | } 23 | 24 | public type OrderItemExtended record {| 25 | readonly string orderId; 26 | readonly string itemId; 27 | int CustomerId; 28 | boolean paid; 29 | float ammountPaid; 30 | decimal ammountPaidDecimal; 31 | time:Civil arivalTimeCivil; 32 | time:Utc arivalTimeUtc; 33 | time:Date arivalTimeDate; 34 | time:TimeOfDay arivalTimeTimeOfDay; 35 | OrderType orderType; 36 | |}; 37 | 38 | public type OrderItemExtendedOptionalized record {| 39 | string orderId?; 40 | string itemId?; 41 | int CustomerId?; 42 | boolean paid?; 43 | float ammountPaid?; 44 | decimal ammountPaidDecimal?; 45 | time:Civil arivalTimeCivil?; 46 | time:Utc arivalTimeUtc?; 47 | time:Date arivalTimeDate?; 48 | time:TimeOfDay arivalTimeTimeOfDay?; 49 | OrderType orderType?; 50 | |}; 51 | 52 | public type OrderItemExtendedTargetType typedesc; 53 | 54 | public type OrderItemExtendedInsert OrderItemExtended; 55 | 56 | public type OrderItemExtendedUpdate record {| 57 | int CustomerId?; 58 | boolean paid?; 59 | float ammountPaid?; 60 | decimal ammountPaidDecimal?; 61 | time:Civil arivalTimeCivil?; 62 | time:Utc arivalTimeUtc?; 63 | time:Date arivalTimeDate?; 64 | time:TimeOfDay arivalTimeTimeOfDay?; 65 | OrderType orderType?; 66 | |}; 67 | 68 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: PR build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - 2201.[0-9]+.x 8 | 9 | jobs: 10 | ubuntu-build: 11 | name: Build on Ubuntu 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up JDK 21 18 | uses: actions/setup-java@v3 19 | with: 20 | distribution: 'temurin' 21 | java-version: 21.0.3 22 | 23 | - name: Set ENV Variables 24 | run: | 25 | echo -e '${{ toJson(secrets) }}' | jq -r 'to_entries[] | .key + "=" + .value' >> $GITHUB_ENV 26 | 27 | - name: Build with Gradle 28 | env: 29 | packageUser: ${{ github.actor }} 30 | packagePAT: ${{ secrets.GITHUB_TOKEN }} 31 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 32 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 33 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 34 | run: | 35 | ./gradlew build -x test 36 | 37 | - name: Generate Codecov Report 38 | uses: codecov/codecov-action@v3 39 | with: 40 | token: ${{ secrets.CODECOV_TOKEN }} 41 | 42 | windows-build: 43 | name: Build on Windows 44 | runs-on: windows-latest 45 | concurrency: 46 | group: ${{ github.head_ref }}-windows-build 47 | cancel-in-progress: true 48 | steps: 49 | - name: Checkout Repository 50 | uses: actions/checkout@v3 51 | 52 | - name: Set up JDK 21 53 | uses: actions/setup-java@v3 54 | with: 55 | distribution: 'temurin' 56 | java-version: 21.0.3 57 | 58 | - name: Set ENV Variables 59 | run: | 60 | echo '${{ toJson(secrets) }}' | jq -r 'to_entries[] | .key + "=" + .value' | Out-File -FilePath $env:GITHUB_ENV -Append 61 | 62 | - name: Build with Gradle 63 | env: 64 | packageUser: ${{ github.actor }} 65 | packagePAT: ${{ secrets.GITHUB_TOKEN }} 66 | JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 67 | run: ./gradlew.bat build -x test 68 | # Disabling tests because no docker in git-action windows 69 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ballerina/tests/gsheets-all_data_types-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/lang.runtime; 19 | 20 | @test:Config { 21 | groups: ["composite-key", "google-sheets"], 22 | enable: true 23 | } 24 | function gsheetsAllDataTypeCreateTest() returns error? { 25 | runtime:sleep(40); 26 | [string, string][] ids = check rainierClientAllDataType->/orderitemextendeds.post([orderItemExtended1, orderItemExtended2]); 27 | test:assertEquals(ids, [[orderItemExtended1.orderId, orderItemExtended1.itemId], [orderItemExtended2.orderId, orderItemExtended2.itemId]]); 28 | 29 | OrderItemExtended orderItemRetrieved = check rainierClientAllDataType->/orderitemextendeds/[orderItemExtended1.orderId]/[orderItemExtended1.itemId].get(); 30 | test:assertEquals(orderItemRetrieved, orderItemExtended1); 31 | 32 | orderItemRetrieved = check rainierClientAllDataType->/orderitemextendeds/[orderItemExtended2.orderId]/[orderItemExtended2.itemId].get(); 33 | test:assertEquals(orderItemRetrieved, orderItemExtended2); 34 | } 35 | 36 | @test:Config { 37 | groups: ["all-types", "google-sheets"], 38 | dependsOn: [gsheetsAllDataTypeCreateTest], 39 | enable: true 40 | } 41 | function gsheetsAllTypesReadManyTest() returns error? { 42 | stream orderItemStream = rainierClientAllDataType->/orderitemextendeds.get(); 43 | OrderItemExtended[] orderitem = check from OrderItemExtended orderItem in orderItemStream 44 | select orderItem; 45 | 46 | test:assertEquals(orderitem, [orderItemExtended1, orderItemExtended2]); 47 | } 48 | 49 | @test:Config { 50 | groups: ["all-types", "google-sheets"], 51 | dependsOn: [gsheetsAllDataTypeCreateTest], 52 | enable: true 53 | } 54 | function gsheetsAllDataTypeUpdateTest() returns error? { 55 | OrderItemExtended orderItemRetrieved = check rainierClientAllDataType->/orderitemextendeds/[orderItemExtended2.orderId]/[orderItemExtended2.itemId].put({ 56 | arivalTimeCivil : orderItemExtended3.arivalTimeCivil, 57 | paid: orderItemExtended3.paid 58 | }); 59 | test:assertEquals(orderItemRetrieved, orderItemExtended2Updated); 60 | 61 | orderItemRetrieved = check rainierClientAllDataType->/orderitemextendeds/[orderItemExtended2.orderId]/[orderItemExtended2.itemId].get(); 62 | test:assertEquals(orderItemRetrieved, orderItemExtended2Updated); 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/build-timestamped-master.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - 2201.[0-9]+.x 9 | paths-ignore: 10 | - 'load-tests/**' 11 | - '*.md' 12 | - 'docs/**' 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | if: github.repository_owner == 'ballerina-platform' 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up JDK 21 23 | uses: actions/setup-java@v3 24 | with: 25 | distribution: 'temurin' 26 | java-version: 21.0.3 27 | 28 | - name: Change to Timestamped Version 29 | run: | 30 | initialVersion=$((grep -w 'version' | cut -d= -f2) < gradle.properties ) 31 | echo "Initial_Version=$initialVersion" >> $GITHUB_ENV 32 | startTime=$(TZ="Asia/Kolkata" date +'%Y%m%d-%H%M00') 33 | latestCommit=$(git log -n 1 --pretty=format:"%h") 34 | VERSION=$((grep -w 'version' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev) 35 | updatedVersion=$VERSION-$startTime-$latestCommit 36 | echo $updatedVersion 37 | sed -i "s/version=\(.*\)/version=$updatedVersion/g" gradle.properties 38 | 39 | - name: Grant execute permission for gradlew 40 | run: chmod +x gradlew 41 | 42 | - name: Set ENV Variables 43 | run: | 44 | echo -e '${{ toJson(secrets) }}' | jq -r 'to_entries[] | .key + "=" + .value' >> $GITHUB_ENV 45 | 46 | - name: Build with Gradle 47 | env: 48 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 49 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 50 | publishUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 51 | publishPAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 52 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 53 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 54 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 55 | run: | 56 | ./gradlew clean build publishAllPublicationsToGitHubPackagesRepository --scan --no-daemon 57 | 58 | - name: Generate CodeCov Report 59 | uses: codecov/codecov-action@v3 60 | - name: Upload Artifact 61 | uses: actions/upload-artifact@v4 62 | with: 63 | name: ballerina-runtime 64 | path: target/ballerina-runtime/ 65 | 66 | - name: Revert to SNAPSHOT Version 67 | run: | 68 | echo "version=${{ env.Initial_Version }}" 69 | sed -i "s/version=\(.*\)/version=${{ env.Initial_Version }}/g" gradle.properties 70 | - name: Publish to Nexus 71 | env: 72 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 73 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 74 | nexusUser: ${{ secrets.NEXUS_USERNAME }} 75 | nexusPassword: ${{ secrets.NEXUS_PASSWORD }} 76 | run: | 77 | ./gradlew clean publishMavenJavaPublicationToWSO2NexusRepository -x test --scan --no-daemon 78 | 79 | -------------------------------------------------------------------------------- /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/stream_types.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/persist; 18 | 19 | public class PersistGoogleSheetsStream { 20 | 21 | private stream? anydataStream; 22 | private persist:Error? err; 23 | private string[] fields; 24 | private string[] include; 25 | private typedesc[] typeDescriptions; 26 | private GoogleSheetsClient? persistClient; 27 | private typedesc targetType; 28 | 29 | public isolated function init(stream? anydataStream, typedesc targetType, string[] fields, string[] include, any[] typeDescriptions, GoogleSheetsClient persistClient, persist:Error? err = ()) { 30 | self.anydataStream = anydataStream; 31 | self.fields = fields; 32 | self.include = include; 33 | self.targetType = targetType; 34 | 35 | typedesc[] typeDescriptionsArray = []; 36 | foreach any typeDescription in typeDescriptions { 37 | typeDescriptionsArray.push(>typeDescription); 38 | } 39 | self.typeDescriptions = typeDescriptionsArray; 40 | 41 | self.persistClient = persistClient; 42 | self.err = err; 43 | } 44 | 45 | public isolated function next() returns record {|record {} value;|}|persist:Error? { 46 | if self.err is persist:Error { 47 | return self.err; 48 | } else if self.anydataStream is stream { 49 | var anydataStream = >self.anydataStream; 50 | var streamValue = anydataStream.next(); 51 | if streamValue is () { 52 | return streamValue; 53 | } else if (streamValue is error) { 54 | return error(streamValue.message()); 55 | } else { 56 | record {}|error value = streamValue.value; 57 | if value is error { 58 | return error(value.message()); 59 | } 60 | check (self.persistClient).getManyRelations(value, self.fields, self.include, self.typeDescriptions); 61 | 62 | string[] keyFields = (self.persistClient).getKeyFields(); 63 | foreach string keyField in keyFields { 64 | if self.fields.indexOf(keyField) is () { 65 | _ = value.remove(keyField); 66 | } 67 | } 68 | record {|record {} value;|} nextRecord = {value: checkpanic value.cloneWithType(self.targetType)}; 69 | return nextRecord; 70 | } 71 | } else { 72 | return (); 73 | } 74 | } 75 | 76 | public isolated function close() returns persist:Error? { 77 | if self.anydataStream is stream { 78 | error? e = (>self.anydataStream).close(); 79 | if e is error { 80 | return error(e.message()); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/central-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to the Ballerina central 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | environment: 7 | type: choice 8 | description: Select environment 9 | required: true 10 | options: 11 | - CENTRAL 12 | - DEV CENTRAL 13 | - STAGE CENTRAL 14 | 15 | jobs: 16 | publish-release: 17 | runs-on: ubuntu-latest 18 | if: github.repository_owner == 'ballerina-platform' 19 | steps: 20 | - name: Checkout Repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Set up JDK 21 24 | uses: actions/setup-java@v3 25 | with: 26 | distribution: "temurin" 27 | java-version: 21.0.3 28 | 29 | - name: Set ENV Variables 30 | run: | 31 | echo -e '${{ toJson(secrets) }}' | jq -r 'to_entries[] | .key + "=" + .value' >> $GITHUB_ENV 32 | 33 | - name: Build with Gradle 34 | env: 35 | packageUser: ${{ github.actor }} 36 | packagePAT: ${{ secrets.GITHUB_TOKEN }} 37 | run: ./gradlew build -x check -x test 38 | 39 | - name: Create lib directory if not exists 40 | run: mkdir -p ballerina/lib 41 | 42 | - name: Run Trivy vulnerability scanner 43 | uses: aquasecurity/trivy-action@master 44 | env: 45 | TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db 46 | TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db 47 | with: 48 | scan-type: "rootfs" 49 | scan-ref: "${{ github.workspace }}/ballerina/lib" 50 | format: "table" 51 | timeout: "10m0s" 52 | exit-code: "1" 53 | scanners: "vuln" 54 | cache-dir: "/tmp/trivy-cache" 55 | 56 | - name: Ballerina Central Push 57 | if: ${{ github.event.inputs.environment == 'CENTRAL' }} 58 | env: 59 | BALLERINA_DEV_CENTRAL: false 60 | BALLERINA_STAGE_CENTRAL: false 61 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} 62 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 63 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 64 | GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} 65 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 66 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 67 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 68 | run: | 69 | ./gradlew clean build -PpublishToCentral=true 70 | 71 | - name: Ballerina Central Dev Push 72 | if: ${{ github.event.inputs.environment == 'DEV CENTRAL' }} 73 | env: 74 | BALLERINA_DEV_CENTRAL: true 75 | BALLERINA_STAGE_CENTRAL: false 76 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_DEV_ACCESS_TOKEN }} 77 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 78 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 79 | GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} 80 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 81 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 82 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 83 | run: | 84 | sed -i 's/version=\(.*\)-SNAPSHOT/version=\1/g' gradle.properties 85 | ./gradlew clean build -PpublishToCentral=true 86 | 87 | - name: Ballerina Central Stage Push 88 | if: ${{ github.event.inputs.environment == 'STAGE CENTRAL' }} 89 | env: 90 | BALLERINA_DEV_CENTRAL: false 91 | BALLERINA_STAGE_CENTRAL: true 92 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_STAGE_ACCESS_TOKEN }} 93 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 94 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 95 | GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} 96 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 97 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 98 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 99 | run: | 100 | sed -i 's/version=\(.*\)-SNAPSHOT/version=\1/g' gradle.properties 101 | ./gradlew clean build -PpublishToCentral=true 102 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: [ stdlib-release-pipeline ] 7 | 8 | jobs: 9 | publish-release: 10 | runs-on: ubuntu-latest 11 | if: github.repository_owner == 'ballerina-platform' 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v3 18 | with: 19 | distribution: "temurin" 20 | java-version: 21.0.3 21 | 22 | - name: Set ENV Variables 23 | run: | 24 | echo -e '${{ toJson(secrets) }}' | jq -r 'to_entries[] | .key + "=" + .value' >> $GITHUB_ENV 25 | 26 | - name: Build with Gradle 27 | env: 28 | packageUser: ${{ github.actor }} 29 | packagePAT: ${{ secrets.GITHUB_TOKEN }} 30 | run: | 31 | git config --global user.name ${{ secrets.BALLERINA_BOT_USERNAME }} 32 | git config --global user.email ${{ secrets.BALLERINA_BOT_EMAIL }} 33 | ./gradlew build -x check -x test 34 | 35 | - name: Create lib directory if not exists 36 | run: mkdir -p ballerina/lib 37 | 38 | - name: Run Trivy vulnerability scanner 39 | uses: aquasecurity/trivy-action@master 40 | env: 41 | TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db,public.ecr.aws/aquasecurity/trivy-db 42 | TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db,public.ecr.aws/aquasecurity/trivy-java-db 43 | with: 44 | scan-type: "rootfs" 45 | scan-ref: "${{ github.workspace }}/ballerina/lib" 46 | format: "table" 47 | timeout: "10m0s" 48 | exit-code: "1" 49 | scanners: "vuln" 50 | cache-dir: "/tmp/trivy-cache" 51 | 52 | - name: Set version env variable 53 | run: echo "VERSION=$((grep -w 'version' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)" >> $GITHUB_ENV 54 | - name: Pre release dependency version update 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} 57 | run: | 58 | echo "Version: ${VERSION}" 59 | git checkout -b release-${VERSION} 60 | sed -i 's/ballerinaLangVersion=\(.*\)-SNAPSHOT/ballerinaLangVersion=\1/g' gradle.properties 61 | sed -i 's/ballerinaLangVersion=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/ballerinaLangVersion=\1/g' gradle.properties 62 | sed -i 's/stdlib\(.*\)=\(.*\)-SNAPSHOT/stdlib\1=\2/g' gradle.properties 63 | sed -i 's/stdlib\(.*\)=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/stdlib\1=\2/g' gradle.properties 64 | sed -i 's/observe\(.*\)=\(.*\)-SNAPSHOT/observe\1=\2/g' gradle.properties 65 | sed -i 's/observe\(.*\)=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/observe\1=\2/g' gradle.properties 66 | git add gradle.properties 67 | git commit -m "Move dependencies to stable version" || echo "No changes to commit" 68 | - name: Grant execute permission for gradlew 69 | run: chmod +x gradlew 70 | 71 | - name: Publish artifact 72 | env: 73 | BALLERINA_CENTRAL_ACCESS_TOKEN: ${{ secrets.BALLERINA_CENTRAL_ACCESS_TOKEN }} 74 | GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} 75 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 76 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 77 | publishUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 78 | publishPAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 79 | nexusUser: ${{ secrets.NEXUS_USERNAME }} 80 | nexusPassword: ${{ secrets.NEXUS_PASSWORD }} 81 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 82 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 83 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 84 | run: | 85 | ./gradlew clean release -Prelease.useAutomaticVersion=true 86 | ./gradlew -Pversion=${VERSION} publish -x test -PpublishToCentral=true 87 | - name: GitHub Release and Release Sync PR 88 | env: 89 | GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }} 90 | run: | 91 | gh release create v$VERSION --title "module-ballerinax-persist.googlesheets-v$VERSION" 92 | gh pr create --title "[Automated] Sync main after $VERSION release" --body "Sync main after $VERSION release" 93 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/persist/googlesheets/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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.stdlib.persist.googlesheets; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.creators.TypeCreator; 23 | import io.ballerina.runtime.api.creators.ValueCreator; 24 | import io.ballerina.runtime.api.types.Field; 25 | import io.ballerina.runtime.api.types.MapType; 26 | import io.ballerina.runtime.api.types.PredefinedTypes; 27 | import io.ballerina.runtime.api.types.RecordType; 28 | import io.ballerina.runtime.api.types.Type; 29 | import io.ballerina.runtime.api.utils.StringUtils; 30 | import io.ballerina.runtime.api.utils.TypeUtils; 31 | import io.ballerina.runtime.api.values.BArray; 32 | import io.ballerina.runtime.api.values.BError; 33 | import io.ballerina.runtime.api.values.BMap; 34 | import io.ballerina.runtime.api.values.BObject; 35 | import io.ballerina.runtime.api.values.BStream; 36 | import io.ballerina.runtime.api.values.BString; 37 | import io.ballerina.runtime.api.values.BTypedesc; 38 | 39 | import java.util.Locale; 40 | import java.util.Map; 41 | 42 | import static io.ballerina.runtime.api.utils.StringUtils.fromString; 43 | import static io.ballerina.stdlib.persist.googlesheets.Constants.PERSIST_GOOGLE_SHEETS_STREAM; 44 | import static io.ballerina.stdlib.persist.googlesheets.ModuleUtils.getModule; 45 | 46 | /** 47 | * This class has the utility methods required for the Persist module. 48 | * 49 | * @since 0.1.0 50 | */ 51 | public class Utils { 52 | public static BString getEntityFromStreamMethod(Environment env) { 53 | String functionName = env.getFunctionName(); 54 | String entity = functionName.substring(5, functionName.length() - 6).toLowerCase(Locale.ENGLISH); 55 | return fromString(entity); 56 | } 57 | public static BMap getFieldTypes(RecordType recordType) { 58 | MapType stringMapType = TypeCreator.createMapType(PredefinedTypes.TYPE_STRING); 59 | BMap typeMap = ValueCreator.createMapValue(stringMapType); 60 | Map fieldsMap = recordType.getFields(); 61 | for (Field field : fieldsMap.values()) { 62 | 63 | Type type = field.getFieldType(); 64 | String fieldName = field.getFieldName(); 65 | typeMap.put(StringUtils.fromString(fieldName), StringUtils.fromString(type.getName())); 66 | } 67 | return typeMap; 68 | } 69 | 70 | private static BObject createPersistGSheetsStream(BStream sqlStream, BTypedesc targetType, BArray fields, 71 | BArray includes, BArray typeDescriptions, BObject persistClient, 72 | BError persistError) { 73 | return ValueCreator.createObjectValue(getModule(), PERSIST_GOOGLE_SHEETS_STREAM, 74 | sqlStream, targetType, fields, includes, typeDescriptions, persistClient, persistError); 75 | } 76 | 77 | private static BStream createPersistGSheetsStreamValue(BTypedesc targetType, BObject persistSQLStream) { 78 | RecordType streamConstraint = 79 | (RecordType) TypeUtils.getReferredType(targetType.getDescribingType()); 80 | return ValueCreator.createStreamValue( 81 | TypeCreator.createStreamType(streamConstraint, PredefinedTypes.TYPE_NULL), persistSQLStream); 82 | } 83 | 84 | public static BStream createPersistGSheetsStreamValue(BStream sqlStream, BTypedesc targetType, BArray fields, 85 | BArray includes, BArray typeDescriptions, 86 | BObject persistClient, BError persistError) { 87 | BObject persistSQLStream = createPersistGSheetsStream(sqlStream, targetType, fields, includes, typeDescriptions, 88 | persistClient, persistError); 89 | return createPersistGSheetsStreamValue(targetType, persistSQLStream); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ballerina/Module.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This package provides Google sheets support for the `bal persist` feature, which provides functionality to store and query data from a Google sheets conveniently through a data model. 4 | 5 | The Google Sheets data store is a cloud-based spreadsheet application that stores data in tables. The Google Sheets data store is useful for storing data in a spreadsheet format. 6 | 7 | ## How to use with `bal persist` 8 | 9 | By default, `bal persist` utilizes the in-memory data store. Therefore, you must explicitly specify Google Sheets data store as follows: 10 | 11 | ### Integrate to `bal build` 12 | 13 | 1. Initialize `bal persist` and integrate to `bal build` using the following command, 14 | 15 | ``` 16 | $ bal persist add --datastore googlesheets --module 17 | ``` 18 | 19 | 2. After defining the entities, build the application using the following command, 20 | 21 | ``` 22 | $ bal build 23 | ``` 24 | 25 | ### One time generation 26 | 27 | 1. Initialize `bal persist` using the following command, 28 | 29 | ``` 30 | $ bal persist init 31 | ``` 32 | 33 | 2. Generate the persist client using the following command, 34 | 35 | ``` 36 | $ bal persist generate --datastore googlesheets --module 37 | ``` 38 | 39 | ## Supported Ballerina Types 40 | The following table lists the Ballerina types supported by the Google Sheets data store and the corresponding Google Sheets types used to store the data in the spreadsheet. 41 | 42 | | Ballerina Type | Google Sheets Type | 43 | |:------------------------:|:-------------------------:| 44 | | int | NUMBER | 45 | | float | NUMBER | 46 | | decimal | NUMBER | 47 | | string | STRING | 48 | | boolean | BOOLEAN | 49 | | time:Date | STRING | 50 | | time:TimeOfDay | STRING | 51 | | time:Utc | STRING | 52 | | time:Civil | STRING | 53 | 54 | ## Configuration 55 | 56 | You need to set values for the following basic configuration parameters in the `Config.toml` file in your project to use the Google Sheets data store. 57 | 58 | | Parameter | Description | 59 | |:--------------:|:--------------------------------------------:| 60 | | clientId | The client ID of the Google Sheets API. | 61 | | clientSecret | The client secret of the Google Sheets API. | 62 | | refreshToken | The refresh token of the Google Sheets API. | 63 | | spreadsheetId | The ID of the spreadsheet to be used. | 64 | 65 | The following is a sample `Config.toml` file with the Google Sheets data store configuration. This is generated by the `bal persist generate` command. 66 | 67 | ```toml 68 | [.] 69 | spreadsheetId = "" 70 | clientId = "" 71 | clientSecret = "" 72 | refreshToken = "" 73 | ``` 74 | 75 | Please refer to the [Google API documentation](https://developers.google.com/identity/protocols/oauth2) for more information on how to obtain the client ID, client secret, and refresh token. 76 | 77 | ## How to Set up 78 | 79 | ### How to run `script.gs` in the worksheet 80 | 81 | The `script.gs` file generated from the `bal persist generate` command can initiate the google sheets client. This file can be executed in the Google Apps Script console using the following steps. 82 | * Go to the respective spreadsheet. 83 | * Open the AppScript console from the menu item `Extensions - > Apps Script` 84 | * Copy the content of the script.gs file into the console. 85 | * Click the Deploy button to Deploy the project as a Web Application. 86 | * Click on the Run button to execute the selected function. 87 | 88 | ### How to obtain Google API tokens 89 | 90 | API tokens for the Google sheets can be obtained using the following procedure. 91 | 1. Get the clientID, and client secret using the code following [guidelines](https://developers.google.com/identity/protocols/oauth2). 92 | 2. If you want to use [OAuth 2.0 playground](https://developers.google.com/oauthplayground) to receive the authorization code and obtain the access token and refresh token, Follow below steps. 93 | 1. Go to the [OAuth 2.0 playground](https://developers.google.com/oauthplayground). 94 | 2. Click the settings icon ![settings icon](https://developers.google.com/oauthplayground/assets/images/settings.png) on the top right corner of the page. 95 | 3. Select the checkbox `Use your own OAuth credentials`. 96 | 4. Enter the OAuth Client ID and OAuth Client secret. 97 | 5. Click `Close`. 98 | 6. Select the required Google Sheets API scopes. 99 | 7. Click `Authorize APIs`. 100 | 8. Click `Exchange authorization code for tokens` to obtain the access token and refresh token. 101 | -------------------------------------------------------------------------------- /ballerina/Package.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This package provides Google sheets support for the `bal persist` feature, which provides functionality to store and query data from a Google sheets conveniently through a data model. 4 | 5 | The Google Sheets data store is a cloud-based spreadsheet application that stores data in tables. The Google Sheets data store is useful for storing data in a spreadsheet format. 6 | 7 | ## How to use with `bal persist` 8 | 9 | By default, `bal persist` utilizes the in-memory data store. Therefore, you must explicitly specify Google Sheets data store as follows: 10 | 11 | ### Integrate to `bal build` 12 | 13 | 1. Initialize `bal persist` and integrate to `bal build` using the following command, 14 | 15 | ``` 16 | $ bal persist add --datastore googlesheets --module 17 | ``` 18 | 19 | 2. After defining the entities, build the application using the following command, 20 | 21 | ``` 22 | $ bal build 23 | ``` 24 | 25 | ### One time generation 26 | 27 | 1. Initialize `bal persist` using the following command, 28 | 29 | ``` 30 | $ bal persist init 31 | ``` 32 | 33 | 2. Generate the persist client using the following command, 34 | 35 | ``` 36 | $ bal persist generate --datastore googlesheets --module 37 | ``` 38 | 39 | ## Supported Ballerina Types 40 | The following table lists the Ballerina types supported by the Google Sheets data store and the corresponding Google Sheets types used to store the data in the spreadsheet. 41 | 42 | | Ballerina Type | Google Sheets Type | 43 | |:------------------------:|:-------------------------:| 44 | | int | NUMBER | 45 | | float | NUMBER | 46 | | decimal | NUMBER | 47 | | string | STRING | 48 | | boolean | BOOLEAN | 49 | | time:Date | STRING | 50 | | time:TimeOfDay | STRING | 51 | | time:Utc | STRING | 52 | | time:Civil | STRING | 53 | 54 | ## Configuration 55 | 56 | You need to set values for the following basic configuration parameters in the `Config.toml` file in your project to use the Google Sheets data store. 57 | 58 | | Parameter | Description | 59 | |:--------------:|:--------------------------------------------:| 60 | | clientId | The client ID of the Google Sheets API. | 61 | | clientSecret | The client secret of the Google Sheets API. | 62 | | refreshToken | The refresh token of the Google Sheets API. | 63 | | spreadsheetId | The ID of the spreadsheet to be used. | 64 | 65 | The following is a sample `Config.toml` file with the Google Sheets data store configuration. This is generated by the `bal persist generate` command. 66 | 67 | ```toml 68 | [.] 69 | spreadsheetId = "" 70 | clientId = "" 71 | clientSecret = "" 72 | refreshToken = "" 73 | ``` 74 | 75 | Please refer to the [Google API documentation](https://developers.google.com/identity/protocols/oauth2) for more information on how to obtain the client ID, client secret, and refresh token. 76 | 77 | ## How to Set up 78 | 79 | ### How to run `script.gs` in the worksheet 80 | 81 | The `script.gs` file generated from the `bal persist generate` command can initiate the google sheets client. This file can be executed in the Google Apps Script console using the following steps. 82 | * Go to the respective spreadsheet. 83 | * Open the AppScript console from the menu item `Extensions - > Apps Script` 84 | * Copy the content of the script.gs file into the console. 85 | * Click the Deploy button to Deploy the project as a Web Application. 86 | * Click on the Run button to execute the selected function. 87 | 88 | ### How to obtain Google API tokens 89 | 90 | API tokens for the Google sheets can be obtained using the following procedure. 91 | 1. Get the clientID, and client secret using the code following [guidelines](https://developers.google.com/identity/protocols/oauth2). 92 | 2. If you want to use [OAuth 2.0 playground](https://developers.google.com/oauthplayground) to receive the authorization code and obtain the access token and refresh token, Follow below steps. 93 | 1. Go to the [OAuth 2.0 playground](https://developers.google.com/oauthplayground). 94 | 2. Click the settings icon ![settings icon](https://developers.google.com/oauthplayground/assets/images/settings.png) on the top right corner of the page. 95 | 3. Select the checkbox `Use your own OAuth credentials`. 96 | 4. Enter the OAuth Client ID and OAuth Client secret. 97 | 5. Click `Close`. 98 | 6. Select the required Google Sheets API scopes. 99 | 7. Click `Authorize APIs`. 100 | 8. Click `Exchange authorization code for tokens` to obtain the access token and refresh token. 101 | 102 | ## Report issues 103 | 104 | To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina standard library parent repository](https://github.com/ballerina-platform/ballerina-standard-library). 105 | 106 | ## Useful links 107 | - Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 108 | - Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. 109 | -------------------------------------------------------------------------------- /ballerina/tests/rainier_generated_types.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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 | 19 | public enum Gender { 20 | MALE, 21 | FEMALE 22 | } 23 | 24 | public type Employee record {| 25 | readonly string empNo; 26 | string firstName; 27 | string lastName; 28 | time:Date birthDate; 29 | Gender gender; 30 | time:Date hireDate; 31 | string departmentDeptNo; 32 | string workspaceWorkspaceId; 33 | |}; 34 | 35 | public type EmployeeOptionalized record {| 36 | string empNo?; 37 | string firstName?; 38 | string lastName?; 39 | time:Date birthDate?; 40 | Gender gender?; 41 | time:Date hireDate?; 42 | string departmentDeptNo?; 43 | string workspaceWorkspaceId?; 44 | |}; 45 | 46 | public type EmployeeWithRelations record {| 47 | *EmployeeOptionalized; 48 | DepartmentOptionalized department?; 49 | WorkspaceOptionalized workspace?; 50 | |}; 51 | 52 | public type EmployeeTargetType typedesc; 53 | 54 | public type EmployeeInsert Employee; 55 | 56 | public type EmployeeUpdate record {| 57 | string firstName?; 58 | string lastName?; 59 | time:Date birthDate?; 60 | Gender gender?; 61 | time:Date hireDate?; 62 | string departmentDeptNo?; 63 | string workspaceWorkspaceId?; 64 | |}; 65 | 66 | public type Workspace record {| 67 | readonly string workspaceId; 68 | string workspaceType; 69 | string locationBuildingCode; 70 | |}; 71 | 72 | public type WorkspaceOptionalized record {| 73 | string workspaceId?; 74 | string workspaceType?; 75 | string locationBuildingCode?; 76 | |}; 77 | 78 | public type WorkspaceWithRelations record {| 79 | *WorkspaceOptionalized; 80 | BuildingOptionalized location?; 81 | EmployeeOptionalized[] employees?; 82 | |}; 83 | 84 | public type WorkspaceTargetType typedesc; 85 | 86 | public type WorkspaceInsert Workspace; 87 | 88 | public type WorkspaceUpdate record {| 89 | string workspaceType?; 90 | string locationBuildingCode?; 91 | |}; 92 | 93 | public type Building record {| 94 | readonly string buildingCode; 95 | string city; 96 | string state; 97 | string country; 98 | string postalCode; 99 | string 'type; 100 | |}; 101 | 102 | public type BuildingOptionalized record {| 103 | string buildingCode?; 104 | string city?; 105 | string state?; 106 | string country?; 107 | string postalCode?; 108 | string 'type?; 109 | |}; 110 | 111 | public type BuildingWithRelations record {| 112 | *BuildingOptionalized; 113 | WorkspaceOptionalized[] workspaces?; 114 | |}; 115 | 116 | public type BuildingTargetType typedesc; 117 | 118 | public type BuildingInsert Building; 119 | 120 | public type BuildingUpdate record {| 121 | string city?; 122 | string state?; 123 | string country?; 124 | string postalCode?; 125 | string 'type?; 126 | |}; 127 | 128 | public type Department record {| 129 | readonly string deptNo; 130 | string deptName; 131 | |}; 132 | 133 | public type DepartmentOptionalized record {| 134 | string deptNo?; 135 | string deptName?; 136 | |}; 137 | 138 | public type DepartmentWithRelations record {| 139 | *DepartmentOptionalized; 140 | EmployeeOptionalized[] employees?; 141 | |}; 142 | 143 | public type DepartmentTargetType typedesc; 144 | 145 | public type DepartmentInsert Department; 146 | 147 | public type DepartmentUpdate record {| 148 | string deptName?; 149 | |}; 150 | 151 | public type OrderItem record {| 152 | readonly string orderId; 153 | readonly string itemId; 154 | int quantity; 155 | string notes; 156 | |}; 157 | 158 | public type OrderItemOptionalized record {| 159 | string orderId?; 160 | string itemId?; 161 | int quantity?; 162 | string notes?; 163 | |}; 164 | 165 | public type OrderItemTargetType typedesc; 166 | 167 | public type OrderItemInsert OrderItem; 168 | 169 | public type OrderItemUpdate record {| 170 | int quantity?; 171 | string notes?; 172 | |}; 173 | 174 | public type OrderItemFalse record {| 175 | readonly string orderId; 176 | readonly string itemId; 177 | int quantity; 178 | string notes; 179 | string notesdummy; 180 | |}; 181 | 182 | public type OrderItemFalseOptionalized record {| 183 | string orderId?; 184 | string itemId?; 185 | int quantity?; 186 | string notes?; 187 | string notesdummy?; 188 | |}; 189 | 190 | public type OrderItemFalseTargetType typedesc; 191 | 192 | public type OrderItemFalseInsert OrderItemFalse; 193 | 194 | public type OrderItemFalseUpdate record {| 195 | int quantity?; 196 | string notes?; 197 | string notesdummy?; 198 | |}; 199 | 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ballerina Persist - Google Sheets Library 2 | =================== 3 | 4 | [![Build](https://github.com/ballerina-platform/module-ballerinax-persist.googlesheets/actions/workflows/build-timestamped-master.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-persist.googlesheets/actions/workflows/build-timestamped-master.yml) 5 | [![codecov](https://codecov.io/gh/ballerina-platform/module-ballerinax-persist.googlesheets/branch/main/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/module-ballerina-persist) 6 | [![Trivy](https://github.com/ballerina-platform/module-ballerinax-persist.googlesheets/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-persist.googlesheets/actions/workflows/trivy-scan.yml) 7 | [![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-persist.googlesheets/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-persist.googlesheets/actions/workflows/build-with-bal-test-graalvm.yml) 8 | [![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-persist.googlesheets.svg)](https://github.com/ballerina-platform/module-ballerina-persist/commits/main) 9 | [![GitHub Issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-standard-library/module/persist.googlesheets.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-standard-library/labels/module%2Fpersist.googlesheets) 10 | 11 | This library provides Google sheets support for the `bal persist` feature, which provides functionality to store and query data conveniently through a data model. 12 | 13 | The `persist` command will make it easy to enable `bal persist` feature in a Ballerina project. With this support, users need not worry about the persistence in a project. Users can define an entity data model, validate the model and generate `persist` clients for Google sheets, which provide convenient APIs to store and query data in a data store. 14 | 15 | For more information, see [`persist.googlesheets` API Documentation](https://lib.ballerina.io/ballerinax/persist.googlesheets/latest). 16 | 17 | ## Issues and projects 18 | 19 | Issues and Projects tabs are disabled for this repository as this is part of the Ballerina standard library. To report bugs, request new features, start new discussions, view project boards, etc. please visit Ballerina standard library [parent repository](https://github.com/ballerina-platform/ballerina-standard-library). 20 | 21 | This repository only contains the source code for the package. 22 | 23 | ## Building from the source 24 | 25 | ### Set up the prerequisites 26 | 27 | 1. Download and install Java SE Development Kit (JDK) version 21 (from one of the following locations). 28 | * [Oracle](https://www.oracle.com/java/technologies/downloads/) 29 | * [OpenJDK](https://adoptium.net/) 30 | 31 | 2. Download and install [Docker](https://www.docker.com/get-started) 32 | 33 | 3. Export your GitHub personal access token with the read package permissions as follows. 34 | 35 | export packageUser= 36 | export packagePAT= 37 | 38 | ### Building the source 39 | 40 | Execute the commands below to build from source. 41 | 42 | 1. To build the library: 43 | 44 | ./gradlew clean build 45 | 46 | 2. To run the integration tests: 47 | 48 | ./gradlew clean test 49 | 50 | 3. To build the package without the tests: 51 | 52 | ./gradlew clean build -x test 53 | 54 | 4. To run only specific tests: 55 | 56 | ./gradlew clean build -Pgroups= 57 | 58 | **Tip:** The following groups of test cases are available. 59 | 60 | | Groups | Test cases | 61 | |:---------------:|:-----------------------------:| 62 | | basic | basic | 63 | | associations | associations
one-to-many | 64 | | composite-keys | composite-keys | 65 | 66 | 5. To disable some specific test groups: 67 | 68 | ./gradlew clean build -Pdisable-groups= 69 | 70 | 6. To debug the tests: 71 | 72 | ./gradlew clean build -Pdebug= 73 | ./gradlew clean test -Pdebug= 74 | 75 | 7. To debug the package with Ballerina language: 76 | 77 | ./gradlew clean build -PbalJavaDebug= 78 | ./gradlew clean test -PbalJavaDebug= 79 | 80 | 8. Publish ZIP artifact to the local `.m2` repository: 81 | 82 | ./gradlew clean build publishToMavenLocal 83 | 84 | 9. Publish the generated artifacts to the local Ballerina central repository: 85 | 86 | ./gradlew clean build -PpublishToLocalCentral=true 87 | 88 | 10. Publish the generated artifacts to the Ballerina central repository: 89 | 90 | ./gradlew clean build -PpublishToCentral=true 91 | 92 | ## Contributing to Ballerina 93 | 94 | As an open source project, Ballerina welcomes contributions from the community. 95 | 96 | For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). 97 | 98 | ## Code of conduct 99 | 100 | All contributors are encouraged to read the [Ballerina code of conduct](https://ballerina.io/code-of-conduct). 101 | 102 | ## Useful links 103 | 104 | * For more information go to the [`persist.googlesheets` library](https://lib.ballerina.io/ballerinax/persist.googlesheets/latest). 105 | * Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 106 | * Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. 107 | -------------------------------------------------------------------------------- /native/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 LLC. (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 'maven-publish' 24 | } 25 | 26 | description = 'Ballerina - Persist Java Native' 27 | 28 | configurations { 29 | jacocoRuntime 30 | } 31 | 32 | dependencies { 33 | jacocoRuntime "org.jacoco:org.jacoco.agent:${jacoco.toolVersion}:runtime" 34 | 35 | checkstyle project(":checkstyle") 36 | checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" 37 | implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" 38 | implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" 39 | implementation group: 'io.ballerina.stdlib', name: 'persist-native', version: "${stdlibPersistVersion}" 40 | } 41 | 42 | tasks.withType(JavaCompile) { 43 | options.encoding = 'UTF-8' 44 | } 45 | 46 | sourceCompatibility = JavaVersion.VERSION_21 47 | 48 | jacoco { 49 | toolVersion = "0.8.6" 50 | } 51 | 52 | test { 53 | useTestNG() { 54 | suites 'src/test/resources/testng.xml' 55 | } 56 | testLogging.showStandardStreams = true 57 | testLogging { 58 | events "PASSED", "FAILED", "SKIPPED" 59 | afterSuite { desc, result -> 60 | if (!desc.parent) { // will match the outermost suite 61 | def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" 62 | def startItem = '| ', endItem = ' |' 63 | def repeatLength = startItem.length() + output.length() + endItem.length() 64 | println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) 65 | } 66 | } 67 | } 68 | finalizedBy jacocoTestReport 69 | } 70 | 71 | jacocoTestReport { 72 | dependsOn test 73 | reports { 74 | xml.required = true 75 | } 76 | } 77 | 78 | spotbugsMain { 79 | ignoreFailures = true 80 | def classLoader = plugins["com.github.spotbugs"].class.classLoader 81 | def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") 82 | def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") 83 | effort = SpotBugsEffort.MAX 84 | reportLevel = SpotBugsConfidence.LOW 85 | reportsDir = file("$project.buildDir/reports/spotbugs") 86 | def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") 87 | if (excludeFile.exists()) { 88 | it.excludeFilter = excludeFile 89 | } 90 | reports { 91 | text.enabled = true 92 | } 93 | } 94 | 95 | spotbugsTest { 96 | enabled = false 97 | } 98 | 99 | task validateSpotbugs() { 100 | doLast { 101 | if (spotbugsMain.reports.size() > 0 && 102 | spotbugsMain.reports[0].destination.exists() && 103 | spotbugsMain.reports[0].destination.text.readLines().size() > 0) { 104 | spotbugsMain.reports[0].destination?.eachLine { 105 | println 'Failure: ' + it 106 | } 107 | throw new GradleException("Spotbugs rule violations were found."); 108 | } 109 | } 110 | } 111 | 112 | checkstyle { 113 | toolVersion "${checkstylePluginVersion}" 114 | configFile file("${rootDir}/build-config/checkstyle/build/checkstyle.xml") 115 | configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] 116 | } 117 | 118 | tasks.withType(Checkstyle) { 119 | exclude '**/module-info.java' 120 | } 121 | 122 | spotbugsMain.finalizedBy validateSpotbugs 123 | checkstyleMain.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' 124 | checkstyleTest.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' 125 | 126 | publishing { 127 | publications { 128 | mavenJava(MavenPublication) { 129 | groupId project.group 130 | artifactId "persist.googlesheets-native" 131 | version = project.version 132 | artifact jar 133 | } 134 | } 135 | 136 | repositories { 137 | maven { 138 | name = "GitHubPackages" 139 | url = uri("https://maven.pkg.github.com/ballerina-platform/module-ballerina-persist") 140 | credentials { 141 | username = System.getenv("publishUser") 142 | password = System.getenv("publishPAT") 143 | } 144 | } 145 | maven { 146 | name = "WSO2Nexus" 147 | if(project.version.endsWith('-SNAPSHOT')) { 148 | url "https://maven.wso2.org/nexus/content/repositories/snapshots/" 149 | } else { 150 | url "https://maven.wso2.org/nexus/service/local/staging/deploy/maven2/" 151 | } 152 | credentials { 153 | username System.getenv("nexusUser") 154 | password System.getenv("nexusPassword") 155 | } 156 | } 157 | } 158 | } 159 | 160 | compileJava { 161 | doFirst { 162 | options.compilerArgs = [ 163 | '--module-path', classpath.asPath, 164 | ] 165 | classpath = files() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /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 | buildscript { 21 | repositories { 22 | maven { 23 | url = 'https://maven.pkg.github.com/ballerina-platform/plugin-gradle' 24 | credentials { 25 | username System.getenv('packageUser') 26 | password System.getenv('packagePAT') 27 | } 28 | } 29 | } 30 | dependencies { 31 | classpath "io.ballerina:plugin-gradle:${project.ballerinaGradlePluginVersion}" 32 | } 33 | } 34 | 35 | description = 'Ballerina Persist - Google Sheets Extension' 36 | 37 | def packageName = 'persist.googlesheets' 38 | def packageOrg = 'ballerinax' 39 | def tomlVersion = stripBallerinaExtensionVersion("${project.version}") 40 | 41 | def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") 42 | def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") 43 | def compilerPluginTomlFile = new File("$project.projectDir/CompilerPlugin.toml") 44 | 45 | def stripBallerinaExtensionVersion(String extVersion) { 46 | if (extVersion.matches(project.ext.timestampedVersionRegex)) { 47 | def splitVersion = extVersion.split('-') 48 | if (splitVersion.length > 3) { 49 | def strippedValues = splitVersion[0..-4] 50 | return strippedValues.join('-') 51 | } else { 52 | return extVersion 53 | } 54 | } else { 55 | return extVersion.replace("${project.ext.snapshotVersion}", '') 56 | } 57 | } 58 | 59 | apply plugin: 'io.ballerina.plugin' 60 | 61 | ballerina { 62 | packageOrganization = packageOrg 63 | module = packageName 64 | langVersion = ballerinaLangVersion 65 | testCoverageParam = "--code-coverage --coverage-format=xml --includes=*" 66 | } 67 | 68 | dependencies { 69 | externalJars(group: 'io.ballerina.stdlib', name: 'persist-native', version: "${stdlibPersistVersion}") { 70 | transitive = false 71 | } 72 | } 73 | 74 | task updateTomlFiles { 75 | doLast { 76 | def stdlibDependentPersistVersion = stripBallerinaExtensionVersion(project.stdlibPersistVersion) 77 | 78 | def newConfig = ballerinaTomlFilePlaceHolder.text.replace('@project.version@', project.version.toString()) 79 | newConfig = newConfig.replace('@toml.version@', tomlVersion) 80 | newConfig = newConfig.replace('@persist.version@', stdlibDependentPersistVersion) 81 | newConfig = newConfig.replace('@persist.native.version@', project.stdlibPersistVersion) 82 | ballerinaTomlFile.text = newConfig 83 | } 84 | } 85 | 86 | task commitTomlFiles { 87 | doLast { 88 | project.exec { 89 | ignoreExitValue true 90 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 91 | commandLine 'cmd', '/c', "git commit -m \"[Automated] Update native jar versions in toml files\" Ballerina.toml Dependencies.toml" 92 | } else { 93 | commandLine 'sh', '-c', "git commit -m \"[Automated] Update native jar versions in toml files\" Ballerina.toml Dependencies.toml" 94 | } 95 | } 96 | } 97 | } 98 | 99 | publishing { 100 | publications { 101 | maven(MavenPublication) { 102 | artifact source: createArtifactZip, extension: 'zip' 103 | } 104 | } 105 | repositories { 106 | maven { 107 | name = 'GitHubPackages' 108 | url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") 109 | credentials { 110 | username = System.getenv('publishUser') 111 | password = System.getenv('publishPAT') 112 | } 113 | } 114 | } 115 | } 116 | 117 | def checkExecResult(execResult, failText, standardOutput) { 118 | if (execResult) { 119 | if (execResult.getExitValue() != 0) { 120 | throw new GradleException('Non-zero exit value: ' + execResult.getExitValue()) 121 | } 122 | if (standardOutput.toString().contains(failText)) { 123 | throw new GradleException('"' + failText + '" string in output: ' + standardOutput.toString()) 124 | } 125 | } else { 126 | throw new GradleException('Returned a null execResult object') 127 | } 128 | } 129 | 130 | task pullGoogleSheetsDependency(type: Exec) { 131 | ignoreExitValue(true) 132 | if (!Os.isFamily(Os.FAMILY_WINDOWS)) { 133 | try { 134 | String distributionBinPath = project.projectDir.absolutePath + "/build/jballerina-tools-${project.extensions.ballerina.langVersion}/bin" 135 | commandLine 'sh', '-c', "${distributionBinPath}/bal pull ballerinax/googleapis.sheets:${googleSheetsConnector}" 136 | } catch (all) { 137 | return 1 138 | } 139 | } else { 140 | try { 141 | String distributionBinPath = project.projectDir.absolutePath + "/build/jballerina-tools-${project.extensions.ballerina.langVersion}/bin" 142 | commandLine 'cmd', '/c', "${distributionBinPath}/bal.bat pull ballerinax/googleapis.sheets:${googleSheetsConnector}" 143 | } catch (all) { 144 | return 1 145 | } 146 | } 147 | } 148 | 149 | updateTomlFiles.dependsOn copyStdlibs 150 | pullGoogleSheetsDependency.dependsOn unpackJballerinaTools 151 | 152 | build.dependsOn "generatePomFileForMavenPublication" 153 | build.dependsOn ":${packageName}-native:build" 154 | build.dependsOn pullGoogleSheetsDependency 155 | 156 | test.dependsOn ":${packageName}-native:build" 157 | -------------------------------------------------------------------------------- /ballerina/tests/gsheets-department-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/persist; 19 | import ballerina/lang.runtime; 20 | 21 | @test:Config { 22 | groups: ["department", "google-sheets"], 23 | dependsOn: [gsheetsBuildingDeleteTestNegative], 24 | enable: true 25 | } 26 | function gsheetsDepartmentCreateTest() returns error? { 27 | runtime:sleep(40); 28 | string[] deptNos = check rainierClient->/departments.post([department1]); 29 | test:assertEquals(deptNos, [department1.deptNo]); 30 | 31 | Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); 32 | test:assertEquals(departmentRetrieved, department1); 33 | } 34 | 35 | @test:Config { 36 | groups: ["department", "igoogle-sheets"], 37 | dependsOn: [gsheetsDepartmentCreateTest], 38 | enable: true 39 | } 40 | function gsheetsDepartmentCreateTest2() returns error? { 41 | string[] deptNos = check rainierClient->/departments.post([department2, department3]); 42 | 43 | test:assertEquals(deptNos, [department2.deptNo, department3.deptNo]); 44 | 45 | Department departmentRetrieved = check rainierClient->/departments/[department2.deptNo].get(); 46 | test:assertEquals(departmentRetrieved, department2); 47 | 48 | departmentRetrieved = check rainierClient->/departments/[department3.deptNo].get(); 49 | test:assertEquals(departmentRetrieved, department3); 50 | } 51 | 52 | @test:Config { 53 | groups: ["department", "google-sheets"], 54 | dependsOn: [gsheetsDepartmentCreateTest], 55 | enable: true 56 | } 57 | function gsheetsDepartmentReadOneTest() returns error? { 58 | Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); 59 | test:assertEquals(departmentRetrieved, department1); 60 | } 61 | 62 | @test:Config { 63 | groups: ["department", "google-sheets"], 64 | dependsOn: [gsheetsDepartmentCreateTest], 65 | enable: true 66 | } 67 | function gsheetsDepartmentReadOneTestNegative() returns error? { 68 | Department|error departmentRetrieved = rainierClient->/departments/["invalid-department-id"].get(); 69 | if departmentRetrieved is persist:NotFoundError { 70 | test:assertEquals(departmentRetrieved.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); 71 | } else { 72 | test:assertFail("NotFoundError expected."); 73 | } 74 | } 75 | 76 | @test:Config { 77 | groups: ["department", "google-sheets"], 78 | dependsOn: [gsheetsDepartmentCreateTest, gsheetsDepartmentCreateTest2], 79 | enable: true 80 | } 81 | function gsheetsDepartmentReadManyTest() returns error? { 82 | stream departmentStream = rainierClient->/departments.get(); 83 | Department[] departments = check from Department department in departmentStream 84 | select department; 85 | 86 | test:assertEquals(departments, [department1, department2, department3]); 87 | } 88 | 89 | @test:Config { 90 | groups: ["department", "google-sheets"], 91 | dependsOn: [gsheetsDepartmentCreateTest, gsheetsDepartmentCreateTest2], 92 | enable: true 93 | } 94 | function gsheetsDepartmentReadManyTestDependent() returns error? { 95 | stream departmentStream = rainierClient->/departments.get(); 96 | DepartmentInfo2[] departments = check from DepartmentInfo2 department in departmentStream 97 | select department; 98 | 99 | test:assertEquals(departments, [ 100 | {deptName: department1.deptName}, 101 | {deptName: department2.deptName}, 102 | {deptName: department3.deptName} 103 | ]); 104 | } 105 | 106 | @test:Config { 107 | groups: ["department", "google-sheets"], 108 | dependsOn: [gsheetsDepartmentReadOneTest, gsheetsDepartmentReadManyTest, gsheetsDepartmentReadManyTestDependent], 109 | enable: true 110 | } 111 | function gsheetsDepartmentUpdateTest() returns error? { 112 | Department department = check rainierClient->/departments/[department1.deptNo].put({ 113 | deptName: "Finance & Legalities" 114 | }); 115 | 116 | test:assertEquals(department, updatedDepartment1); 117 | 118 | Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); 119 | test:assertEquals(departmentRetrieved, updatedDepartment1); 120 | } 121 | 122 | @test:Config { 123 | groups: ["department", "google-sheets"], 124 | dependsOn: [gsheetsDepartmentReadOneTest, gsheetsDepartmentReadManyTest, gsheetsDepartmentReadManyTestDependent], 125 | enable: true 126 | } 127 | function gsheetsDepartmentUpdateTestNegative1() returns error? { 128 | Department|error department = rainierClient->/departments/["invalid-department-id"].put({ 129 | deptName: "Human Resources" 130 | }); 131 | 132 | if department is persist:NotFoundError { 133 | test:assertEquals(department.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); 134 | } else { 135 | test:assertFail("NotFoundError expected."); 136 | } 137 | } 138 | 139 | @test:Config { 140 | groups: ["department", "google-sheets"], 141 | dependsOn: [gsheetsDepartmentUpdateTest], 142 | enable: true 143 | } 144 | function gsheetsDepartmentDeleteTest() returns error? { 145 | Department department = check rainierClient->/departments/[department1.deptNo].delete(); 146 | test:assertEquals(department, updatedDepartment1); 147 | 148 | stream departmentStream = rainierClient->/departments.get(); 149 | Department[] departments = check from Department department2 in departmentStream 150 | select department2; 151 | 152 | test:assertEquals(departments, [department2, department3]); 153 | } 154 | 155 | @test:Config { 156 | groups: ["department", "google-sheets"], 157 | dependsOn: [gsheetsDepartmentDeleteTest], 158 | enable: true 159 | } 160 | function gsheetsDepartmentDeleteTestNegative() returns error? { 161 | Department|error department = rainierClient->/departments/[department1.deptNo].delete(); 162 | 163 | if department is persist:NotFoundError { 164 | test:assertEquals(department.message(), "A record with the key 'department-1' does not exist for the entity 'Department'."); 165 | } else { 166 | test:assertFail("NotFoundError expected."); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /ballerina/tests/gsheets-building-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/persist; 19 | import ballerina/lang.runtime; 20 | 21 | @test:Config { 22 | groups: ["building", "google-sheets"], 23 | dependsOn: [gsheetsCompositeKeyDeleteTestNegative], 24 | enable: true 25 | } 26 | function gsheetsBuildingCreateTest() returns error? { 27 | runtime:sleep(40); 28 | string[] buildingCodes = check rainierClient->/buildings.post([building1]); 29 | test:assertEquals(buildingCodes, [building1.buildingCode]); 30 | 31 | Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); 32 | test:assertEquals(buildingRetrieved, building1); 33 | } 34 | 35 | @test:Config { 36 | groups: ["building", "google-sheets"], 37 | dependsOn: [gsheetsBuildingCreateTest], 38 | enable: true 39 | } 40 | function gsheetsBuildingCreateTest2() returns error? { 41 | string[] buildingCodes = check rainierClient->/buildings.post([building2, building3]); 42 | 43 | test:assertEquals(buildingCodes, [building2.buildingCode, building3.buildingCode]); 44 | 45 | Building buildingRetrieved = check rainierClient->/buildings/[building2.buildingCode].get(); 46 | test:assertEquals(buildingRetrieved, building2); 47 | 48 | buildingRetrieved = check rainierClient->/buildings/[building3.buildingCode].get(); 49 | test:assertEquals(buildingRetrieved, building3); 50 | } 51 | 52 | @test:Config { 53 | groups: ["building", "google-sheets"], 54 | dependsOn: [gsheetsBuildingCreateTest], 55 | enable: true 56 | } 57 | function gsheetsBuildingReadOneTest() returns error? { 58 | Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); 59 | test:assertEquals(buildingRetrieved, building1); 60 | } 61 | 62 | @test:Config { 63 | groups: ["building", "google-sheets"], 64 | dependsOn: [gsheetsBuildingCreateTest], 65 | enable: true 66 | } 67 | function gsheetsBuildingReadOneTestNegative() returns error? { 68 | Building|error buildingRetrieved = rainierClient->/buildings/["invalid-building-code"].get(); 69 | if buildingRetrieved is persist:NotFoundError { 70 | test:assertEquals(buildingRetrieved.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); 71 | } else { 72 | test:assertFail("NotFoundError expected."); 73 | } 74 | } 75 | 76 | @test:Config { 77 | groups: ["building", "google-sheets"], 78 | dependsOn: [gsheetsBuildingCreateTest, gsheetsBuildingCreateTest2], 79 | enable: true 80 | } 81 | function gsheetsBuildingReadManyTest() returns error? { 82 | stream buildingStream = rainierClient->/buildings.get(); 83 | Building[] buildings = check from Building building in buildingStream 84 | select building; 85 | test:assertEquals(buildings, [building1, building2, building3]); 86 | } 87 | 88 | @test:Config { 89 | groups: ["building", "google-sheets"], 90 | dependsOn: [gsheetsBuildingCreateTest, gsheetsBuildingCreateTest2], 91 | enable: true 92 | } 93 | function gsheetsBuildingReadManyDependentTest() returns error? { 94 | stream buildingStream = rainierClient->/buildings.get(); 95 | BuildingInfo2[] buildings = check from BuildingInfo2 building in buildingStream 96 | select building; 97 | test:assertEquals(buildings, [ 98 | {city: building1.city, state: building1.state, country: building1.country, postalCode: building1.postalCode, 'type: building1.'type}, 99 | {city: building2.city, state: building2.state, country: building2.country, postalCode: building2.postalCode, 'type: building2.'type}, 100 | {city: building3.city, state: building3.state, country: building3.country, postalCode: building3.postalCode, 'type: building3.'type} 101 | ]); 102 | } 103 | 104 | @test:Config { 105 | groups: ["building", "google-sheets"], 106 | dependsOn: [gsheetsBuildingReadOneTest, gsheetsBuildingReadManyTest, gsheetsBuildingReadManyDependentTest], 107 | enable: true 108 | } 109 | function gsheetsBuildingUpdateTest() returns error? { 110 | Building building = check rainierClient->/buildings/[building1.buildingCode].put({ 111 | city: "Galle", 112 | state: "Southern Province", 113 | postalCode: "10890", 114 | 'type: "owned" 115 | }); 116 | test:assertEquals(building, updatedBuilding1); 117 | Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); 118 | test:assertEquals(buildingRetrieved, updatedBuilding1); 119 | } 120 | 121 | @test:Config { 122 | groups: ["building", "google-sheets"], 123 | dependsOn: [gsheetsBuildingReadOneTest, gsheetsBuildingReadManyTest, gsheetsBuildingReadManyDependentTest], 124 | enable: true 125 | } 126 | function gsheetsBuildingUpdateTestNegative1() returns error? { 127 | Building|error building = rainierClient->/buildings/["invalid-building-code"].put({ 128 | city: "Galle", 129 | state: "Southern Province", 130 | postalCode: "10890" 131 | }); 132 | if building is persist:NotFoundError { 133 | test:assertEquals(building.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); 134 | } else { 135 | test:assertFail("NotFoundError expected."); 136 | } 137 | } 138 | 139 | @test:Config { 140 | groups: ["building", "google-sheets"], 141 | dependsOn: [gsheetsBuildingUpdateTest], 142 | enable: true 143 | } 144 | function gsheetsBuildingDeleteTest() returns error? { 145 | Building building = check rainierClient->/buildings/[building1.buildingCode].delete(); 146 | test:assertEquals(building, updatedBuilding1); 147 | stream buildingStream = rainierClient->/buildings.get(); 148 | Building[] buildings = check from Building building2 in buildingStream 149 | select building2; 150 | 151 | test:assertEquals(buildings, [building2, building3]); 152 | } 153 | 154 | @test:Config { 155 | groups: ["building", "google-sheets"], 156 | dependsOn: [gsheetsBuildingDeleteTest], 157 | enable: true 158 | } 159 | function gsheetsBuildingDeleteTestNegative() returns error? { 160 | Building|error building = rainierClient->/buildings/[building1.buildingCode].delete(); 161 | if building is error { 162 | test:assertEquals(building.message(), "A record with the key 'building-1' does not exist for the entity 'Building'."); 163 | } else { 164 | test:assertFail("NotFoundError expected."); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /ballerina/tests/googlesheets_negative_test_client.bal: -------------------------------------------------------------------------------- 1 | // AUTO-GENERATED FILE. DO NOT MODIFY. 2 | 3 | // This file is an auto-generated file by Ballerina persistence layer for model. 4 | // It should not be modified by hand. 5 | 6 | import ballerina/persist; 7 | import ballerina/jballerina.java; 8 | import ballerina/http; 9 | import ballerinax/googleapis.sheets; 10 | 11 | const ORDER_ITEM_FALSE = "orderitemfalses"; 12 | 13 | public isolated client class GooglesheetsNegativeClient { 14 | *persist:AbstractPersistClient; 15 | 16 | private final sheets:Client googleSheetClient; 17 | 18 | private final http:Client httpSheetsClient; 19 | 20 | private final map persistClients; 21 | 22 | public isolated function init() returns persist:Error? { 23 | final record {|SheetMetadata...;|} & readonly metadata = { 24 | [ORDER_ITEM_FALSE] : { 25 | entityName: "OrderItemFalse", 26 | tableName: "OrderItem", 27 | keyFields: ["orderId", "itemId"], 28 | range: "A:E", 29 | query: self.queryOrderitemfalses, 30 | queryOne: self.queryOneOrderitemfalses, 31 | dataTypes: { 32 | orderId: "string", 33 | itemId: "string", 34 | quantity: "int", 35 | notes: "string", 36 | notesdummy: "string" 37 | }, 38 | fieldMetadata: { 39 | orderId: {columnName: "orderId", columnId: "A"}, 40 | itemId: {columnName: "itemId", columnId: "B"}, 41 | quantity: {columnName: "quantity", columnId: "C"}, 42 | notes: {columnName: "notes", columnId: "D"}, 43 | notesdummy: {columnName: "notesdummy", columnId: "E"} 44 | }, 45 | associationsMethods: {} 46 | } 47 | }; 48 | sheets:ConnectionConfig sheetsClientConfig = { 49 | auth: { 50 | clientId: clientId, 51 | clientSecret: clientSecret, 52 | refreshUrl: sheets:REFRESH_URL, 53 | refreshToken: refreshToken 54 | } 55 | }; 56 | http:ClientConfiguration httpClientConfiguration = { 57 | auth: { 58 | clientId: clientId, 59 | clientSecret: clientSecret, 60 | refreshUrl: sheets:REFRESH_URL, 61 | refreshToken: refreshToken 62 | } 63 | }; 64 | http:Client|error httpSheetsClient = new (string `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values`, httpClientConfiguration); 65 | if httpSheetsClient is error { 66 | return error(httpSheetsClient.message()); 67 | } 68 | sheets:Client|error googleSheetClient = new (sheetsClientConfig); 69 | if googleSheetClient is error { 70 | return error(googleSheetClient.message()); 71 | } 72 | self.googleSheetClient = googleSheetClient; 73 | self.httpSheetsClient = httpSheetsClient; 74 | map sheetIds = check getSheetIds(self.googleSheetClient, metadata, spreadsheetId); 75 | self.persistClients = {[ORDER_ITEM_FALSE] : check new (self.googleSheetClient, self.httpSheetsClient, metadata.get(ORDER_ITEM_FALSE).cloneReadOnly(), spreadsheetId.cloneReadOnly(), sheetIds.get(ORDER_ITEM_FALSE).cloneReadOnly())}; 76 | } 77 | 78 | isolated resource function get orderitemfalses(OrderItemFalseTargetType targetType = <>) returns stream = @java:Method { 79 | 'class: "io.ballerina.stdlib.persist.googlesheets.datastore.GoogleSheetsProcessor", 80 | name: "query" 81 | } external; 82 | 83 | isolated resource function get orderitemfalses/[string orderId]/[string itemId](OrderItemFalseTargetType targetType = <>) returns targetType|persist:Error = @java:Method { 84 | 'class: "io.ballerina.stdlib.persist.googlesheets.datastore.GoogleSheetsProcessor", 85 | name: "queryOne" 86 | } external; 87 | 88 | isolated resource function post orderitemfalses(OrderItemFalseInsert[] data) returns [string, string][]|persist:Error { 89 | GoogleSheetsClient googleSheetsClient; 90 | lock { 91 | googleSheetsClient = self.persistClients.get(ORDER_ITEM_FALSE); 92 | } 93 | _ = check googleSheetsClient.runBatchInsertQuery(data); 94 | return from OrderItemFalseInsert inserted in data 95 | select [inserted.orderId, inserted.itemId]; 96 | } 97 | 98 | isolated resource function put orderitemfalses/[string orderId]/[string itemId](OrderItemFalseUpdate value) returns OrderItemFalse|persist:Error { 99 | GoogleSheetsClient googleSheetsClient; 100 | lock { 101 | googleSheetsClient = self.persistClients.get(ORDER_ITEM_FALSE); 102 | } 103 | _ = check googleSheetsClient.runUpdateQuery({"orderId": orderId, "itemId": itemId}, value); 104 | return self->/orderitemfalses/[orderId]/[itemId].get(); 105 | } 106 | 107 | isolated resource function delete orderitemfalses/[string orderId]/[string itemId]() returns OrderItemFalse|persist:Error { 108 | OrderItemFalse result = check self->/orderitemfalses/[orderId]/[itemId].get(); 109 | GoogleSheetsClient googleSheetsClient; 110 | lock { 111 | googleSheetsClient = self.persistClients.get(ORDER_ITEM_FALSE); 112 | } 113 | _ = check googleSheetsClient.runDeleteQuery({"orderId": orderId, "itemId": itemId}); 114 | return result; 115 | } 116 | 117 | private isolated function queryOrderitemfalses(string[] fields) returns stream|persist:Error { 118 | stream orderitemfalsesStream = self.queryOrderitemfalsesStream(); 119 | record {}[] outputArray = check from record {} 'object in orderitemfalsesStream 120 | select persist:filterRecord({ 121 | ...'object 122 | }, fields); 123 | return outputArray.toStream(); 124 | } 125 | 126 | private isolated function queryOneOrderitemfalses(anydata key) returns record {}|persist:NotFoundError { 127 | stream orderitemfalsesStream = self.queryOrderitemfalsesStream(); 128 | error? unionResult = from record {} 'object in orderitemfalsesStream 129 | where persist:getKey('object, ["orderId", "itemId"]) == key 130 | do { 131 | return { 132 | ...'object 133 | }; 134 | }; 135 | if unionResult is error { 136 | return error(unionResult.message()); 137 | } 138 | return error("Invalid key: " + key.toString()); 139 | } 140 | 141 | private isolated function queryOrderitemfalsesStream(OrderItemFalseTargetType targetType = <>) returns stream = @java:Method { 142 | 'class: "io.ballerina.stdlib.persist.googlesheets.datastore.GoogleSheetsProcessor", 143 | name: "queryStream" 144 | } external; 145 | 146 | public isolated function close() returns persist:Error? { 147 | return (); 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /ballerina/tests/gsheets-workspace-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/persist; 19 | import ballerina/lang.runtime; 20 | 21 | @test:Config { 22 | groups: ["workspace", "google-sheets"], 23 | dependsOn: [gsheetsDepartmentDeleteTestNegative], 24 | enable: true 25 | } 26 | function gsheetsWorkspaceCreateTest() returns error? { 27 | runtime:sleep(40); 28 | string[] workspaceIds = check rainierClient->/workspaces.post([workspace1]); 29 | test:assertEquals(workspaceIds, [workspace1.workspaceId]); 30 | 31 | Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); 32 | test:assertEquals(workspaceRetrieved, workspace1); 33 | } 34 | 35 | @test:Config { 36 | groups: ["workspace", "google-sheets"], 37 | enable: true 38 | } 39 | function gsheetsWorkspaceCreateTest2() returns error? { 40 | string[] workspaceIds = check rainierClient->/workspaces.post([workspace2, workspace3]); 41 | 42 | test:assertEquals(workspaceIds, [workspace2.workspaceId, workspace3.workspaceId]); 43 | 44 | Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace2.workspaceId].get(); 45 | test:assertEquals(workspaceRetrieved, workspace2); 46 | 47 | workspaceRetrieved = check rainierClient->/workspaces/[workspace3.workspaceId].get(); 48 | test:assertEquals(workspaceRetrieved, workspace3); 49 | } 50 | 51 | @test:Config { 52 | groups: ["workspace", "google-sheets"], 53 | dependsOn: [gsheetsWorkspaceCreateTest], 54 | enable: true 55 | } 56 | function gsheetsWorkspaceReadOneTest() returns error? { 57 | Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); 58 | test:assertEquals(workspaceRetrieved, workspace1); 59 | } 60 | 61 | @test:Config { 62 | groups: ["workspace", "google-sheets"], 63 | dependsOn: [gsheetsWorkspaceCreateTest], 64 | enable: true 65 | } 66 | function gsheetsWorkspaceReadOneDependentTest() returns error? { 67 | WorkspaceInfo2 workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); 68 | test:assertEquals(workspaceRetrieved, 69 | { 70 | workspaceType: workspace1.workspaceType, 71 | locationBuildingCode: workspace1.locationBuildingCode 72 | } 73 | ); 74 | } 75 | 76 | @test:Config { 77 | groups: ["workspace", "google-sheets"], 78 | dependsOn: [gsheetsWorkspaceCreateTest], 79 | enable: true 80 | } 81 | function gsheetsWorkspaceReadOneTestNegative() returns error? { 82 | Workspace|error workspaceRetrieved = rainierClient->/workspaces/["invalid-workspace-id"].get(); 83 | if workspaceRetrieved is persist:NotFoundError { 84 | test:assertEquals(workspaceRetrieved.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); 85 | } else { 86 | test:assertFail("NotFoundError expected."); 87 | } 88 | } 89 | 90 | @test:Config { 91 | groups: ["workspace", "google-sheets"], 92 | dependsOn: [gsheetsWorkspaceCreateTest, gsheetsWorkspaceCreateTest2], 93 | enable: true 94 | } 95 | function gsheetsWorkspaceReadManyTest() returns error? { 96 | stream workspaceStream = rainierClient->/workspaces.get(); 97 | Workspace[] workspaces = check from Workspace workspace in workspaceStream 98 | select workspace; 99 | 100 | test:assertEquals(workspaces, [workspace1, workspace2, workspace3]); 101 | } 102 | 103 | @test:Config { 104 | groups: ["workspace", "dependent"], 105 | dependsOn: [gsheetsWorkspaceCreateTest, gsheetsWorkspaceCreateTest2], 106 | enable: true 107 | } 108 | function gsheetsWorkspaceReadManyDependentTest() returns error? { 109 | stream workspaceStream = rainierClient->/workspaces.get(); 110 | WorkspaceInfo2[] workspaces = check from WorkspaceInfo2 workspace in workspaceStream 111 | select workspace; 112 | 113 | test:assertEquals(workspaces, [ 114 | {workspaceType: workspace1.workspaceType, locationBuildingCode: workspace1.locationBuildingCode}, 115 | {workspaceType: workspace2.workspaceType, locationBuildingCode: workspace2.locationBuildingCode}, 116 | {workspaceType: workspace3.workspaceType, locationBuildingCode: workspace3.locationBuildingCode} 117 | ]); 118 | } 119 | 120 | @test:Config { 121 | groups: ["workspace", "google-sheets"], 122 | dependsOn: [gsheetsWorkspaceReadOneTest, gsheetsWorkspaceReadManyTest, gsheetsWorkspaceReadManyDependentTest], 123 | enable: true 124 | } 125 | function gsheetsWorkspaceUpdateTest() returns error? { 126 | Workspace workspace = check rainierClient->/workspaces/[workspace1.workspaceId].put({ 127 | workspaceType: "large" 128 | }); 129 | 130 | test:assertEquals(workspace, updatedWorkspace1); 131 | 132 | Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); 133 | test:assertEquals(workspaceRetrieved, updatedWorkspace1); 134 | } 135 | 136 | @test:Config { 137 | groups: ["workspace", "google-sheets"], 138 | dependsOn: [gsheetsWorkspaceReadOneTest, gsheetsWorkspaceReadManyTest, gsheetsWorkspaceReadManyDependentTest], 139 | enable: true 140 | } 141 | function gsheetsWorkspaceUpdateTestNegative1() returns error? { 142 | Workspace|error workspace = rainierClient->/workspaces/["invalid-workspace-id"].put({ 143 | workspaceType: "large" 144 | }); 145 | 146 | if workspace is persist:NotFoundError { 147 | test:assertEquals(workspace.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); 148 | } else { 149 | test:assertFail("NotFoundError expected."); 150 | } 151 | } 152 | 153 | @test:Config { 154 | groups: ["workspace", "google-sheets"], 155 | dependsOn: [gsheetsWorkspaceUpdateTest, gsheetsWorkspaceUpdateTestNegative1], 156 | enable: true 157 | } 158 | function gsheetsWorkspaceDeleteTest() returns error? { 159 | Workspace workspace = check rainierClient->/workspaces/[workspace1.workspaceId].delete(); 160 | test:assertEquals(workspace, updatedWorkspace1); 161 | 162 | stream workspaceStream = rainierClient->/workspaces.get(); 163 | Workspace[] workspaces = check from Workspace workspace2 in workspaceStream 164 | select workspace2; 165 | 166 | test:assertEquals(workspaces, [workspace2, workspace3]); 167 | } 168 | 169 | @test:Config { 170 | groups: ["workspace", "google-sheets"], 171 | dependsOn: [gsheetsWorkspaceDeleteTest], 172 | enable: true 173 | } 174 | function gsheetsWorkspaceDeleteTestNegative() returns error? { 175 | Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].delete(); 176 | 177 | if workspace is persist:NotFoundError { 178 | test:assertEquals(workspace.message(), "A record with the key 'workspace-1' does not exist for the entity 'Workspace'."); 179 | } else { 180 | test:assertFail("NotFoundError expected."); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /ballerina/tests/gsheets-employee-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/persist; 19 | import ballerina/lang.runtime; 20 | 21 | @test:Config { 22 | groups: ["employee", "google-sheets"], 23 | dependsOn: [gsheetsWorkspaceDeleteTestNegative, gsheetsDepartmentDeleteTestNegative], 24 | enable: true 25 | } 26 | function gsheetsEmployeeCreateTest() returns error? { 27 | runtime:sleep(40); 28 | string[] empNos = check rainierClient->/employees.post([employee1]); 29 | test:assertEquals(empNos, [employee1.empNo]); 30 | 31 | Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); 32 | test:assertEquals(employeeRetrieved, employee1); 33 | } 34 | 35 | @test:Config { 36 | groups: ["employee", "google-sheets"], 37 | dependsOn: [gsheetsWorkspaceDeleteTestNegative, gsheetsDepartmentDeleteTestNegative], 38 | enable: true 39 | } 40 | function gsheetsEmployeeCreateTest2() returns error? { 41 | string[] empNos = check rainierClient->/employees.post([employee2, employee3]); 42 | 43 | test:assertEquals(empNos, [employee2.empNo, employee3.empNo]); 44 | 45 | Employee employeeRetrieved = check rainierClient->/employees/[employee2.empNo].get(); 46 | test:assertEquals(employeeRetrieved, employee2); 47 | 48 | employeeRetrieved = check rainierClient->/employees/[employee3.empNo].get(); 49 | test:assertEquals(employeeRetrieved, employee3); 50 | } 51 | 52 | @test:Config { 53 | groups: ["employee", "google-sheets"], 54 | dependsOn: [gsheetsEmployeeCreateTest], 55 | enable: true 56 | } 57 | function gsheetsEmployeeReadOneTest() returns error? { 58 | Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); 59 | test:assertEquals(employeeRetrieved, employee1); 60 | } 61 | 62 | @test:Config { 63 | groups: ["employee", "google-sheets"], 64 | dependsOn: [gsheetsEmployeeCreateTest], 65 | enable: true 66 | } 67 | function gsheetsEmployeeReadOneTestNegative() returns error? { 68 | Employee|error employeeRetrieved = rainierClient->/employees/["invalid-employee-id"].get(); 69 | if employeeRetrieved is persist:NotFoundError { 70 | test:assertEquals(employeeRetrieved.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); 71 | } else { 72 | test:assertFail("NotFoundError expected."); 73 | } 74 | } 75 | 76 | @test:Config { 77 | groups: ["employee", "google-sheets"], 78 | dependsOn: [gsheetsEmployeeCreateTest, gsheetsEmployeeCreateTest2], 79 | enable: true 80 | } 81 | function gsheetsEmployeeReadManyTest() returns error? { 82 | stream employeeStream = rainierClient->/employees.get(); 83 | Employee[] employees = check from Employee employee in employeeStream 84 | select employee; 85 | 86 | test:assertEquals(employees, [employee1, employee2, employee3]); 87 | } 88 | 89 | @test:Config { 90 | groups: ["dependent", "employee"], 91 | dependsOn: [gsheetsEmployeeCreateTest, gsheetsEmployeeCreateTest2], 92 | enable: true 93 | } 94 | function gsheetsEmployeeReadManyDependentTest1() returns error? { 95 | stream employeeStream = rainierClient->/employees.get(); 96 | EmployeeName[] employees = check from EmployeeName employee in employeeStream 97 | select employee; 98 | 99 | test:assertEquals(employees, [ 100 | {firstName: employee1.firstName, lastName: employee1.lastName}, 101 | {firstName: employee2.firstName, lastName: employee2.lastName}, 102 | {firstName: employee3.firstName, lastName: employee3.lastName} 103 | ]); 104 | } 105 | 106 | @test:Config { 107 | groups: ["dependent", "employee"], 108 | dependsOn: [gsheetsEmployeeCreateTest, gsheetsEmployeeCreateTest2], 109 | enable: true 110 | } 111 | function gsheetsEmployeeReadManyDependentTest2() returns error? { 112 | stream employeeStream = rainierClient->/employees.get(); 113 | EmployeeInfo2[] employees = check from EmployeeInfo2 employee in employeeStream 114 | select employee; 115 | 116 | test:assertEquals(employees, [ 117 | {empNo: employee1.empNo, birthDate: employee1.birthDate, departmentDeptNo: employee1.departmentDeptNo, workspaceWorkspaceId: employee1.workspaceWorkspaceId}, 118 | {empNo: employee2.empNo, birthDate: employee2.birthDate, departmentDeptNo: employee2.departmentDeptNo, workspaceWorkspaceId: employee2.workspaceWorkspaceId}, 119 | {empNo: employee3.empNo, birthDate: employee3.birthDate, departmentDeptNo: employee3.departmentDeptNo, workspaceWorkspaceId: employee3.workspaceWorkspaceId} 120 | ]); 121 | } 122 | 123 | @test:Config { 124 | groups: ["employee", "google-sheets"], 125 | dependsOn: [gsheetsEmployeeReadOneTest, gsheetsEmployeeReadManyTest, gsheetsEmployeeReadManyDependentTest1, gsheetsEmployeeReadManyDependentTest2], 126 | enable: true 127 | } 128 | function gsheetsEmployeeUpdateTest() returns error? { 129 | Employee employee = check rainierClient->/employees/[employee1.empNo].put({ 130 | lastName: "Jones", 131 | departmentDeptNo: "department-3", 132 | birthDate: {year: 1994, month: 11, day: 13} 133 | }); 134 | 135 | test:assertEquals(employee, updatedEmployee1); 136 | 137 | Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); 138 | test:assertEquals(employeeRetrieved, updatedEmployee1); 139 | } 140 | 141 | @test:Config { 142 | groups: ["employee", "google-sheets"], 143 | dependsOn: [gsheetsEmployeeReadOneTest, gsheetsEmployeeReadManyTest, gsheetsEmployeeReadManyDependentTest1, gsheetsEmployeeReadManyDependentTest2], 144 | enable: true 145 | } 146 | function gsheetsEmployeeUpdateTestNegative1() returns error? { 147 | Employee|error employee = rainierClient->/employees/["invalid-employee-id"].put({ 148 | lastName: "Jones" 149 | }); 150 | 151 | if employee is persist:NotFoundError { 152 | test:assertEquals(employee.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); 153 | } else { 154 | test:assertFail("NotFoundError expected."); 155 | } 156 | } 157 | 158 | @test:Config { 159 | groups: ["employee", "google-sheets"], 160 | dependsOn: [gsheetsEmployeeUpdateTest], 161 | enable: true 162 | } 163 | function gsheetsEmployeeDeleteTest() returns error? { 164 | Employee employee = check rainierClient->/employees/[employee1.empNo].delete(); 165 | test:assertEquals(employee, updatedEmployee1); 166 | 167 | stream employeeStream = rainierClient->/employees.get(); 168 | Employee[] employees = check from Employee employee2 in employeeStream 169 | select employee2; 170 | 171 | test:assertEquals(employees, [employee2, employee3]); 172 | } 173 | 174 | @test:Config { 175 | groups: ["employee", "google-sheets"], 176 | dependsOn: [gsheetsEmployeeDeleteTest], 177 | enable: true 178 | } 179 | function gsheetsEmployeeDeleteTestNegative() returns error? { 180 | Employee|error employee = rainierClient->/employees/[employee1.empNo].delete(); 181 | 182 | if employee is persist:NotFoundError { 183 | test:assertEquals(employee.message(), "A record with the key 'employee-1' does not exist for the entity 'Employee'."); 184 | } else { 185 | test:assertFail("NotFoundError expected."); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ballerina/tests/googlesheets_all_data_types_generated_client.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/persist; 18 | import ballerina/jballerina.java; 19 | import ballerina/http; 20 | import ballerinax/googleapis.sheets; 21 | 22 | const ORDER_ITEM_EXTENDED = "orderitemextendeds"; 23 | 24 | public isolated client class GoogleSheetsRainierClientAllDataType { 25 | *persist:AbstractPersistClient; 26 | 27 | private final sheets:Client googleSheetClient; 28 | 29 | private final http:Client httpSheetsClient; 30 | 31 | private final map persistClients; 32 | 33 | public isolated function init() returns persist:Error? { 34 | final record {|SheetMetadata...;|} & readonly metadata = { 35 | [ORDER_ITEM_EXTENDED] : { 36 | entityName: "OrderItemExtended", 37 | tableName: "OrderItemExtended", 38 | keyFields: ["orderId", "itemId"], 39 | range: "A:K", 40 | query: self.queryOrderitemextendeds, 41 | queryOne: self.queryOneOrderitemextendeds, 42 | dataTypes: { 43 | orderId: "string", 44 | itemId: "string", 45 | CustomerId: "int", 46 | paid: "boolean", 47 | ammountPaid: "float", 48 | ammountPaidDecimal: "decimal", 49 | arivalTimeCivil: "time:Civil", 50 | arivalTimeUtc: "time:Utc", 51 | arivalTimeDate: "time:Date", 52 | arivalTimeTimeOfDay: "time:TimeOfDay", 53 | orderType: "ENUM" 54 | }, 55 | fieldMetadata: { 56 | orderId: {columnName: "orderId", columnId: "A"}, 57 | itemId: {columnName: "itemId", columnId: "B"}, 58 | CustomerId: {columnName: "CustomerId", columnId: "C"}, 59 | paid: {columnName: "paid", columnId: "D"}, 60 | ammountPaid: {columnName: "ammountPaid", columnId: "E"}, 61 | ammountPaidDecimal: {columnName: "ammountPaidDecimal", columnId: "F"}, 62 | arivalTimeCivil: {columnName: "arivalTimeCivil", columnId: "G"}, 63 | arivalTimeUtc: {columnName: "arivalTimeUtc", columnId: "H"}, 64 | arivalTimeDate: {columnName: "arivalTimeDate", columnId: "I"}, 65 | arivalTimeTimeOfDay: {columnName: "arivalTimeTimeOfDay", columnId: "J"}, 66 | orderType: {columnName: "orderType", columnId: "K"} 67 | }, 68 | associationsMethods: {} 69 | } 70 | }; 71 | sheets:ConnectionConfig sheetsClientConfig = { 72 | auth: { 73 | clientId: clientId, 74 | clientSecret: clientSecret, 75 | refreshUrl: sheets:REFRESH_URL, 76 | refreshToken: refreshToken 77 | } 78 | }; 79 | http:ClientConfiguration httpClientConfiguration = { 80 | auth: { 81 | clientId: clientId, 82 | clientSecret: clientSecret, 83 | refreshUrl: sheets:REFRESH_URL, 84 | refreshToken: refreshToken 85 | } 86 | }; 87 | http:Client|error httpSheetsClient = new (string `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values`, httpClientConfiguration); 88 | if httpSheetsClient is error { 89 | return error(httpSheetsClient.message()); 90 | } 91 | sheets:Client|error googleSheetClient = new (sheetsClientConfig); 92 | if googleSheetClient is error { 93 | return error(googleSheetClient.message()); 94 | } 95 | self.googleSheetClient = googleSheetClient; 96 | self.httpSheetsClient = httpSheetsClient; 97 | map sheetIds = check getSheetIds(self.googleSheetClient, metadata, spreadsheetId); 98 | self.persistClients = {[ORDER_ITEM_EXTENDED] : check new (self.googleSheetClient, self.httpSheetsClient, metadata.get(ORDER_ITEM_EXTENDED).cloneReadOnly(), spreadsheetId.cloneReadOnly(), sheetIds.get(ORDER_ITEM_EXTENDED).cloneReadOnly())}; 99 | } 100 | 101 | isolated resource function get orderitemextendeds(OrderItemExtendedTargetType targetType = <>) returns stream = @java:Method { 102 | 'class: "io.ballerina.stdlib.persist.googlesheets.datastore.GoogleSheetsProcessor", 103 | name: "query" 104 | } external; 105 | 106 | isolated resource function get orderitemextendeds/[string orderId]/[string itemId](OrderItemExtendedTargetType targetType = <>) returns targetType|persist:Error = @java:Method { 107 | 'class: "io.ballerina.stdlib.persist.googlesheets.datastore.GoogleSheetsProcessor", 108 | name: "queryOne" 109 | } external; 110 | 111 | isolated resource function post orderitemextendeds(OrderItemExtendedInsert[] data) returns [string, string][]|persist:Error { 112 | GoogleSheetsClient googleSheetsClient; 113 | lock { 114 | googleSheetsClient = self.persistClients.get(ORDER_ITEM_EXTENDED); 115 | } 116 | _ = check googleSheetsClient.runBatchInsertQuery(data); 117 | return from OrderItemExtendedInsert inserted in data 118 | select [inserted.orderId, inserted.itemId]; 119 | } 120 | 121 | isolated resource function put orderitemextendeds/[string orderId]/[string itemId](OrderItemExtendedUpdate value) returns OrderItemExtended|persist:Error { 122 | GoogleSheetsClient googleSheetsClient; 123 | lock { 124 | googleSheetsClient = self.persistClients.get(ORDER_ITEM_EXTENDED); 125 | } 126 | _ = check googleSheetsClient.runUpdateQuery({"orderId": orderId, "itemId": itemId}, value); 127 | return self->/orderitemextendeds/[orderId]/[itemId].get(); 128 | } 129 | 130 | isolated resource function delete orderitemextendeds/[string orderId]/[string itemId]() returns OrderItemExtended|persist:Error { 131 | OrderItemExtended result = check self->/orderitemextendeds/[orderId]/[itemId].get(); 132 | GoogleSheetsClient googleSheetsClient; 133 | lock { 134 | googleSheetsClient = self.persistClients.get(ORDER_ITEM_EXTENDED); 135 | } 136 | _ = check googleSheetsClient.runDeleteQuery({"orderId": orderId, "itemId": itemId}); 137 | return result; 138 | } 139 | 140 | private isolated function queryOrderitemextendeds(string[] fields) returns stream|persist:Error { 141 | stream orderitemextendedsStream = self.queryOrderitemextendedsStream(); 142 | record {}[] outputArray = check from record {} 'object in orderitemextendedsStream 143 | select persist:filterRecord({ 144 | ...'object 145 | }, fields); 146 | return outputArray.toStream(); 147 | } 148 | 149 | private isolated function queryOneOrderitemextendeds(anydata key) returns record {}|persist:Error { 150 | stream orderitemextendedsStream = self.queryOrderitemextendedsStream(); 151 | error? unionResult = from record {} 'object in orderitemextendedsStream 152 | where persist:getKey('object, ["orderId", "itemId"]) == key 153 | do { 154 | return { 155 | ...'object 156 | }; 157 | }; 158 | if unionResult is error { 159 | return error persist:Error(unionResult.message()); 160 | } 161 | return persist:getNotFoundError("OrderItemExtended", key); 162 | } 163 | 164 | private isolated function queryOrderitemextendedsStream(OrderItemExtendedTargetType targetType = <>) returns stream = @java:Method { 165 | 'class: "io.ballerina.stdlib.persist.googlesheets.datastore.GoogleSheetsProcessor", 166 | name: "queryStream" 167 | } external; 168 | 169 | public isolated function close() returns persist:Error? { 170 | return (); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/persist/googlesheets/datastore/GoogleSheetsProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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.stdlib.persist.googlesheets.datastore; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.creators.ValueCreator; 23 | import io.ballerina.runtime.api.types.RecordType; 24 | import io.ballerina.runtime.api.values.BArray; 25 | import io.ballerina.runtime.api.values.BError; 26 | import io.ballerina.runtime.api.values.BMap; 27 | import io.ballerina.runtime.api.values.BObject; 28 | import io.ballerina.runtime.api.values.BStream; 29 | import io.ballerina.runtime.api.values.BString; 30 | import io.ballerina.runtime.api.values.BTypedesc; 31 | import io.ballerina.stdlib.persist.googlesheets.Utils; 32 | 33 | import static io.ballerina.stdlib.persist.Constants.KEY_FIELDS; 34 | import static io.ballerina.stdlib.persist.Constants.RUN_READ_QUERY_METHOD; 35 | import static io.ballerina.stdlib.persist.ErrorGenerator.wrapError; 36 | import static io.ballerina.stdlib.persist.Utils.getEntity; 37 | import static io.ballerina.stdlib.persist.Utils.getKey; 38 | import static io.ballerina.stdlib.persist.Utils.getMetadata; 39 | import static io.ballerina.stdlib.persist.Utils.getPersistClient; 40 | import static io.ballerina.stdlib.persist.Utils.getRecordTypeWithKeyFields; 41 | import static io.ballerina.stdlib.persist.googlesheets.Constants.RUN_READ_TABLE_AS_STREAM_METHOD; 42 | import static io.ballerina.stdlib.persist.googlesheets.Utils.getEntityFromStreamMethod; 43 | import static io.ballerina.stdlib.persist.googlesheets.Utils.getFieldTypes; 44 | 45 | /** 46 | * This class provides the GoogleSheets query processing implementations for 47 | * persistence. 48 | * 49 | * @since 0.3.0 50 | */ 51 | 52 | public class GoogleSheetsProcessor { 53 | 54 | private GoogleSheetsProcessor() { 55 | }; 56 | 57 | public static BStream query(Environment env, BObject client, BTypedesc targetType) { 58 | // This method will return `stream` 59 | 60 | BString entity = getEntity(env); 61 | BObject persistClient = getPersistClient(client, entity); 62 | BArray keyFields = (BArray) persistClient.get(KEY_FIELDS); 63 | RecordType recordType = (RecordType) targetType.getDescribingType(); 64 | 65 | RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); 66 | BTypedesc targetTypeWithIdFields = ValueCreator.createTypedescValue(recordTypeWithIdFields); 67 | 68 | BArray[] metadata = getMetadata(recordType); 69 | BArray fields = metadata[0]; 70 | BArray includes = metadata[1]; 71 | BArray typeDescriptions = metadata[2]; 72 | BMap typeMap = getFieldTypes(recordType); 73 | return env.yieldAndRun(() -> { 74 | try { 75 | Object result = env.getRuntime().callMethod( 76 | // Call `RedisClient.runReadQuery( 77 | // typedesc rowType, map typeMap, string[] fields = [], 78 | // string[] include = [] 79 | // )` 80 | // which returns `stream|persist:Error` 81 | persistClient, RUN_READ_QUERY_METHOD, null, targetTypeWithIdFields, typeMap, fields, includes); 82 | if (result instanceof BStream bStream) { // stream 83 | return Utils.createPersistGSheetsStreamValue(bStream, targetType, fields, includes, 84 | typeDescriptions, persistClient, null); 85 | } 86 | // persist:Error 87 | return Utils.createPersistGSheetsStreamValue(null, targetType, fields, includes, typeDescriptions, 88 | persistClient, (BError) result); 89 | } catch (BError bError) { 90 | return Utils.createPersistGSheetsStreamValue(null, targetType, fields, includes, typeDescriptions, 91 | persistClient, bError); 92 | } 93 | }); 94 | } 95 | 96 | public static BStream queryStream(Environment env, BObject client, BTypedesc targetType) { 97 | // This method will return `stream` 98 | 99 | BString entity = getEntityFromStreamMethod(env); 100 | BObject persistClient = getPersistClient(client, entity); 101 | BArray keyFields = (BArray) persistClient.get(KEY_FIELDS); 102 | RecordType recordType = (RecordType) targetType.getDescribingType(); 103 | 104 | RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); 105 | BTypedesc targetTypeWithIdFields = ValueCreator.createTypedescValue(recordTypeWithIdFields); 106 | 107 | BArray[] metadata = getMetadata(recordType); 108 | BArray fields = metadata[0]; 109 | BArray includes = metadata[1]; 110 | BArray typeDescriptions = metadata[2]; 111 | BMap typeMap = getFieldTypes(recordType); 112 | return env.yieldAndRun(() -> { 113 | try { 114 | Object result = env.getRuntime().callMethod( 115 | // Call `RedisClient.runReadQuery( 116 | // typedesc rowType, map typeMap, string[] fields = [], 117 | // string[] include = [] 118 | // )` 119 | // which returns `stream|persist:Error` 120 | persistClient, RUN_READ_TABLE_AS_STREAM_METHOD, null, targetTypeWithIdFields, typeMap, fields 121 | , includes); 122 | if (result instanceof BStream bStream) { // stream 123 | return Utils.createPersistGSheetsStreamValue(bStream, targetType, fields, includes, 124 | typeDescriptions, persistClient, null); 125 | } 126 | // persist:Error 127 | return Utils.createPersistGSheetsStreamValue(null, targetType, fields, includes, 128 | typeDescriptions, persistClient, (BError) result); 129 | } catch (BError bError) { 130 | return Utils.createPersistGSheetsStreamValue(null, targetType, fields, includes, typeDescriptions, 131 | persistClient, bError); 132 | } 133 | }); 134 | } 135 | 136 | public static Object queryOne(Environment env, BObject client, BArray path, BTypedesc targetType) { 137 | // This method will return `targetType|persist:Error` 138 | 139 | BString entity = getEntity(env); 140 | BObject persistClient = getPersistClient(client, entity); 141 | BArray keyFields = (BArray) persistClient.get(KEY_FIELDS); 142 | RecordType recordType = (RecordType) targetType.getDescribingType(); 143 | 144 | RecordType recordTypeWithIdFields = getRecordTypeWithKeyFields(keyFields, recordType); 145 | BTypedesc targetTypeWithIdFields = ValueCreator.createTypedescValue(recordTypeWithIdFields); 146 | 147 | BArray[] metadata = getMetadata(recordType); 148 | BArray fields = metadata[0]; 149 | BArray includes = metadata[1]; 150 | BArray typeDescriptions = metadata[2]; 151 | BMap typeMap = getFieldTypes(recordType); 152 | Object key = getKey(env, path); 153 | return env.yieldAndRun(() -> { 154 | try { 155 | return env.getRuntime().callMethod( 156 | // Call `RedisClient.runReadQuery( 157 | // typedesc rowType, map typeMap, string[] fields = [], 158 | // string[] include = [] 159 | // )` 160 | // which returns `stream|persist:Error` 161 | persistClient, RUN_READ_TABLE_AS_STREAM_METHOD, null, targetType, targetTypeWithIdFields, 162 | typeMap, key, fields, includes, typeDescriptions); 163 | } catch (BError bError) { 164 | return wrapError(bError); 165 | } 166 | }); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /ballerina/tests/gsheets-composite-key-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/persist; 19 | import ballerina/lang.runtime; 20 | 21 | OrderItem orderItem1 = { 22 | orderId: "order-1", 23 | itemId: "item-1", 24 | quantity: 5, 25 | notes: "none" 26 | }; 27 | 28 | OrderItem orderItem2 = { 29 | orderId: "order-2", 30 | itemId: "item-2", 31 | quantity: 10, 32 | notes: "more" 33 | }; 34 | 35 | OrderItem orderItem2Updated = { 36 | orderId: "order-2", 37 | itemId: "item-2", 38 | quantity: 20, 39 | notes: "more than more" 40 | }; 41 | 42 | @test:Config { 43 | groups: ["composite-key", "google-sheets"], 44 | enable: true 45 | } 46 | function gsheetsCompositeKeyCreateTest() returns error? { 47 | runtime:sleep(25); 48 | [string, string][] ids = check rainierClient->/orderitems.post([orderItem1, orderItem2]); 49 | test:assertEquals(ids, [[orderItem1.orderId, orderItem1.itemId], [orderItem2.orderId, orderItem2.itemId]]); 50 | 51 | OrderItem orderItemRetrieved = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); 52 | test:assertEquals(orderItemRetrieved, orderItem1); 53 | 54 | orderItemRetrieved = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); 55 | test:assertEquals(orderItemRetrieved, orderItem2); 56 | } 57 | 58 | @test:Config { 59 | groups: ["composite-key", "google-sheets"], 60 | dependsOn: [gsheetsCompositeKeyCreateTest], 61 | enable: true 62 | } 63 | function gsheetsCmpositeKeyCreateTestNegative() returns error? { 64 | [string, string][]|error ids = rainierClient->/orderitems.post([orderItem1]); 65 | if ids is persist:AlreadyExistsError { 66 | test:assertEquals(ids.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"item-1\"}' already exists for the entity 'OrderItem'."); 67 | } else { 68 | test:assertFail("AlreadyExistsError expected"); 69 | } 70 | } 71 | 72 | @test:Config { 73 | groups: ["composite-key", "google-sheets"], 74 | dependsOn: [gsheetsCompositeKeyCreateTest], 75 | enable: true 76 | } 77 | function gsheetsCompositeKeyReadManyTest() returns error? { 78 | stream orderItemStream = rainierClient->/orderitems.get(); 79 | OrderItem[] orderitem = check from OrderItem orderItem in orderItemStream 80 | select orderItem; 81 | 82 | test:assertEquals(orderitem, [orderItem1, orderItem2]); 83 | } 84 | 85 | @test:Config { 86 | groups: ["composite-key", "google-sheets"], 87 | dependsOn: [gsheetsCompositeKeyCreateTest], 88 | enable: true 89 | } 90 | function gsheetsCompositeKeyReadOneTest() returns error? { 91 | OrderItem orderItem = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); 92 | test:assertEquals(orderItem, orderItem1); 93 | } 94 | 95 | @test:Config { 96 | groups: ["composite-key", "google-sheets"], 97 | dependsOn: [gsheetsCompositeKeyCreateTest], 98 | enable: true 99 | } 100 | function gsheetsCompositeKeyReadOneTest2() returns error? { 101 | OrderItem orderItem = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); 102 | test:assertEquals(orderItem, orderItem1); 103 | } 104 | 105 | @test:Config { 106 | groups: ["composite-key", "google-sheets"], 107 | dependsOn: [gsheetsCompositeKeyCreateTest], 108 | enable: true 109 | } 110 | function gsheetsCompositeKeyReadOneTestNegative1() returns error? { 111 | OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem1.itemId].get(); 112 | if orderItem is persist:NotFoundError { 113 | test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-1\"}' does not exist for the entity 'OrderItem'."); 114 | } else { 115 | test:assertFail("Error expected."); 116 | } 117 | } 118 | 119 | @test:Config { 120 | groups: ["composite-key", "google-sheets"], 121 | dependsOn: [gsheetsCompositeKeyCreateTest], 122 | enable: true 123 | } 124 | function gsheetsCompositeKeyReadOneTestNegative2() returns error? { 125 | OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/["invalid-item-id"].get(); 126 | if orderItem is persist:NotFoundError { 127 | test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"invalid-item-id\"}' does not exist for the entity 'OrderItem'."); 128 | } else { 129 | test:assertFail("Error expected."); 130 | } 131 | } 132 | 133 | @test:Config { 134 | groups: ["composite-key", "google-sheets"], 135 | dependsOn: [gsheetsCompositeKeyCreateTest, gsheetsCompositeKeyReadOneTest, gsheetsCompositeKeyReadManyTest, gsheetsCompositeKeyReadOneTest2], 136 | enable: true 137 | } 138 | function gsheetsCompositeKeyUpdateTest() returns error? { 139 | OrderItem orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].put({ 140 | quantity: orderItem2Updated.quantity, 141 | notes: orderItem2Updated.notes 142 | }); 143 | test:assertEquals(orderItem, orderItem2Updated); 144 | 145 | orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); 146 | test:assertEquals(orderItem, orderItem2Updated); 147 | } 148 | 149 | @test:Config { 150 | groups: ["composite-key", "google-sheets"], 151 | dependsOn: [gsheetsCompositeKeyCreateTest, gsheetsCompositeKeyReadOneTest, gsheetsCompositeKeyReadManyTest, gsheetsCompositeKeyReadOneTest2], 152 | enable: true 153 | } 154 | function gsheetsCompositeKeyUpdateTestNegative() returns error? { 155 | OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/[orderItem2.itemId].put({ 156 | quantity: 239, 157 | notes: "updated notes" 158 | }); 159 | if orderItem is persist:NotFoundError { 160 | test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); 161 | } else { 162 | test:assertFail("Error expected."); 163 | } 164 | } 165 | 166 | @test:Config { 167 | groups: ["composite-key", "google-sheets"], 168 | dependsOn: [gsheetsCompositeKeyUpdateTest], 169 | enable: true 170 | } 171 | function gsheetsCompositeKeyDeleteTest() returns error? { 172 | OrderItem orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].delete(); 173 | test:assertEquals(orderItem, orderItem2Updated); 174 | 175 | OrderItem|error orderItemRetrieved = rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); 176 | test:assertTrue(orderItemRetrieved is persist:NotFoundError); 177 | } 178 | 179 | @test:Config { 180 | groups: ["composite-key", "google-sheets"], 181 | dependsOn: [gsheetsCompositeKeyDeleteTest], 182 | enable: true 183 | } 184 | function gsheetsCompositeKeyDeleteTestNegative() returns error? { 185 | OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem2.itemId].delete(); 186 | if orderItem is persist:NotFoundError { 187 | test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); 188 | } else { 189 | test:assertFail("Error expected."); 190 | } 191 | } 192 | 193 | @test:Config { 194 | groups: ["composite-key", "google-sheets"], 195 | dependsOn: [gsheetsCompositeKeyDeleteTestNegative], 196 | enable: true 197 | } 198 | function gsheetsCompositeKeyNegativeGetTest() returns error? { 199 | GooglesheetsNegativeClient gsclient = check new (); 200 | OrderItemFalse|error orderItem = gsclient->/orderitemfalses/[orderItem1.orderId]/[orderItem1.itemId].get(); 201 | if orderItem is persist:Error { 202 | test:assertEquals(orderItem.message(), "Error: the spreadsheet is not initialised correctly. Number of columns in the sheet does not match with the entity. "); 203 | } else { 204 | test:assertFail("Error expected."); 205 | } 206 | } 207 | 208 | @test:Config { 209 | groups: ["composite-key", "google-sheets"], 210 | dependsOn: [gsheetsCompositeKeyNegativeGetTest], 211 | enable: true 212 | } 213 | function gsheetsCompositeKeyNegativeGetStreamTest() returns error? { 214 | GooglesheetsNegativeClient gsclient = check new (); 215 | stream orderItemStream = gsclient->/orderitemfalses.get(); 216 | record {|OrderItemFalse value;|}|error? orderItemFalse = orderItemStream.next(); 217 | if orderItemFalse is persist:Error { 218 | test:assertEquals(orderItemFalse.message(), "Error: the spreadsheet is not initialised correctly. Number of columns in the sheet does not match with the entity. "); 219 | } else { 220 | test:assertFail("Error expected."); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ballerina/tests/gsheets-associations-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 18 | import ballerina/persist; 19 | import ballerina/lang.runtime; 20 | 21 | @test:Config { 22 | groups: ["associations", "google-sheets"], 23 | dependsOn: [gsheetsEmployeeDeleteTestNegative], 24 | enable: true 25 | } 26 | function gsheetsEmployeeRelationsTest() returns error? { 27 | runtime:sleep(40); 28 | Employee employee21 = { 29 | empNo: "employee-21", 30 | firstName: "Tom", 31 | lastName: "Scott", 32 | birthDate: {year: 1992, month: 11, day: 13}, 33 | gender: MALE, 34 | hireDate: {year: 2022, month: 8, day: 1}, 35 | departmentDeptNo: "department-22", 36 | workspaceWorkspaceId: "workspace-22" 37 | }; 38 | 39 | Workspace workspace22 = { 40 | workspaceId: "workspace-22", 41 | workspaceType: "medium", 42 | locationBuildingCode: "building-22" 43 | }; 44 | 45 | BuildingInsert building22 = { 46 | buildingCode: "building-22", 47 | city: "Manhattan", 48 | state: "New York", 49 | country: "USA", 50 | postalCode: "10570", 51 | 'type: "owned" 52 | }; 53 | 54 | Department department22 = { 55 | deptNo: "department-22", 56 | deptName: "Marketing" 57 | }; 58 | 59 | _ = check rainierClient->/buildings.post([building22]); 60 | _ = check rainierClient->/departments.post([department22]); 61 | _ = check rainierClient->/workspaces.post([workspace22]); 62 | _ = check rainierClient->/employees.post([employee21]); 63 | 64 | stream employeeStream = rainierClient->/employees.get(); 65 | EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream 66 | select employee; 67 | 68 | EmployeeInfo retrieved = check rainierClient->/employees/["employee-21"].get(); 69 | 70 | EmployeeInfo expected = { 71 | firstName: "Tom", 72 | lastName: "Scott", 73 | department: { 74 | deptName: "Marketing" 75 | }, 76 | workspace: { 77 | workspaceId: "workspace-22", 78 | workspaceType: "medium", 79 | locationBuildingCode: "building-22" 80 | } 81 | }; 82 | 83 | test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); 84 | test:assertEquals(retrieved, expected); 85 | } 86 | 87 | @test:Config { 88 | groups: ["associations", "google-sheets"], 89 | dependsOn: [gsheetsEmployeeDeleteTestNegative], 90 | enable: true 91 | } 92 | function gsheetsDepartmentRelationsTest() returns error? { 93 | Employee employee11 = { 94 | empNo: "employee-11", 95 | firstName: "Tom", 96 | lastName: "Scott", 97 | birthDate: {year: 1992, month: 11, day: 13}, 98 | gender: MALE, 99 | hireDate: {year: 2022, month: 8, day: 1}, 100 | departmentDeptNo: "department-12", 101 | workspaceWorkspaceId: "workspace-12" 102 | }; 103 | 104 | Employee employee12 = { 105 | empNo: "employee-12", 106 | firstName: "Jane", 107 | lastName: "Doe", 108 | birthDate: {year: 1996, month: 9, day: 15}, 109 | gender: FEMALE, 110 | hireDate: {year: 2022, month: 6, day: 1}, 111 | departmentDeptNo: "department-12", 112 | workspaceWorkspaceId: "workspace-12" 113 | }; 114 | 115 | Workspace workspace12 = { 116 | workspaceId: "workspace-12", 117 | workspaceType: "medium", 118 | locationBuildingCode: "building-12" 119 | }; 120 | 121 | BuildingInsert building12 = { 122 | buildingCode: "building-12", 123 | city: "Manhattan", 124 | state: "New York", 125 | country: "USA", 126 | postalCode: "10570", 127 | 'type: "owned" 128 | }; 129 | 130 | Department department12 = { 131 | deptNo: "department-12", 132 | deptName: "Marketing" 133 | }; 134 | 135 | _ = check rainierClient->/buildings.post([building12]); 136 | _ = check rainierClient->/departments.post([department12]); 137 | _ = check rainierClient->/workspaces.post([workspace12]); 138 | _ = check rainierClient->/employees.post([employee11, employee12]); 139 | 140 | stream departmentStream = rainierClient->/departments.get(); 141 | DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream 142 | select department; 143 | runtime:sleep(40); // avoid quote exceed issue 144 | DepartmentInfo retrieved = check rainierClient->/departments/["department-12"].get(); 145 | 146 | DepartmentInfo expected = { 147 | deptNo: "department-12", 148 | deptName: "Marketing", 149 | employees: [ 150 | { 151 | firstName: "Tom", 152 | lastName: "Scott" 153 | }, 154 | { 155 | firstName: "Jane", 156 | lastName: "Doe" 157 | } 158 | ] 159 | }; 160 | 161 | test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); 162 | test:assertEquals(retrieved, expected); 163 | } 164 | 165 | @test:Config { 166 | groups: ["associations", "google-sheets"], 167 | dependsOn: [gsheetsEmployeeRelationsTest], 168 | enable: true 169 | } 170 | function gsheetsWorkspaceRelationsTest() returns error? { 171 | Employee employee22 = { 172 | empNo: "employee-22", 173 | firstName: "James", 174 | lastName: "David", 175 | birthDate: {year: 1996, month: 11, day: 13}, 176 | gender: FEMALE, 177 | hireDate: {year: 2021, month: 8, day: 1}, 178 | departmentDeptNo: "department-22", 179 | workspaceWorkspaceId: "workspace-22" 180 | }; 181 | _ = check rainierClient->/employees.post([employee22]); 182 | 183 | stream workspaceStream = rainierClient->/workspaces.get(); 184 | WorkspaceInfo[] workspaces = check from WorkspaceInfo workspace in workspaceStream 185 | select workspace; 186 | 187 | WorkspaceInfo retrieved = check rainierClient->/workspaces/["workspace-22"].get(); 188 | 189 | WorkspaceInfo expected = { 190 | workspaceType: "medium", 191 | location: { 192 | buildingCode: "building-22", 193 | city: "Manhattan", 194 | state: "New York", 195 | country: "USA", 196 | postalCode: "10570", 197 | 'type: "owned" 198 | }, 199 | employees: [ 200 | { 201 | empNo: "employee-21", 202 | firstName: "Tom", 203 | lastName: "Scott", 204 | birthDate: {year: 1992, month: 11, day: 13}, 205 | gender: MALE, 206 | hireDate: {year: 2022, month: 8, day: 1}, 207 | departmentDeptNo: "department-22", 208 | workspaceWorkspaceId: "workspace-22" 209 | }, 210 | { 211 | empNo: "employee-22", 212 | firstName: "James", 213 | lastName: "David", 214 | birthDate: {year: 1996, month: 11, day: 13}, 215 | gender: FEMALE, 216 | hireDate: {year: 2021, month: 8, day: 1}, 217 | departmentDeptNo: "department-22", 218 | workspaceWorkspaceId: "workspace-22" 219 | } 220 | ] 221 | }; 222 | boolean found = false; 223 | _ = from WorkspaceInfo workspace in workspaces 224 | do { 225 | if workspace == expected { 226 | found = true; 227 | } 228 | }; 229 | 230 | if !found { 231 | test:assertFail("Expected WorkspaceInfo not found."); 232 | } 233 | test:assertEquals(retrieved, expected); 234 | } 235 | 236 | @test:Config { 237 | groups: ["associations", "google-sheets"], 238 | dependsOn: [gsheetsEmployeeRelationsTest], 239 | enable: true 240 | } 241 | function gsheetsBuildingRelationsTest() returns error? { 242 | stream buildingStream = rainierClient->/buildings.get(); 243 | BuildingInfo[] buildings = check from BuildingInfo building in buildingStream 244 | select building; 245 | 246 | BuildingInfo retrieved = check rainierClient->/buildings/["building-22"].get(); 247 | 248 | BuildingInfo expected = { 249 | buildingCode: "building-22", 250 | city: "Manhattan", 251 | state: "New York", 252 | country: "USA", 253 | postalCode: "10570", 254 | 'type: "owned", 255 | workspaces: [ 256 | { 257 | workspaceId: "workspace-22", 258 | workspaceType: "medium", 259 | locationBuildingCode: "building-22" 260 | } 261 | ] 262 | }; 263 | 264 | boolean found = false; 265 | _ = from BuildingInfo building in buildings 266 | do { 267 | if (building.buildingCode == "building-22") { 268 | found = true; 269 | test:assertEquals(building, expected); 270 | } 271 | }; 272 | 273 | if !found { 274 | test:assertFail("Expected BuildingInfo not found."); 275 | } 276 | test:assertEquals(retrieved, expected); 277 | } 278 | -------------------------------------------------------------------------------- /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.12.0" 9 | 10 | [[package]] 11 | org = "ballerina" 12 | name = "auth" 13 | version = "2.14.0" 14 | dependencies = [ 15 | {org = "ballerina", name = "crypto"}, 16 | {org = "ballerina", name = "jballerina.java"}, 17 | {org = "ballerina", name = "lang.array"}, 18 | {org = "ballerina", name = "lang.string"}, 19 | {org = "ballerina", name = "log"} 20 | ] 21 | 22 | [[package]] 23 | org = "ballerina" 24 | name = "cache" 25 | version = "3.10.0" 26 | dependencies = [ 27 | {org = "ballerina", name = "constraint"}, 28 | {org = "ballerina", name = "jballerina.java"}, 29 | {org = "ballerina", name = "task"}, 30 | {org = "ballerina", name = "time"} 31 | ] 32 | 33 | [[package]] 34 | org = "ballerina" 35 | name = "constraint" 36 | version = "1.7.0" 37 | dependencies = [ 38 | {org = "ballerina", name = "jballerina.java"} 39 | ] 40 | 41 | [[package]] 42 | org = "ballerina" 43 | name = "crypto" 44 | version = "2.9.0" 45 | dependencies = [ 46 | {org = "ballerina", name = "jballerina.java"}, 47 | {org = "ballerina", name = "time"} 48 | ] 49 | 50 | [[package]] 51 | org = "ballerina" 52 | name = "data.jsondata" 53 | version = "1.1.0" 54 | dependencies = [ 55 | {org = "ballerina", name = "jballerina.java"}, 56 | {org = "ballerina", name = "lang.object"} 57 | ] 58 | 59 | [[package]] 60 | org = "ballerina" 61 | name = "file" 62 | version = "1.12.0" 63 | dependencies = [ 64 | {org = "ballerina", name = "io"}, 65 | {org = "ballerina", name = "jballerina.java"}, 66 | {org = "ballerina", name = "os"}, 67 | {org = "ballerina", name = "time"} 68 | ] 69 | 70 | [[package]] 71 | org = "ballerina" 72 | name = "http" 73 | version = "2.14.0" 74 | dependencies = [ 75 | {org = "ballerina", name = "auth"}, 76 | {org = "ballerina", name = "cache"}, 77 | {org = "ballerina", name = "constraint"}, 78 | {org = "ballerina", name = "crypto"}, 79 | {org = "ballerina", name = "data.jsondata"}, 80 | {org = "ballerina", name = "file"}, 81 | {org = "ballerina", name = "io"}, 82 | {org = "ballerina", name = "jballerina.java"}, 83 | {org = "ballerina", name = "jwt"}, 84 | {org = "ballerina", name = "lang.array"}, 85 | {org = "ballerina", name = "lang.decimal"}, 86 | {org = "ballerina", name = "lang.int"}, 87 | {org = "ballerina", name = "lang.regexp"}, 88 | {org = "ballerina", name = "lang.runtime"}, 89 | {org = "ballerina", name = "lang.string"}, 90 | {org = "ballerina", name = "lang.value"}, 91 | {org = "ballerina", name = "log"}, 92 | {org = "ballerina", name = "mime"}, 93 | {org = "ballerina", name = "oauth2"}, 94 | {org = "ballerina", name = "observe"}, 95 | {org = "ballerina", name = "time"}, 96 | {org = "ballerina", name = "url"} 97 | ] 98 | modules = [ 99 | {org = "ballerina", packageName = "http", moduleName = "http"}, 100 | {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} 101 | ] 102 | 103 | [[package]] 104 | org = "ballerina" 105 | name = "io" 106 | version = "1.8.0" 107 | dependencies = [ 108 | {org = "ballerina", name = "jballerina.java"}, 109 | {org = "ballerina", name = "lang.value"} 110 | ] 111 | 112 | [[package]] 113 | org = "ballerina" 114 | name = "jballerina.java" 115 | version = "0.0.0" 116 | modules = [ 117 | {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} 118 | ] 119 | 120 | [[package]] 121 | org = "ballerina" 122 | name = "jwt" 123 | version = "2.15.0" 124 | dependencies = [ 125 | {org = "ballerina", name = "cache"}, 126 | {org = "ballerina", name = "crypto"}, 127 | {org = "ballerina", name = "io"}, 128 | {org = "ballerina", name = "jballerina.java"}, 129 | {org = "ballerina", name = "lang.int"}, 130 | {org = "ballerina", name = "lang.string"}, 131 | {org = "ballerina", name = "log"}, 132 | {org = "ballerina", name = "time"} 133 | ] 134 | 135 | [[package]] 136 | org = "ballerina" 137 | name = "lang.__internal" 138 | version = "0.0.0" 139 | dependencies = [ 140 | {org = "ballerina", name = "jballerina.java"}, 141 | {org = "ballerina", name = "lang.object"} 142 | ] 143 | 144 | [[package]] 145 | org = "ballerina" 146 | name = "lang.array" 147 | version = "0.0.0" 148 | dependencies = [ 149 | {org = "ballerina", name = "jballerina.java"}, 150 | {org = "ballerina", name = "lang.__internal"} 151 | ] 152 | 153 | [[package]] 154 | org = "ballerina" 155 | name = "lang.decimal" 156 | version = "0.0.0" 157 | dependencies = [ 158 | {org = "ballerina", name = "jballerina.java"} 159 | ] 160 | 161 | [[package]] 162 | org = "ballerina" 163 | name = "lang.error" 164 | version = "0.0.0" 165 | scope = "testOnly" 166 | dependencies = [ 167 | {org = "ballerina", name = "jballerina.java"} 168 | ] 169 | 170 | [[package]] 171 | org = "ballerina" 172 | name = "lang.int" 173 | version = "0.0.0" 174 | dependencies = [ 175 | {org = "ballerina", name = "jballerina.java"}, 176 | {org = "ballerina", name = "lang.__internal"}, 177 | {org = "ballerina", name = "lang.object"} 178 | ] 179 | 180 | [[package]] 181 | org = "ballerina" 182 | name = "lang.object" 183 | version = "0.0.0" 184 | 185 | [[package]] 186 | org = "ballerina" 187 | name = "lang.regexp" 188 | version = "0.0.0" 189 | dependencies = [ 190 | {org = "ballerina", name = "jballerina.java"} 191 | ] 192 | modules = [ 193 | {org = "ballerina", packageName = "lang.regexp", moduleName = "lang.regexp"} 194 | ] 195 | 196 | [[package]] 197 | org = "ballerina" 198 | name = "lang.runtime" 199 | version = "0.0.0" 200 | dependencies = [ 201 | {org = "ballerina", name = "jballerina.java"} 202 | ] 203 | modules = [ 204 | {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} 205 | ] 206 | 207 | [[package]] 208 | org = "ballerina" 209 | name = "lang.string" 210 | version = "0.0.0" 211 | dependencies = [ 212 | {org = "ballerina", name = "jballerina.java"}, 213 | {org = "ballerina", name = "lang.regexp"} 214 | ] 215 | 216 | [[package]] 217 | org = "ballerina" 218 | name = "lang.value" 219 | version = "0.0.0" 220 | dependencies = [ 221 | {org = "ballerina", name = "jballerina.java"} 222 | ] 223 | 224 | [[package]] 225 | org = "ballerina" 226 | name = "log" 227 | version = "2.12.0" 228 | dependencies = [ 229 | {org = "ballerina", name = "io"}, 230 | {org = "ballerina", name = "jballerina.java"}, 231 | {org = "ballerina", name = "lang.value"}, 232 | {org = "ballerina", name = "observe"} 233 | ] 234 | 235 | [[package]] 236 | org = "ballerina" 237 | name = "mime" 238 | version = "2.12.0" 239 | dependencies = [ 240 | {org = "ballerina", name = "io"}, 241 | {org = "ballerina", name = "jballerina.java"}, 242 | {org = "ballerina", name = "lang.int"}, 243 | {org = "ballerina", name = "log"} 244 | ] 245 | 246 | [[package]] 247 | org = "ballerina" 248 | name = "oauth2" 249 | version = "2.14.0" 250 | dependencies = [ 251 | {org = "ballerina", name = "cache"}, 252 | {org = "ballerina", name = "crypto"}, 253 | {org = "ballerina", name = "jballerina.java"}, 254 | {org = "ballerina", name = "log"}, 255 | {org = "ballerina", name = "time"}, 256 | {org = "ballerina", name = "url"} 257 | ] 258 | 259 | [[package]] 260 | org = "ballerina" 261 | name = "observe" 262 | version = "1.5.0" 263 | dependencies = [ 264 | {org = "ballerina", name = "jballerina.java"} 265 | ] 266 | 267 | [[package]] 268 | org = "ballerina" 269 | name = "os" 270 | version = "1.10.0" 271 | dependencies = [ 272 | {org = "ballerina", name = "io"}, 273 | {org = "ballerina", name = "jballerina.java"} 274 | ] 275 | modules = [ 276 | {org = "ballerina", packageName = "os", moduleName = "os"} 277 | ] 278 | 279 | [[package]] 280 | org = "ballerina" 281 | name = "persist" 282 | version = "1.6.0" 283 | dependencies = [ 284 | {org = "ballerina", name = "jballerina.java"} 285 | ] 286 | modules = [ 287 | {org = "ballerina", packageName = "persist", moduleName = "persist"} 288 | ] 289 | 290 | [[package]] 291 | org = "ballerina" 292 | name = "task" 293 | version = "2.7.0" 294 | dependencies = [ 295 | {org = "ballerina", name = "jballerina.java"}, 296 | {org = "ballerina", name = "time"} 297 | ] 298 | 299 | [[package]] 300 | org = "ballerina" 301 | name = "test" 302 | version = "0.0.0" 303 | scope = "testOnly" 304 | dependencies = [ 305 | {org = "ballerina", name = "jballerina.java"}, 306 | {org = "ballerina", name = "lang.array"}, 307 | {org = "ballerina", name = "lang.error"} 308 | ] 309 | modules = [ 310 | {org = "ballerina", packageName = "test", moduleName = "test"} 311 | ] 312 | 313 | [[package]] 314 | org = "ballerina" 315 | name = "time" 316 | version = "2.7.0" 317 | dependencies = [ 318 | {org = "ballerina", name = "jballerina.java"} 319 | ] 320 | modules = [ 321 | {org = "ballerina", packageName = "time", moduleName = "time"} 322 | ] 323 | 324 | [[package]] 325 | org = "ballerina" 326 | name = "url" 327 | version = "2.6.0" 328 | dependencies = [ 329 | {org = "ballerina", name = "jballerina.java"} 330 | ] 331 | modules = [ 332 | {org = "ballerina", packageName = "url", moduleName = "url"} 333 | ] 334 | 335 | [[package]] 336 | org = "ballerinai" 337 | name = "observe" 338 | version = "0.0.0" 339 | dependencies = [ 340 | {org = "ballerina", name = "jballerina.java"}, 341 | {org = "ballerina", name = "observe"} 342 | ] 343 | 344 | [[package]] 345 | org = "ballerinax" 346 | name = "client.config" 347 | version = "1.0.1" 348 | dependencies = [ 349 | {org = "ballerina", name = "http"}, 350 | {org = "ballerina", name = "oauth2"} 351 | ] 352 | 353 | [[package]] 354 | org = "ballerinax" 355 | name = "googleapis.sheets" 356 | version = "3.5.0" 357 | dependencies = [ 358 | {org = "ballerina", name = "http"}, 359 | {org = "ballerina", name = "jballerina.java"}, 360 | {org = "ballerina", name = "lang.array"}, 361 | {org = "ballerina", name = "lang.int"}, 362 | {org = "ballerina", name = "lang.regexp"}, 363 | {org = "ballerina", name = "log"}, 364 | {org = "ballerina", name = "os"}, 365 | {org = "ballerinai", name = "observe"}, 366 | {org = "ballerinax", name = "client.config"} 367 | ] 368 | modules = [ 369 | {org = "ballerinax", packageName = "googleapis.sheets", moduleName = "googleapis.sheets"} 370 | ] 371 | 372 | [[package]] 373 | org = "ballerinax" 374 | name = "persist.googlesheets" 375 | version = "1.6.0" 376 | dependencies = [ 377 | {org = "ballerina", name = "http"}, 378 | {org = "ballerina", name = "jballerina.java"}, 379 | {org = "ballerina", name = "lang.regexp"}, 380 | {org = "ballerina", name = "lang.runtime"}, 381 | {org = "ballerina", name = "os"}, 382 | {org = "ballerina", name = "persist"}, 383 | {org = "ballerina", name = "test"}, 384 | {org = "ballerina", name = "time"}, 385 | {org = "ballerina", name = "url"}, 386 | {org = "ballerinax", name = "googleapis.sheets"} 387 | ] 388 | modules = [ 389 | {org = "ballerinax", packageName = "persist.googlesheets", moduleName = "persist.googlesheets"} 390 | ] 391 | 392 | -------------------------------------------------------------------------------- /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/init-tests.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) 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/test; 19 | import ballerinax/googleapis.sheets; 20 | import ballerina/os; 21 | 22 | configurable string & readonly refreshToken = os:getEnv("REFRESH_TOKEN"); 23 | configurable string & readonly clientId = os:getEnv("CLIENT_ID"); 24 | configurable string & readonly clientSecret = os:getEnv("CLIENT_SECRET"); 25 | configurable string & readonly spreadsheetId = ?; 26 | 27 | @test:BeforeSuite 28 | function initSpreadsheet() returns error? { 29 | sheets:ConnectionConfig spreadsheetConfig = { 30 | auth: { 31 | clientId: clientId, 32 | clientSecret: clientSecret, 33 | refreshToken: refreshToken, 34 | refreshUrl:sheets:REFRESH_URL 35 | } 36 | }; 37 | sheets:Client spreadsheetClient = check new (spreadsheetConfig); 38 | sheets:Spreadsheet spreadSheet = check spreadsheetClient->openSpreadsheetById(spreadsheetId); 39 | check resetExistingSheets(spreadsheetClient, spreadSheet); 40 | check addSheetHeaders(spreadsheetClient, spreadSheet); 41 | rainierClient = check new (); 42 | rainierClientAllDataType = check new (); 43 | } 44 | 45 | function resetExistingSheets(sheets:Client spreadsheetClient, sheets:Spreadsheet spreadSheet) returns error? { 46 | string[] sheetNames = ["OrderItem", "Employee", "Workspace", "Building", "Department", "OrderItemExtended"]; 47 | foreach sheets:Sheet sheet in spreadSheet.sheets { 48 | if sheetNames.indexOf(sheet.properties.title, 0) !is () { 49 | check spreadsheetClient->removeSheet(spreadsheetId, sheet.properties.sheetId); 50 | } 51 | } 52 | foreach string sheetName in sheetNames { 53 | _ = check spreadsheetClient->addSheet(spreadSheet.spreadsheetId, sheetName); 54 | } 55 | } 56 | 57 | function addSheetHeaders(sheets:Client spreadsheetClient, sheets:Spreadsheet spreadSheet) returns error? { 58 | _ = check spreadsheetClient->appendValue(spreadSheet.spreadsheetId, ["orderId", "itemId", "quantity", "notes"], {sheetName: "OrderItem", startIndex: "A1", endIndex: "E1"}, "USER_ENTERED"); 59 | _ = check spreadsheetClient->appendValue(spreadSheet.spreadsheetId, ["empNo", "firstName", "lastName", "birthDate", "gender", "hireDate", "departmentDeptNo", "workspaceWorkspaceId"], {sheetName: "Employee", startIndex: "A1", endIndex: "I1"}, "USER_ENTERED"); 60 | _ = check spreadsheetClient->appendValue(spreadSheet.spreadsheetId, ["workspaceId", "workspaceType", "locationBuildingCode"], {sheetName: "Workspace", startIndex: "A1", endIndex: "D1"}, "USER_ENTERED"); 61 | _ = check spreadsheetClient->appendValue(spreadSheet.spreadsheetId, ["buildingCode", "city", "state", "country", "postalCode", "type"], {sheetName: "Building", startIndex: "A1", endIndex: "G1"}, "USER_ENTERED"); 62 | _ = check spreadsheetClient->appendValue(spreadSheet.spreadsheetId, ["deptNo", "deptName"], {sheetName: "Department", startIndex: "A1", endIndex: "C1"}, "USER_ENTERED"); 63 | _ = check spreadsheetClient->appendValue(spreadSheet.spreadsheetId, ["orderId", "itemId", "CustomerId", "paid", "ammountPaid", "ammountPaidDecimal", "arivalTimeCivil", "arivalTimeUtc", "arivalTimeDate", "arivalTimeTimeOfDay", "orderType"], {sheetName: "OrderItemExtended", startIndex: "A1", endIndex: "L1"}, "USER_ENTERED"); 64 | } 65 | 66 | GoogleSheetsRainierClient rainierClient = check new (); 67 | GoogleSheetsRainierClientAllDataType rainierClientAllDataType = check new (); 68 | 69 | OrderItemExtended orderItemExtended1 = { 70 | orderId: "order-1", 71 | itemId: "item-1", 72 | CustomerId: 1, 73 | paid: false, 74 | ammountPaid: 10.5f, 75 | ammountPaidDecimal: 10.5, 76 | arivalTimeCivil: {"utcOffset":{"hours":-5,"minutes":-30, seconds: 0},"timeAbbrev":"Asia/Colombo","year":2021,"month":4,"day":12,"hour":23,"minute":20,"second":50.52}, 77 | arivalTimeUtc: [1684493685, 0.998012000], 78 | arivalTimeDate: {year: 2021, month: 4, day: 12}, 79 | arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, 80 | orderType: INSTORE 81 | }; 82 | 83 | OrderItemExtended orderItemExtended2 = { 84 | orderId: "order-2", 85 | itemId: "item-2", 86 | CustomerId: 1, 87 | paid: false, 88 | ammountPaid: 10.5f, 89 | ammountPaidDecimal: 10.5, 90 | arivalTimeCivil: {"utcOffset":{"hours":5,"minutes":30, seconds: 0},"timeAbbrev":"Asia/Colombo","year":2024,"month":4,"day":12,"hour":17,"minute":50,"second":50.52}, 91 | arivalTimeUtc: [1684493685, 0.998012000], 92 | arivalTimeDate: {year: 2021, month: 4, day: 12}, 93 | arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, 94 | orderType: ONLINE 95 | }; 96 | 97 | OrderItemExtended orderItemExtended2Updated = { 98 | orderId: "order-2", 99 | itemId: "item-2", 100 | CustomerId: 1, 101 | paid: true, 102 | ammountPaid: 10.5f, 103 | ammountPaidDecimal: 10.5, 104 | arivalTimeCivil: {"utcOffset":{"hours":5,"minutes":30, seconds: 0},"timeAbbrev":"Asia/Colombo","year":2021,"month":4,"day":12,"hour":23,"minute":20,"second":50.52}, 105 | arivalTimeUtc: [1684493685, 0.998012000], 106 | arivalTimeDate: {year: 2021, month: 4, day: 12}, 107 | arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, 108 | orderType: ONLINE 109 | }; 110 | 111 | public type EmployeeInfo record {| 112 | string firstName; 113 | string lastName; 114 | record {| 115 | string deptName; 116 | |} department; 117 | Workspace workspace; 118 | |}; 119 | 120 | 121 | 122 | OrderItemExtended orderItemExtended3 = { 123 | orderId: "order-3", 124 | itemId: "item-3", 125 | CustomerId: 4, 126 | paid: true, 127 | ammountPaid: 20.5f, 128 | ammountPaidDecimal: 20.5, 129 | arivalTimeCivil: {"utcOffset":{"hours":5,"minutes":30, seconds: 0},"timeAbbrev":"Asia/Colombo","year":2021,"month":4,"day":12,"hour":23,"minute":20,"second":50.52}, 130 | arivalTimeUtc: [1684493685, 0.998012000], 131 | arivalTimeDate: {year: 2021, month: 4, day: 12}, 132 | arivalTimeTimeOfDay: {hour: 17, minute: 50, second: 50.52}, 133 | orderType: INSTORE 134 | }; 135 | 136 | public type DepartmentInfo record {| 137 | string deptNo; 138 | string deptName; 139 | record {| 140 | string firstName; 141 | string lastName; 142 | |}[] employees; 143 | |}; 144 | 145 | public type WorkspaceInfo record {| 146 | string workspaceType; 147 | Building location; 148 | Employee[] employees; 149 | |}; 150 | 151 | public type BuildingInfo record {| 152 | string buildingCode; 153 | string city; 154 | string state; 155 | string country; 156 | string postalCode; 157 | string 'type; 158 | Workspace[] workspaces; 159 | |}; 160 | 161 | Building building1 = { 162 | buildingCode: "building-1", 163 | city: "Colombo", 164 | state: "Western Province", 165 | country: "Sri Lanka", 166 | postalCode: "10370", 167 | 'type: "rented" 168 | }; 169 | 170 | Building invalidBuilding = { 171 | buildingCode: "building-invalid-extra-characters-to-force-failure", 172 | city: "Colombo", 173 | state: "Western Province", 174 | country: "Sri Lanka", 175 | postalCode: "10370", 176 | 'type: "owned" 177 | }; 178 | 179 | BuildingInsert building2 = { 180 | buildingCode: "building-2", 181 | city: "Manhattan", 182 | state: "New York", 183 | country: "USA", 184 | postalCode: "10570", 185 | 'type: "owned" 186 | }; 187 | 188 | BuildingInsert building3 = { 189 | buildingCode: "building-3", 190 | city: "London", 191 | state: "London", 192 | country: "United Kingdom", 193 | postalCode: "39202", 194 | 'type: "rented" 195 | }; 196 | 197 | Building updatedBuilding1 = { 198 | buildingCode: "building-1", 199 | city: "Galle", 200 | state: "Southern Province", 201 | country: "Sri Lanka", 202 | postalCode: "10890", 203 | 'type: "owned" 204 | }; 205 | 206 | Department department1 = { 207 | deptNo: "department-1", 208 | deptName: "Finance" 209 | }; 210 | 211 | Department invalidDepartment = { 212 | deptNo: "invalid-department-extra-characters-to-force-failure", 213 | deptName: "Finance" 214 | }; 215 | 216 | Department department2 = { 217 | deptNo: "department-2", 218 | deptName: "Marketing" 219 | }; 220 | 221 | Department department3 = { 222 | deptNo: "department-3", 223 | deptName: "Engineering" 224 | }; 225 | 226 | Department updatedDepartment1 = { 227 | deptNo: "department-1", 228 | deptName: "Finance & Legalities" 229 | }; 230 | 231 | Employee employee1 = { 232 | empNo: "employee-1", 233 | firstName: "Tom", 234 | lastName: "Scott", 235 | birthDate: {year: 1992, month: 11, day: 13}, 236 | gender: MALE, 237 | hireDate: {year: 2022, month: 8, day: 1}, 238 | departmentDeptNo: "department-2", 239 | workspaceWorkspaceId: "workspace-2" 240 | }; 241 | 242 | Employee invalidEmployee = { 243 | empNo: "invalid-employee-no-extra-characters-to-force-failure", 244 | firstName: "Tom", 245 | lastName: "Scott", 246 | birthDate: {year: 1992, month: 11, day: 13}, 247 | gender: MALE, 248 | hireDate: {year: 2022, month: 8, day: 1}, 249 | departmentDeptNo: "department-2", 250 | workspaceWorkspaceId: "workspace-2" 251 | }; 252 | 253 | Employee employee2 = { 254 | empNo: "employee-2", 255 | firstName: "Jane", 256 | lastName: "Doe", 257 | birthDate: {year: 1996, month: 9, day: 15}, 258 | gender: FEMALE, 259 | hireDate: {year: 2022, month: 6, day: 1}, 260 | departmentDeptNo: "department-2", 261 | workspaceWorkspaceId: "workspace-2" 262 | }; 263 | 264 | Employee employee3 = { 265 | empNo: "employee-3", 266 | firstName: "Hugh", 267 | lastName: "Smith", 268 | birthDate: {year: 1986, month: 9, day: 15}, 269 | gender: FEMALE, 270 | hireDate: {year: 2021, month: 6, day: 1}, 271 | departmentDeptNo: "department-3", 272 | workspaceWorkspaceId: "workspace-3" 273 | }; 274 | 275 | Employee updatedEmployee1 = { 276 | empNo: "employee-1", 277 | firstName: "Tom", 278 | lastName: "Jones", 279 | birthDate: {year: 1994, month: 11, day: 13}, 280 | gender: MALE, 281 | hireDate: {year: 2022, month: 8, day: 1}, 282 | departmentDeptNo: "department-3", 283 | workspaceWorkspaceId: "workspace-2" 284 | }; 285 | 286 | public type IntIdRecordDependent record {| 287 | string randomField; 288 | |}; 289 | 290 | public type StringIdRecordDependent record {| 291 | string randomField; 292 | |}; 293 | 294 | public type FloatIdRecordDependent record {| 295 | string randomField; 296 | |}; 297 | 298 | public type DecimalIdRecordDependent record {| 299 | string randomField; 300 | |}; 301 | 302 | public type BooleanIdRecordDependent record {| 303 | string randomField; 304 | |}; 305 | 306 | public type AllTypesIdRecordDependent record {| 307 | string randomField; 308 | |}; 309 | 310 | public type CompositeAssociationRecordDependent record {| 311 | string randomField; 312 | int alltypesidrecordIntType; 313 | decimal alltypesidrecordDecimalType; 314 | record {| 315 | int intType; 316 | string stringType; 317 | boolean booleanType; 318 | string randomField; 319 | |} allTypesIdRecord; 320 | |}; 321 | 322 | Workspace workspace1 = { 323 | workspaceId: "workspace-1", 324 | workspaceType: "small", 325 | locationBuildingCode: "building-2" 326 | }; 327 | 328 | Workspace invalidWorkspace = { 329 | workspaceId: "invalid-workspace-extra-characters-to-force-failure", 330 | workspaceType: "small", 331 | locationBuildingCode: "building-2" 332 | }; 333 | 334 | Workspace workspace2 = { 335 | workspaceId: "workspace-2", 336 | workspaceType: "medium", 337 | locationBuildingCode: "building-2" 338 | }; 339 | 340 | Workspace workspace3 = { 341 | workspaceId: "workspace-3", 342 | workspaceType: "small", 343 | locationBuildingCode: "building-2" 344 | }; 345 | 346 | Workspace updatedWorkspace1 = { 347 | workspaceId: "workspace-1", 348 | workspaceType: "large", 349 | locationBuildingCode: "building-2" 350 | }; 351 | 352 | public type EmployeeName record {| 353 | string firstName; 354 | string lastName; 355 | |}; 356 | 357 | public type EmployeeInfo2 record {| 358 | readonly string empNo; 359 | time:Date birthDate; 360 | string departmentDeptNo; 361 | string workspaceWorkspaceId; 362 | |}; 363 | 364 | public type WorkspaceInfo2 record {| 365 | string workspaceType; 366 | string locationBuildingCode; 367 | |}; 368 | 369 | public type DepartmentInfo2 record {| 370 | string deptName; 371 | |}; 372 | 373 | public type BuildingInfo2 record {| 374 | string city; 375 | string state; 376 | string country; 377 | string postalCode; 378 | string 'type; 379 | |}; 380 | --------------------------------------------------------------------------------