diff --git a/README.md b/README.md index fefad25..5c0d736 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,6 @@ Execute the commands below to build from source. ./gradlew clean build -Pgroups= - **Tip:** The following groups of test cases are available. - -| Groups | Test cases | -|:---------------:|:-----------------------------:| -| basic | basic | -| associations | associations
one-to-many | -| composite-keys | composite-keys | - 5. To disable some specific test groups: ./gradlew clean build -Pdisable-groups= diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index dbb78b0..43f4a33 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -16,7 +16,7 @@ graalvmCompatible = true groupId = "io.ballerina.stdlib" artifactId = "persist.sql-native" version = "1.2.1" -path = "../native/build/libs/persist.sql-native-1.2.1.jar" +path = "../native/build/libs/persist.sql-native-1.2.1-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 01dc245..185af12 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,7 +3,7 @@ id = "persist.sql-compiler-plugin" class = "io.ballerina.stdlib.persist.sql.compiler.PersistSqlCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/persist.sql-compiler-plugin-1.2.1.jar" +path = "../compiler-plugin/build/libs/persist.sql-compiler-plugin-1.2.1-SNAPSHOT.jar" [[dependency]] path = "./lib/persist-native-1.2.0.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index c1945da..d0364f7 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.0" +distribution-version = "2201.8.3-20231119-151400-9cb79da7" [[package]] org = "ballerina" @@ -23,7 +23,7 @@ dependencies = [ [[package]] org = "ballerina" name = "cache" -version = "3.7.0" +version = "3.7.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "constraint"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.10.0" +version = "2.10.4" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -105,6 +105,7 @@ dependencies = [ org = "ballerina" name = "jballerina.java" version = "0.0.0" +scope = "testOnly" modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] @@ -294,7 +295,7 @@ modules = [ [[package]] org = "ballerina" name = "sql" -version = "1.11.0" +version = "1.11.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, @@ -332,6 +333,7 @@ modules = [ org = "ballerina" name = "time" version = "2.4.0" +scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"} ] @@ -385,7 +387,7 @@ modules = [ [[package]] org = "ballerinax" name = "mssql" -version = "1.11.0" +version = "1.11.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "crypto"}, @@ -446,9 +448,36 @@ dependencies = [ {org = "ballerinax", name = "mssql"}, {org = "ballerinax", name = "mssql.driver"}, {org = "ballerinax", name = "mysql"}, - {org = "ballerinax", name = "mysql.driver"} + {org = "ballerinax", name = "mysql.driver"}, + {org = "ballerinax", name = "postgresql"}, + {org = "ballerinax", name = "postgresql.driver"} ] modules = [ {org = "ballerinax", packageName = "persist.sql", moduleName = "persist.sql"} ] +[[package]] +org = "ballerinax" +name = "postgresql" +version = "1.11.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "sql"}, + {org = "ballerina", name = "time"} +] +modules = [ + {org = "ballerinax", packageName = "postgresql", moduleName = "postgresql"} +] + +[[package]] +org = "ballerinax" +name = "postgresql.driver" +version = "1.5.0" +scope = "testOnly" +modules = [ + {org = "ballerinax", packageName = "postgresql.driver", moduleName = "postgresql.driver"} +] + diff --git a/ballerina/Module.md b/ballerina/Module.md index 53028ed..b36a4cd 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -2,12 +2,14 @@ This module provides relation database support for the `bal persist` feature, which provides functionality to store and query data from a relational database conveniently through a data model instead of using SQL query language. -Currently, this package supports only MySQL as the relational database. However, you are planning to add support for other relational databases such as PostgreSQL, Oracle, and Microsoft SQL Server. +Currently, this package supports MySQL, MSSQL, PostgreSQL as the relational database. However, we are also planning to add support for other relational databases such as Oracle. -The MySQL data store is a relational database management system that stores data in tables. The MySQL data store is useful for storing data in a relational format. The MySQL data store is not the default data store for the `bal persist` feature. Therefore, you need to explicitly specify the data store when initializing `bal persist` in your application. as follows, +The MySQL, MSSQL, PostgreSQL data stores are relational database management systems that stores data in tables. None of these data stores is the default data store for the `bal persist` feature. Therefore, you need to explicitly specify the data store when initializing `bal persist` in your application. as follows, ``` $ bal persist init --datastore mysql +$ bal persist init --datastore mssql +$ bal persist init --datastore postgresql ``` ## Supported Ballerina Types @@ -33,13 +35,13 @@ If you want to map a Ballerina type to a different SQL type or want to change th ## Configuration You need to set values for the following basic configuration parameters in the `Config.toml` file in your project to use the MySQL data store. -| Parameter | Description | -|:----------:|:-------------------------------------:| -| host | The hostname of the MySQL server. | -| port | The port of the MySQL server. | -| username | The username of the MySQL server. | -| password | The password of the MySQL server. | -| database | The name of the database to be used. | +| Parameter | Description | +|:----------:|:------------------------------------:| +| host | The hostname of the DB server. | +| port | The port of the DB server. | +| username | The username of the DB server. | +| password | The password of the DB server. | +| database | The name of the database to be used. | The following is a sample `Config.toml` file with the MySQL data store configuration. This is generated by the `bal persist generate` command. @@ -52,20 +54,23 @@ password = "" database = "" ``` -Additionally, you can set values for advanced configuration parameters in the `Config.toml` file in your project to use the MySQL data store. Please refer to the [MySQL Connector documentation](https://lib.ballerina.io/ballerinax/mysql/latest) for more information on these parameters. +Additionally, you can set values for advanced configuration parameters in the `Config.toml` file in your project to use the data store. Please refer to the individual connection documentation for more information on these parameters. +- [MySQL Connector documentation](https://lib.ballerina.io/ballerinax/mysql/latest) +- [MSSQL Connector documentation](https://lib.ballerina.io/ballerinax/mssql/latest) +- [PostgreSQL Connector documentation](https://lib.ballerina.io/ballerinax/postgresql/latest) ## How to Setup -### Set up a MySQL server instance -Select one of the methods below to set up a MySQL server. +### Set up a MySQL/MSSQL/PostgreSQL server instance +Select one of the methods below to set up a DB server. -> Tip: Keep the connection and authentication details for connecting to the MySQL server including the hostname, port, username, and password noted down. +> Tip: Keep the connection and authentication details for connecting to the DB server including the hostname, port, username, and password noted down. -* Install a MySQL server on your machine locally by downloading and installing MySQL for different platforms. +* Install a DB server on your machine locally by downloading and installing the server software for different platforms. * Use a cross-platform web-server solution such as XAMPP or WampServer. -* Use Docker to create a MySQL server deployment. -* Use a cloud-based MySQL solution such as Google’s CloudSQL, Amazon’s RDS for MySQL, or Microsoft’s Azure Database for MySQL. +* Use Docker to create a DB server deployment. +* Use a cloud-based DB solution such as Google’s CloudSQL, Amazon’s RDS, or Microsoft’s Azure Database. ### Run the script to create the database and tables -The `bal persist generate` command generates a script.sql file in the generated directory of your project. This file contains the SQL script to create the tables required for your application. You need to run this script to create the database and tables in the MySQL server using a MySQL client such as MySQL Workbench or the MySQL command line client. +The `bal persist generate` command generates a script.sql file in the generated directory of your project. This file contains the SQL script to create the tables required for your application. You need to run this script to create the database and tables in the DB server using a DB client or a command line client. diff --git a/ballerina/Package.md b/ballerina/Package.md index 96b4c26..589a059 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,13 +1,15 @@ -## Package overview +# Module Overview -This package provides relation database support for the `bal persist` feature, which provides functionality to store and query data from a relational database conveniently through a data model instead of using SQL query language. +This module provides relation database support for the `bal persist` feature, which provides functionality to store and query data from a relational database conveniently through a data model instead of using SQL query language. -Currently, this package supports only MySQL as the relational database. However, we are planning to add support for other relational databases such as PostgreSQL, Oracle, and Microsoft SQL Server. +Currently, this package supports MySQL, MSSQL, PostgreSQL as the relational database. However, we are also planning to add support for other relational databases such as Oracle. -The MySQL data store is a relational database management system that stores data in tables. The MySQL data store is useful for storing data in a relational format. The MySQL data store is not the default data store for the `bal persist` feature. Therefore, you need to explicitly specify the data store when initializing `bal persist` in your application. as follows, +The MySQL, MSSQL, PostgreSQL data stores are relational database management systems that stores data in tables. None of these data stores is the default data store for the `bal persist` feature. Therefore, you need to explicitly specify the data store when initializing `bal persist` in your application. as follows, ``` $ bal persist init --datastore mysql +$ bal persist init --datastore mssql +$ bal persist init --datastore postgresql ``` ## Supported Ballerina Types @@ -26,21 +28,22 @@ The following table lists the Ballerina types supported by the MySQL data store | time:TimeOfDay | TIME | | time:Utc | TIMESTAMP | | time:Civil | DATETIME | +| enum | ENUM | -If you want to map a Ballerina type to a different SQL type or want to change the default length of a SQL type, you can change it in the `script.sql` file generated by the `persist generate` command before executing the script. +If you want to map a Ballerina type to a different SQL type or want to change the default length of a SQL type, you can change it in the `script.sql` file generated by the `bal persist generate` command before executing the script. ## Configuration You need to set values for the following basic configuration parameters in the `Config.toml` file in your project to use the MySQL data store. -| Parameter | Description | -|:----------:|:-------------------------------------:| -| host | The hostname of the MySQL server. | -| port | The port of the MySQL server. | -| username | The username of the MySQL server. | -| password | The password of the MySQL server. | -| database | The name of the database to be used. | +| Parameter | Description | +|:----------:|:------------------------------------:| +| host | The hostname of the DB server. | +| port | The port of the DB server. | +| username | The username of the DB server. | +| password | The password of the DB server. | +| database | The name of the database to be used. | -The following is a sample `Config.toml` file with the MySQL data store configuration. This is generated by the `persist generate` command. +The following is a sample `Config.toml` file with the MySQL data store configuration. This is generated by the `bal persist generate` command. ```toml [.] @@ -51,23 +54,26 @@ password = "" database = "" ``` -Additionally, you can set values for the following advanced configuration parameters in the Config.toml file in your project to use the MySQL data store. Please refer to the MySQL Connector documentation for more information on these parameters. +Additionally, you can set values for advanced configuration parameters in the `Config.toml` file in your project to use the data store. Please refer to the individual connection documentation for more information on these parameters. +- [MySQL Connector documentation](https://lib.ballerina.io/ballerinax/mysql/latest) +- [MSSQL Connector documentation](https://lib.ballerina.io/ballerinax/mssql/latest) +- [PostgreSQL Connector documentation](https://lib.ballerina.io/ballerinax/postgresql/latest) ## How to Setup -### Set up a MySQL server instance -Select one of the methods below to set up a MySQL server. +### Set up a MySQL/MSSQL/PostgreSQL server instance +Select one of the methods below to set up a DB server. -> Tip: Keep the connection and authentication details for connecting to the MySQL server including the hostname, port, username, and password noted down. +> Tip: Keep the connection and authentication details for connecting to the DB server including the hostname, port, username, and password noted down. -* Install a MySQL server on your machine locally by downloading and installing MySQL for different platforms. +* Install a DB server on your machine locally by downloading and installing the server software for different platforms. * Use a cross-platform web-server solution such as XAMPP or WampServer. -* Use Docker to create a MySQL server deployment. -* Use a cloud-based MySQL solution such as Google’s CloudSQL, Amazon’s RDS for MySQL, or Microsoft’s Azure Database for MySQL. +* Use Docker to create a DB server deployment. +* Use a cloud-based DB solution such as Google’s CloudSQL, Amazon’s RDS, or Microsoft’s Azure Database. ### Run the script to create the database and tables -The `bal persist generate` command generates a script.sql file in the generated directory of your project. This file contains the SQL script to create the tables required for your application. You need to run this script to create the database and tables in the MySQL server using a MySQL client such as MySQL Workbench or the MySQL command line client. +The `bal persist generate` command generates a script.sql file in the generated directory of your project. This file contains the SQL script to create the tables required for your application. You need to run this script to create the database and tables in the DB server using a DB client or a command line client. ## Report issues diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 54014d2..5096aa4 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -262,17 +262,81 @@ task stopMSSQLTestDockerContainer() { } } +task createPostgreSQLTestDockerImage(type: Exec) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + def standardOutput = new ByteArrayOutputStream() + commandLine 'sh', '-c', "docker build -f ${project.projectDir}/tests/resources/postgresql/Dockerfile -t ballerina-persist-postgresql" + + " -q ${project.projectDir}/tests/resources/postgresql" + doLast { + checkExecResult(executionResult, 'Error', standardOutput) + sleep(10 * 1000) + } + } +} + +def checkPostgreSQLTestDockerContainerStatus(containerName) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + try { + return exec { + commandLine 'sh', '-c', "docker exec ${containerName} psql -U postgres -h localhost -p 5432" + }.exitValue + } catch (all) { + return 1; + } + } +} + +task startPostgreSQLTestDockerContainer(type: Exec) { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + def standardOutput = new ByteArrayOutputStream() + commandLine 'sh', '-c', + "docker run --rm -d --name ballerina-persist-postgresql -p 5432:5432 -d ballerina-persist-postgresql" + def healthCheck = 1; + def counter = 0; + doLast { + checkExecResult(executionResult, 'Error', standardOutput) + while (healthCheck != 0 && counter < 12) { + sleep(5 * 1000) + healthCheck = checkPostgreSQLTestDockerContainerStatus("ballerina-persist-postgresql") + counter = counter + 1; + } + if (healthCheck != 0) { + throw new GradleException("Docker container 'ballerina-persist-postgresql' health test exceeded timeout!") + } + } + } +} + +task stopPostgreSQLTestDockerContainer() { + doLast { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + try { + def stdOut = new ByteArrayOutputStream() + exec { + commandLine 'sh', '-c', "docker stop ballerina-persist-postgresql" + standardOutput = stdOut + } + } catch (all) { + println("Process can safely ignore stopPostgreSQLTestDockerContainer task") + } + } + } +} + updateTomlFiles.dependsOn copyStdlibs startMySQLTestDockerContainer.dependsOn createMySQLTestDockerImage startMSSQLTestDockerContainer.dependsOn createMSSQLTestDockerImage +startPostgreSQLTestDockerContainer.dependsOn createPostgreSQLTestDockerImage build.dependsOn "generatePomFileForMavenPublication" build.dependsOn ":${packageName}-compiler-plugin:build" build.dependsOn ":${packageName}-native:build" build.finalizedBy stopMySQLTestDockerContainer build.finalizedBy stopMSSQLTestDockerContainer +build.finalizedBy stopPostgreSQLTestDockerContainer test.dependsOn ":${packageName}-compiler-plugin:build" test.dependsOn ":${packageName}-native:build" test.dependsOn startMySQLTestDockerContainer test.dependsOn startMSSQLTestDockerContainer +test.dependsOn startPostgreSQLTestDockerContainer diff --git a/ballerina/constants.bal b/ballerina/constants.bal index eba4c63..3dcf544 100644 --- a/ballerina/constants.bal +++ b/ballerina/constants.bal @@ -20,7 +20,8 @@ public final DataSourceSpecifics & readonly MYSQL_SPECIFICS = { constraintViolationErrorMessage: "a foreign key constraint fails", duplicateEntryErrorMessage: "Duplicate entry", duplicateKeyStartIndicator: ".Duplicate entry '", - duplicateKeyEndIndicator: "' for key" + duplicateKeyEndIndicator: "' for key", + columnIdentifier: "" }; public final DataSourceSpecifics & readonly MSSQL_SPECIFICS = { @@ -29,5 +30,16 @@ public final DataSourceSpecifics & readonly MSSQL_SPECIFICS = { constraintViolationErrorMessage: "conflicted with the FOREIGN KEY constraint", duplicateEntryErrorMessage: "Cannot insert duplicate key", duplicateKeyStartIndicator: "The duplicate key value is (", - duplicateKeyEndIndicator: ").." + duplicateKeyEndIndicator: ")..", + columnIdentifier: "" +}; + +public final DataSourceSpecifics & readonly POSTGRESQL_SPECIFICS = { + quoteOpen: "", + quoteClose: "", + constraintViolationErrorMessage: "violates foreign key constraint", + duplicateEntryErrorMessage: "duplicate key value violates unique constraint", + duplicateKeyStartIndicator: "Detail: Key ", + duplicateKeyEndIndicator: " already exists.", + columnIdentifier: "\"" }; diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index dd2a34a..2670838 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -102,4 +102,5 @@ public type DataSourceSpecifics record {| string duplicateEntryErrorMessage; string duplicateKeyStartIndicator; string duplicateKeyEndIndicator; + string columnIdentifier; |}; diff --git a/ballerina/sql_client.bal b/ballerina/sql_client.bal index abbcb6c..18aa9cb 100644 --- a/ballerina/sql_client.bal +++ b/ballerina/sql_client.bal @@ -295,7 +295,7 @@ public isolated client class SQLClient { columnNames.push(self.escape(self.entityName) + "." + self.escape(fieldMetadata.columnName) + " AS " + self.escape(key)); } else { // column is in another entity's table - columnNames.push(self.escape(fieldName) + "." + self.escape(fieldMetadata.relation.refField) + " AS " + self.escape(fieldName + "." + fieldMetadata.relation.refField)); + columnNames.push(self.escape(fieldName) + "." + self.escape(fieldMetadata.relation.refField) + " AS " + self.dataSourceSpecifics.columnIdentifier + self.escape(fieldName + "." + fieldMetadata.relation.refField) + self.dataSourceSpecifics.columnIdentifier); } } diff --git a/ballerina/tests/Config.toml b/ballerina/tests/Config.toml index 893c331..15feb58 100644 --- a/ballerina/tests/Config.toml +++ b/ballerina/tests/Config.toml @@ -15,3 +15,10 @@ password="Test123#" host="localhost" port=1433 database="test" + +[postgresql] +user="postgres" +password="postgres" +host="localhost" +port=5432 +database="test" diff --git a/ballerina/tests/init-tests.bal b/ballerina/tests/init-tests.bal index eddd456..f235bf7 100644 --- a/ballerina/tests/init-tests.bal +++ b/ballerina/tests/init-tests.bal @@ -19,6 +19,8 @@ import ballerinax/mysql; import ballerinax/mysql.driver as _; import ballerinax/mssql; import ballerinax/mssql.driver as _; +import ballerinax/postgresql; +import ballerinax/postgresql.driver as _; import ballerina/time; configurable record {| @@ -39,6 +41,15 @@ configurable record {| mssql:Options connectionOptions = {}; |} mssql = ?; +configurable record {| + int port; + string host; + string user; + string database; + string password; + postgresql:Options connectionOptions = {}; +|} postgresql = ?; + @test:BeforeSuite function initTests() returns error? { // MySQL @@ -210,6 +221,23 @@ function initTests() returns error? { PRIMARY KEY(id) ); `); + + // PostgreSQL + postgresql:Client postgresqlDbClient = check new (host = postgresql.host, username = postgresql.user, password = postgresql.password, database = postgresql.database, port = postgresql.port); + _ = check postgresqlDbClient->execute(`TRUNCATE Employee CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE Workspace CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE Building CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE Department CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE OrderItem CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE AllTypes CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE FloatIdRecord CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE StringIdRecord CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE DecimalIdRecord CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE BooleanIdRecord CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE IntIdRecord CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE AllTypesIdRecord CASCADE`); + _ = check postgresqlDbClient->execute(`TRUNCATE CompositeAssociationRecord CASCADE`); + check postgresqlDbClient.close(); } AllTypes allTypes1 = { @@ -218,7 +246,7 @@ AllTypes allTypes1 = { intType: 5, floatType: 6.0, decimalType: 23.44, - stringType: "test", + stringType: "test-2", byteArrayType: base16 `55 EE 66 FF 77 AB`, dateType: {year: 1993, month: 11, day: 3}, timeOfDayType: {hour: 12, minute: 32, second: 34}, diff --git a/ballerina/tests/postgresql-all-types-tests.bal b/ballerina/tests/postgresql-all-types-tests.bal new file mode 100644 index 0000000..fe4fd24 --- /dev/null +++ b/ballerina/tests/postgresql-all-types-tests.bal @@ -0,0 +1,228 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["all-types", "postgresql"] +} +function postgresqlAllTypesCreateTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + int[] ids = check testEntitiesClient->/alltypes.post([allTypes1, allTypes2]); + test:assertEquals(ids, [allTypes1.id, allTypes2.id]); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes1.id].get(); + test:assertEquals(allTypesRetrieved, allTypes1Expected); + + allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes2.id].get(); + test:assertEquals(allTypesRetrieved, allTypes2Expected); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql"] +} +function postgresqlAllTypesCreateOptionalTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + int[] ids = check testEntitiesClient->/alltypes.post([allTypes3]); + test:assertEquals(ids, [allTypes3.id]); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes3.id].get(); + test:assertEquals(allTypesRetrieved, allTypes3Expected); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql"], + dependsOn: [postgresqlAllTypesCreateTest, postgresqlAllTypesCreateOptionalTest] +} +function postgresqlAllTypesReadTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + stream allTypesStream = testEntitiesClient->/alltypes.get(); + AllTypes[] allTypes = check from AllTypes allTypesRecord in allTypesStream + select allTypesRecord; + + test:assertEquals(allTypes, [allTypes3Expected, allTypes1Expected, allTypes2Expected]); + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql", "dependent"], + dependsOn: [postgresqlAllTypesCreateTest, postgresqlAllTypesCreateOptionalTest] +} +function postgresqlAllTypesReadDependentTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + stream allTypesStream = testEntitiesClient->/alltypes.get(); + AllTypesDependent[] allTypes = check from AllTypesDependent allTypesRecord in allTypesStream + select allTypesRecord; + + test:assertEquals(allTypes, [ + { + booleanType: allTypes3Expected.booleanType, + intType: allTypes3Expected.intType, + floatType: allTypes3Expected.floatType, + decimalType: allTypes3Expected.decimalType, + stringType: allTypes3Expected.stringType, + byteArrayType: allTypes3Expected.byteArrayType, + dateType: allTypes3Expected.dateType, + timeOfDayType: allTypes3Expected.timeOfDayType, + civilType: allTypes3Expected.civilType, + booleanTypeOptional: allTypes3Expected.booleanTypeOptional, + intTypeOptional: allTypes3Expected.intTypeOptional, + floatTypeOptional: allTypes3Expected.floatTypeOptional, + decimalTypeOptional: allTypes3Expected.decimalTypeOptional, + stringTypeOptional: allTypes3Expected.stringTypeOptional, + dateTypeOptional: allTypes3Expected.dateTypeOptional, + timeOfDayTypeOptional: allTypes3Expected.timeOfDayTypeOptional, + civilTypeOptional: allTypes3Expected.civilTypeOptional + }, + { + booleanType: allTypes1Expected.booleanType, + intType: allTypes1Expected.intType, + floatType: allTypes1Expected.floatType, + decimalType: allTypes1Expected.decimalType, + stringType: allTypes1Expected.stringType, + byteArrayType: allTypes1Expected.byteArrayType, + dateType: allTypes1Expected.dateType, + timeOfDayType: allTypes1Expected.timeOfDayType, + civilType: allTypes1Expected.civilType, + booleanTypeOptional: allTypes1Expected.booleanTypeOptional, + intTypeOptional: allTypes1Expected.intTypeOptional, + floatTypeOptional: allTypes1Expected.floatTypeOptional, + decimalTypeOptional: allTypes1Expected.decimalTypeOptional, + stringTypeOptional: allTypes1Expected.stringTypeOptional, + dateTypeOptional: allTypes1Expected.dateTypeOptional, + timeOfDayTypeOptional: allTypes1Expected.timeOfDayTypeOptional, + civilTypeOptional: allTypes1Expected.civilTypeOptional + }, + { + booleanType: allTypes2Expected.booleanType, + intType: allTypes2Expected.intType, + floatType: allTypes2Expected.floatType, + decimalType: allTypes2Expected.decimalType, + stringType: allTypes2Expected.stringType, + byteArrayType: allTypes2Expected.byteArrayType, + dateType: allTypes2Expected.dateType, + timeOfDayType: allTypes2Expected.timeOfDayType, + civilType: allTypes2Expected.civilType, + booleanTypeOptional: allTypes2Expected.booleanTypeOptional, + intTypeOptional: allTypes2Expected.intTypeOptional, + floatTypeOptional: allTypes2Expected.floatTypeOptional, + decimalTypeOptional: allTypes2Expected.decimalTypeOptional, + stringTypeOptional: allTypes2Expected.stringTypeOptional, + dateTypeOptional: allTypes2Expected.dateTypeOptional, + timeOfDayTypeOptional: allTypes2Expected.timeOfDayTypeOptional, + civilTypeOptional: allTypes2Expected.civilTypeOptional + } + ]); + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql"], + dependsOn: [postgresqlAllTypesCreateTest, postgresqlAllTypesCreateOptionalTest] +} +function postgresqlAllTypesReadOneTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes1.id].get(); + test:assertEquals(allTypesRetrieved, allTypes1Expected); + + allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes2.id].get(); + test:assertEquals(allTypesRetrieved, allTypes2Expected); + + allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes3.id].get(); + test:assertEquals(allTypesRetrieved, allTypes3Expected); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql"] +} +function postgresqlAllTypesReadOneTestNegative() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + AllTypes|persist:Error allTypesRetrieved = testEntitiesClient->/alltypes/[4].get(); + if allTypesRetrieved is persist:NotFoundError { + test:assertEquals(allTypesRetrieved.message(), "A record with the key '4' does not exist for the entity 'AllTypes'."); + } + else { + test:assertFail("persist:NotFoundError expected."); + } + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql"], + dependsOn: [postgresqlAllTypesReadOneTest, postgresqlAllTypesReadTest, postgresqlAllTypesReadDependentTest] +} +function postgresqlAllTypesUpdateTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + AllTypes allTypes = check testEntitiesClient->/alltypes/[allTypes1.id].put({ + booleanType: allTypes3.booleanType, + intType: allTypes1Updated.intType, + floatType: allTypes1Updated.floatType, + decimalType: allTypes1Updated.decimalType, + stringType: allTypes1Updated.stringType, + byteArrayType: allTypes1Updated.byteArrayType, + dateType: allTypes1Updated.dateType, + timeOfDayType: allTypes1Updated.timeOfDayType, + civilType: allTypes1Updated.civilType, + booleanTypeOptional: allTypes1Updated.booleanTypeOptional, + intTypeOptional: allTypes1Updated.intTypeOptional, + floatTypeOptional: allTypes1Updated.floatTypeOptional, + decimalTypeOptional: allTypes1Updated.decimalTypeOptional, + stringTypeOptional: allTypes1Updated.stringTypeOptional, + dateTypeOptional: allTypes1Updated.dateTypeOptional, + timeOfDayTypeOptional: allTypes1Updated.timeOfDayTypeOptional, + civilTypeOptional: allTypes1Updated.civilTypeOptional, + enumType: allTypes1Updated.enumType, + enumTypeOptional: allTypes1Updated.enumTypeOptional + }); + test:assertEquals(allTypes, allTypes1UpdatedExpected); + + AllTypes allTypesRetrieved = check testEntitiesClient->/alltypes/[allTypes1.id].get(); + test:assertEquals(allTypesRetrieved, allTypes1UpdatedExpected); + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["all-types", "postgresql"], + dependsOn: [postgresqlAllTypesUpdateTest] +} +function postgresqlAllTypesDeleteTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + AllTypes allTypes = check testEntitiesClient->/alltypes/[allTypes2.id].delete(); + test:assertEquals(allTypes, allTypes2Expected); + + stream allTypesStream = testEntitiesClient->/alltypes.get(); + AllTypes[] allTypesCollection = check from AllTypes allTypesRecord in allTypesStream + select allTypesRecord; + + test:assertEquals(allTypesCollection, [allTypes3Expected, allTypes1UpdatedExpected]); + check testEntitiesClient.close(); +} diff --git a/ballerina/tests/postgresql-associations-tests.bal b/ballerina/tests/postgresql-associations-tests.bal new file mode 100644 index 0000000..3ee5f4b --- /dev/null +++ b/ballerina/tests/postgresql-associations-tests.bal @@ -0,0 +1,288 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["associations", "postgresql"], + dependsOn: [postgresqlEmployeeDeleteTestNegative] +} +function postgresqlEmployeeRelationsTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee employee21 = { + empNo: "employee-21", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + }; + + Workspace workspace22 = { + workspaceId: "workspace-22", + workspaceType: "medium", + locationBuildingCode: "building-22" + }; + + BuildingInsert building22 = { + buildingCode: "building-22", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" + }; + + Department department22 = { + deptNo: "department-22", + deptName: "Marketing" + }; + + _ = check rainierClient->/buildings.post([building22]); + _ = check rainierClient->/departments.post([department22]); + _ = check rainierClient->/workspaces.post([workspace22]); + _ = check rainierClient->/employees.post([employee21]); + + stream employeeStream = rainierClient->/employees.get(); + EmployeeInfo[] employees = check from EmployeeInfo employee in employeeStream + select employee; + + EmployeeInfo retrieved = check rainierClient->/employees/["employee-21"].get(); + + EmployeeInfo expected = { + firstName: "Tom", + lastName: "Scott", + department: { + deptName: "Marketing" + }, + workspace: { + workspaceId: "workspace-22", + workspaceType: "medium", + locationBuildingCode: "building-22" + } + }; + + test:assertTrue(employees.indexOf(expected) is int, "Expected EmployeeInfo not found."); + test:assertEquals(retrieved, expected); + check rainierClient.close(); +} + +@test:Config { + groups: ["associations", "postgresql"], + dependsOn: [postgresqlEmployeeDeleteTestNegative] +} +function postgresqlDepartmentRelationsTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee employee11 = { + empNo: "employee-11", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-12", + workspaceWorkspaceId: "workspace-12" + }; + + Employee employee12 = { + empNo: "employee-12", + firstName: "Jane", + lastName: "Doe", + birthDate: {year: 1996, month: 9, day: 15}, + gender: FEMALE, + hireDate: {year: 2022, month: 6, day: 1}, + departmentDeptNo: "department-12", + workspaceWorkspaceId: "workspace-12" + }; + + Workspace workspace12 = { + workspaceId: "workspace-12", + workspaceType: "medium", + locationBuildingCode: "building-12" + }; + + BuildingInsert building12 = { + buildingCode: "building-12", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" + }; + + Department department12 = { + deptNo: "department-12", + deptName: "Marketing" + }; + + _ = check rainierClient->/buildings.post([building12]); + _ = check rainierClient->/departments.post([department12]); + _ = check rainierClient->/workspaces.post([workspace12]); + _ = check rainierClient->/employees.post([employee11, employee12]); + + stream departmentStream = rainierClient->/departments.get(); + DepartmentInfo[] departments = check from DepartmentInfo department in departmentStream + select department; + + DepartmentInfo retrieved = check rainierClient->/departments/["department-12"].get(); + + DepartmentInfo expected = { + deptNo: "department-12", + deptName: "Marketing", + employees: [ + { + firstName: "Tom", + lastName: "Scott" + }, + { + firstName: "Jane", + lastName: "Doe" + } + ] + }; + + test:assertTrue(departments.indexOf(expected) is int, "Expected DepartmentInfo not found."); + test:assertEquals(retrieved, expected); + check rainierClient.close(); +} + +@test:Config { + groups: ["associations", "postgresql"], + dependsOn: [postgresqlEmployeeRelationsTest] +} +function postgresqlWorkspaceRelationsTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee employee22 = { + empNo: "employee-22", + firstName: "James", + lastName: "David", + birthDate: {year: 1996, month: 11, day: 13}, + gender: FEMALE, + hireDate: {year: 2021, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + }; + _ = check rainierClient->/employees.post([employee22]); + + stream workspaceStream = rainierClient->/workspaces.get(); + WorkspaceInfo[] workspaces = check from WorkspaceInfo workspace in workspaceStream + select workspace; + + WorkspaceInfo retrieved = check rainierClient->/workspaces/["workspace-22"].get(); + + WorkspaceInfo expected = { + workspaceType: "medium", + location: { + buildingCode: "building-22", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned" + }, + employees: [ + { + empNo: "employee-21", + firstName: "Tom", + lastName: "Scott", + birthDate: {year: 1992, month: 11, day: 13}, + gender: MALE, + hireDate: {year: 2022, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + }, + { + empNo: "employee-22", + firstName: "James", + lastName: "David", + birthDate: {year: 1996, month: 11, day: 13}, + gender: FEMALE, + hireDate: {year: 2021, month: 8, day: 1}, + departmentDeptNo: "department-22", + workspaceWorkspaceId: "workspace-22" + } + ] + }; + + boolean found = false; + _ = from WorkspaceInfo workspace in workspaces + do { + if workspace == expected { + found = true; + } + }; + + if !found { + test:assertFail("Expected WorkspaceInfo not found."); + } + + test:assertEquals(retrieved, expected); + + check rainierClient.close(); +} + +@test:Config { + groups: ["associations", "postgresql"], + dependsOn: [postgresqlEmployeeRelationsTest] +} +function postgresqlBuildingRelationsTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream buildingStream = rainierClient->/buildings.get(); + BuildingInfo[] buildings = check from BuildingInfo building in buildingStream + select building; + + BuildingInfo retrieved = check rainierClient->/buildings/["building-22"].get(); + + BuildingInfo expected = { + buildingCode: "building-22", + city: "Manhattan", + state: "New York", + country: "USA", + postalCode: "10570", + 'type: "owned", + workspaces: [ + { + workspaceId: "workspace-22", + workspaceType: "medium", + locationBuildingCode: "building-22" + } + ] + }; + + boolean found = false; + _ = from BuildingInfo building in buildings + do { + if (building.buildingCode == "building-22") { + found = true; + test:assertEquals(building, expected); + } + }; + + if !found { + test:assertFail("Expected BuildingInfo not found."); + } + + test:assertEquals(retrieved, expected); + + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-building-tests.bal b/ballerina/tests/postgresql-building-tests.bal new file mode 100644 index 0000000..4d3dbe3 --- /dev/null +++ b/ballerina/tests/postgresql-building-tests.bal @@ -0,0 +1,226 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["building", "postgresql"] +} +function postgresqlBuildingCreateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] buildingCodes = check rainierClient->/buildings.post([building1]); + test:assertEquals(buildingCodes, [building1.buildingCode]); + + Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); + test:assertEquals(buildingRetrieved, building1); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"] +} +function postgresqlBuildingCreateTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] buildingCodes = check rainierClient->/buildings.post([building2, building3]); + + test:assertEquals(buildingCodes, [building2.buildingCode, building3.buildingCode]); + + Building buildingRetrieved = check rainierClient->/buildings/[building2.buildingCode].get(); + test:assertEquals(buildingRetrieved, building2); + + buildingRetrieved = check rainierClient->/buildings/[building3.buildingCode].get(); + test:assertEquals(buildingRetrieved, building3); + + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"] +} +function postgresqlBuildingCreateTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[]|error building = rainierClient->/buildings.post([invalidBuilding]); + if building is persist:Error { + test:assertTrue(building.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingCreateTest] +} +function postgresqlBuildingReadOneTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); + test:assertEquals(buildingRetrieved, building1); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingCreateTest] +} +function postgresqlBuildingReadOneTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building|error buildingRetrieved = rainierClient->/buildings/["invalid-building-code"].get(); + if buildingRetrieved is persist:NotFoundError { + test:assertEquals(buildingRetrieved.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingCreateTest, postgresqlBuildingCreateTest2] +} +function postgresqlBuildingReadManyTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream buildingStream = rainierClient->/buildings.get(); + Building[] buildings = check from Building building in buildingStream + select building; + + test:assertEquals(buildings, [building1, building2, building3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql", "dependent"], + dependsOn: [postgresqlBuildingCreateTest, postgresqlBuildingCreateTest2] +} +function postgresqlBuildingReadManyDependentTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream buildingStream = rainierClient->/buildings.get(); + BuildingInfo2[] buildings = check from BuildingInfo2 building in buildingStream + select building; + + test:assertEquals(buildings, [ + {city: building1.city, state: building1.state, country: building1.country, postalCode: building1.postalCode, 'type: building1.'type}, + {city: building2.city, state: building2.state, country: building2.country, postalCode: building2.postalCode, 'type: building2.'type}, + {city: building3.city, state: building3.state, country: building3.country, postalCode: building3.postalCode, 'type: building3.'type} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingReadOneTest, postgresqlBuildingReadManyTest, postgresqlBuildingReadManyDependentTest] +} +function postgresqlBuildingUpdateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building building = check rainierClient->/buildings/[building1.buildingCode].put({ + city: "Galle", + state: "Southern Province", + postalCode: "10890", + 'type: "owned" + }); + + test:assertEquals(building, updatedBuilding1); + + Building buildingRetrieved = check rainierClient->/buildings/[building1.buildingCode].get(); + test:assertEquals(buildingRetrieved, updatedBuilding1); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingReadOneTest, postgresqlBuildingReadManyTest, postgresqlBuildingReadManyDependentTest] +} +function postgresqlBuildingUpdateTestNegative1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building|error building = rainierClient->/buildings/["invalid-building-code"].put({ + city: "Galle", + state: "Southern Province", + postalCode: "10890" + }); + + if building is persist:NotFoundError { + test:assertEquals(building.message(), "A record with the key 'invalid-building-code' does not exist for the entity 'Building'."); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingReadOneTest, postgresqlBuildingReadManyTest, postgresqlBuildingReadManyDependentTest] +} +function postgresqlBuildingUpdateTestNegative2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building|error building = rainierClient->/buildings/[building1.buildingCode].put({ + city: "unncessarily-long-city-name-to-force-error-on-update", + state: "Southern Province", + postalCode: "10890" + }); + + if building is persist:Error { + test:assertTrue(building.message().includes("value too long for type character varying")); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingUpdateTest, postgresqlBuildingUpdateTestNegative2] +} +function postgresqlBuildingDeleteTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building building = check rainierClient->/buildings/[building1.buildingCode].delete(); + test:assertEquals(building, updatedBuilding1); + + stream buildingStream = rainierClient->/buildings.get(); + Building[] buildings = check from Building building2 in buildingStream + select building2; + + test:assertEquals(buildings, [building2, building3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["building", "postgresql"], + dependsOn: [postgresqlBuildingDeleteTest] +} +function postgresqlBuildingDeleteTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Building|error building = rainierClient->/buildings/[building1.buildingCode].delete(); + + if building is error { + test:assertEquals(building.message(), string `A record with the key '${building1.buildingCode}' does not exist for the entity 'Building'.`); + } else { + test:assertFail("persist:NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-composite-key-tests.bal b/ballerina/tests/postgresql-composite-key-tests.bal new file mode 100644 index 0000000..5af7ff2 --- /dev/null +++ b/ballerina/tests/postgresql-composite-key-tests.bal @@ -0,0 +1,196 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["composite-key", "postgresql"] +} +function postgresqlCompositeKeyCreateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + [string, string][] ids = check rainierClient->/orderitems.post([orderItem1, orderItem2]); + test:assertEquals(ids, [[orderItem1.orderId, orderItem1.itemId], [orderItem2.orderId, orderItem2.itemId]]); + + OrderItem orderItemRetrieved = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); + test:assertEquals(orderItemRetrieved, orderItem1); + + orderItemRetrieved = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); + test:assertEquals(orderItemRetrieved, orderItem2); + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest] +} +function postgresqlCompositeKeyCreateTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + [string, string][]|error ids = rainierClient->/orderitems.post([orderItem1]); + if ids is persist:AlreadyExistsError { + test:assertEquals(ids.message(), "A record with the key '(orderid, itemid)=(order-1, item-1)' already exists for the entity 'OrderItem'."); + } else { + test:assertFail("persist:AlreadyExistsError expected"); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest] +} +function postgresqlCompositeKeyReadManyTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream orderItemStream = rainierClient->/orderitems.get(); + OrderItem[] orderitem = check from OrderItem orderItem in orderItemStream + select orderItem; + + test:assertEquals(orderitem, [orderItem1, orderItem2]); + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest] +} +function postgresqlCompositeKeyReadOneTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + OrderItem orderItem = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); + test:assertEquals(orderItem, orderItem1); + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key2"], + dependsOn: [postgresqlCompositeKeyCreateTest] +} +function postgresqlCompositeKeyReadOneTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + OrderItem orderItem = check rainierClient->/orderitems/[orderItem1.orderId]/[orderItem1.itemId].get(); + test:assertEquals(orderItem, orderItem1); + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest] +} +function postgresqlCompositeKeyReadOneTestNegative1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem1.itemId].get(); + + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-1\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest] +} +function postgresqlCompositeKeyReadOneTestNegative2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/["invalid-item-id"].get(); + + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"invalid-item-id\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest, postgresqlCompositeKeyReadOneTest, postgresqlCompositeKeyReadManyTest, postgresqlCompositeKeyReadOneTest2] +} +function postgresqlCompositeKeyUpdateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + OrderItem orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].put({ + quantity: orderItem2Updated.quantity, + notes: orderItem2Updated.notes + }); + test:assertEquals(orderItem, orderItem2Updated); + + orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); + test:assertEquals(orderItem, orderItem2Updated); + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyCreateTest, postgresqlCompositeKeyReadOneTest, postgresqlCompositeKeyReadManyTest, postgresqlCompositeKeyReadOneTest2] +} +function postgresqlCompositeKeyUpdateTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + OrderItem|error orderItem = rainierClient->/orderitems/[orderItem1.orderId]/[orderItem2.itemId].put({ + quantity: 239, + notes: "updated notes" + }); + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"order-1\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyUpdateTest] +} +function postgresqlCompositeKeyDeleteTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + OrderItem orderItem = check rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].delete(); + test:assertEquals(orderItem, orderItem2Updated); + + OrderItem|error orderItemRetrieved = rainierClient->/orderitems/[orderItem2.orderId]/[orderItem2.itemId].get(); + test:assertTrue(orderItemRetrieved is persist:NotFoundError); + + check rainierClient.close(); +} + +@test:Config { + groups: ["composite-key", "postgresql"], + dependsOn: [postgresqlCompositeKeyUpdateTest] +} +function postgresqlCompositeKeyDeleteTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + OrderItem|error orderItem = rainierClient->/orderitems/["invalid-order-id"]/[orderItem2.itemId].delete(); + if orderItem is persist:NotFoundError { + test:assertEquals(orderItem.message(), "A record with the key '{\"orderId\":\"invalid-order-id\",\"itemId\":\"item-2\"}' does not exist for the entity 'OrderItem'."); + } else { + test:assertFail("Error expected."); + } + + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-department-tests.bal b/ballerina/tests/postgresql-department-tests.bal new file mode 100644 index 0000000..69a643f --- /dev/null +++ b/ballerina/tests/postgresql-department-tests.bal @@ -0,0 +1,217 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["department", "postgresql"] +} +function postgresqlDepartmentCreateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] deptNos = check rainierClient->/departments.post([department1]); + test:assertEquals(deptNos, [department1.deptNo]); + + Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); + test:assertEquals(departmentRetrieved, department1); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"] +} +function postgresqlDepartmentCreateTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] deptNos = check rainierClient->/departments.post([department2, department3]); + + test:assertEquals(deptNos, [department2.deptNo, department3.deptNo]); + + Department departmentRetrieved = check rainierClient->/departments/[department2.deptNo].get(); + test:assertEquals(departmentRetrieved, department2); + + departmentRetrieved = check rainierClient->/departments/[department3.deptNo].get(); + test:assertEquals(departmentRetrieved, department3); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"] +} +function postgresqlDepartmentCreateTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[]|error department = rainierClient->/departments.post([invalidDepartment]); + if department is persist:Error { + test:assertTrue(department.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentCreateTest] +} +function postgresqlDepartmentReadOneTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); + test:assertEquals(departmentRetrieved, department1); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentCreateTest] +} +function postgresqlDepartmentReadOneTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department|error departmentRetrieved = rainierClient->/departments/["invalid-department-id"].get(); + if departmentRetrieved is persist:NotFoundError { + test:assertEquals(departmentRetrieved.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentCreateTest, postgresqlDepartmentCreateTest2] +} +function postgresqlDepartmentReadManyTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + stream departmentStream = rainierClient->/departments.get(); + Department[] departments = check from Department department in departmentStream + select department; + + test:assertEquals(departments, [department1, department2, department3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql", "dependent"], + dependsOn: [postgresqlDepartmentCreateTest, postgresqlDepartmentCreateTest2] +} +function postgresqlDepartmentReadManyTestDependent() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream departmentStream = rainierClient->/departments.get(); + DepartmentInfo2[] departments = check from DepartmentInfo2 department in departmentStream + select department; + + test:assertEquals(departments, [ + {deptName: department1.deptName}, + {deptName: department2.deptName}, + {deptName: department3.deptName} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentReadOneTest, postgresqlDepartmentReadManyTest, postgresqlDepartmentReadManyTestDependent] +} +function postgresqlDepartmentUpdateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department department = check rainierClient->/departments/[department1.deptNo].put({ + deptName: "Finance & Legalities" + }); + + test:assertEquals(department, updatedDepartment1); + + Department departmentRetrieved = check rainierClient->/departments/[department1.deptNo].get(); + test:assertEquals(departmentRetrieved, updatedDepartment1); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentReadOneTest, postgresqlDepartmentReadManyTest, postgresqlDepartmentReadManyTestDependent] +} +function postgresqlDepartmentUpdateTestNegative1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department|error department = rainierClient->/departments/["invalid-department-id"].put({ + deptName: "Human Resources" + }); + + if department is persist:NotFoundError { + test:assertEquals(department.message(), "A record with the key 'invalid-department-id' does not exist for the entity 'Department'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentReadOneTest, postgresqlDepartmentReadManyTest, postgresqlDepartmentReadManyTestDependent] +} +function postgresqlDepartmentUpdateTestNegative2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department|error department = rainierClient->/departments/[department1.deptNo].put({ + deptName: "unncessarily-long-department-name-to-force-error-on-update" + }); + + if department is persist:Error { + test:assertTrue(department.message().includes("value too long for type character varying")); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentUpdateTest, postgresqlDepartmentUpdateTestNegative2] +} +function postgresqlDepartmentDeleteTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department department = check rainierClient->/departments/[department1.deptNo].delete(); + test:assertEquals(department, updatedDepartment1); + + stream departmentStream = rainierClient->/departments.get(); + Department[] departments = check from Department department2 in departmentStream + select department2; + + test:assertEquals(departments, [department2, department3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["department", "postgresql"], + dependsOn: [postgresqlDepartmentDeleteTest] +} +function postgresqlDepartmentDeleteTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Department|error department = rainierClient->/departments/[department1.deptNo].delete(); + + if department is persist:NotFoundError { + test:assertEquals(department.message(), string `A record with the key '${department1.deptNo}' does not exist for the entity 'Department'.`); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-employee-tests.bal b/ballerina/tests/postgresql-employee-tests.bal new file mode 100644 index 0000000..f471d68 --- /dev/null +++ b/ballerina/tests/postgresql-employee-tests.bal @@ -0,0 +1,260 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlWorkspaceDeleteTestNegative, postgresqlDepartmentDeleteTestNegative] +} +function postgresqlEmployeeCreateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] empNos = check rainierClient->/employees.post([employee1]); + test:assertEquals(empNos, [employee1.empNo]); + + Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); + test:assertEquals(employeeRetrieved, employee1); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlWorkspaceDeleteTestNegative, postgresqlDepartmentDeleteTestNegative] +} +function postgresqlEmployeeCreateTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] empNos = check rainierClient->/employees.post([employee2, employee3]); + + test:assertEquals(empNos, [employee2.empNo, employee3.empNo]); + + Employee employeeRetrieved = check rainierClient->/employees/[employee2.empNo].get(); + test:assertEquals(employeeRetrieved, employee2); + + employeeRetrieved = check rainierClient->/employees/[employee3.empNo].get(); + test:assertEquals(employeeRetrieved, employee3); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"] +} +function postgresqlEmployeeCreateTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[]|error employee = rainierClient->/employees.post([invalidEmployee]); + if employee is persist:Error { + test:assertTrue(employee.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeCreateTest] +} +function postgresqlEmployeeReadOneTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); + test:assertEquals(employeeRetrieved, employee1); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeCreateTest] +} +function postgresqlEmployeeReadOneTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee|error employeeRetrieved = rainierClient->/employees/["invalid-employee-id"].get(); + if employeeRetrieved is persist:NotFoundError { + test:assertEquals(employeeRetrieved.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeCreateTest, postgresqlEmployeeCreateTest2] +} +function postgresqlEmployeeReadManyTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream employeeStream = rainierClient->/employees.get(); + Employee[] employees = check from Employee employee in employeeStream + select employee; + + test:assertEquals(employees, [employee1, employee2, employee3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["dependent", "employee"], + dependsOn: [postgresqlEmployeeCreateTest, postgresqlEmployeeCreateTest2] +} +function postgresqlEmployeeReadManyDependentTest1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream employeeStream = rainierClient->/employees.get(); + EmployeeName[] employees = check from EmployeeName employee in employeeStream + select employee; + + test:assertEquals(employees, [ + {firstName: employee1.firstName, lastName: employee1.lastName}, + {firstName: employee2.firstName, lastName: employee2.lastName}, + {firstName: employee3.firstName, lastName: employee3.lastName} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["dependent", "employee"], + dependsOn: [postgresqlEmployeeCreateTest, postgresqlEmployeeCreateTest2] +} +function postgresqlEmployeeReadManyDependentTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream employeeStream = rainierClient->/employees.get(); + EmployeeInfo2[] employees = check from EmployeeInfo2 employee in employeeStream + select employee; + + test:assertEquals(employees, [ + {empNo: employee1.empNo, birthDate: employee1.birthDate, departmentDeptNo: employee1.departmentDeptNo, workspaceWorkspaceId: employee1.workspaceWorkspaceId}, + {empNo: employee2.empNo, birthDate: employee2.birthDate, departmentDeptNo: employee2.departmentDeptNo, workspaceWorkspaceId: employee2.workspaceWorkspaceId}, + {empNo: employee3.empNo, birthDate: employee3.birthDate, departmentDeptNo: employee3.departmentDeptNo, workspaceWorkspaceId: employee3.workspaceWorkspaceId} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeReadOneTest, postgresqlEmployeeReadManyTest, postgresqlEmployeeReadManyDependentTest1, postgresqlEmployeeReadManyDependentTest2] +} +function postgresqlEmployeeUpdateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee employee = check rainierClient->/employees/[employee1.empNo].put({ + lastName: "Jones", + departmentDeptNo: "department-3", + birthDate: {year: 1994, month: 11, day: 13} + }); + + test:assertEquals(employee, updatedEmployee1); + + Employee employeeRetrieved = check rainierClient->/employees/[employee1.empNo].get(); + test:assertEquals(employeeRetrieved, updatedEmployee1); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeReadOneTest, postgresqlEmployeeReadManyTest, postgresqlEmployeeReadManyDependentTest1, postgresqlEmployeeReadManyDependentTest2] +} +function postgresqlEmployeeUpdateTestNegative1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/["invalid-employee-id"].put({ + lastName: "Jones" + }); + + if employee is persist:NotFoundError { + test:assertEquals(employee.message(), "A record with the key 'invalid-employee-id' does not exist for the entity 'Employee'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeReadOneTest, postgresqlEmployeeReadManyTest, postgresqlEmployeeReadManyDependentTest1, postgresqlEmployeeReadManyDependentTest2] +} +function postgresqlEmployeeUpdateTestNegative2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ + firstName: "unncessarily-long-employee-name-to-force-error-on-update" + }); + + if employee is persist:Error { + test:assertTrue(employee.message().includes("value too long for type character varying")); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeReadOneTest, postgresqlEmployeeReadManyTest, postgresqlEmployeeReadManyDependentTest1, postgresqlEmployeeReadManyDependentTest2] +} +function postgresqlEmployeeUpdateTestNegative3() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/[employee1.empNo].put({ + workspaceWorkspaceId: "invalid-workspaceWorkspaceId" + }); + + if employee is persist:ConstraintViolationError { + test:assertTrue(employee.message().includes("Detail: Key (workspaceworkspaceid)=(invalid-workspaceWorkspaceId) is not present in table \"workspace\"..")); + } else { + test:assertFail("persist:ConstraintViolationError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeUpdateTest, postgresqlEmployeeUpdateTestNegative2, postgresqlEmployeeUpdateTestNegative3] +} +function postgresqlEmployeeDeleteTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee employee = check rainierClient->/employees/[employee1.empNo].delete(); + test:assertEquals(employee, updatedEmployee1); + + stream employeeStream = rainierClient->/employees.get(); + Employee[] employees = check from Employee employee2 in employeeStream + select employee2; + + test:assertEquals(employees, [employee2, employee3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["employee", "postgresql"], + dependsOn: [postgresqlEmployeeDeleteTest] +} +function postgresqlEmployeeDeleteTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Employee|error employee = rainierClient->/employees/[employee1.empNo].delete(); + + if employee is persist:NotFoundError { + test:assertEquals(employee.message(), string `A record with the key '${employee1.empNo}' does not exist for the entity 'Employee'.`); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-id-fields-tests.bal b/ballerina/tests/postgresql-id-fields-tests.bal new file mode 100644 index 0000000..36e8543 --- /dev/null +++ b/ballerina/tests/postgresql-id-fields-tests.bal @@ -0,0 +1,477 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +@test:Config { + groups: ["id-fields", "postgresql"] +} +function postgresqlIntIdFieldTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + IntIdRecord intIdRecord1 = { + id: 1, + randomField: "test1" + }; + IntIdRecord intIdRecord2 = { + id: 2, + randomField: "test2" + }; + IntIdRecord intIdRecord3 = { + id: 3, + randomField: "test3" + }; + IntIdRecord intIdRecord1Updated = { + id: 1, + randomField: "test1Updated" + }; + + // create + int[] ids = check testEntitiesClient->/intidrecords.post([intIdRecord1, intIdRecord2, intIdRecord3]); + test:assertEquals(ids, [intIdRecord1.id, intIdRecord2.id, intIdRecord3.id]); + + // read one + IntIdRecord retrievedRecord1 = check testEntitiesClient->/intidrecords/[intIdRecord1.id].get(); + test:assertEquals(intIdRecord1, retrievedRecord1); + + // read one dependent + IntIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/intidrecords/[intIdRecord1.id].get(); + test:assertEquals({randomField: intIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + IntIdRecord[] intIdRecords = check from IntIdRecord intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecord) + select intIdRecord; + test:assertEquals(intIdRecords, [intIdRecord1, intIdRecord2, intIdRecord3]); + + // read dependent + IntIdRecordDependent[] intIdRecordsDependent = check from IntIdRecordDependent intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecordDependent) + select intIdRecord; + test:assertEquals(intIdRecordsDependent, [{randomField: intIdRecord1.randomField}, {randomField: intIdRecord2.randomField}, {randomField: intIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/intidrecords/[intIdRecord1.id].put({randomField: intIdRecord1Updated.randomField}); + test:assertEquals(intIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/intidrecords/[intIdRecord1.id].get(); + test:assertEquals(intIdRecord1Updated, retrievedRecord1); + + // delete + IntIdRecord retrievedRecord2 = check testEntitiesClient->/intidrecords/[intIdRecord2.id].delete(); + test:assertEquals(intIdRecord2, retrievedRecord2); + intIdRecords = check from IntIdRecord intIdRecord in testEntitiesClient->/intidrecords.get(IntIdRecord) + select intIdRecord; + test:assertEquals(intIdRecords, [intIdRecord3, intIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "postgresql"] +} +function postgresqlStringIdFieldTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + StringIdRecord stringIdRecord1 = { + id: "id-1", + randomField: "test1" + }; + StringIdRecord stringIdRecord2 = { + id: "id-2", + randomField: "test2" + }; + StringIdRecord stringIdRecord3 = { + id: "id-3", + randomField: "test3" + }; + StringIdRecord stringIdRecord1Updated = { + id: "id-1", + randomField: "test1Updated" + }; + + // create + string[] ids = check testEntitiesClient->/stringidrecords.post([stringIdRecord1, stringIdRecord2, stringIdRecord3]); + test:assertEquals(ids, [stringIdRecord1.id, stringIdRecord2.id, stringIdRecord3.id]); + + // read one + StringIdRecord retrievedRecord1 = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].get(); + test:assertEquals(stringIdRecord1, retrievedRecord1); + + // read one dependent + StringIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].get(); + test:assertEquals({randomField: stringIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + StringIdRecord[] stringIdRecords = check from StringIdRecord stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecord) + select stringIdRecord; + test:assertEquals(stringIdRecords, [stringIdRecord1, stringIdRecord2, stringIdRecord3]); + + // read dependent + StringIdRecordDependent[] stringIdRecordsDependent = check from StringIdRecordDependent stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecordDependent) + select stringIdRecord; + test:assertEquals(stringIdRecordsDependent, [{randomField: stringIdRecord1.randomField}, {randomField: stringIdRecord2.randomField}, {randomField: stringIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].put({randomField: stringIdRecord1Updated.randomField}); + test:assertEquals(stringIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/stringidrecords/[stringIdRecord1.id].get(); + test:assertEquals(stringIdRecord1Updated, retrievedRecord1); + + // delete + StringIdRecord retrievedRecord2 = check testEntitiesClient->/stringidrecords/[stringIdRecord2.id].delete(); + test:assertEquals(stringIdRecord2, retrievedRecord2); + stringIdRecords = check from StringIdRecord stringIdRecord in testEntitiesClient->/stringidrecords.get(StringIdRecord) + select stringIdRecord; + test:assertEquals(stringIdRecords, [stringIdRecord3, stringIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "postgresql"] +} +function postgresqlFloatIdFieldTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + FloatIdRecord floatIdRecord1 = { + id: 1.0, + randomField: "test1" + }; + FloatIdRecord floatIdRecord2 = { + id: 2.0, + randomField: "test2" + }; + FloatIdRecord floatIdRecord3 = { + id: 3.0, + randomField: "test3" + }; + FloatIdRecord floatIdRecord1Updated = { + id: 1.0, + randomField: "test1Updated" + }; + + // create + float[] ids = check testEntitiesClient->/floatidrecords.post([floatIdRecord1, floatIdRecord2, floatIdRecord3]); + test:assertEquals(ids, [floatIdRecord1.id, floatIdRecord2.id, floatIdRecord3.id]); + + // read one + FloatIdRecord retrievedRecord1 = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].get(); + test:assertEquals(floatIdRecord1, retrievedRecord1); + + // read one dependent + FloatIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].get(); + test:assertEquals({randomField: floatIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + FloatIdRecord[] floatIdRecords = check from FloatIdRecord floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecord) + select floatIdRecord; + test:assertEquals(floatIdRecords, [floatIdRecord1, floatIdRecord2, floatIdRecord3]); + + // read dependent + FloatIdRecordDependent[] floatIdRecordsDependent = check from FloatIdRecordDependent floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecordDependent) + select floatIdRecord; + test:assertEquals(floatIdRecordsDependent, [{randomField: floatIdRecord1.randomField}, {randomField: floatIdRecord2.randomField}, {randomField: floatIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].put({randomField: floatIdRecord1Updated.randomField}); + test:assertEquals(floatIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/floatidrecords/[floatIdRecord1.id].get(); + test:assertEquals(floatIdRecord1Updated, retrievedRecord1); + + // delete + FloatIdRecord retrievedRecord2 = check testEntitiesClient->/floatidrecords/[floatIdRecord2.id].delete(); + test:assertEquals(floatIdRecord2, retrievedRecord2); + floatIdRecords = check from FloatIdRecord floatIdRecord in testEntitiesClient->/floatidrecords.get(FloatIdRecord) + select floatIdRecord; + test:assertEquals(floatIdRecords, [floatIdRecord3, floatIdRecord1Updated]); +} + +@test:Config { + groups: ["id-fields", "postgresql"] +} +function postgresqlDecimalIdFieldTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + DecimalIdRecord decimalIdRecord1 = { + id: 1.1d, + randomField: "test1" + }; + DecimalIdRecord decimalIdRecord2 = { + id: 2.2d, + randomField: "test2" + }; + DecimalIdRecord decimalIdRecord3 = { + id: 3.3d, + randomField: "test3" + }; + DecimalIdRecord decimalIdRecord1Updated = { + id: 1.1d, + randomField: "test1Updated" + }; + + // create + decimal[] ids = check testEntitiesClient->/decimalidrecords.post([decimalIdRecord1, decimalIdRecord2, decimalIdRecord3]); + test:assertEquals(ids, [decimalIdRecord1.id, decimalIdRecord2.id, decimalIdRecord3.id]); + + // read one + DecimalIdRecord retrievedRecord1 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].get(); + test:assertEquals(decimalIdRecord1, retrievedRecord1); + + // read one dependent + DecimalIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].get(); + test:assertEquals({randomField: decimalIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + DecimalIdRecord[] decimalIdRecords = check from DecimalIdRecord decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecord) + select decimalIdRecord; + test:assertEquals(decimalIdRecords, [decimalIdRecord1, decimalIdRecord2, decimalIdRecord3]); + + // read dependent + DecimalIdRecordDependent[] decimalIdRecordsDependent = check from DecimalIdRecordDependent decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecordDependent) + select decimalIdRecord; + test:assertEquals(decimalIdRecordsDependent, [{randomField: decimalIdRecord1.randomField}, {randomField: decimalIdRecord2.randomField}, {randomField: decimalIdRecord3.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].put({randomField: decimalIdRecord1Updated.randomField}); + test:assertEquals(decimalIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord1.id].get(); + test:assertEquals(decimalIdRecord1Updated, retrievedRecord1); + + // delete + DecimalIdRecord retrievedRecord2 = check testEntitiesClient->/decimalidrecords/[decimalIdRecord2.id].delete(); + test:assertEquals(decimalIdRecord2, retrievedRecord2); + decimalIdRecords = check from DecimalIdRecord decimalIdRecord in testEntitiesClient->/decimalidrecords.get(DecimalIdRecord) + select decimalIdRecord; + test:assertEquals(decimalIdRecords, [decimalIdRecord3, decimalIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "postgresql"], + enable: false +} +function postgresqlBooleanIdFieldTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + BooleanIdRecord booleanIdRecord1 = { + id: true, + randomField: "test1" + }; + BooleanIdRecord booleanIdRecord2 = { + id: false, + randomField: "test2" + }; + BooleanIdRecord booleanIdRecord1Updated = { + id: true, + randomField: "test1Updated" + }; + + // create + boolean[] ids = check testEntitiesClient->/booleanidrecords.post([booleanIdRecord1, booleanIdRecord2]); + test:assertEquals(ids, [booleanIdRecord1.id, booleanIdRecord2.id]); + + // read one + BooleanIdRecord retrievedRecord1 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].get(); + test:assertEquals(booleanIdRecord1, retrievedRecord1); + + // read one dependent + BooleanIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].get(); + test:assertEquals({randomField: booleanIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + BooleanIdRecord[] booleanIdRecords = check from BooleanIdRecord booleanIdRecord in testEntitiesClient->/booleanidrecords.get(BooleanIdRecord) + select booleanIdRecord; + test:assertEquals(booleanIdRecords, [booleanIdRecord2, booleanIdRecord1]); + + // read dependent + BooleanIdRecordDependent[] booleanIdRecordsDependent = check from BooleanIdRecordDependent booleanIdRecord in testEntitiesClient->/booleanidrecords.get(BooleanIdRecordDependent) + select booleanIdRecord; + test:assertEquals(booleanIdRecordsDependent, [{randomField: booleanIdRecord2.randomField}, {randomField: booleanIdRecord1.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].put({randomField: booleanIdRecord1Updated.randomField}); + test:assertEquals(booleanIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord1.id].get(); + test:assertEquals(booleanIdRecord1Updated, retrievedRecord1); + + // delete + BooleanIdRecord retrievedRecord2 = check testEntitiesClient->/booleanidrecords/[booleanIdRecord2.id].delete(); + test:assertEquals(booleanIdRecord2, retrievedRecord2); + booleanIdRecords = check from BooleanIdRecord booleanIdRecord in testEntitiesClient->/booleanidrecords.get(BooleanIdRecord) + select booleanIdRecord; + test:assertEquals(booleanIdRecords, [booleanIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "postgresql"], + enable: false +} +function postgresqlAllTypesIdFieldTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + AllTypesIdRecord allTypesIdRecord1 = { + intType: 1, + stringType: "id-1", + floatType: 1.0, + booleanType: true, + decimalType: 1.1d, + randomField: "test1" + }; + AllTypesIdRecord allTypesIdRecord2 = { + intType: 2, + stringType: "id-2", + floatType: 2.0, + booleanType: false, + decimalType: 2.2d, + randomField: "test2" + }; + AllTypesIdRecord allTypesIdRecord1Updated = { + intType: 1, + stringType: "id-1", + floatType: 1.0, + booleanType: true, + decimalType: 1.1d, + randomField: "test1Updated" + }; + + // create + [boolean, int, float, decimal, string][] ids = check testEntitiesClient->/alltypesidrecords.post([allTypesIdRecord1, allTypesIdRecord2]); + test:assertEquals(ids, [ + [allTypesIdRecord1.booleanType, allTypesIdRecord1.intType, allTypesIdRecord1.floatType, allTypesIdRecord1.decimalType, allTypesIdRecord1.stringType], + [allTypesIdRecord2.booleanType, allTypesIdRecord2.intType, allTypesIdRecord2.floatType, allTypesIdRecord2.decimalType, allTypesIdRecord2.stringType] + ]); + + // read one + AllTypesIdRecord retrievedRecord1 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].get(); + test:assertEquals(allTypesIdRecord1, retrievedRecord1); + + // read one dependent + AllTypesIdRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].get(); + test:assertEquals({randomField: allTypesIdRecord1.randomField}, retrievedRecord1Dependent); + + // read + AllTypesIdRecord[] allTypesIdRecords = check from AllTypesIdRecord allTypesIdRecord in testEntitiesClient->/alltypesidrecords.get(AllTypesIdRecord) + select allTypesIdRecord; + test:assertEquals(allTypesIdRecords, [allTypesIdRecord2, allTypesIdRecord1]); + + // read dependent + AllTypesIdRecordDependent[] allTypesIdRecordsDependent = check from AllTypesIdRecordDependent allTypesIdRecord in testEntitiesClient->/alltypesidrecords.get(AllTypesIdRecordDependent) + select allTypesIdRecord; + test:assertEquals(allTypesIdRecordsDependent, [{randomField: allTypesIdRecord2.randomField}, {randomField: allTypesIdRecord1.randomField}]); + + // update + retrievedRecord1 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].put({randomField: allTypesIdRecord1Updated.randomField}); + test:assertEquals(allTypesIdRecord1Updated, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord1.booleanType]/[allTypesIdRecord1.intType]/[allTypesIdRecord1.floatType]/[allTypesIdRecord1.decimalType]/[allTypesIdRecord1.stringType].get(); + test:assertEquals(allTypesIdRecord1Updated, retrievedRecord1); + + // delete + AllTypesIdRecord retrievedRecord2 = check testEntitiesClient->/alltypesidrecords/[allTypesIdRecord2.booleanType]/[allTypesIdRecord2.intType]/[allTypesIdRecord2.floatType]/[allTypesIdRecord2.decimalType]/[allTypesIdRecord2.stringType].delete(); + test:assertEquals(allTypesIdRecord2, retrievedRecord2); + allTypesIdRecords = check from AllTypesIdRecord allTypesIdRecord in testEntitiesClient->/alltypesidrecords.get(AllTypesIdRecord) + select allTypesIdRecord; + test:assertEquals(allTypesIdRecords, [allTypesIdRecord1Updated]); + + check testEntitiesClient.close(); +} + +@test:Config { + groups: ["id-fields", "postgresql", "associations"], + dependsOn: [postgresqlAllTypesIdFieldTest], + enable: false +} +function postgresqlCompositeAssociationsTest() returns error? { + PostgreSQLTestEntitiesClient testEntitiesClient = check new (); + + CompositeAssociationRecord compositeAssociationRecord1 = { + id: "id-1", + randomField: "test1", + alltypesidrecordIntType: 1, + alltypesidrecordStringType: "id-1", + alltypesidrecordFloatType: 1.0, + alltypesidrecordBooleanType: true, + alltypesidrecordDecimalType: 1.10 + }; + + CompositeAssociationRecord compositeAssociationRecord2 = { + id: "id-2", + randomField: "test2", + alltypesidrecordIntType: 1, + alltypesidrecordStringType: "id-1", + alltypesidrecordFloatType: 1.0, + alltypesidrecordBooleanType: true, + alltypesidrecordDecimalType: 1.10 + }; + + CompositeAssociationRecord compositeAssociationRecordUpdated1 = { + id: "id-1", + randomField: "test1Updated", + alltypesidrecordIntType: 1, + alltypesidrecordStringType: "id-1", + alltypesidrecordFloatType: 1.0, + alltypesidrecordBooleanType: true, + alltypesidrecordDecimalType: 1.10 + }; + + AllTypesIdRecordOptionalized allTypesIdRecord1 = { + intType: 1, + stringType: "id-1", + floatType: 1.0, + booleanType: true, + decimalType: 1.10, + randomField: "test1Updated" + }; + + // create + string[] ids = check testEntitiesClient->/compositeassociationrecords.post([compositeAssociationRecord1, compositeAssociationRecord2]); + test:assertEquals(ids, [compositeAssociationRecord1.id, compositeAssociationRecord2.id]); + + // read one + CompositeAssociationRecord retrievedRecord1 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].get(); + test:assertEquals(compositeAssociationRecord1, retrievedRecord1); + + // read one dependent + CompositeAssociationRecordDependent retrievedRecord1Dependent = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].get(); + test:assertEquals({ + randomField: compositeAssociationRecord1.randomField, + alltypesidrecordIntType: compositeAssociationRecord1.alltypesidrecordIntType, + alltypesidrecordDecimalType: compositeAssociationRecord1.alltypesidrecordDecimalType, + allTypesIdRecord: {intType: allTypesIdRecord1.intType, stringType: allTypesIdRecord1.stringType, booleanType: allTypesIdRecord1.booleanType, randomField: allTypesIdRecord1.randomField} + }, retrievedRecord1Dependent); + + // read + CompositeAssociationRecord[] compositeAssociationRecords = check from CompositeAssociationRecord compositeAssociationRecord in testEntitiesClient->/compositeassociationrecords.get(CompositeAssociationRecord) + select compositeAssociationRecord; + test:assertEquals(compositeAssociationRecords, [compositeAssociationRecord1, compositeAssociationRecord2]); + + // read dependent + CompositeAssociationRecordDependent[] compositeAssociationRecordsDependent = check from CompositeAssociationRecordDependent compositeAssociationRecord in testEntitiesClient->/compositeassociationrecords.get(CompositeAssociationRecordDependent) + select compositeAssociationRecord; + test:assertEquals(compositeAssociationRecordsDependent, [ + {randomField: compositeAssociationRecord1.randomField, alltypesidrecordIntType: compositeAssociationRecord1.alltypesidrecordIntType, alltypesidrecordDecimalType: compositeAssociationRecord1.alltypesidrecordDecimalType, allTypesIdRecord: {intType: allTypesIdRecord1.intType, stringType: allTypesIdRecord1.stringType, booleanType: allTypesIdRecord1.booleanType, randomField: allTypesIdRecord1.randomField}}, + {randomField: compositeAssociationRecord2.randomField, alltypesidrecordIntType: compositeAssociationRecord2.alltypesidrecordIntType, alltypesidrecordDecimalType: compositeAssociationRecord2.alltypesidrecordDecimalType, allTypesIdRecord: {intType: allTypesIdRecord1.intType, stringType: allTypesIdRecord1.stringType, booleanType: allTypesIdRecord1.booleanType, randomField: allTypesIdRecord1.randomField}} + ]); + + // update + retrievedRecord1 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].put({randomField: "test1Updated"}); + test:assertEquals(compositeAssociationRecordUpdated1, retrievedRecord1); + retrievedRecord1 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord1.id].get(); + test:assertEquals(compositeAssociationRecordUpdated1, retrievedRecord1); + + // delete + CompositeAssociationRecord retrievedRecord2 = check testEntitiesClient->/compositeassociationrecords/[compositeAssociationRecord2.id].delete(); + test:assertEquals(compositeAssociationRecord2, retrievedRecord2); + compositeAssociationRecords = check from CompositeAssociationRecord compositeAssociationRecord in testEntitiesClient->/compositeassociationrecords.get(CompositeAssociationRecord) + select compositeAssociationRecord; + test:assertEquals(compositeAssociationRecords, [compositeAssociationRecordUpdated1]); + + check testEntitiesClient.close(); +} diff --git a/ballerina/tests/postgresql-native-tests.bal b/ballerina/tests/postgresql-native-tests.bal new file mode 100644 index 0000000..1f41aec --- /dev/null +++ b/ballerina/tests/postgresql-native-tests.bal @@ -0,0 +1,303 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["native", "postgresql"], + dependsOn: [postgresqlEmployeeRelationsTest, postgresqlWorkspaceRelationsTest, postgresqlBuildingRelationsTest, postgresqlDepartmentRelationsTest] +} +function postgresqlNativeExecuteTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Employee`); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Workspace`); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Building`); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Department`); + + ExecutionResult executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Department (deptNo, deptName) + VALUES + (${departmentNative1.deptNo}, ${departmentNative1.deptName}), + (${departmentNative2.deptNo}, ${departmentNative2.deptName}), + (${departmentNative3.deptNo}, ${departmentNative3.deptName}) + `); + test:assertEquals(executionResult, {affectedRowCount: 3, lastInsertId: "department-native-1"}); + + executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Building (buildingCode, city, state, country, postalCode, type) + VALUES + (${buildingNative1.buildingCode}, ${buildingNative1.city}, ${buildingNative1.state}, ${buildingNative1.country}, ${buildingNative1.postalCode}, ${buildingNative1.'type}), + (${buildingNative2.buildingCode}, ${buildingNative2.city}, ${buildingNative2.state}, ${buildingNative2.country}, ${buildingNative2.postalCode}, ${buildingNative2.'type}), + (${buildingNative3.buildingCode}, ${buildingNative3.city}, ${buildingNative3.state}, ${buildingNative3.country}, ${buildingNative3.postalCode}, ${buildingNative3.'type}) + `); + test:assertEquals(executionResult, {affectedRowCount: 3, lastInsertId: "building-native-1"}); + + executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Workspace (workspaceId, workspaceType, locationBuildingCode) + VALUES + (${workspaceNative1.workspaceId}, ${workspaceNative1.workspaceType}, ${workspaceNative1.locationBuildingCode}), + (${workspaceNative2.workspaceId}, ${workspaceNative2.workspaceType}, ${workspaceNative2.locationBuildingCode}), + (${workspaceNative3.workspaceId}, ${workspaceNative3.workspaceType}, ${workspaceNative3.locationBuildingCode}) + `); + test:assertEquals(executionResult, {affectedRowCount: 3, lastInsertId: "workspace-native-1"}); + + executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Employee (empNo, firstName, lastName, birthDate, gender, hireDate, departmentDeptNo, workspaceWorkspaceId) + VALUES + (${employeeNative1.empNo}, ${employeeNative1.firstName}, ${employeeNative1.lastName}, ${employeeNative1.birthDate}, ${employeeNative1.gender}, ${employeeNative1.hireDate}, ${employeeNative1.departmentDeptNo}, ${employeeNative1.workspaceWorkspaceId}), + (${employeeNative2.empNo}, ${employeeNative2.firstName}, ${employeeNative2.lastName}, ${employeeNative2.birthDate}, ${employeeNative2.gender}, ${employeeNative2.hireDate}, ${employeeNative2.departmentDeptNo}, ${employeeNative2.workspaceWorkspaceId}), + (${employeeNative3.empNo}, ${employeeNative3.firstName}, ${employeeNative3.lastName}, ${employeeNative3.birthDate}, ${employeeNative3.gender}, ${employeeNative3.hireDate}, ${employeeNative3.departmentDeptNo}, ${employeeNative3.workspaceWorkspaceId}) + `); + test:assertEquals(executionResult, {affectedRowCount: 3, lastInsertId: "employee-native-1"}); + + check rainierClient.close(); +} + +@test:Config { + groups: ["native", "postgresql"], + dependsOn: [postgresqlNativeExecuteTest] +} +function postgresqlNativeExecuteTestNegative1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + ExecutionResult|persist:Error executionResult = rainierClient->executeNativeSQL(` + INSERT INTO Department (deptNo, deptName) + VALUES (${departmentNative1.deptNo}, ${departmentNative1.deptName}) + `); + + if executionResult is persist:Error { + test:assertTrue(executionResult.message().includes("duplicate key value violates unique constraint \"department_pkey\"")); + } else { + test:assertFail("persist:Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["native", "postgresql"], + dependsOn: [postgresqlNativeExecuteTest] +} +function postgresqlNativeExecuteTestNegative2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + ExecutionResult|persist:Error executionResult = rainierClient->executeNativeSQL(` + INSERT INTO Departments (deptNo, deptName) + VALUES (${departmentNative1.deptNo}, ${departmentNative1.deptName}) + `); + + if executionResult is persist:Error { + test:assertTrue(executionResult.message().includes("relation \"departments\" does not exist")); + } else { + test:assertFail("persist:Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["native", "postgresql"], + dependsOn: [postgresqlNativeExecuteTest] +} +function postgresqlNativeQueryTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + stream departmentStream = rainierClient->queryNativeSQL(`SELECT * FROM Department`); + Department[] departments = check from Department department in departmentStream + select department; + check departmentStream.close(); + test:assertEquals(departments, [departmentNative1, departmentNative2, departmentNative3]); + + stream buildingStream = rainierClient->queryNativeSQL(`SELECT * FROM Building`); + Building[] buildings = check from Building building in buildingStream + select building; + check buildingStream.close(); + test:assertEquals(buildings, [buildingNative1, buildingNative2, buildingNative3]); + + stream workspaceStream = rainierClient->queryNativeSQL(`SELECT * FROM Workspace`); + Workspace[] workspaces = check from Workspace workspace in workspaceStream + select workspace; + check workspaceStream.close(); + test:assertEquals(workspaces, [workspaceNative1, workspaceNative2, workspaceNative3]); + + stream employeeStream = rainierClient->queryNativeSQL(`SELECT * FROM Employee`); + Employee[] employees = check from Employee employee in employeeStream + select employee; + check employeeStream.close(); + test:assertEquals(employees, [employeeNative1, employeeNative2, employeeNative3]); + + check rainierClient.close(); +} + +@test:Config { + groups: ["native", "postgresql"], + dependsOn: [postgresqlNativeExecuteTest] +} +function postgresqlNativeQueryTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + stream departmentStream = rainierClient->queryNativeSQL(`SELECT * FROM Departments`); + Department[]|persist:Error departments = from Department department in departmentStream + select department; + check departmentStream.close(); + + if departments is persist:Error { + test:assertTrue(departments.message().includes("relation \"departments\" does not exist")); + } else { + test:assertFail("persist:Error expected."); + } + + check rainierClient.close(); +} + +@test:Config { + groups: ["native", "postgresql"], + dependsOn: [postgresqlNativeExecuteTest] +} +function postgresqlNativeQueryComplexTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + stream employeeInfoStream = rainierClient->queryNativeSQL(` + SELECT + firstName, + lastName, + department.deptName AS "department.deptName", + workspace.workspaceId AS "workspace.workspaceId", + workspace.workspaceType AS "workspace.workspaceType", + workspace.locationBuildingCode AS "workspace.locationBuildingCode" + FROM Employee + INNER JOIN + Department department ON Employee.departmentDeptNo = department.deptNo + INNER JOIN + Workspace workspace ON Employee.workspaceWorkspaceId = workspace.workspaceId + `); + EmployeeInfo[] employees = check from EmployeeInfo employee in employeeInfoStream + select employee; + check employeeInfoStream.close(); + test:assertEquals(employees, [employeeInfoNative1, employeeInfoNative2, employeeInfoNative3]); + + check rainierClient.close(); +} + +@test:Config { + groups: ["transactions", "postgresql", "native"], + dependsOn: [postgresqlNativeExecuteTestNegative1, postgresqlNativeQueryTest, postgresqlNativeQueryTestNegative, postgresqlNativeQueryComplexTest] +} +function postgresqlNativeTransactionTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Employee`); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Workspace`); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Building`); + _ = check rainierClient->executeNativeSQL(`DELETE FROM Department`); + + transaction { + ExecutionResult executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Building (buildingCode, city, state, country, postalCode, type) + VALUES + (${building31.buildingCode}, ${building31.city}, ${building31.state}, ${building31.country}, ${building31.postalCode}, ${building31.'type}), + (${building32.buildingCode}, ${building32.city}, ${building32.state}, ${building32.country}, ${building32.postalCode}, ${building32.'type}) + `); + test:assertEquals(executionResult, {affectedRowCount: 2, lastInsertId: "building-31"}); + + stream buildingStream = rainierClient->queryNativeSQL(`SELECT * FROM Building`); + Building[] buildings = check from Building building in buildingStream + select building; + check buildingStream.close(); + test:assertEquals(buildings, [building31, building32]); + + executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Building (buildingCode, city, state, country, postalCode, type) + VALUES + (${building31.buildingCode}, ${building31.city}, ${building31.state}, ${building31.country}, ${building31.postalCode}, ${building31.'type}) + `); + check commit; + } on fail error e { + test:assertTrue(e is persist:Error, "persist:Error expected"); + } + + stream buildingStream = rainierClient->queryNativeSQL(`SELECT * FROM Building`); + Building[] buildings = check from Building building in buildingStream + select building; + check buildingStream.close(); + test:assertEquals(buildings, []); + + check rainierClient.close(); +} + +@test:Config { + groups: ["transactions", "postgresql", "native"], + dependsOn: [postgresqlNativeExecuteTestNegative1, postgresqlNativeQueryTest, postgresqlNativeQueryTestNegative, postgresqlNativeQueryComplexTest] +} +function postgresqlNativeTransactionTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + ExecutionResult executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO Building (buildingCode, city, state, country, postalCode, type) + VALUES + (${building33.buildingCode}, ${building33.city}, ${building33.state}, ${building33.country}, ${building33.postalCode}, ${building33.'type}) + `); + test:assertEquals(executionResult, {affectedRowCount: 1, lastInsertId: "building-33"}); + + stream buildingStream = rainierClient->queryNativeSQL(`SELECT * FROM Building WHERE buildingCode = ${building33.buildingCode}`); + Building[] buildings = check from Building building in buildingStream + select building; + check buildingStream.close(); + test:assertEquals(buildings, [building33]); + + transaction { + _ = check rainierClient->executeNativeSQL(` + UPDATE Building + SET + city = ${building33Updated.city}, + state = ${building33Updated.state}, + country = ${building33Updated.country} + WHERE buildingCode = ${building33.buildingCode} + `); + check commit; + } + + stream buildingStream3 = rainierClient->queryNativeSQL(`SELECT * FROM Building WHERE buildingCode = ${building33.buildingCode}`); + Building[] buildings3 = check from Building building in buildingStream3 + select building; + check buildingStream3.close(); + test:assertEquals(buildings3, [building33Updated]); + + check rainierClient.close(); +} + +@test:Config { + groups: ["postgresql", "native"], + dependsOn: [postgresqlAllTypesDeleteTest] +} +function postgresqlNativeAllTypesTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + _ = check rainierClient->executeNativeSQL(`DELETE FROM AllTypes`); + + ExecutionResult executionResult = check rainierClient->executeNativeSQL(` + INSERT INTO AllTypes ( + id, booleanType, intType, floatType, decimalType, stringType, byteArrayType, dateType, timeOfDayType, civilType, booleanTypeOptional, intTypeOptional, + floatTypeOptional, decimalTypeOptional, stringTypeOptional, dateTypeOptional, timeOfDayTypeOptional, civilTypeOptional, enumType, enumTypeOptional + ) VALUES ( + ${allTypes1.id}, ${allTypes1.booleanType}, ${allTypes1.intType}, ${allTypes1.floatType}, ${allTypes1.decimalType}, ${allTypes1.stringType}, ${allTypes1.byteArrayType}, + ${allTypes1.dateType}, ${allTypes1.timeOfDayType}, ${allTypes1.civilType}, ${allTypes1.booleanTypeOptional}, ${allTypes1.intTypeOptional}, ${allTypes1.floatTypeOptional}, + ${allTypes1.decimalTypeOptional}, ${allTypes1.stringTypeOptional}, ${allTypes1.dateTypeOptional}, ${allTypes1.timeOfDayTypeOptional}, ${allTypes1.civilTypeOptional}, ${allTypes1.enumType}, ${allTypes1.enumTypeOptional} + ) + `); + test:assertEquals(executionResult, {affectedRowCount: 1, lastInsertId: 1}); + + stream allTypesStream = rainierClient->queryNativeSQL(`SELECT * FROM AllTypes WHERE id = ${allTypes1.id}`); + AllTypes[] allTypes = check from AllTypes allType in allTypesStream + select allType; + check allTypesStream.close(); + test:assertEquals(allTypes, [allTypes1]); + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-transaction-tests.bal b/ballerina/tests/postgresql-transaction-tests.bal new file mode 100644 index 0000000..4118fc2 --- /dev/null +++ b/ballerina/tests/postgresql-transaction-tests.bal @@ -0,0 +1,75 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["transactions", "postgresql"] +} +function postgresqlTransactionTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + transaction { + string[] buildingCodes = check rainierClient->/buildings.post([building31, building32]); + test:assertEquals(buildingCodes, [building31.buildingCode, building32.buildingCode]); + + buildingCodes = check rainierClient->/buildings.post([building31]); + check commit; + } on fail error e { + test:assertTrue(e is persist:AlreadyExistsError, "AlreadyExistsError expected"); + } + + Building|persist:Error buildingRetrieved = rainierClient->/buildings/[building31.buildingCode].get(); + test:assertTrue(buildingRetrieved is persist:NotFoundError, "NotFoundError expected"); + + buildingRetrieved = rainierClient->/buildings/[building32.buildingCode].get(); + test:assertTrue(buildingRetrieved is persist:NotFoundError, "NotFoundError expected"); + + check rainierClient.close(); +} + +@test:Config { + groups: ["transactions", "postgresql"] +} +function postgresqlTransactionTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + _ = check rainierClient->/buildings.post([building33]); + Building buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); + test:assertEquals(buildingRetrieved, building33); + + transaction { + Building building = check rainierClient->/buildings/[building33.buildingCode].put({ + city: "ColomboUpdated", + state: "Western ProvinceUpdated", + country: "Sri LankaUpdated" + }); + + test:assertEquals(building, building33Updated); + + // below should retrieve the updated building record + buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); + test:assertEquals(buildingRetrieved, building33Updated); + + check commit; + } + + buildingRetrieved = check rainierClient->/buildings/[building33.buildingCode].get(); + test:assertEquals(buildingRetrieved, building33Updated); + + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql-workspace-tests.bal b/ballerina/tests/postgresql-workspace-tests.bal new file mode 100644 index 0000000..b2dfb39 --- /dev/null +++ b/ballerina/tests/postgresql-workspace-tests.bal @@ -0,0 +1,251 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; +import ballerina/persist; + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlBuildingDeleteTestNegative] +} +function postgresqlWorkspaceCreateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] workspaceIds = check rainierClient->/workspaces.post([workspace1]); + test:assertEquals(workspaceIds, [workspace1.workspaceId]); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace1); +} + +@test:Config { + groups: ["workspace", "postgresql"] +} +function postgresqlWorkspaceCreateTest2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[] workspaceIds = check rainierClient->/workspaces.post([workspace2, workspace3]); + + test:assertEquals(workspaceIds, [workspace2.workspaceId, workspace3.workspaceId]); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace2.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace2); + + workspaceRetrieved = check rainierClient->/workspaces/[workspace3.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace3); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"] +} +function postgresqlWorkspaceCreateTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + string[]|error workspace = rainierClient->/workspaces.post([invalidWorkspace]); + if workspace is persist:Error { + test:assertTrue(workspace.message().includes("value too long for type character varying")); + } else { + test:assertFail("Error expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceCreateTest] +} +function postgresqlWorkspaceReadOneTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, workspace1); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceCreateTest] +} +function postgresqlWorkspaceReadOneDependentTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + WorkspaceInfo2 workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, + { + workspaceType: workspace1.workspaceType, + locationBuildingCode: workspace1.locationBuildingCode + } + ); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceCreateTest] +} +function postgresqlWorkspaceReadOneTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace|error workspaceRetrieved = rainierClient->/workspaces/["invalid-workspace-id"].get(); + if workspaceRetrieved is persist:NotFoundError { + test:assertEquals(workspaceRetrieved.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceCreateTest, postgresqlWorkspaceCreateTest2] +} +function postgresqlWorkspaceReadManyTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream workspaceStream = rainierClient->/workspaces.get(); + Workspace[] workspaces = check from Workspace workspace in workspaceStream + select workspace; + + test:assertEquals(workspaces, [workspace1, workspace2, workspace3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql", "dependent"], + dependsOn: [postgresqlWorkspaceCreateTest, postgresqlWorkspaceCreateTest2] +} +function postgresqlWorkspaceReadManyDependentTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + stream workspaceStream = rainierClient->/workspaces.get(); + WorkspaceInfo2[] workspaces = check from WorkspaceInfo2 workspace in workspaceStream + select workspace; + + test:assertEquals(workspaces, [ + {workspaceType: workspace1.workspaceType, locationBuildingCode: workspace1.locationBuildingCode}, + {workspaceType: workspace2.workspaceType, locationBuildingCode: workspace2.locationBuildingCode}, + {workspaceType: workspace3.workspaceType, locationBuildingCode: workspace3.locationBuildingCode} + ]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceReadOneTest, postgresqlWorkspaceReadManyTest, postgresqlWorkspaceReadManyDependentTest] +} +function postgresqlWorkspaceUpdateTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace workspace = check rainierClient->/workspaces/[workspace1.workspaceId].put({ + workspaceType: "large" + }); + + test:assertEquals(workspace, updatedWorkspace1); + + Workspace workspaceRetrieved = check rainierClient->/workspaces/[workspace1.workspaceId].get(); + test:assertEquals(workspaceRetrieved, updatedWorkspace1); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceCreateTest, postgresqlWorkspaceCreateTest2] +} +function postgresqlWorkspaceReadWithClauses() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + string value = "small"; + string id = "Workspace.workspaceId"; + int count = 2; + stream workspaceStream = rainierClient->/workspaces.get(whereClause = `Workspace.workspaceType = ${value} OR Workspace.workspaceType = 'medium'`, orderByClause = `Workspace.workspaceId DESC `, limitClause = ` ${count}`, groupByClause = `${id}`); + Workspace[] workspaces = check from Workspace workspace in workspaceStream + select workspace; + test:assertEquals(workspaces, [workspace3, workspace2]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceReadOneTest, postgresqlWorkspaceReadManyTest, postgresqlWorkspaceReadManyDependentTest, postgresqlWorkspaceReadWithClauses] +} +function postgresqlWorkspaceUpdateTestNegative1() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace|error workspace = rainierClient->/workspaces/["invalid-workspace-id"].put({ + workspaceType: "large" + }); + + if workspace is persist:NotFoundError { + test:assertEquals(workspace.message(), "A record with the key 'invalid-workspace-id' does not exist for the entity 'Workspace'."); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceReadOneTest, postgresqlWorkspaceReadManyTest, postgresqlWorkspaceReadManyDependentTest, postgresqlWorkspaceReadWithClauses] +} +function postgresqlWorkspaceUpdateTestNegative2() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].put({ + workspaceType: "unncessarily-long-workspace-type-to-force-error-on-update" + }); + + if workspace is persist:Error { + test:assertTrue(workspace.message().includes("value too long for type character varying")); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceUpdateTest, postgresqlWorkspaceUpdateTestNegative2] +} +function postgresqlWorkspaceDeleteTest() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace workspace = check rainierClient->/workspaces/[workspace1.workspaceId].delete(); + test:assertEquals(workspace, updatedWorkspace1); + + stream workspaceStream = rainierClient->/workspaces.get(); + Workspace[] workspaces = check from Workspace workspace2 in workspaceStream + select workspace2; + + test:assertEquals(workspaces, [workspace2, workspace3]); + check rainierClient.close(); +} + +@test:Config { + groups: ["workspace", "postgresql"], + dependsOn: [postgresqlWorkspaceDeleteTest] +} +function postgresqlWorkspaceDeleteTestNegative() returns error? { + PostgreSQLRainierClient rainierClient = check new (); + + Workspace|error workspace = rainierClient->/workspaces/[workspace1.workspaceId].delete(); + + if workspace is persist:NotFoundError { + test:assertEquals(workspace.message(), string `A record with the key '${workspace1.workspaceId}' does not exist for the entity 'Workspace'.`); + } else { + test:assertFail("NotFoundError expected."); + } + check rainierClient.close(); +} diff --git a/ballerina/tests/postgresql_rainier_generated_client.bal b/ballerina/tests/postgresql_rainier_generated_client.bal new file mode 100644 index 0000000..024d355 --- /dev/null +++ b/ballerina/tests/postgresql_rainier_generated_client.bal @@ -0,0 +1,362 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/persist; +import ballerina/jballerina.java; +import ballerinax/postgresql; +import ballerinax/postgresql.driver as _; +import ballerina/sql; + +const EMPLOYEE = "employees"; +const WORKSPACE = "workspaces"; +const BUILDING = "buildings"; +const DEPARTMENT = "departments"; +const ORDER_ITEM = "orderitems"; + +public isolated client class PostgreSQLRainierClient { + *persist:AbstractPersistClient; + + private final postgresql:Client dbClient; + + private final map persistClients; + + private final record {|SQLMetadata...;|} & readonly metadata = { + [EMPLOYEE] : { + entityName: "Employee", + tableName: "Employee", + fieldMetadata: { + empNo: {columnName: "empNo"}, + firstName: {columnName: "firstName"}, + lastName: {columnName: "lastName"}, + birthDate: {columnName: "birthDate"}, + gender: {columnName: "gender"}, + hireDate: {columnName: "hireDate"}, + departmentDeptNo: {columnName: "departmentDeptNo"}, + workspaceWorkspaceId: {columnName: "workspaceWorkspaceId"}, + "department.deptNo": {relation: {entityName: "department", refField: "deptNo"}}, + "department.deptName": {relation: {entityName: "department", refField: "deptName"}}, + "workspace.workspaceId": {relation: {entityName: "workspace", refField: "workspaceId"}}, + "workspace.workspaceType": {relation: {entityName: "workspace", refField: "workspaceType"}}, + "workspace.locationBuildingCode": {relation: {entityName: "workspace", refField: "locationBuildingCode"}} + }, + keyFields: ["empNo"], + joinMetadata: { + department: {entity: Department, fieldName: "department", refTable: "Department", refColumns: ["deptNo"], joinColumns: ["departmentDeptNo"], 'type: ONE_TO_MANY}, + workspace: {entity: Workspace, fieldName: "workspace", refTable: "Workspace", refColumns: ["workspaceId"], joinColumns: ["workspaceWorkspaceId"], 'type: ONE_TO_MANY} + } + }, + [WORKSPACE] : { + entityName: "Workspace", + tableName: "Workspace", + fieldMetadata: { + workspaceId: {columnName: "workspaceId"}, + workspaceType: {columnName: "workspaceType"}, + locationBuildingCode: {columnName: "locationBuildingCode"}, + "location.buildingCode": {relation: {entityName: "location", refField: "buildingCode"}}, + "location.city": {relation: {entityName: "location", refField: "city"}}, + "location.state": {relation: {entityName: "location", refField: "state"}}, + "location.country": {relation: {entityName: "location", refField: "country"}}, + "location.postalCode": {relation: {entityName: "location", refField: "postalCode"}}, + "location.type": {relation: {entityName: "location", refField: "type"}}, + "employees[].empNo": {relation: {entityName: "employees", refField: "empNo"}}, + "employees[].firstName": {relation: {entityName: "employees", refField: "firstName"}}, + "employees[].lastName": {relation: {entityName: "employees", refField: "lastName"}}, + "employees[].birthDate": {relation: {entityName: "employees", refField: "birthDate"}}, + "employees[].gender": {relation: {entityName: "employees", refField: "gender"}}, + "employees[].hireDate": {relation: {entityName: "employees", refField: "hireDate"}}, + "employees[].departmentDeptNo": {relation: {entityName: "employees", refField: "departmentDeptNo"}}, + "employees[].workspaceWorkspaceId": {relation: {entityName: "employees", refField: "workspaceWorkspaceId"}} + }, + keyFields: ["workspaceId"], + joinMetadata: { + location: {entity: Building, fieldName: "location", refTable: "Building", refColumns: ["buildingCode"], joinColumns: ["locationBuildingCode"], 'type: ONE_TO_MANY}, + employees: {entity: Employee, fieldName: "employees", refTable: "Employee", refColumns: ["workspaceWorkspaceId"], joinColumns: ["workspaceId"], 'type: MANY_TO_ONE} + } + }, + [BUILDING] : { + entityName: "Building", + tableName: "Building", + fieldMetadata: { + buildingCode: {columnName: "buildingCode"}, + city: {columnName: "city"}, + state: {columnName: "state"}, + country: {columnName: "country"}, + postalCode: {columnName: "postalCode"}, + 'type: {columnName: "type"}, + "workspaces[].workspaceId": {relation: {entityName: "workspaces", refField: "workspaceId"}}, + "workspaces[].workspaceType": {relation: {entityName: "workspaces", refField: "workspaceType"}}, + "workspaces[].locationBuildingCode": {relation: {entityName: "workspaces", refField: "locationBuildingCode"}} + }, + keyFields: ["buildingCode"], + joinMetadata: {workspaces: {entity: Workspace, fieldName: "workspaces", refTable: "Workspace", refColumns: ["locationBuildingCode"], joinColumns: ["buildingCode"], 'type: MANY_TO_ONE}} + }, + [DEPARTMENT] : { + entityName: "Department", + tableName: "Department", + fieldMetadata: { + deptNo: {columnName: "deptNo"}, + deptName: {columnName: "deptName"}, + "employees[].empNo": {relation: {entityName: "employees", refField: "empNo"}}, + "employees[].firstName": {relation: {entityName: "employees", refField: "firstName"}}, + "employees[].lastName": {relation: {entityName: "employees", refField: "lastName"}}, + "employees[].birthDate": {relation: {entityName: "employees", refField: "birthDate"}}, + "employees[].gender": {relation: {entityName: "employees", refField: "gender"}}, + "employees[].hireDate": {relation: {entityName: "employees", refField: "hireDate"}}, + "employees[].departmentDeptNo": {relation: {entityName: "employees", refField: "departmentDeptNo"}}, + "employees[].workspaceWorkspaceId": {relation: {entityName: "employees", refField: "workspaceWorkspaceId"}} + }, + keyFields: ["deptNo"], + joinMetadata: {employees: {entity: Employee, fieldName: "employees", refTable: "Employee", refColumns: ["departmentDeptNo"], joinColumns: ["deptNo"], 'type: MANY_TO_ONE}} + }, + [ORDER_ITEM] : { + entityName: "OrderItem", + tableName: "OrderItem", + fieldMetadata: { + orderId: {columnName: "orderId"}, + itemId: {columnName: "itemId"}, + quantity: {columnName: "quantity"}, + notes: {columnName: "notes"} + }, + keyFields: ["orderId", "itemId"] + } + }; + + public isolated function init() returns persist:Error? { + postgresql:Client|error dbClient = new (host = postgresql.host, username = postgresql.user, password = postgresql.password, database = postgresql.database, port = postgresql.port); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + self.persistClients = { + [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE), POSTGRESQL_SPECIFICS), + [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE), POSTGRESQL_SPECIFICS), + [BUILDING] : check new (dbClient, self.metadata.get(BUILDING), POSTGRESQL_SPECIFICS), + [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT), POSTGRESQL_SPECIFICS), + [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM), POSTGRESQL_SPECIFICS) + }; + } + + isolated resource function get employees(EmployeeTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get employees/[string empNo](EmployeeTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post employees(EmployeeInsert[] data) returns string[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(EMPLOYEE); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from EmployeeInsert inserted in data + select inserted.empNo; + } + + isolated resource function put employees/[string empNo](EmployeeUpdate value) returns Employee|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(EMPLOYEE); + } + _ = check sqlClient.runUpdateQuery(empNo, value); + return self->/employees/[empNo].get(); + } + + isolated resource function delete employees/[string empNo]() returns Employee|persist:Error { + Employee result = check self->/employees/[empNo].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(EMPLOYEE); + } + _ = check sqlClient.runDeleteQuery(empNo); + return result; + } + + isolated resource function get workspaces(WorkspaceTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get workspaces/[string workspaceId](WorkspaceTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post workspaces(WorkspaceInsert[] data) returns string[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(WORKSPACE); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from WorkspaceInsert inserted in data + select inserted.workspaceId; + } + + isolated resource function put workspaces/[string workspaceId](WorkspaceUpdate value) returns Workspace|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(WORKSPACE); + } + _ = check sqlClient.runUpdateQuery(workspaceId, value); + return self->/workspaces/[workspaceId].get(); + } + + isolated resource function delete workspaces/[string workspaceId]() returns Workspace|persist:Error { + Workspace result = check self->/workspaces/[workspaceId].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(WORKSPACE); + } + _ = check sqlClient.runDeleteQuery(workspaceId); + return result; + } + + isolated resource function get buildings(BuildingTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get buildings/[string buildingCode](BuildingTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post buildings(BuildingInsert[] data) returns string[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(BUILDING); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from BuildingInsert inserted in data + select inserted.buildingCode; + } + + isolated resource function put buildings/[string buildingCode](BuildingUpdate value) returns Building|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(BUILDING); + } + _ = check sqlClient.runUpdateQuery(buildingCode, value); + return self->/buildings/[buildingCode].get(); + } + + isolated resource function delete buildings/[string buildingCode]() returns Building|persist:Error { + Building result = check self->/buildings/[buildingCode].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(BUILDING); + } + _ = check sqlClient.runDeleteQuery(buildingCode); + return result; + } + + isolated resource function get departments(DepartmentTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get departments/[string deptNo](DepartmentTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post departments(DepartmentInsert[] data) returns string[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DEPARTMENT); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from DepartmentInsert inserted in data + select inserted.deptNo; + } + + isolated resource function put departments/[string deptNo](DepartmentUpdate value) returns Department|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DEPARTMENT); + } + _ = check sqlClient.runUpdateQuery(deptNo, value); + return self->/departments/[deptNo].get(); + } + + isolated resource function delete departments/[string deptNo]() returns Department|persist:Error { + Department result = check self->/departments/[deptNo].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DEPARTMENT); + } + _ = check sqlClient.runDeleteQuery(deptNo); + return result; + } + + isolated resource function get orderitems(OrderItemTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get orderitems/[string orderId]/[string itemId](OrderItemTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post orderitems(OrderItemInsert[] data) returns [string, string][]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ORDER_ITEM); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from OrderItemInsert inserted in data + select [inserted.orderId, inserted.itemId]; + } + + isolated resource function put orderitems/[string orderId]/[string itemId](OrderItemUpdate value) returns OrderItem|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ORDER_ITEM); + } + _ = check sqlClient.runUpdateQuery({"orderId": orderId, "itemId": itemId}, value); + return self->/orderitems/[orderId]/[itemId].get(); + } + + isolated resource function delete orderitems/[string orderId]/[string itemId]() returns OrderItem|persist:Error { + OrderItem result = check self->/orderitems/[orderId]/[itemId].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ORDER_ITEM); + } + _ = check sqlClient.runDeleteQuery({"orderId": orderId, "itemId": itemId}); + return result; + } + + remote isolated function queryNativeSQL(sql:ParameterizedQuery sqlQuery, typedesc rowType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor" + } external; + + remote isolated function executeNativeSQL(sql:ParameterizedQuery sqlQuery) returns ExecutionResult|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor" + } external; + + public isolated function close() returns persist:Error? { + error? result = self.dbClient.close(); + if result is error { + return error(result.message()); + } + return result; + } +} diff --git a/ballerina/tests/postgresql_test_entities_generated_client.bal b/ballerina/tests/postgresql_test_entities_generated_client.bal new file mode 100644 index 0000000..4605da5 --- /dev/null +++ b/ballerina/tests/postgresql_test_entities_generated_client.bal @@ -0,0 +1,502 @@ +// Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/persist; +import ballerina/jballerina.java; +import ballerinax/postgresql; +import ballerinax/postgresql.driver as _; +import ballerina/sql; + +const ALL_TYPES = "alltypes"; +const STRING_ID_RECORD = "stringidrecords"; +const INT_ID_RECORD = "intidrecords"; +const FLOAT_ID_RECORD = "floatidrecords"; +const DECIMAL_ID_RECORD = "decimalidrecords"; +const BOOLEAN_ID_RECORD = "booleanidrecords"; +const COMPOSITE_ASSOCIATION_RECORD = "compositeassociationrecords"; +const ALL_TYPES_ID_RECORD = "alltypesidrecords"; + +public isolated client class PostgreSQLTestEntitiesClient { + *persist:AbstractPersistClient; + + private final postgresql:Client dbClient; + + private final map persistClients; + + private final record {|SQLMetadata...;|} & readonly metadata = { + [ALL_TYPES] : { + entityName: "AllTypes", + tableName: "AllTypes", + fieldMetadata: { + id: {columnName: "id"}, + booleanType: {columnName: "booleanType"}, + intType: {columnName: "intType"}, + floatType: {columnName: "floatType"}, + decimalType: {columnName: "decimalType"}, + stringType: {columnName: "stringType"}, + byteArrayType: {columnName: "byteArrayType"}, + dateType: {columnName: "dateType"}, + timeOfDayType: {columnName: "timeOfDayType"}, + civilType: {columnName: "civilType"}, + booleanTypeOptional: {columnName: "booleanTypeOptional"}, + intTypeOptional: {columnName: "intTypeOptional"}, + floatTypeOptional: {columnName: "floatTypeOptional"}, + decimalTypeOptional: {columnName: "decimalTypeOptional"}, + stringTypeOptional: {columnName: "stringTypeOptional"}, + dateTypeOptional: {columnName: "dateTypeOptional"}, + timeOfDayTypeOptional: {columnName: "timeOfDayTypeOptional"}, + civilTypeOptional: {columnName: "civilTypeOptional"}, + enumType: {columnName: "enumType"}, + enumTypeOptional: {columnName: "enumTypeOptional"} + }, + keyFields: ["id"] + }, + [STRING_ID_RECORD] : { + entityName: "StringIdRecord", + tableName: "StringIdRecord", + fieldMetadata: { + id: {columnName: "id"}, + randomField: {columnName: "randomField"} + }, + keyFields: ["id"] + }, + [INT_ID_RECORD] : { + entityName: "IntIdRecord", + tableName: "IntIdRecord", + fieldMetadata: { + id: {columnName: "id"}, + randomField: {columnName: "randomField"} + }, + keyFields: ["id"] + }, + [FLOAT_ID_RECORD] : { + entityName: "FloatIdRecord", + tableName: "FloatIdRecord", + fieldMetadata: { + id: {columnName: "id"}, + randomField: {columnName: "randomField"} + }, + keyFields: ["id"] + }, + [DECIMAL_ID_RECORD] : { + entityName: "DecimalIdRecord", + tableName: "DecimalIdRecord", + fieldMetadata: { + id: {columnName: "id"}, + randomField: {columnName: "randomField"} + }, + keyFields: ["id"] + }, + [BOOLEAN_ID_RECORD] : { + entityName: "BooleanIdRecord", + tableName: "BooleanIdRecord", + fieldMetadata: { + id: {columnName: "id"}, + randomField: {columnName: "randomField"} + }, + keyFields: ["id"] + }, + [COMPOSITE_ASSOCIATION_RECORD] : { + entityName: "CompositeAssociationRecord", + tableName: "CompositeAssociationRecord", + fieldMetadata: { + id: {columnName: "id"}, + randomField: {columnName: "randomField"}, + alltypesidrecordBooleanType: {columnName: "alltypesidrecordBooleanType"}, + alltypesidrecordIntType: {columnName: "alltypesidrecordIntType"}, + alltypesidrecordFloatType: {columnName: "alltypesidrecordFloatType"}, + alltypesidrecordDecimalType: {columnName: "alltypesidrecordDecimalType"}, + alltypesidrecordStringType: {columnName: "alltypesidrecordStringType"}, + "allTypesIdRecord.booleanType": {relation: {entityName: "allTypesIdRecord", refField: "booleanType"}}, + "allTypesIdRecord.intType": {relation: {entityName: "allTypesIdRecord", refField: "intType"}}, + "allTypesIdRecord.floatType": {relation: {entityName: "allTypesIdRecord", refField: "floatType"}}, + "allTypesIdRecord.decimalType": {relation: {entityName: "allTypesIdRecord", refField: "decimalType"}}, + "allTypesIdRecord.stringType": {relation: {entityName: "allTypesIdRecord", refField: "stringType"}}, + "allTypesIdRecord.randomField": {relation: {entityName: "allTypesIdRecord", refField: "randomField"}} + }, + keyFields: ["id"], + joinMetadata: {allTypesIdRecord: {entity: AllTypesIdRecord, fieldName: "allTypesIdRecord", refTable: "AllTypesIdRecord", refColumns: ["booleanType", "intType", "floatType", "decimalType", "stringType"], joinColumns: ["alltypesidrecordBooleanType", "alltypesidrecordIntType", "alltypesidrecordFloatType", "alltypesidrecordDecimalType", "alltypesidrecordStringType"], 'type: ONE_TO_ONE}} + }, + [ALL_TYPES_ID_RECORD] : { + entityName: "AllTypesIdRecord", + tableName: "AllTypesIdRecord", + fieldMetadata: { + booleanType: {columnName: "booleanType"}, + intType: {columnName: "intType"}, + floatType: {columnName: "floatType"}, + decimalType: {columnName: "decimalType"}, + stringType: {columnName: "stringType"}, + randomField: {columnName: "randomField"}, + "compositeAssociationRecord.id": {relation: {entityName: "compositeAssociationRecord", refField: "id"}}, + "compositeAssociationRecord.randomField": {relation: {entityName: "compositeAssociationRecord", refField: "randomField"}}, + "compositeAssociationRecord.alltypesidrecordBooleanType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordBooleanType"}}, + "compositeAssociationRecord.alltypesidrecordIntType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordIntType"}}, + "compositeAssociationRecord.alltypesidrecordFloatType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordFloatType"}}, + "compositeAssociationRecord.alltypesidrecordDecimalType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordDecimalType"}}, + "compositeAssociationRecord.alltypesidrecordStringType": {relation: {entityName: "compositeAssociationRecord", refField: "alltypesidrecordStringType"}} + }, + keyFields: ["booleanType", "intType", "floatType", "decimalType", "stringType"], + joinMetadata: {compositeAssociationRecord: {entity: CompositeAssociationRecord, fieldName: "compositeAssociationRecord", refTable: "CompositeAssociationRecord", refColumns: ["alltypesidrecordBooleanType", "alltypesidrecordIntType", "alltypesidrecordFloatType", "alltypesidrecordDecimalType", "alltypesidrecordStringType"], joinColumns: ["booleanType", "intType", "floatType", "decimalType", "stringType"], 'type: ONE_TO_ONE}} + } + }; + + public isolated function init() returns persist:Error? { + postgresql:Client|error dbClient = new (host = postgresql.host, username = postgresql.user, password = postgresql.password, database = postgresql.database, port = postgresql.port); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + self.persistClients = { + [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES), POSTGRESQL_SPECIFICS), + [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD), POSTGRESQL_SPECIFICS), + [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD), POSTGRESQL_SPECIFICS), + [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD), POSTGRESQL_SPECIFICS), + [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD), POSTGRESQL_SPECIFICS), + [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD), POSTGRESQL_SPECIFICS), + [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD), POSTGRESQL_SPECIFICS), + [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD), POSTGRESQL_SPECIFICS) + }; + } + + isolated resource function get alltypes(AllTypesTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get alltypes/[int id](AllTypesTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post alltypes(AllTypesInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ALL_TYPES); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from AllTypesInsert inserted in data + select inserted.id; + } + + isolated resource function put alltypes/[int id](AllTypesUpdate value) returns AllTypes|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ALL_TYPES); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/alltypes/[id].get(); + } + + isolated resource function delete alltypes/[int id]() returns AllTypes|persist:Error { + AllTypes result = check self->/alltypes/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ALL_TYPES); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get stringidrecords(StringIdRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get stringidrecords/[string id](StringIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post stringidrecords(StringIdRecordInsert[] data) returns string[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(STRING_ID_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from StringIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put stringidrecords/[string id](StringIdRecordUpdate value) returns StringIdRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(STRING_ID_RECORD); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/stringidrecords/[id].get(); + } + + isolated resource function delete stringidrecords/[string id]() returns StringIdRecord|persist:Error { + StringIdRecord result = check self->/stringidrecords/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(STRING_ID_RECORD); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get intidrecords(IntIdRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get intidrecords/[int id](IntIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post intidrecords(IntIdRecordInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(INT_ID_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from IntIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put intidrecords/[int id](IntIdRecordUpdate value) returns IntIdRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(INT_ID_RECORD); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/intidrecords/[id].get(); + } + + isolated resource function delete intidrecords/[int id]() returns IntIdRecord|persist:Error { + IntIdRecord result = check self->/intidrecords/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(INT_ID_RECORD); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get floatidrecords(FloatIdRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get floatidrecords/[float id](FloatIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post floatidrecords(FloatIdRecordInsert[] data) returns float[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(FLOAT_ID_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from FloatIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put floatidrecords/[float id](FloatIdRecordUpdate value) returns FloatIdRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(FLOAT_ID_RECORD); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/floatidrecords/[id].get(); + } + + isolated resource function delete floatidrecords/[float id]() returns FloatIdRecord|persist:Error { + FloatIdRecord result = check self->/floatidrecords/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(FLOAT_ID_RECORD); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get decimalidrecords(DecimalIdRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get decimalidrecords/[decimal id](DecimalIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post decimalidrecords(DecimalIdRecordInsert[] data) returns decimal[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DECIMAL_ID_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from DecimalIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put decimalidrecords/[decimal id](DecimalIdRecordUpdate value) returns DecimalIdRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DECIMAL_ID_RECORD); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/decimalidrecords/[id].get(); + } + + isolated resource function delete decimalidrecords/[decimal id]() returns DecimalIdRecord|persist:Error { + DecimalIdRecord result = check self->/decimalidrecords/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DECIMAL_ID_RECORD); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get booleanidrecords(BooleanIdRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get booleanidrecords/[boolean id](BooleanIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post booleanidrecords(BooleanIdRecordInsert[] data) returns boolean[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(BOOLEAN_ID_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from BooleanIdRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put booleanidrecords/[boolean id](BooleanIdRecordUpdate value) returns BooleanIdRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(BOOLEAN_ID_RECORD); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/booleanidrecords/[id].get(); + } + + isolated resource function delete booleanidrecords/[boolean id]() returns BooleanIdRecord|persist:Error { + BooleanIdRecord result = check self->/booleanidrecords/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(BOOLEAN_ID_RECORD); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get compositeassociationrecords(CompositeAssociationRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get compositeassociationrecords/[string id](CompositeAssociationRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post compositeassociationrecords(CompositeAssociationRecordInsert[] data) returns string[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(COMPOSITE_ASSOCIATION_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from CompositeAssociationRecordInsert inserted in data + select inserted.id; + } + + isolated resource function put compositeassociationrecords/[string id](CompositeAssociationRecordUpdate value) returns CompositeAssociationRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(COMPOSITE_ASSOCIATION_RECORD); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/compositeassociationrecords/[id].get(); + } + + isolated resource function delete compositeassociationrecords/[string id]() returns CompositeAssociationRecord|persist:Error { + CompositeAssociationRecord result = check self->/compositeassociationrecords/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(COMPOSITE_ASSOCIATION_RECORD); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get alltypesidrecords(AllTypesIdRecordTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "query" + } external; + + isolated resource function get alltypesidrecords/[boolean booleanType]/[int intType]/[float floatType]/[decimal decimalType]/[string stringType](AllTypesIdRecordTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post alltypesidrecords(AllTypesIdRecordInsert[] data) returns [boolean, int, float, decimal, string][]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ALL_TYPES_ID_RECORD); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from AllTypesIdRecordInsert inserted in data + select [inserted.booleanType, inserted.intType, inserted.floatType, inserted.decimalType, inserted.stringType]; + } + + isolated resource function put alltypesidrecords/[boolean booleanType]/[int intType]/[float floatType]/[decimal decimalType]/[string stringType](AllTypesIdRecordUpdate value) returns AllTypesIdRecord|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ALL_TYPES_ID_RECORD); + } + _ = check sqlClient.runUpdateQuery({"booleanType": booleanType, "intType": intType, "floatType": floatType, "decimalType": decimalType, "stringType": stringType}, value); + return self->/alltypesidrecords/[booleanType]/[intType]/[floatType]/[decimalType]/[stringType].get(); + } + + isolated resource function delete alltypesidrecords/[boolean booleanType]/[int intType]/[float floatType]/[decimal decimalType]/[string stringType]() returns AllTypesIdRecord|persist:Error { + AllTypesIdRecord result = check self->/alltypesidrecords/[booleanType]/[intType]/[floatType]/[decimalType]/[stringType].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(ALL_TYPES_ID_RECORD); + } + _ = check sqlClient.runDeleteQuery({"booleanType": booleanType, "intType": intType, "floatType": floatType, "decimalType": decimalType, "stringType": stringType}); + return result; + } + + remote isolated function queryNativeSQL(sql:ParameterizedQuery sqlQuery, typedesc rowType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor" + } external; + + remote isolated function executeNativeSQL(sql:ParameterizedQuery sqlQuery) returns ExecutionResult|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor" + } external; + + public isolated function close() returns persist:Error? { + error? result = self.dbClient.close(); + if result is error { + return error(result.message()); + } + return result; + } +} + diff --git a/ballerina/tests/resources/postgresql/Dockerfile b/ballerina/tests/resources/postgresql/Dockerfile new file mode 100644 index 0000000..dd05d7f --- /dev/null +++ b/ballerina/tests/resources/postgresql/Dockerfile @@ -0,0 +1,25 @@ +# Copyright (c) 2023 WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Use this if testing on an M1 machine +# FROM mcr.microsoft.com/azure-sql-edge + +FROM postgres + +ENV POSTGRES_USER postgres +ENV POSTGRES_PASSWORD postgres + +COPY init.sql /docker-entrypoint-initdb.d/init.sql diff --git a/ballerina/tests/resources/postgresql/init.sql b/ballerina/tests/resources/postgresql/init.sql new file mode 100644 index 0000000..91ba0a3 --- /dev/null +++ b/ballerina/tests/resources/postgresql/init.sql @@ -0,0 +1,121 @@ +CREATE DATABASE test; + +\c test; + +CREATE TABLE Building ( + buildingCode VARCHAR(36) PRIMARY KEY, + city VARCHAR(50), + state VARCHAR(50), + country VARCHAR(50), + postalCode VARCHAR(50), + type VARCHAR(50) +); + +CREATE TABLE Workspace ( + workspaceId VARCHAR(36) PRIMARY KEY, + workspaceType VARCHAR(10), + locationBuildingCode VARCHAR(36), + FOREIGN KEY (locationBuildingCode) REFERENCES Building(buildingCode) +); + +CREATE TABLE Department ( + deptNo VARCHAR(36) PRIMARY KEY, + deptName VARCHAR(30) +); + +CREATE TABLE Employee ( + empNo VARCHAR(36) PRIMARY KEY, + firstName VARCHAR(30), + lastName VARCHAR(30), + birthDate DATE, + gender VARCHAR(6) CHECK (gender IN ('MALE', 'FEMALE')) NOT NULL, + hireDate DATE, + departmentDeptNo VARCHAR(36), + workspaceWorkspaceId VARCHAR(36), + FOREIGN KEY (departmentDeptNo) REFERENCES Department(deptNo), + FOREIGN KEY (workspaceWorkspaceId) REFERENCES Workspace(workspaceId) +); + +CREATE TABLE OrderItem ( + orderId VARCHAR(36), + itemId VARCHAR(30), + quantity INTEGER, + notes VARCHAR(255), + PRIMARY KEY(orderId, itemId) +); + +CREATE TABLE AllTypes ( + id INT NOT NULL, + booleanType BOOLEAN NOT NULL, + intType INT NOT NULL, + floatType FLOAT NOT NULL, + decimalType DECIMAL(10, 2) NOT NULL, + stringType VARCHAR(191) NOT NULL, + byteArrayType BYTEA NOT NULL, + dateType DATE NOT NULL, + timeOfDayType TIME NOT NULL, + civilType TIMESTAMP NOT NULL, + booleanTypeOptional BOOLEAN, + intTypeOptional INT, + floatTypeOptional FLOAT, + decimalTypeOptional DECIMAL(10, 2), + stringTypeOptional VARCHAR(191), + dateTypeOptional DATE, + timeOfDayTypeOptional TIME, + civilTypeOptional TIMESTAMP, + enumType VARCHAR(10) CHECK (enumType IN ('TYPE_1', 'TYPE_2', 'TYPE_3', 'TYPE_4')) NOT NULL, + enumTypeOptional VARCHAR(10) CHECK (enumTypeOptional IN ('TYPE_1', 'TYPE_2', 'TYPE_3', 'TYPE_4')), + PRIMARY KEY(id) +); + +CREATE TABLE FloatIdRecord ( + id FLOAT NOT NULL, + randomField VARCHAR(191) NOT NULL, + PRIMARY KEY(id) +); + +CREATE TABLE StringIdRecord ( + id VARCHAR(191) NOT NULL, + randomField VARCHAR(191) NOT NULL, + PRIMARY KEY(id) +); + +CREATE TABLE DecimalIdRecord ( + id DECIMAL(10, 2) NOT NULL, + randomField VARCHAR(191) NOT NULL, + PRIMARY KEY(id) +); + +CREATE TABLE BooleanIdRecord ( + id BOOLEAN NOT NULL, + randomField VARCHAR(191) NOT NULL, + PRIMARY KEY(id) +); + +CREATE TABLE IntIdRecord ( + id INT NOT NULL, + randomField VARCHAR(191) NOT NULL, + PRIMARY KEY(id) +); + +CREATE TABLE AllTypesIdRecord ( + booleanType BOOLEAN NOT NULL, + intType INT NOT NULL, + floatType FLOAT NOT NULL, + decimalType DECIMAL(10, 2) NOT NULL, + stringType VARCHAR(191) NOT NULL, + randomField VARCHAR(191) NOT NULL, + PRIMARY KEY(booleanType, intType, floatType, decimalType, stringType) +); + +CREATE TABLE CompositeAssociationRecord ( + id VARCHAR(191) NOT NULL, + randomField VARCHAR(191) NOT NULL, + alltypesidrecordBooleanType BOOLEAN NOT NULL, + alltypesidrecordIntType INT NOT NULL, + alltypesidrecordFloatType FLOAT NOT NULL, + alltypesidrecordDecimalType DECIMAL(10, 2) NOT NULL, + alltypesidrecordStringType VARCHAR(191) NOT NULL, + FOREIGN KEY(alltypesidrecordBooleanType, alltypesidrecordIntType, alltypesidrecordFloatType, alltypesidrecordDecimalType, alltypesidrecordStringType) REFERENCES AllTypesIdRecord(booleanType, intType, floatType, decimalType, stringType), + PRIMARY KEY(id) +); diff --git a/build.gradle b/build.gradle index db5ef2b..91aadff 100644 --- a/build.gradle +++ b/build.gradle @@ -92,8 +92,10 @@ subprojects { ballerinaStdLibs "io.ballerina.stdlib:persist-ballerina:${stdlibPersistVersion}" ballerinaStdLibs "io.ballerina.stdlib:mysql-ballerina:${stdlibMysqlVersion}" ballerinaStdLibs "io.ballerina.stdlib:mssql-ballerina:${stdlibMssqlVersion}" + ballerinaStdLibs "io.ballerina.stdlib:postgresql-ballerina:${stdlibPostgresqlVersion}" ballerinaStdLibs "io.ballerina.stdlib:mysql.driver-ballerina:${stdlibMysqlDriverVersion}" ballerinaStdLibs "io.ballerina.stdlib:mssql.driver-ballerina:${stdlibMssqlDriverVersion}" + ballerinaStdLibs "io.ballerina.stdlib:postgresql.driver-ballerina:${stdlibPostgresqlDriverVersion}" ballerinaStdLibs "io.ballerina.stdlib:observe-ballerina:${observeVersion}" ballerinaStdLibs "io.ballerina:observe-ballerina:${observeInternalVersion}" } diff --git a/changelog.md b/changelog.md index 1121bba..5e7ec6a 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- [Added support for PostgreSQL as a datasource](https://github.com/ballerina-platform/ballerina-library/issues/5829) ### Changed - [Fix class cast exception when executing query with non-global variable](https://github.com/ballerina-platform/persist-tools/issues/311) diff --git a/gradle.properties b/gradle.properties index 9c6f050..047bf86 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,6 +39,7 @@ stdlibPersistVersion=1.2.0 # Ballerina external dependency stdlibMysqlDriverVersion=1.5.0 stdlibMssqlDriverVersion=1.5.0 +stdlibPostgresqlDriverVersion=1.5.0 # Ballerinax Observer observeVersion=1.2.0 @@ -68,6 +69,7 @@ stdlibTransactionVersion=1.8.0 # Test Dependencies stdlibMysqlVersion=1.11.0 stdlibMssqlVersion=1.11.0 +stdlibPostgresqlVersion=1.11.0 # Enabled publishing insecure checksums, due to fail to publish to maven central diff --git a/native/src/main/java/io/ballerina/stdlib/persist/sql/datastore/PostgreSQLProcessor.java b/native/src/main/java/io/ballerina/stdlib/persist/sql/datastore/PostgreSQLProcessor.java new file mode 100644 index 0000000..9a63859 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/persist/sql/datastore/PostgreSQLProcessor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.persist.sql.datastore; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BStream; +import io.ballerina.runtime.api.values.BTypedesc; + +/** + * This class provides the PostgreSQL query processing implementations for persistence. + * + * @since 1.3.0 + */ +public class PostgreSQLProcessor { + + private PostgreSQLProcessor() { + } + + public static BStream query(Environment env, BObject client, BTypedesc targetType, BObject whereClause, + BObject orderClause, BObject limitClause, BObject groupByClause) { + return SQLProcessor.query(env, client, targetType, whereClause, orderClause, limitClause, groupByClause); + } + + public static Object queryOne(Environment env, BObject client, BArray path, BTypedesc targetType) { + return SQLProcessor.queryOne(env, client, path, targetType); + } + + public static Object executeNativeSQL(Environment env, BObject client, BObject paramSQLString) { + return SQLProcessor.executeNativeSQL(env, client, paramSQLString); + } + + public static BStream queryNativeSQL(Environment env, BObject client, BObject paramSQLString, + BTypedesc targetType) { + return SQLProcessor.queryNativeSQL(env, client, paramSQLString, targetType); + } + +}