From 3d4a3441498ef4e447f80197bb849e5617367c3a Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Thu, 30 Jan 2025 18:00:58 +0530 Subject: [PATCH 01/11] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 60f8c6e..3c85a79 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -111,6 +111,9 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] [[package]] org = "ballerina" @@ -475,6 +478,7 @@ org = "ballerinax" name = "persist.sql" version = "1.5.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "persist"}, From 001c333de3d57d01c05cc0602af81ccc0bde4b11 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Thu, 30 Jan 2025 19:05:44 +0530 Subject: [PATCH 02/11] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 3c85a79..60f8c6e 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -111,9 +111,6 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "lang.value"} ] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] [[package]] org = "ballerina" @@ -478,7 +475,6 @@ org = "ballerinax" name = "persist.sql" version = "1.5.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "log"}, {org = "ballerina", name = "persist"}, From 2938d21b1d5ebaf000e3be042362ca33f2b0a151 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Mon, 3 Mar 2025 15:05:26 +0530 Subject: [PATCH 03/11] [Automated] Update native jar versions in toml files --- ballerina/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 60f8c6e..9c61919 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -76,7 +76,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.13.0" +version = "2.13.3" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, From ffdeb3889b6613be9d976e91f48d8903c938d17c Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Mon, 3 Mar 2025 20:55:06 +0530 Subject: [PATCH 04/11] Add schema support for bal persist sql modules --- ballerina/annotations.bal | 10 + ballerina/build.gradle | 8 +- ballerina/metadata_types.bal | 6 +- ballerina/sql_client.bal | 27 +- ballerina/tests/Config.toml | 26 ++ .../tests/h2_api_subscription_client.bal | 6 +- .../tests/h2_hospital_persist_client.bal | 28 +- ...h2_hospital_with_schema_persist_client.bal | 276 +++++++++++++ .../tests/h2_hospital_with_schema_tests.bal | 363 ++++++++++++++++++ .../tests/h2_rainier_generated_client.bal | 12 +- .../h2_test_entities_generated_client.bal | 18 +- ballerina/tests/init-schema-tests.bal | 205 ++++++++++ ballerina/tests/init-tests.bal | 10 +- .../tests/mssql_api_subscription_client.bal | 6 +- .../tests/mssql_hospital_persist_client.bal | 28 +- ...ql_hospital_with_schema_persist_client.bal | 275 +++++++++++++ .../mssql_hospital_with_schema_tests.bal | 358 +++++++++++++++++ .../tests/mssql_rainier_generated_client.bal | 12 +- .../mssql_test_entities_generated_client.bal | 18 +- .../tests/mysql_api_subscription_client.bal | 6 +- .../tests/mysql_hospital_persist_client.bal | 8 +- .../tests/mysql_rainier_generated_client.bal | 12 +- .../mysql_test_entities_generated_client.bal | 18 +- ballerina/tests/persist/hospital.bal | 3 +- .../postgresql_api_subscription_client.bal | 6 +- ...=> postgresql_hospital_persist_client.bal} | 28 +- ...ql_hospital_with_schema_persist_client.bal | 276 +++++++++++++ .../postgresql_hospital_with_schema_tests.bal | 357 +++++++++++++++++ .../postgresql_rainier_generated_client.bal | 12 +- ...tgresql_test_entities_generated_client.bal | 18 +- ballerina/tests/resources/mssql/Dockerfile | 2 +- 31 files changed, 2335 insertions(+), 103 deletions(-) create mode 100644 ballerina/tests/h2_hospital_with_schema_persist_client.bal create mode 100644 ballerina/tests/h2_hospital_with_schema_tests.bal create mode 100644 ballerina/tests/init-schema-tests.bal create mode 100644 ballerina/tests/mssql_hospital_with_schema_persist_client.bal create mode 100644 ballerina/tests/mssql_hospital_with_schema_tests.bal rename ballerina/tests/{postgres_hospital_persist_client.bal => postgresql_hospital_persist_client.bal} (91%) create mode 100644 ballerina/tests/postgresql_hospital_with_schema_persist_client.bal create mode 100644 ballerina/tests/postgresql_hospital_with_schema_tests.bal diff --git a/ballerina/annotations.bal b/ballerina/annotations.bal index 4d4c349..30edb50 100644 --- a/ballerina/annotations.bal +++ b/ballerina/annotations.bal @@ -24,6 +24,16 @@ public type NameConfig record {| # The Annotation used to specify the mapping of an entity/field to a database table/column. public annotation NameConfig Name on type, record field; +# Groups the entity to a specific schema in the database. +# +# + value - name of the schema in the database +public type SchemaConfig record {| + string value; +|}; + +# The Annotation used to specify the schema of an entity in the database. +public annotation SchemaConfig Schema on type; + # Marks the entity field as an index field. # # + name - specify a single index name or an array of index names diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 59ca09a..89988c3 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -206,12 +206,16 @@ task stopMySQLTestDockerContainer() { task createMSSQLTestDockerImage(type: Exec) { if (!Os.isFamily(Os.FAMILY_WINDOWS)) { def standardOutput = new ByteArrayOutputStream() - commandLine 'sh', '-c', "docker build -f $project.projectDir/tests/resources/mssql/Dockerfile -t ballerina-persist-mssql" + - " -q $project.projectDir/tests/resources/mssql/" + def errorOutput = new ByteArrayOutputStream() + commandLine 'sh', '-c', "docker build -f $project.projectDir/tests/resources/mssql/Dockerfile -t ballerina-persist-mssql -q $project.projectDir/tests/resources/mssql/" doLast { checkExecResult(executionResult, 'Error', standardOutput) + println "Standard Output: ${standardOutput.toString()}" + println "Error Output: ${errorOutput.toString()}" sleep(10 * 1000) } + standardOutput = standardOutput + errorOutput = errorOutput } } diff --git a/ballerina/metadata_types.bal b/ballerina/metadata_types.bal index b7646fc..4fa6ae3 100644 --- a/ballerina/metadata_types.bal +++ b/ballerina/metadata_types.bal @@ -18,12 +18,14 @@ # # + entityName - Name of the entity # + tableName - Table name of the entity +# + schemaName - Schema name of the entity # + fieldMetadata - Metadata of all the fields of the entity # + keyFields - Names of the identity fields # + joinMetadata - Metadata of the fields that are used for `JOIN` operations public type SQLMetadata record {| string entityName; string tableName; + string schemaName?; map fieldMetadata; string[] keyFields; map joinMetadata?; @@ -67,7 +69,8 @@ public type RelationMetadata record {| # Only used by the generated persist clients and `persist:SQLClient`. # # + entity - The name of the entity that is being joined -# + fieldName - The name of the field in the `entity` that is being joined +# + fieldName - The name of the field in the `entity` that is being joined +# + refSchema - The name of the SQL schema to be joined # + refTable - The name of the SQL table to be joined # + refColumns - The names of the referenced columns of the referenced table # + joinColumns - The names of the join columns @@ -78,6 +81,7 @@ public type RelationMetadata record {| public type JoinMetadata record {| typedesc entity; string fieldName; + string refSchema?; string refTable; string[] refColumns; string[] joinColumns; diff --git a/ballerina/sql_client.bal b/ballerina/sql_client.bal index 57474f4..f390427 100644 --- a/ballerina/sql_client.bal +++ b/ballerina/sql_client.bal @@ -25,6 +25,7 @@ public isolated client class SQLClient { private final string & readonly entityName; private final string & readonly tableName; + private final string? & readonly schemaName; private final map & readonly fieldMetadata; private final string[] & readonly keyFields; private final map & readonly joinMetadata; @@ -40,6 +41,7 @@ public isolated client class SQLClient { self.tableName = metadata.tableName; self.fieldMetadata = metadata.fieldMetadata; self.keyFields = metadata.keyFields; + self.schemaName = metadata.schemaName; self.dbClient = dbClient; if metadata.joinMetadata is map { self.joinMetadata = & readonly>metadata.joinMetadata; @@ -457,12 +459,12 @@ public isolated client class SQLClient { private isolated function getInsertQueries(record {}[] insertRecords) returns sql:ParameterizedQuery[] { return from record {} insertRecord in insertRecords - select sql:queryConcat(`INSERT INTO `, stringToParameterizedQuery(self.escape(self.tableName)), ` (`, self.getInsertColumnNames(), ` ) `, `VALUES `, self.getInsertQueryParams(insertRecord)); + select sql:queryConcat(`INSERT INTO `, stringToParameterizedQuery(self.getTableName()), ` (`, self.getInsertColumnNames(), ` ) `, `VALUES `, self.getInsertQueryParams(insertRecord)); } private isolated function getSelectQuery(string[] selectableFields) returns sql:ParameterizedQuery { return sql:queryConcat( - `SELECT `, self.getSelectColumnNames(selectableFields), ` FROM `, stringToParameterizedQuery(self.escape(self.tableName)), ` AS `, stringToParameterizedQuery(self.escape(self.entityName)) + `SELECT `, self.getSelectColumnNames(selectableFields), ` FROM `, stringToParameterizedQuery(self.getTableName()), ` AS `, stringToParameterizedQuery(self.escape(self.entityName)) ); } @@ -472,16 +474,25 @@ public isolated client class SQLClient { private isolated function getUpdateQuery(record {} updateRecord) returns sql:ParameterizedQuery|persist:Error { if self.dataSourceSpecifics == MSSQL_SPECIFICS { - return sql:queryConcat(`UPDATE `, stringToParameterizedQuery(self.escape(self.entityName)), ` SET `, check self.getSetClauses(updateRecord), ` FROM `, stringToParameterizedQuery(self.escape(self.tableName)), ` `, stringToParameterizedQuery(self.escape(self.entityName))); + return sql:queryConcat(`UPDATE `, stringToParameterizedQuery(self.escape(self.entityName)), ` SET `, check self.getSetClauses(updateRecord), ` FROM `, stringToParameterizedQuery(self.getTableName()), ` `, stringToParameterizedQuery(self.escape(self.entityName))); } - return sql:queryConcat(`UPDATE `, stringToParameterizedQuery(self.escape(self.tableName)), ` AS `, stringToParameterizedQuery(self.escape(self.entityName)), ` SET `, check self.getSetClauses(updateRecord)); + return sql:queryConcat(`UPDATE `, stringToParameterizedQuery(self.getTableName()), ` AS `, stringToParameterizedQuery(self.escape(self.entityName)), ` SET `, check self.getSetClauses(updateRecord)); } private isolated function getDeleteQuery() returns sql:ParameterizedQuery { if self.dataSourceSpecifics == MSSQL_SPECIFICS { - return sql:queryConcat(`DELETE `, stringToParameterizedQuery(self.escape(self.entityName)), ` FROM `, stringToParameterizedQuery(self.escape(self.tableName)), ` AS `, stringToParameterizedQuery(self.escape(self.entityName))); + return sql:queryConcat(`DELETE `, stringToParameterizedQuery(self.escape(self.entityName)), ` FROM `, stringToParameterizedQuery(self.getTableName()), ` AS `, stringToParameterizedQuery(self.escape(self.entityName))); } - return sql:queryConcat(`DELETE FROM `, stringToParameterizedQuery(self.escape(self.tableName)), ` AS `, stringToParameterizedQuery(self.escape(self.entityName))); + return sql:queryConcat(`DELETE FROM `, stringToParameterizedQuery(self.getTableName()), ` AS `, stringToParameterizedQuery(self.escape(self.entityName))); + } + + // Constructs table name with schema name if schema name is available + private isolated function getTableName() returns string { + string? schemaName = self.schemaName; + if schemaName is () { + return self.escape(self.tableName); + } + return string `${schemaName}.${self.escape(self.tableName)}`; } private isolated function getJoinFields(string[] include) returns string[] { @@ -512,7 +523,9 @@ public isolated client class SQLClient { private isolated function getJoinQuery(string joinKey) returns sql:ParameterizedQuery|persist:Error { JoinMetadata joinMetadata = self.joinMetadata.get(joinKey); - return sql:queryConcat(` LEFT JOIN `, stringToParameterizedQuery(self.escape(joinMetadata.refTable) + " " + self.escape(joinKey)), + string refSchema = joinMetadata.refSchema ?: ""; + refSchema = joinMetadata.refSchema != () ? string `${refSchema}.` : refSchema; + return sql:queryConcat(` LEFT JOIN `, stringToParameterizedQuery(string `${refSchema}${self.escape(joinMetadata.refTable)} ${self.escape(joinKey)}`), ` ON `, check self.getJoinFilters(joinKey, joinMetadata.refColumns, joinMetadata.joinColumns)); } diff --git a/ballerina/tests/Config.toml b/ballerina/tests/Config.toml index 62d9c85..2d05cd3 100644 --- a/ballerina/tests/Config.toml +++ b/ballerina/tests/Config.toml @@ -27,3 +27,29 @@ database="test" url="jdbc:h2:./build/test" user="sa" password="" + +[mssqlWithSchema] +user="sa" +password="Test123#" +host="localhost" +port=1433 +database="testschema" +defaultSchema="persist" + +[postgresqlWithSchema] +user="postgres" +password="postgres" +host="localhost" +port=5432 +database="test" +defaultSchema="persist" + +[h2WithSchema] +url="jdbc:h2:./build/test" +user="sa" +password="" +defaultSchema="persist" + +[[ballerina.log.modules]] +name = "ballerina/persist" +level = "DEBUG" diff --git a/ballerina/tests/h2_api_subscription_client.bal b/ballerina/tests/h2_api_subscription_client.bal index c707aa5..9f29470 100644 --- a/ballerina/tests/h2_api_subscription_client.bal +++ b/ballerina/tests/h2_api_subscription_client.bal @@ -30,7 +30,7 @@ public isolated client class H2ApimClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [SUBSCRIPTION]: { entityName: "Subscription", tableName: "Subscription", @@ -72,8 +72,8 @@ public isolated client class H2ApimClient { } self.dbClient = dbClient; self.persistClients = { - [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION), H2_SPECIFICS), - [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA), H2_SPECIFICS) + [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION).cloneReadOnly(), H2_SPECIFICS), + [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA).cloneReadOnly(), H2_SPECIFICS) }; } diff --git a/ballerina/tests/h2_hospital_persist_client.bal b/ballerina/tests/h2_hospital_persist_client.bal index 9785982..e8a689a 100644 --- a/ballerina/tests/h2_hospital_persist_client.bal +++ b/ballerina/tests/h2_hospital_persist_client.bal @@ -34,7 +34,7 @@ public isolated client class H2HospitalClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [APPOINTMENT]: { entityName: "Appointment", tableName: "appointment", @@ -110,10 +110,30 @@ public isolated client class H2HospitalClient { return error(dbClient.message()); } self.dbClient = dbClient; + // Update the metadata with the schema name + if h2.defaultSchema != () { + lock { + foreach string key in self.metadata.keys() { + SQLMetadata metadata = self.metadata.get(key); + if metadata.schemaName == () { + metadata.schemaName = h2.defaultSchema; + } + map? joinMetadataMap = metadata.joinMetadata; + if joinMetadataMap != () { + foreach string joinKey in joinMetadataMap.keys() { + JoinMetadata joinMetadata = joinMetadataMap.get(joinKey); + if joinMetadata.refSchema == () { + joinMetadata.refSchema = h2.defaultSchema; + } + } + } + } + } + } self.persistClients = { - [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT), H2_SPECIFICS), - [PATIENT]: check new (dbClient, self.metadata.get(PATIENT), H2_SPECIFICS), - [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR), H2_SPECIFICS) + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), H2_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), H2_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), H2_SPECIFICS) }; } diff --git a/ballerina/tests/h2_hospital_with_schema_persist_client.bal b/ballerina/tests/h2_hospital_with_schema_persist_client.bal new file mode 100644 index 0000000..444af25 --- /dev/null +++ b/ballerina/tests/h2_hospital_with_schema_persist_client.bal @@ -0,0 +1,276 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com). +// +// 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. + +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is an auto-generated file by Ballerina persistence layer for model. +// It should not be modified by hand. +import ballerina/jballerina.java; +import ballerina/persist; +import ballerina/sql; +import ballerinax/h2.driver as _; +import ballerinax/java.jdbc as jdbc; + +const APPOINTMENT = "appointments"; +const PATIENT = "patients"; +const DOCTOR = "doctors"; + +public isolated client class H2HospitalWithSchemaClient { + *persist:AbstractPersistClient; + + private final jdbc:Client dbClient; + + private final map persistClients; + + private final record {|SQLMetadata...;|} metadata = { + [APPOINTMENT]: { + entityName: "Appointment", + tableName: "appointment", + schemaName: "hospital", + fieldMetadata: { + id: {columnName: "id"}, + reason: {columnName: "reason"}, + appointmentTime: {columnName: "appointmentTime"}, + status: {columnName: "status"}, + patientId: {columnName: "patient_id"}, + doctorId: {columnName: "doctorId"}, + "patient.id": {relation: {entityName: "patient", refField: "id", refColumn: "IDP"}}, + "patient.name": {relation: {entityName: "patient", refField: "name"}}, + "patient.age": {relation: {entityName: "patient", refField: "age"}}, + "patient.address": {relation: {entityName: "patient", refField: "address", refColumn: "ADD_RESS"}}, + "patient.phoneNumber": {relation: {entityName: "patient", refField: "phoneNumber"}}, + "patient.gender": {relation: {entityName: "patient", refField: "gender"}}, + "doctor.id": {relation: {entityName: "doctor", refField: "id"}}, + "doctor.name": {relation: {entityName: "doctor", refField: "name"}}, + "doctor.specialty": {relation: {entityName: "doctor", refField: "specialty"}}, + "doctor.phoneNumber": {relation: {entityName: "doctor", refField: "phoneNumber", refColumn: "phone_number"}}, + "doctor.salary": {relation: {entityName: "doctor", refField: "salary"}} + }, + keyFields: ["id"], + joinMetadata: { + patient: {entity: Patient, fieldName: "patient", refTable: "patients", refColumns: ["IDP"], joinColumns: ["patient_id"], 'type: ONE_TO_MANY}, + doctor: {entity: Doctor, fieldName: "doctor", refTable: "Doctor", refColumns: ["id"], joinColumns: ["doctorId"], 'type: ONE_TO_MANY} + } + }, + [PATIENT]: { + entityName: "Patient", + tableName: "patients", + fieldMetadata: { + id: {columnName: "IDP", dbGenerated: true}, + name: {columnName: "name"}, + age: {columnName: "age"}, + address: {columnName: "ADD_RESS"}, + phoneNumber: {columnName: "phoneNumber"}, + gender: {columnName: "gender"}, + "appointments[].id": {relation: {entityName: "appointments", refField: "id"}}, + "appointments[].reason": {relation: {entityName: "appointments", refField: "reason"}}, + "appointments[].appointmentTime": {relation: {entityName: "appointments", refField: "appointmentTime"}}, + "appointments[].status": {relation: {entityName: "appointments", refField: "status"}}, + "appointments[].patientId": {relation: {entityName: "appointments", refField: "patientId", refColumn: "patient_id"}}, + "appointments[].doctorId": {relation: {entityName: "appointments", refField: "doctorId"}} + }, + keyFields: ["id"], + joinMetadata: {appointments: {entity: Appointment, fieldName: "appointments", refSchema: "hospital", refTable: "appointment", refColumns: ["patient_id"], joinColumns: ["IDP"], 'type: MANY_TO_ONE}} + }, + [DOCTOR]: { + entityName: "Doctor", + tableName: "Doctor", + fieldMetadata: { + id: {columnName: "id"}, + name: {columnName: "name"}, + specialty: {columnName: "specialty"}, + phoneNumber: {columnName: "phone_number"}, + salary: {columnName: "salary"}, + "appointments[].id": {relation: {entityName: "appointments", refField: "id"}}, + "appointments[].reason": {relation: {entityName: "appointments", refField: "reason"}}, + "appointments[].appointmentTime": {relation: {entityName: "appointments", refField: "appointmentTime"}}, + "appointments[].status": {relation: {entityName: "appointments", refField: "status"}}, + "appointments[].patientId": {relation: {entityName: "appointments", refField: "patientId", refColumn: "patient_id"}}, + "appointments[].doctorId": {relation: {entityName: "appointments", refField: "doctorId"}} + }, + keyFields: ["id"], + joinMetadata: {appointments: {entity: Appointment, fieldName: "appointments", refSchema: "hospital", refTable: "appointment", refColumns: ["doctorId"], joinColumns: ["id"], 'type: MANY_TO_ONE}} + } + }; + + public isolated function init() returns persist:Error? { + jdbc:Client|error dbClient = new (url = h2WithSchema.url, user = h2WithSchema.user, password = h2WithSchema.password, options = {...h2WithSchema.connectionOptions}); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + // Update the metadata with the schema name + if h2WithSchema.defaultSchema != () { + lock { + foreach string key in self.metadata.keys() { + SQLMetadata metadata = self.metadata.get(key); + if metadata.schemaName == () { + metadata.schemaName = h2WithSchema.defaultSchema; + } + map? joinMetadataMap = metadata.joinMetadata; + if joinMetadataMap != () { + foreach string joinKey in joinMetadataMap.keys() { + JoinMetadata joinMetadata = joinMetadataMap.get(joinKey); + if joinMetadata.refSchema == () { + joinMetadata.refSchema = h2WithSchema.defaultSchema; + } + } + } + } + } + } + + self.persistClients = { + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), H2_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), H2_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), H2_SPECIFICS) + }; + } + + isolated resource function get appointments(AppointmentTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor", + name: "query" + } external; + + isolated resource function get appointments/[int id](AppointmentTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor", + name: "queryOne" + } external; + + isolated resource function post appointments(AppointmentInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from AppointmentInsert inserted in data + select inserted.id; + } + + isolated resource function put appointments/[int id](AppointmentUpdate value) returns Appointment|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/appointments/[id].get(); + } + + isolated resource function delete appointments/[int id]() returns Appointment|persist:Error { + Appointment result = check self->/appointments/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get patients(PatientTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor", + name: "query" + } external; + + isolated resource function get patients/[int id](PatientTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor", + name: "queryOne" + } external; + + isolated resource function post patients(PatientInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + sql:ExecutionResult[] result = check sqlClient.runBatchInsertQuery(data); + return from sql:ExecutionResult inserted in result + where inserted.lastInsertId != () + select inserted.lastInsertId; + } + + isolated resource function put patients/[int id](PatientUpdate value) returns Patient|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/patients/[id].get(); + } + + isolated resource function delete patients/[int id]() returns Patient|persist:Error { + Patient result = check self->/patients/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get doctors(DoctorTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor", + name: "query" + } external; + + isolated resource function get doctors/[int id](DoctorTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor", + name: "queryOne" + } external; + + isolated resource function post doctors(DoctorInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from DoctorInsert inserted in data + select inserted.id; + } + + isolated resource function put doctors/[int id](DoctorUpdate value) returns Doctor|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/doctors/[id].get(); + } + + isolated resource function delete doctors/[int id]() returns Doctor|persist:Error { + Doctor result = check self->/doctors/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + remote isolated function queryNativeSQL(sql:ParameterizedQuery sqlQuery, typedesc rowType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor" + } external; + + remote isolated function executeNativeSQL(sql:ParameterizedQuery sqlQuery) returns ExecutionResult|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.H2Processor" + } 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/h2_hospital_with_schema_tests.bal b/ballerina/tests/h2_hospital_with_schema_tests.bal new file mode 100644 index 0000000..f6473cc --- /dev/null +++ b/ballerina/tests/h2_hospital_with_schema_tests.bal @@ -0,0 +1,363 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com). +// +// 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/log; +import ballerina/persist; +import ballerina/test; + +@test:Config { + groups: ["annotation", "h2", "schema"] +} +function testCreatePatientH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + PatientInsert patient = { + name: "John K", + age: 37, + phoneNumber: "0777860000", + gender: "MALE", + address: "123, Main Street, Colombo 03" + }; + int[] unionResult = check h2DbHospital->/patients.post([patient]); + test:assertEquals(unionResult[0], 1, "Patient should be created"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"] +} +function testCreateDoctorH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + DoctorInsert doctor = { + id: 1, + name: "Dr Ruberu", + specialty: "Physician", + phoneNumber: "077100100", + salary: 50000 + }; + int[] res = check h2DbHospital->/doctors.post([doctor]); + test:assertEquals(res[0], 1, "Doctor should be created"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreateDoctorH2WithSchema] +} +function testCreateDoctorAlreadyExistsH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + DoctorInsert doctor = { + id: 1, + name: "Dr Ruberu", + specialty: "Physician", + phoneNumber: "077100100", + salary: 50000 + }; + int[]|persist:Error res = h2DbHospital->/doctors.post([doctor]); + if !(res is persist:AlreadyExistsError) { + test:assertFail("Doctor should not be created"); + } +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreatePatientH2WithSchema, testCreateDoctorH2WithSchema] +} +function testCreateAppointmentH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + AppointmentInsert appointment = { + id: 1, + patientId: 1, + doctorId: 1, + appointmentTime: {year: 2024, month: 5, day: 31, hour: 10, minute: 30}, + status: "SCHEDULED", + reason: "Headache" + }; + int[] res = check h2DbHospital->/appointments.post([appointment]); + test:assertEquals(res[0], 1, "Appointment should be created"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreatePatientH2WithSchema, testCreateDoctorH2WithSchema, testCreateAppointmentH2WithSchema] +} +function testCreateAppointmentAlreadyExistsH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + AppointmentInsert appointment = { + id: 1, + patientId: 1, + doctorId: 1, + appointmentTime: {year: 2023, month: 7, day: 1, hour: 10, minute: 30}, + status: "SCHEDULED", + reason: "Headache" + }; + int[]|persist:Error res = h2DbHospital->/appointments.post([appointment]); + if !(res is persist:AlreadyExistsError) { + test:assertFail("Appointment should not be created"); + } +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreateDoctorH2WithSchema] +} +function testGetDoctorsH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + stream doctors = h2DbHospital->/doctors.get(); + Doctor[]|persist:Error doctorsArr = from Doctor doctor in doctors + select doctor; + Doctor[] expected = [ + {id: 1, name: "Dr Ruberu", specialty: "Physician", phoneNumber: "077100100", salary: 50000} + ]; + test:assertEquals(doctorsArr, expected, "Doctor details should be returned"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreatePatientH2WithSchema] +} +function testGetPatientByIdH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + Patient|persist:Error patient = h2DbHospital->/patients/[1].get(); + Patient expected = {"id": 1, "name": "John K", "age": 37, "address": "123, Main Street, Colombo 03", "phoneNumber": "0777860000", "gender": "MALE"}; + test:assertEquals(patient, expected, "Patient details should be returned"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"] +} +function testGetPatientNotFoundH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + Patient|persist:Error patient = h2DbHospital->/patients/[10].get(); + if !(patient is persist:NotFoundError) { + test:assertFail("Patient should be not found"); + } +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreateAppointmentH2WithSchema] +} +function testGetAppointmentByDoctorH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + stream appointments = h2DbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.doctorId == 1 && + appointment.appointmentTime?.year == 2024 && + appointment.appointmentTime?.month == 5 && + appointment.appointmentTime?.day == 31 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2024, + "month": 5, + "day": 31, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "SCHEDULED", + "patient": { + "id": 1, + "name": "John K", + "age": 37, + "address": "123, Main Street, Colombo 03", + "phoneNumber": "0777860000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Dr Ruberu", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 50000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be returned"); + + stream appointments2 = h2DbHospital->/appointments(); + Appointment[]|persist:Error? filteredAppointments2 = from Appointment appointment in appointments2 + where appointment.doctorId == 5 && + appointment.appointmentTime.year == 2023 && + appointment.appointmentTime.month == 7 && + appointment.appointmentTime.day == 1 + select appointment; + test:assertEquals(filteredAppointments2, [], "Appointment details should be empty"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreateAppointmentH2WithSchema] +} +function testGetAppointmentByPatientH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + stream appointments = h2DbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.patientId == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2024, + "month": 5, + "day": 31, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "SCHEDULED", + "patient": { + "id": 1, + "name": "John K", + "age": 37, + "address": "123, Main Street, Colombo 03", + "phoneNumber": "0777860000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Dr Ruberu", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 50000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be returned"); + stream appointments2 = h2DbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments2 = from AppointmentWithRelations appointment in appointments2 + where appointment.patientId == 5 + select appointment; + test:assertEquals(filteredAppointments2, [], "Appointment details should be empty"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreateAppointmentH2WithSchema, testGetAppointmentByDoctorH2WithSchema, testGetAppointmentByPatientH2WithSchema] +} +function testPatchAppointmentH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + Appointment|persist:Error result = h2DbHospital->/appointments/[1].put({status: "STARTED"}); + if result is persist:Error { + test:assertFail("Appointment should be updated"); + } + stream appointments = h2DbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.patientId == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2024, + "month": 5, + "day": 31, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "STARTED", + "patient": { + "id": 1, + "name": "John K", + "age": 37, + "address": "123, Main Street, Colombo 03", + "phoneNumber": "0777860000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Dr Ruberu", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 50000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be updated"); + Appointment|persist:Error result2 = h2DbHospital->/appointments/[0].put({status: "STARTED"}); + if !(result2 is persist:NotFoundError) { + test:assertFail("Appointment should not be found"); + } +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testCreateAppointmentH2WithSchema, testGetAppointmentByDoctorH2WithSchema, testGetAppointmentByPatientH2WithSchema, testPatchAppointmentH2WithSchema] +} +function testDeleteAppointmentByPatientIdH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + stream appointments = h2DbHospital->/appointments; + Appointment[]|persist:Error result = from Appointment appointment in appointments + where appointment.patientId == 1 + && appointment.appointmentTime.year == 2024 + && appointment.appointmentTime.month == 5 + && appointment.appointmentTime.day == 31 + select appointment; + if (result is persist:Error) { + test:assertFail("Appointment should be found"); + } + foreach Appointment appointment in result { + Appointment|persist:Error result2 = h2DbHospital->/appointments/[appointment.id].delete(); + if result2 is persist:Error { + test:assertFail("Appointment should be deleted"); + } + } + stream appointments2 = h2DbHospital->/appointments; + Appointment[]|persist:Error result3 = from Appointment appointment in appointments2 + where appointment.patientId == 1 + && appointment.appointmentTime.year == 2024 + && appointment.appointmentTime.month == 5 + && appointment.appointmentTime.day == 31 + select appointment; + test:assertEquals(result3, [], "Appointment details should be empty"); +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testGetPatientByIdH2WithSchema, testDeleteAppointmentByPatientIdH2WithSchema] +} +function testDeletePatientH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + Patient|persist:Error result = h2DbHospital->/patients/[1].delete(); + if result is persist:Error { + log:printError("Error: ", result); + test:assertFail("Patient should be deleted"); + } +} + +@test:Config { + groups: ["annotation", "h2", "schema"], + dependsOn: [testGetDoctorsH2WithSchema, testDeleteAppointmentByPatientIdH2WithSchema] +} +function testDeleteDoctorH2WithSchema() returns error? { + H2HospitalWithSchemaClient h2DbHospital = check new (); + Doctor|persist:Error result = h2DbHospital->/doctors/[1].delete(); + if result is persist:Error { + log:printError("Error: ", result); + test:assertFail("Patient should be deleted"); + } +} diff --git a/ballerina/tests/h2_rainier_generated_client.bal b/ballerina/tests/h2_rainier_generated_client.bal index d3203f5..78348c1 100644 --- a/ballerina/tests/h2_rainier_generated_client.bal +++ b/ballerina/tests/h2_rainier_generated_client.bal @@ -32,7 +32,7 @@ public isolated client class H2RainierClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [EMPLOYEE] : { entityName: "Employee", tableName: "Employee", @@ -140,11 +140,11 @@ public isolated client class H2RainierClient { } self.dbClient = dbClient; self.persistClients = { - [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE), H2_SPECIFICS), - [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE), H2_SPECIFICS), - [BUILDING] : check new (dbClient, self.metadata.get(BUILDING), H2_SPECIFICS), - [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT), H2_SPECIFICS), - [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM), H2_SPECIFICS) + [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE).cloneReadOnly(), H2_SPECIFICS), + [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE).cloneReadOnly(), H2_SPECIFICS), + [BUILDING] : check new (dbClient, self.metadata.get(BUILDING).cloneReadOnly(), H2_SPECIFICS), + [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT).cloneReadOnly(), H2_SPECIFICS), + [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM).cloneReadOnly(), H2_SPECIFICS) }; } diff --git a/ballerina/tests/h2_test_entities_generated_client.bal b/ballerina/tests/h2_test_entities_generated_client.bal index d530b39..78d954c 100644 --- a/ballerina/tests/h2_test_entities_generated_client.bal +++ b/ballerina/tests/h2_test_entities_generated_client.bal @@ -35,7 +35,7 @@ public isolated client class H2TestEntitiesClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [ALL_TYPES] : { entityName: "AllTypes", tableName: "AllTypes", @@ -159,14 +159,14 @@ public isolated client class H2TestEntitiesClient { } self.dbClient = dbClient; self.persistClients = { - [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES), H2_SPECIFICS), - [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD), H2_SPECIFICS), - [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD), H2_SPECIFICS), - [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD), H2_SPECIFICS), - [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD), H2_SPECIFICS), - [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD), H2_SPECIFICS), - [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD), H2_SPECIFICS), - [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD), H2_SPECIFICS) + [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES).cloneReadOnly(), H2_SPECIFICS), + [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD).cloneReadOnly(), H2_SPECIFICS), + [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD).cloneReadOnly(), H2_SPECIFICS), + [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD).cloneReadOnly(), H2_SPECIFICS), + [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD).cloneReadOnly(), H2_SPECIFICS), + [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD).cloneReadOnly(), H2_SPECIFICS), + [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD).cloneReadOnly(), H2_SPECIFICS), + [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD).cloneReadOnly(), H2_SPECIFICS) }; } diff --git a/ballerina/tests/init-schema-tests.bal b/ballerina/tests/init-schema-tests.bal new file mode 100644 index 0000000..4eb89e3 --- /dev/null +++ b/ballerina/tests/init-schema-tests.bal @@ -0,0 +1,205 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// 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 ballerinax/h2.driver as _; +import ballerinax/java.jdbc; +import ballerinax/mssql; +import ballerinax/mssql.driver as _; +import ballerinax/postgresql; +import ballerinax/postgresql.driver as _; + +configurable record {| + string url; + string user; + string password; + string? defaultSchema = (); + jdbc:Options connectionOptions = {}; +|} & readonly h2WithSchema = ?; + +configurable record {| + int port; + string host; + string user; + string database; + string password; + string? defaultSchema = (); + mssql:Options connectionOptions = {}; +|} & readonly mssqlWithSchema = ?; + +configurable record {| + int port; + string host; + string user; + string database; + string password; + string? defaultSchema = (); + postgresql:Options connectionOptions = {}; +|} & readonly postgresqlWithSchema = ?; + +@test:BeforeSuite +function initSuiteWithSchema() returns error? { + check initPostgreSqlTestsWithSchema(); + check initH2TestsWithSchema(); + check initMsSqlTestsWithSchema(); +} + +function initMsSqlTestsWithSchema() returns error? { + mssql:Client mssqlDbClient = check new (host = mssqlWithSchema.host, user = mssqlWithSchema.user, password = mssqlWithSchema.password, port = mssqlWithSchema.port); + // create `persist` schema and set it as the default schema + + _ = check mssqlDbClient->execute(`CREATE DATABASE testschema`); + + _ = check mssqlDbClient.close(); + + mssqlDbClient = check new (host = mssqlWithSchema.host, user = mssqlWithSchema.user, password = mssqlWithSchema.password, database = mssqlWithSchema.database, port = mssqlWithSchema.port); + + _ = check mssqlDbClient->call(`IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = 'persist') + BEGIN + EXEC ('CREATE SCHEMA persist AUTHORIZATION dbo'); + END`); + _ = check mssqlDbClient->call(`IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = 'hospital') + BEGIN + EXEC ('CREATE SCHEMA hospital AUTHORIZATION dbo'); + END`); + + _ = check mssqlDbClient->execute(` + CREATE TABLE persist.[Doctor] ( + [id] INT NOT NULL, + [name] VARCHAR(191) NOT NULL, + [specialty] VARCHAR(191) NOT NULL, + [phone_number] VARCHAR(191) NOT NULL, + [salary] DECIMAL(10,2), + PRIMARY KEY([id]) + ); + `); + _ = check mssqlDbClient->execute(` + CREATE TABLE persist.[patients] ( + [IDP] INT IDENTITY(1,1), + [name] VARCHAR(191) NOT NULL, + [age] INT NOT NULL, + [ADD_RESS] VARCHAR(191) NOT NULL, + [phoneNumber] CHAR(10) NOT NULL, + [gender] VARCHAR(6) CHECK ([gender] IN ('MALE', 'FEMALE')) NOT NULL, + PRIMARY KEY([IDP]) + ); + `); + _ = check mssqlDbClient->execute(` + CREATE TABLE hospital.[appointment] ( + [id] INT NOT NULL, + [reason] VARCHAR(191) NOT NULL, + [appointmentTime] DATETIME2 NOT NULL, + [status] VARCHAR(9) CHECK ([status] IN ('SCHEDULED', 'STARTED', 'ENDED')) NOT NULL, + [patient_id] INT NOT NULL, + FOREIGN KEY([patient_id]) REFERENCES persist.[patients]([IDP]), + [doctorId] INT NOT NULL, + FOREIGN KEY([doctorId]) REFERENCES persist.[Doctor]([id]), + PRIMARY KEY([id]) + ); + `); + check mssqlDbClient.close(); +} + +function initPostgreSqlTestsWithSchema() returns error? { + postgresql:Client postgresqlDbClient = check new (host = postgresqlWithSchema.host, username = postgresqlWithSchema.user, password = postgresqlWithSchema.password, database = postgresqlWithSchema.database, port = postgresqlWithSchema.port); + _ = check postgresqlDbClient->execute(`CREATE SCHEMA persist`); + _ = check postgresqlDbClient->execute(`CREATE SCHEMA hospital`); + _ = check postgresqlDbClient->execute(`DROP TABLE IF EXISTS persist."Doctor"`); + _ = check postgresqlDbClient->execute(` + CREATE TABLE persist."Doctor" ( + "id" INT NOT NULL, + "name" VARCHAR(191) NOT NULL, + "specialty" VARCHAR(191) NOT NULL, + "phone_number" VARCHAR(191) NOT NULL, + "salary" DECIMAL(10,2), + PRIMARY KEY("id") + ); + `); + _ = check postgresqlDbClient->execute(`DROP TABLE IF EXISTS persist."patients"`); + _ = check postgresqlDbClient->execute(` + CREATE TABLE persist."patients" ( + "IDP" SERIAL, + "name" VARCHAR(191) NOT NULL, + "age" INT NOT NULL, + "ADD_RESS" VARCHAR(191) NOT NULL, + "phoneNumber" CHAR(10) NOT NULL, + "gender" VARCHAR(6) CHECK ("gender" IN ('MALE', 'FEMALE')) NOT NULL, + PRIMARY KEY("IDP") + ); + `); + + _ = check postgresqlDbClient->execute(`DROP TABLE IF EXISTS hospital."appointment"`); + _ = check postgresqlDbClient->execute(`CREATE TABLE hospital."appointment" ( + "id" INT NOT NULL, + "reason" VARCHAR(191) NOT NULL, + "appointmentTime" TIMESTAMP NOT NULL, + "status" VARCHAR(9) CHECK ("status" IN ('SCHEDULED', 'STARTED', 'ENDED')) NOT NULL, + "patient_id" INT NOT NULL, + FOREIGN KEY("patient_id") REFERENCES persist."patients"("IDP"), + "doctorId" INT NOT NULL, + FOREIGN KEY("doctorId") REFERENCES persist."Doctor"("id"), + PRIMARY KEY("id") + );`); + check postgresqlDbClient.close(); +} + +function initH2TestsWithSchema() returns error? { + jdbc:Client h2DbClient = check new (url = h2WithSchema.url, user = h2WithSchema.user, password = h2WithSchema.password); + // create `persist` schema and set it as the default schema + _ = check h2DbClient->execute(`CREATE SCHEMA persist`); + _ = check h2DbClient->execute(`CREATE SCHEMA hospital`); + _ = check h2DbClient->execute(`SET SCHEMA persist`); + + _ = check h2DbClient->execute(`DROP TABLE IF EXISTS "Doctor"`); + _ = check h2DbClient->execute(` + CREATE TABLE "Doctor" ( + "id" INT NOT NULL, + "name" VARCHAR(191) NOT NULL, + "specialty" VARCHAR(191) NOT NULL, + "phone_number" VARCHAR(191) NOT NULL, + "salary" DECIMAL(10,2), + PRIMARY KEY("id") + ); + `); + + _ = check h2DbClient->execute(`DROP TABLE IF EXISTS "patients"`); + _ = check h2DbClient->execute(` + CREATE TABLE "patients" ( + "IDP" INT AUTO_INCREMENT, + "name" VARCHAR(191) NOT NULL, + "age" INT NOT NULL, + "ADD_RESS" VARCHAR(191) NOT NULL, + "phoneNumber" CHAR(10) NOT NULL, + "gender" VARCHAR(6) CHECK ("gender" IN ('MALE', 'FEMALE')) NOT NULL, + PRIMARY KEY("IDP") + ); + `); + + _ = check h2DbClient->execute(`DROP TABLE IF EXISTS hospital."appointment"`); + _ = check h2DbClient->execute(`CREATE TABLE hospital."appointment" ( + "id" INT NOT NULL, + "reason" VARCHAR(191) NOT NULL, + "appointmentTime" DATETIME NOT NULL, + "status" VARCHAR(9) CHECK ("status" IN ('SCHEDULED', 'STARTED', 'ENDED')) NOT NULL, + "patient_id" INT NOT NULL, + FOREIGN KEY("patient_id") REFERENCES persist."patients"("IDP"), + "doctorId" INT NOT NULL, + FOREIGN KEY("doctorId") REFERENCES persist."Doctor"("id"), + PRIMARY KEY("id") + )`); + + check h2DbClient.close(); +} diff --git a/ballerina/tests/init-tests.bal b/ballerina/tests/init-tests.bal index cc769c7..8615922 100644 --- a/ballerina/tests/init-tests.bal +++ b/ballerina/tests/init-tests.bal @@ -29,6 +29,7 @@ configurable record {| string url; string user; string password; + string? defaultSchema = (); jdbc:Options connectionOptions = {}; |} & readonly h2 = ?; @@ -38,8 +39,9 @@ configurable record {| string user; string database; string password; + string? defaultSchema = (); mysql:Options connectionOptions = {}; -|} mysql = ?; +|} & readonly mysql = ?; configurable record {| int port; @@ -47,8 +49,9 @@ configurable record {| string user; string database; string password; + string? defaultSchema = (); mssql:Options connectionOptions = {}; -|} mssql = ?; +|} & readonly mssql = ?; configurable record {| int port; @@ -56,8 +59,9 @@ configurable record {| string user; string database; string password; + string? defaultSchema = (); postgresql:Options connectionOptions = {}; -|} postgresql = ?; +|} & readonly postgresql = ?; @test:BeforeSuite function initSuite() returns error? { diff --git a/ballerina/tests/mssql_api_subscription_client.bal b/ballerina/tests/mssql_api_subscription_client.bal index b16d2ab..5f4a94a 100644 --- a/ballerina/tests/mssql_api_subscription_client.bal +++ b/ballerina/tests/mssql_api_subscription_client.bal @@ -30,7 +30,7 @@ public isolated client class MsSqlApimClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [SUBSCRIPTION]: { entityName: "Subscription", tableName: "Subscription", @@ -72,8 +72,8 @@ public isolated client class MsSqlApimClient { } self.dbClient = dbClient; self.persistClients = { - [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION), MSSQL_SPECIFICS), - [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA), MSSQL_SPECIFICS) + [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION).cloneReadOnly(), MSSQL_SPECIFICS), + [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA).cloneReadOnly(), MSSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mssql_hospital_persist_client.bal b/ballerina/tests/mssql_hospital_persist_client.bal index 3477429..b89bcd6 100644 --- a/ballerina/tests/mssql_hospital_persist_client.bal +++ b/ballerina/tests/mssql_hospital_persist_client.bal @@ -34,7 +34,7 @@ public isolated client class MsSqlHospitalClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [APPOINTMENT]: { entityName: "Appointment", tableName: "appointment", @@ -110,10 +110,30 @@ public isolated client class MsSqlHospitalClient { return error(dbClient.message()); } self.dbClient = dbClient; + // Update the metadata with the schema name + if mssql.defaultSchema != () { + lock { + foreach string key in self.metadata.keys() { + SQLMetadata metadata = self.metadata.get(key); + if metadata.schemaName == () { + metadata.schemaName = mssql.defaultSchema; + } + map? joinMetadataMap = metadata.joinMetadata; + if joinMetadataMap != () { + foreach string joinKey in joinMetadataMap.keys() { + JoinMetadata joinMetadata = joinMetadataMap.get(joinKey); + if joinMetadata.refSchema == () { + joinMetadata.refSchema = mssql.defaultSchema; + } + } + } + } + } + } self.persistClients = { - [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT), MSSQL_SPECIFICS), - [PATIENT]: check new (dbClient, self.metadata.get(PATIENT), MSSQL_SPECIFICS), - [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR), MSSQL_SPECIFICS) + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), MSSQL_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), MSSQL_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), MSSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mssql_hospital_with_schema_persist_client.bal b/ballerina/tests/mssql_hospital_with_schema_persist_client.bal new file mode 100644 index 0000000..5014d2e --- /dev/null +++ b/ballerina/tests/mssql_hospital_with_schema_persist_client.bal @@ -0,0 +1,275 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// 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. + +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is an auto-generated file by Ballerina persistence layer for model. +// It should not be modified by hand. +import ballerina/jballerina.java; +import ballerina/persist; +import ballerina/sql; +import ballerinax/mssql; +import ballerinax/mssql.driver as _; + +const APPOINTMENT = "appointments"; +const PATIENT = "patients"; +const DOCTOR = "doctors"; + +public isolated client class MsSqlHospitalWithSchemaClient { + *persist:AbstractPersistClient; + + private final mssql:Client dbClient; + + private final map persistClients; + + private final record {|SQLMetadata...;|} metadata = { + [APPOINTMENT]: { + entityName: "Appointment", + tableName: "appointment", + schemaName: "hospital", + fieldMetadata: { + id: {columnName: "id"}, + reason: {columnName: "reason"}, + appointmentTime: {columnName: "appointmentTime"}, + status: {columnName: "status"}, + patientId: {columnName: "patient_id"}, + doctorId: {columnName: "doctorId"}, + "patient.id": {relation: {entityName: "patient", refField: "id", refColumn: "IDP"}}, + "patient.name": {relation: {entityName: "patient", refField: "name"}}, + "patient.age": {relation: {entityName: "patient", refField: "age"}}, + "patient.address": {relation: {entityName: "patient", refField: "address", refColumn: "ADD_RESS"}}, + "patient.phoneNumber": {relation: {entityName: "patient", refField: "phoneNumber"}}, + "patient.gender": {relation: {entityName: "patient", refField: "gender"}}, + "doctor.id": {relation: {entityName: "doctor", refField: "id"}}, + "doctor.name": {relation: {entityName: "doctor", refField: "name"}}, + "doctor.specialty": {relation: {entityName: "doctor", refField: "specialty"}}, + "doctor.phoneNumber": {relation: {entityName: "doctor", refField: "phoneNumber", refColumn: "phone_number"}}, + "doctor.salary": {relation: {entityName: "doctor", refField: "salary"}} + }, + keyFields: ["id"], + joinMetadata: { + patient: {entity: Patient, fieldName: "patient", refTable: "patients", refColumns: ["IDP"], joinColumns: ["patient_id"], 'type: ONE_TO_MANY}, + doctor: {entity: Doctor, fieldName: "doctor", refTable: "Doctor", refColumns: ["id"], joinColumns: ["doctorId"], 'type: ONE_TO_MANY} + } + }, + [PATIENT]: { + entityName: "Patient", + tableName: "patients", + fieldMetadata: { + id: {columnName: "IDP", dbGenerated: true}, + name: {columnName: "name"}, + age: {columnName: "age"}, + address: {columnName: "ADD_RESS"}, + phoneNumber: {columnName: "phoneNumber"}, + gender: {columnName: "gender"}, + "appointments[].id": {relation: {entityName: "appointments", refField: "id"}}, + "appointments[].reason": {relation: {entityName: "appointments", refField: "reason"}}, + "appointments[].appointmentTime": {relation: {entityName: "appointments", refField: "appointmentTime"}}, + "appointments[].status": {relation: {entityName: "appointments", refField: "status"}}, + "appointments[].patientId": {relation: {entityName: "appointments", refField: "patientId", refColumn: "patient_id"}}, + "appointments[].doctorId": {relation: {entityName: "appointments", refField: "doctorId"}} + }, + keyFields: ["id"], + joinMetadata: {appointments: {entity: Appointment, fieldName: "appointments", refTable: "appointment", refSchema: "hospital", refColumns: ["patient_id"], joinColumns: ["IDP"], 'type: MANY_TO_ONE}} + }, + [DOCTOR]: { + entityName: "Doctor", + tableName: "Doctor", + fieldMetadata: { + id: {columnName: "id"}, + name: {columnName: "name"}, + specialty: {columnName: "specialty"}, + phoneNumber: {columnName: "phone_number"}, + salary: {columnName: "salary"}, + "appointments[].id": {relation: {entityName: "appointments", refField: "id"}}, + "appointments[].reason": {relation: {entityName: "appointments", refField: "reason"}}, + "appointments[].appointmentTime": {relation: {entityName: "appointments", refField: "appointmentTime"}}, + "appointments[].status": {relation: {entityName: "appointments", refField: "status"}}, + "appointments[].patientId": {relation: {entityName: "appointments", refField: "patientId", refColumn: "patient_id"}}, + "appointments[].doctorId": {relation: {entityName: "appointments", refField: "doctorId"}} + }, + keyFields: ["id"], + joinMetadata: {appointments: {entity: Appointment, fieldName: "appointments", refTable: "appointment", refSchema: "hospital", refColumns: ["doctorId"], joinColumns: ["id"], 'type: MANY_TO_ONE}} + } + }; + + public isolated function init() returns persist:Error? { + mssql:Client|error dbClient = new (host = mssqlWithSchema.host, user = mssqlWithSchema.user, password = mssqlWithSchema.password, database = mssqlWithSchema.database, port = mssqlWithSchema.port); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + // Update the metadata with the schema name + if mssqlWithSchema.defaultSchema != () { + lock { + foreach string key in self.metadata.keys() { + SQLMetadata metadata = self.metadata.get(key); + if metadata.schemaName == () { + metadata.schemaName = mssqlWithSchema.defaultSchema; + } + map? joinMetadataMap = metadata.joinMetadata; + if joinMetadataMap != () { + foreach string joinKey in joinMetadataMap.keys() { + JoinMetadata joinMetadata = joinMetadataMap.get(joinKey); + if joinMetadata.refSchema == () { + joinMetadata.refSchema = mssqlWithSchema.defaultSchema; + } + } + } + } + } + } + self.persistClients = { + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), MSSQL_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), MSSQL_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), MSSQL_SPECIFICS) + }; + } + + isolated resource function get appointments(AppointmentTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor", + name: "query" + } external; + + isolated resource function get appointments/[int id](AppointmentTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post appointments(AppointmentInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from AppointmentInsert inserted in data + select inserted.id; + } + + isolated resource function put appointments/[int id](AppointmentUpdate value) returns Appointment|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/appointments/[id].get(); + } + + isolated resource function delete appointments/[int id]() returns Appointment|persist:Error { + Appointment result = check self->/appointments/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get patients(PatientTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor", + name: "query" + } external; + + isolated resource function get patients/[int id](PatientTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post patients(PatientInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + sql:ExecutionResult[] result = check sqlClient.runBatchInsertQuery(data); + return from sql:ExecutionResult inserted in result + where inserted.lastInsertId != () + select inserted.lastInsertId; + } + + isolated resource function put patients/[int id](PatientUpdate value) returns Patient|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/patients/[id].get(); + } + + isolated resource function delete patients/[int id]() returns Patient|persist:Error { + Patient result = check self->/patients/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get doctors(DoctorTargetType targetType = <>, sql:ParameterizedQuery whereClause = ``, sql:ParameterizedQuery orderByClause = ``, sql:ParameterizedQuery limitClause = ``, sql:ParameterizedQuery groupByClause = ``) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor", + name: "query" + } external; + + isolated resource function get doctors/[int id](DoctorTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post doctors(DoctorInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from DoctorInsert inserted in data + select inserted.id; + } + + isolated resource function put doctors/[int id](DoctorUpdate value) returns Doctor|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/doctors/[id].get(); + } + + isolated resource function delete doctors/[int id]() returns Doctor|persist:Error { + Doctor result = check self->/doctors/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + remote isolated function queryNativeSQL(sql:ParameterizedQuery sqlQuery, typedesc rowType = <>) returns stream = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor" + } external; + + remote isolated function executeNativeSQL(sql:ParameterizedQuery sqlQuery) returns ExecutionResult|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.MSSQLProcessor" + } 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/mssql_hospital_with_schema_tests.bal b/ballerina/tests/mssql_hospital_with_schema_tests.bal new file mode 100644 index 0000000..a3c0b89 --- /dev/null +++ b/ballerina/tests/mssql_hospital_with_schema_tests.bal @@ -0,0 +1,358 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// 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/test; + + +@test:Config{ + groups: ["annotation", "mssql", "schema"] +} +function testCreatePatientMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + PatientInsert patient = { + name: "John Doe", + age: 30, + phoneNumber: "0771690000", + gender: "MALE", + address: "123, Main Street, Colombo 05" + }; + _ = check mssqlDbHospital->/patients.post([patient]); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"] +} +function testCreateDoctorMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + DoctorInsert doctor = { + id: 1, + name: "Doctor Mouse", + specialty: "Physician", + phoneNumber: "077100100", + salary: 20000 + }; + _ = check mssqlDbHospital->/doctors.post([doctor]); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreateDoctorMsSqlWithSchema] +} +function testCreateDoctorAlreadyExistsMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + DoctorInsert doctor = { + id: 1, + name: "Doctor Mouse", + specialty: "Physician", + phoneNumber: "077100100", + salary: 20000.00 + }; + int[]|persist:Error res = mssqlDbHospital->/doctors.post([doctor]); + if !(res is persist:AlreadyExistsError) { + test:assertFail("Doctor should not be created"); + } +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreatePatientMsSqlWithSchema, testCreateDoctorMsSqlWithSchema] +} +function testCreateAppointmentMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + AppointmentInsert appointment = { + id: 1, + patientId: 1, + doctorId: 1, + appointmentTime: {year: 2023, month: 7, day: 1, hour: 10, minute: 30}, + status: "SCHEDULED", + reason: "Headache" + }; + _ = check mssqlDbHospital->/appointments.post([appointment]); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreatePatientMsSqlWithSchema, testCreateDoctorMsSqlWithSchema, testCreateAppointmentMsSqlWithSchema] +} +function testCreateAppointmentAlreadyExistsMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + AppointmentInsert appointment = { + id: 1, + patientId: 1, + doctorId: 1, + appointmentTime: {year: 2023, month: 7, day: 1, hour: 10, minute: 30}, + status: "SCHEDULED", + reason: "Headache" + }; + int[]|persist:Error res = mssqlDbHospital->/appointments.post([appointment]); + if !(res is persist:AlreadyExistsError) { + test:assertFail("Appointment should not be created"); + } +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreateDoctorMsSqlWithSchema] +} +function testGetDoctorsMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + stream doctors = mssqlDbHospital->/doctors.get(); + Doctor[]|persist:Error doctorsArr = from Doctor doctor in doctors select doctor; + Doctor[] expected = [ + {id: 1, name: "Doctor Mouse", specialty: "Physician", phoneNumber: "077100100", salary: 20000} + ]; + test:assertEquals(doctorsArr, expected, "Doctor details should be returned"); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreatePatientMsSqlWithSchema] +} +function testGetPatientByIdMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + Patient|persist:Error patient = mssqlDbHospital->/patients/[1].get(); + Patient expected = {"id":1, "name": "John Doe", "age": 30, "address": "123, Main Street, Colombo 05", "phoneNumber":"0771690000", "gender":"MALE"}; + test:assertEquals(patient, expected, "Patient details should be returned"); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"] +} +function testGetPatientNotFoundMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + Patient|persist:Error patient = mssqlDbHospital->/patients/[10].get(); + if !(patient is persist:NotFoundError) { + test:assertFail("Patient should be not found"); + } +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreateAppointmentMsSqlWithSchema] +} +function testGetAppointmentByDoctorMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + stream appointments = mssqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.doctorId == 1 && + appointment.appointmentTime?.year == 2023 && + appointment.appointmentTime?.month == 7 && + appointment.appointmentTime?.day == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2023, + "month": 7, + "day": 1, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "SCHEDULED", + "patient": { + "id": 1, + "name": "John Doe", + "age": 30, + "address": "123, Main Street, Colombo 05", + "phoneNumber": "0771690000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Doctor Mouse", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 20000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be returned"); + + stream appointments2 = mssqlDbHospital->/appointments(); + Appointment[]|persist:Error? filteredAppointments2 = from Appointment appointment in appointments2 + where appointment.doctorId == 5 && + appointment.appointmentTime.year == 2023 && + appointment.appointmentTime.month == 7 && + appointment.appointmentTime.day == 1 + select appointment; + test:assertEquals(filteredAppointments2, [], "Appointment details should be empty"); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreateAppointmentMsSqlWithSchema] +} +function testGetAppointmentByPatientMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + stream appointments = mssqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.patientId == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2023, + "month": 7, + "day": 1, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "SCHEDULED", + "patient": { + "id": 1, + "name": "John Doe", + "age": 30, + "address": "123, Main Street, Colombo 05", + "phoneNumber": "0771690000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Doctor Mouse", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 20000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be returned"); + stream appointments2 = mssqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments2 = from AppointmentWithRelations appointment in appointments2 + where appointment.patientId == 5 + select appointment; + test:assertEquals(filteredAppointments2, [], "Appointment details should be empty"); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreateAppointmentMsSqlWithSchema, testGetAppointmentByDoctorMsSqlWithSchema, testGetAppointmentByPatientMsSqlWithSchema] +} +function testPatchAppointmentMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + Appointment|persist:Error result = mssqlDbHospital->/appointments/[1].put({status: "STARTED"}); + if result is persist:Error { + test:assertFail("Appointment should be updated"); + } + stream appointments = mssqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.patientId == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2023, + "month": 7, + "day": 1, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "STARTED", + "patient": { + "id": 1, + "name": "John Doe", + "age": 30, + "address": "123, Main Street, Colombo 05", + "phoneNumber": "0771690000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Doctor Mouse", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 20000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be updated"); + Appointment|persist:Error result2 = mssqlDbHospital->/appointments/[0].put({status: "STARTED"}); + if !(result2 is persist:NotFoundError) { + test:assertFail("Appointment should not be found"); + } +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testCreateAppointmentMsSqlWithSchema, testGetAppointmentByDoctorMsSqlWithSchema, testGetAppointmentByPatientMsSqlWithSchema, testPatchAppointmentMsSqlWithSchema] +} +function testDeleteAppointmentByPatientIdMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + stream appointments = mssqlDbHospital->/appointments; + Appointment[]|persist:Error result = from Appointment appointment in appointments + where appointment.patientId == 1 + && appointment.appointmentTime.year == 2023 + && appointment.appointmentTime.month == 7 + && appointment.appointmentTime.day == 1 + select appointment; + if (result is persist:Error) { + test:assertFail("Appointment should be found"); + } + foreach Appointment appointment in result { + Appointment|persist:Error result2 = mssqlDbHospital->/appointments/[appointment.id].delete(); + if result2 is persist:Error { + test:assertFail("Appointment should be deleted"); + } + } + stream appointments2 = mssqlDbHospital->/appointments; + Appointment[]|persist:Error result3 = from Appointment appointment in appointments2 + where appointment.patientId == 1 + && appointment.appointmentTime.year == 2023 + && appointment.appointmentTime.month == 7 + && appointment.appointmentTime.day == 1 + select appointment; + test:assertEquals(result3, [], "Appointment details should be empty"); +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testGetPatientByIdMsSqlWithSchema, testDeleteAppointmentByPatientIdMsSqlWithSchema] +} +function testDeletePatientMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + Patient|persist:Error result = mssqlDbHospital->/patients/[1].delete(); + if result is persist:Error { + test:assertFail("Patient should be deleted"); + } +} + +@test:Config{ + groups: ["annotation", "mssql", "schema"], + dependsOn: [testGetDoctorsMsSqlWithSchema, testDeleteAppointmentByPatientIdMsSqlWithSchema], + enable: true +} +function testDeleteDoctorMsSqlWithSchema() returns error? { + MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); + Doctor|persist:Error result = mssqlDbHospital->/doctors/[1].delete(); + if result is persist:Error { + test:assertFail("Patient should be deleted"); + } +} diff --git a/ballerina/tests/mssql_rainier_generated_client.bal b/ballerina/tests/mssql_rainier_generated_client.bal index 73e16a2..30ba37c 100644 --- a/ballerina/tests/mssql_rainier_generated_client.bal +++ b/ballerina/tests/mssql_rainier_generated_client.bal @@ -33,7 +33,7 @@ public isolated client class MSSQLRainierClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [EMPLOYEE] : { entityName: "Employee", tableName: "Employee", @@ -141,11 +141,11 @@ public isolated client class MSSQLRainierClient { } self.dbClient = dbClient; self.persistClients = { - [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE), MSSQL_SPECIFICS), - [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE), MSSQL_SPECIFICS), - [BUILDING] : check new (dbClient, self.metadata.get(BUILDING), MSSQL_SPECIFICS), - [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT), MSSQL_SPECIFICS), - [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM), MSSQL_SPECIFICS) + [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE).cloneReadOnly(), MSSQL_SPECIFICS), + [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE).cloneReadOnly(), MSSQL_SPECIFICS), + [BUILDING] : check new (dbClient, self.metadata.get(BUILDING).cloneReadOnly(), MSSQL_SPECIFICS), + [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT).cloneReadOnly(), MSSQL_SPECIFICS), + [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM).cloneReadOnly(), MSSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mssql_test_entities_generated_client.bal b/ballerina/tests/mssql_test_entities_generated_client.bal index 4bb79f7..4c91433 100644 --- a/ballerina/tests/mssql_test_entities_generated_client.bal +++ b/ballerina/tests/mssql_test_entities_generated_client.bal @@ -36,7 +36,7 @@ public isolated client class MSSQLTestEntitiesClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [ALL_TYPES] : { entityName: "AllTypes", tableName: "AllTypes", @@ -160,14 +160,14 @@ public isolated client class MSSQLTestEntitiesClient { } self.dbClient = dbClient; self.persistClients = { - [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES), MSSQL_SPECIFICS), - [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD), MSSQL_SPECIFICS), - [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD), MSSQL_SPECIFICS), - [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD), MSSQL_SPECIFICS), - [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD), MSSQL_SPECIFICS), - [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD), MSSQL_SPECIFICS), - [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD), MSSQL_SPECIFICS), - [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD), MSSQL_SPECIFICS) + [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES).cloneReadOnly(), MSSQL_SPECIFICS), + [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD).cloneReadOnly(), MSSQL_SPECIFICS), + [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD).cloneReadOnly(), MSSQL_SPECIFICS), + [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD).cloneReadOnly(), MSSQL_SPECIFICS), + [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD).cloneReadOnly(), MSSQL_SPECIFICS), + [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD).cloneReadOnly(), MSSQL_SPECIFICS), + [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD).cloneReadOnly(), MSSQL_SPECIFICS), + [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD).cloneReadOnly(), MSSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mysql_api_subscription_client.bal b/ballerina/tests/mysql_api_subscription_client.bal index 0793ecc..ad6f669 100644 --- a/ballerina/tests/mysql_api_subscription_client.bal +++ b/ballerina/tests/mysql_api_subscription_client.bal @@ -30,7 +30,7 @@ public isolated client class MySqlApimClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [SUBSCRIPTION]: { entityName: "Subscription", tableName: "Subscription", @@ -72,8 +72,8 @@ public isolated client class MySqlApimClient { } self.dbClient = dbClient; self.persistClients = { - [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION), MYSQL_SPECIFICS), - [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA), MYSQL_SPECIFICS) + [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION).cloneReadOnly(), MYSQL_SPECIFICS), + [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA).cloneReadOnly(), MYSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mysql_hospital_persist_client.bal b/ballerina/tests/mysql_hospital_persist_client.bal index 45f2bcb..073a383 100644 --- a/ballerina/tests/mysql_hospital_persist_client.bal +++ b/ballerina/tests/mysql_hospital_persist_client.bal @@ -34,7 +34,7 @@ public isolated client class MySqlHospitalClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [APPOINTMENT]: { entityName: "Appointment", tableName: "appointment", @@ -111,9 +111,9 @@ public isolated client class MySqlHospitalClient { } self.dbClient = dbClient; self.persistClients = { - [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT), MYSQL_SPECIFICS), - [PATIENT]: check new (dbClient, self.metadata.get(PATIENT), MYSQL_SPECIFICS), - [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR), MYSQL_SPECIFICS) + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), MYSQL_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), MYSQL_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), MYSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mysql_rainier_generated_client.bal b/ballerina/tests/mysql_rainier_generated_client.bal index 11f701b..619f699 100644 --- a/ballerina/tests/mysql_rainier_generated_client.bal +++ b/ballerina/tests/mysql_rainier_generated_client.bal @@ -33,7 +33,7 @@ public isolated client class MySQLRainierClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [EMPLOYEE] : { entityName: "Employee", tableName: "Employee", @@ -141,11 +141,11 @@ public isolated client class MySQLRainierClient { } self.dbClient = dbClient; self.persistClients = { - [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE), MYSQL_SPECIFICS), - [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE), MYSQL_SPECIFICS), - [BUILDING] : check new (dbClient, self.metadata.get(BUILDING), MYSQL_SPECIFICS), - [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT), MYSQL_SPECIFICS), - [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM), MYSQL_SPECIFICS) + [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE).cloneReadOnly(), MYSQL_SPECIFICS), + [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE).cloneReadOnly(), MYSQL_SPECIFICS), + [BUILDING] : check new (dbClient, self.metadata.get(BUILDING).cloneReadOnly(), MYSQL_SPECIFICS), + [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT).cloneReadOnly(), MYSQL_SPECIFICS), + [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM).cloneReadOnly(), MYSQL_SPECIFICS) }; } diff --git a/ballerina/tests/mysql_test_entities_generated_client.bal b/ballerina/tests/mysql_test_entities_generated_client.bal index 4a41c9a..8a4c13a 100644 --- a/ballerina/tests/mysql_test_entities_generated_client.bal +++ b/ballerina/tests/mysql_test_entities_generated_client.bal @@ -36,7 +36,7 @@ public isolated client class MySQLTestEntitiesClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [ALL_TYPES] : { entityName: "AllTypes", tableName: "AllTypes", @@ -160,14 +160,14 @@ public isolated client class MySQLTestEntitiesClient { } self.dbClient = dbClient; self.persistClients = { - [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES), MYSQL_SPECIFICS), - [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD), MYSQL_SPECIFICS), - [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD), MYSQL_SPECIFICS), - [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD), MYSQL_SPECIFICS), - [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD), MYSQL_SPECIFICS), - [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD), MYSQL_SPECIFICS), - [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD), MYSQL_SPECIFICS), - [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD), MYSQL_SPECIFICS) + [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES).cloneReadOnly(), MYSQL_SPECIFICS), + [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD).cloneReadOnly(), MYSQL_SPECIFICS), + [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD).cloneReadOnly(), MYSQL_SPECIFICS), + [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD).cloneReadOnly(), MYSQL_SPECIFICS), + [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD).cloneReadOnly(), MYSQL_SPECIFICS), + [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD).cloneReadOnly(), MYSQL_SPECIFICS), + [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD).cloneReadOnly(), MYSQL_SPECIFICS), + [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD).cloneReadOnly(), MYSQL_SPECIFICS) }; } diff --git a/ballerina/tests/persist/hospital.bal b/ballerina/tests/persist/hospital.bal index 9d3ed0c..4d7fcdd 100644 --- a/ballerina/tests/persist/hospital.bal +++ b/ballerina/tests/persist/hospital.bal @@ -30,6 +30,7 @@ public enum PatientGender { } @sql:Name {value: "appointment"} +@sql:Schema {value: "hospital"} public type Appointment record {| readonly int id; @sql:UniqueIndex {name: "reason_index"} @@ -49,7 +50,7 @@ public type Appointment record {| @sql:Name {value: "patients"} public type Patient record {| - @sql:Name {value: "ID_P"} + @sql:Name {value: "IDP"} @sql:Generated readonly int idP; string name; diff --git a/ballerina/tests/postgresql_api_subscription_client.bal b/ballerina/tests/postgresql_api_subscription_client.bal index 0e1ba02..8b50c93 100644 --- a/ballerina/tests/postgresql_api_subscription_client.bal +++ b/ballerina/tests/postgresql_api_subscription_client.bal @@ -30,7 +30,7 @@ public isolated client class PostgreSqlApimClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [SUBSCRIPTION]: { entityName: "Subscription", tableName: "Subscription", @@ -72,8 +72,8 @@ public isolated client class PostgreSqlApimClient { } self.dbClient = dbClient; self.persistClients = { - [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION), POSTGRESQL_SPECIFICS), - [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA), POSTGRESQL_SPECIFICS) + [SUBSCRIPTION]: check new (dbClient, self.metadata.get(SUBSCRIPTION).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [API_METADATA]: check new (dbClient, self.metadata.get(API_METADATA).cloneReadOnly(), POSTGRESQL_SPECIFICS) }; } diff --git a/ballerina/tests/postgres_hospital_persist_client.bal b/ballerina/tests/postgresql_hospital_persist_client.bal similarity index 91% rename from ballerina/tests/postgres_hospital_persist_client.bal rename to ballerina/tests/postgresql_hospital_persist_client.bal index 612f6e1..41365c8 100644 --- a/ballerina/tests/postgres_hospital_persist_client.bal +++ b/ballerina/tests/postgresql_hospital_persist_client.bal @@ -34,7 +34,7 @@ public isolated client class PostgreSqlHospitalClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [APPOINTMENT]: { entityName: "Appointment", tableName: "appointment", @@ -110,10 +110,30 @@ public isolated client class PostgreSqlHospitalClient { return error(dbClient.message()); } self.dbClient = dbClient; + // Update the metadata with the schema name + if postgresql.defaultSchema != () { + lock { + foreach string key in self.metadata.keys() { + SQLMetadata metadata = self.metadata.get(key); + if metadata.schemaName == () { + metadata.schemaName = postgresql.defaultSchema; + } + map? joinMetadataMap = metadata.joinMetadata; + if joinMetadataMap != () { + foreach string joinKey in joinMetadataMap.keys() { + JoinMetadata joinMetadata = joinMetadataMap.get(joinKey); + if joinMetadata.refSchema == () { + joinMetadata.refSchema = postgresql.defaultSchema; + } + } + } + } + } + } self.persistClients = { - [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT), POSTGRESQL_SPECIFICS), - [PATIENT]: check new (dbClient, self.metadata.get(PATIENT), POSTGRESQL_SPECIFICS), - [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR), POSTGRESQL_SPECIFICS) + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), POSTGRESQL_SPECIFICS) }; } diff --git a/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal b/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal new file mode 100644 index 0000000..1cb303c --- /dev/null +++ b/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal @@ -0,0 +1,276 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// 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. + +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is an auto-generated file by Ballerina persistence layer for model. +// It should not be modified by hand. +import ballerina/jballerina.java; +import ballerina/persist; +import ballerina/sql; +import ballerinax/postgresql; +import ballerinax/postgresql.driver as _; + +const APPOINTMENT = "appointments"; +const PATIENT = "patients"; +const DOCTOR = "doctors"; + +public isolated client class PostgreSqlHospitalWithSchemaClient { + *persist:AbstractPersistClient; + + private final postgresql:Client dbClient; + + private final map persistClients; + + private final record {|SQLMetadata...;|} metadata = { + [APPOINTMENT]: { + entityName: "Appointment", + tableName: "appointment", + schemaName: "hospital", + fieldMetadata: { + id: {columnName: "id"}, + reason: {columnName: "reason"}, + appointmentTime: {columnName: "appointmentTime"}, + status: {columnName: "status"}, + patientId: {columnName: "patient_id"}, + doctorId: {columnName: "doctorId"}, + "patient.id": {relation: {entityName: "patient", refField: "id", refColumn: "IDP"}}, + "patient.name": {relation: {entityName: "patient", refField: "name"}}, + "patient.age": {relation: {entityName: "patient", refField: "age"}}, + "patient.address": {relation: {entityName: "patient", refField: "address", refColumn: "ADD_RESS"}}, + "patient.phoneNumber": {relation: {entityName: "patient", refField: "phoneNumber"}}, + "patient.gender": {relation: {entityName: "patient", refField: "gender"}}, + "doctor.id": {relation: {entityName: "doctor", refField: "id"}}, + "doctor.name": {relation: {entityName: "doctor", refField: "name"}}, + "doctor.specialty": {relation: {entityName: "doctor", refField: "specialty"}}, + "doctor.phoneNumber": {relation: {entityName: "doctor", refField: "phoneNumber", refColumn: "phone_number"}}, + "doctor.salary": {relation: {entityName: "doctor", refField: "salary"}} + }, + keyFields: ["id"], + joinMetadata: { + patient: {entity: Patient, fieldName: "patient", refTable: "patients", refColumns: ["IDP"], joinColumns: ["patient_id"], 'type: ONE_TO_MANY}, + doctor: {entity: Doctor, fieldName: "doctor", refTable: "Doctor", refColumns: ["id"], joinColumns: ["doctorId"], 'type: ONE_TO_MANY} + } + }, + [PATIENT]: { + entityName: "Patient", + tableName: "patients", + fieldMetadata: { + id: {columnName: "IDP", dbGenerated: true}, + name: {columnName: "name"}, + age: {columnName: "age"}, + address: {columnName: "ADD_RESS"}, + phoneNumber: {columnName: "phoneNumber"}, + gender: {columnName: "gender"}, + "appointments[].id": {relation: {entityName: "appointments", refField: "id"}}, + "appointments[].reason": {relation: {entityName: "appointments", refField: "reason"}}, + "appointments[].appointmentTime": {relation: {entityName: "appointments", refField: "appointmentTime"}}, + "appointments[].status": {relation: {entityName: "appointments", refField: "status"}}, + "appointments[].patientId": {relation: {entityName: "appointments", refField: "patientId", refColumn: "patient_id"}}, + "appointments[].doctorId": {relation: {entityName: "appointments", refField: "doctorId"}} + }, + keyFields: ["id"], + joinMetadata: {appointments: {entity: Appointment, fieldName: "appointments", refTable: "appointment", refSchema: "hospital", refColumns: ["patient_id"], joinColumns: ["IDP"], 'type: MANY_TO_ONE}} + }, + [DOCTOR]: { + entityName: "Doctor", + tableName: "Doctor", + fieldMetadata: { + id: {columnName: "id"}, + name: {columnName: "name"}, + specialty: {columnName: "specialty"}, + phoneNumber: {columnName: "phone_number"}, + salary: {columnName: "salary"}, + "appointments[].id": {relation: {entityName: "appointments", refField: "id"}}, + "appointments[].reason": {relation: {entityName: "appointments", refField: "reason"}}, + "appointments[].appointmentTime": {relation: {entityName: "appointments", refField: "appointmentTime"}}, + "appointments[].status": {relation: {entityName: "appointments", refField: "status"}}, + "appointments[].patientId": {relation: {entityName: "appointments", refField: "patientId", refColumn: "patient_id"}}, + "appointments[].doctorId": {relation: {entityName: "appointments", refField: "doctorId"}} + }, + keyFields: ["id"], + joinMetadata: {appointments: {entity: Appointment, fieldName: "appointments", refTable: "appointment", refSchema: "hospital", refColumns: ["doctorId"], joinColumns: ["id"], 'type: MANY_TO_ONE}} + } + }; + + public isolated function init() returns persist:Error? { + postgresql:Client|error dbClient = new (host = postgresqlWithSchema.host, username = postgresqlWithSchema.user, password = postgresqlWithSchema.password, database = postgresqlWithSchema.database, port = postgresqlWithSchema.port); + if dbClient is error { + return error(dbClient.message()); + } + self.dbClient = dbClient; + // Update the metadata with the schema name + if postgresqlWithSchema.defaultSchema != () { + lock { + foreach string key in self.metadata.keys() { + SQLMetadata metadata = self.metadata.get(key); + if metadata.schemaName == () { + metadata.schemaName = postgresqlWithSchema.defaultSchema; + } + map? joinMetadataMap = metadata.joinMetadata; + if joinMetadataMap != () { + foreach string joinKey in joinMetadataMap.keys() { + JoinMetadata joinMetadata = joinMetadataMap.get(joinKey); + if joinMetadata.refSchema == () { + joinMetadata.refSchema = postgresqlWithSchema.defaultSchema; + } + } + } + } + } + } + + self.persistClients = { + [APPOINTMENT]: check new (dbClient, self.metadata.get(APPOINTMENT).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [PATIENT]: check new (dbClient, self.metadata.get(PATIENT).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [DOCTOR]: check new (dbClient, self.metadata.get(DOCTOR).cloneReadOnly(), POSTGRESQL_SPECIFICS) + }; + } + + isolated resource function get appointments(AppointmentTargetType 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 appointments/[int id](AppointmentTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post appointments(AppointmentInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from AppointmentInsert inserted in data + select inserted.id; + } + + isolated resource function put appointments/[int id](AppointmentUpdate value) returns Appointment|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/appointments/[id].get(); + } + + isolated resource function delete appointments/[int id]() returns Appointment|persist:Error { + Appointment result = check self->/appointments/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(APPOINTMENT); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get patients(PatientTargetType 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 patients/[int id](PatientTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post patients(PatientInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + sql:ExecutionResult[] result = check sqlClient.runBatchInsertQuery(data); + return from sql:ExecutionResult inserted in result + where inserted.lastInsertId != () + select inserted.lastInsertId; + } + + isolated resource function put patients/[int id](PatientUpdate value) returns Patient|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/patients/[id].get(); + } + + isolated resource function delete patients/[int id]() returns Patient|persist:Error { + Patient result = check self->/patients/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(PATIENT); + } + _ = check sqlClient.runDeleteQuery(id); + return result; + } + + isolated resource function get doctors(DoctorTargetType 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 doctors/[int id](DoctorTargetType targetType = <>) returns targetType|persist:Error = @java:Method { + 'class: "io.ballerina.stdlib.persist.sql.datastore.PostgreSQLProcessor", + name: "queryOne" + } external; + + isolated resource function post doctors(DoctorInsert[] data) returns int[]|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runBatchInsertQuery(data); + return from DoctorInsert inserted in data + select inserted.id; + } + + isolated resource function put doctors/[int id](DoctorUpdate value) returns Doctor|persist:Error { + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runUpdateQuery(id, value); + return self->/doctors/[id].get(); + } + + isolated resource function delete doctors/[int id]() returns Doctor|persist:Error { + Doctor result = check self->/doctors/[id].get(); + SQLClient sqlClient; + lock { + sqlClient = self.persistClients.get(DOCTOR); + } + _ = check sqlClient.runDeleteQuery(id); + 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_hospital_with_schema_tests.bal b/ballerina/tests/postgresql_hospital_with_schema_tests.bal new file mode 100644 index 0000000..03409c6 --- /dev/null +++ b/ballerina/tests/postgresql_hospital_with_schema_tests.bal @@ -0,0 +1,357 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// 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/test; + + +@test:Config { + groups: ["annotation", "postgresql", "schema"] +} +function testCreatePatientPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgreSqlDbHospital = check new(); + PatientInsert patient = { + name: "John Doe", + age: 30, + phoneNumber: "0771690000", + gender: "MALE", + address: "123, Main Street, Colombo 05" + }; + _ = check postgreSqlDbHospital->/patients.post([patient]); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"] +} +function testCreateDoctorPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + DoctorInsert doctor = { + id: 1, + name: "Doctor Mouse", + specialty: "Physician", + phoneNumber: "077100100", + salary: 20000 + }; + _ = check postgresSqlDbHospital->/doctors.post([doctor]); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreateDoctorPostgreSqlWithSchema] +} +function testCreateDoctorAlreadyExistsPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + DoctorInsert doctor = { + id: 1, + name: "Doctor Mouse", + specialty: "Physician", + phoneNumber: "077100100", + salary: 20000.00 + }; + int[]|persist:Error res = postgresSqlDbHospital->/doctors.post([doctor]); + if !(res is persist:AlreadyExistsError) { + test:assertFail("Doctor should not be created"); + } +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreatePatientPostgreSqlWithSchema, testCreateDoctorPostgreSqlWithSchema] +} +function testCreateAppointmentPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + AppointmentInsert appointment = { + id: 1, + patientId: 1, + doctorId: 1, + appointmentTime: {year: 2023, month: 7, day: 1, hour: 10, minute: 30}, + status: "SCHEDULED", + reason: "Headache" + }; + _ = check postgresSqlDbHospital->/appointments.post([appointment]); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreatePatientPostgreSqlWithSchema, testCreateDoctorPostgreSqlWithSchema, testCreateAppointmentPostgreSqlWithSchema] +} +function testCreateAppointmentAlreadyExistsPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + AppointmentInsert appointment = { + id: 1, + patientId: 1, + doctorId: 1, + appointmentTime: {year: 2023, month: 7, day: 1, hour: 10, minute: 30}, + status: "SCHEDULED", + reason: "Headache" + }; + int[]|persist:Error res = postgresSqlDbHospital->/appointments.post([appointment]); + if !(res is persist:AlreadyExistsError) { + test:assertFail("Appointment should not be created"); + } +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreateDoctorPostgreSqlWithSchema] +} +function testGetDoctorsPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + stream doctors = postgresSqlDbHospital->/doctors.get(); + Doctor[]|persist:Error doctorsArr = from Doctor doctor in doctors select doctor; + Doctor[] expected = [ + {id: 1, name: "Doctor Mouse", specialty: "Physician", phoneNumber: "077100100", salary: 20000} + ]; + test:assertEquals(doctorsArr, expected, "Doctor details should be returned"); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreatePatientPostgreSqlWithSchema] +} +function testGetPatientByIdPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + Patient|persist:Error patient = postgresSqlDbHospital->/patients/[1].get(); + Patient expected = {"id":1, "name": "John Doe", "age": 30, "address": "123, Main Street, Colombo 05", "phoneNumber":"0771690000", "gender":"MALE"}; + test:assertEquals(patient, expected, "Patient details should be returned"); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"] +} +function testGetPatientNotFoundPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + Patient|persist:Error patient = postgresSqlDbHospital->/patients/[10].get(); + if !(patient is persist:NotFoundError) { + test:assertFail("Patient should be not found"); + } +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreateAppointmentPostgreSqlWithSchema] +} +function testGetAppointmentByDoctorPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + stream appointments = postgresSqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.doctorId == 1 && + appointment.appointmentTime?.year == 2023 && + appointment.appointmentTime?.month == 7 && + appointment.appointmentTime?.day == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2023, + "month": 7, + "day": 1, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "SCHEDULED", + "patient": { + "id": 1, + "name": "John Doe", + "age": 30, + "address": "123, Main Street, Colombo 05", + "phoneNumber": "0771690000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Doctor Mouse", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 20000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be returned"); + + stream appointments2 = postgresSqlDbHospital->/appointments(); + Appointment[]|persist:Error? filteredAppointments2 = from Appointment appointment in appointments2 + where appointment.doctorId == 5 && + appointment.appointmentTime.year == 2023 && + appointment.appointmentTime.month == 7 && + appointment.appointmentTime.day == 1 + select appointment; + test:assertEquals(filteredAppointments2, [], "Appointment details should be empty"); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreateAppointmentPostgreSqlWithSchema] +} +function testGetAppointmentByPatientPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + stream appointments = postgresSqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.patientId == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2023, + "month": 7, + "day": 1, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "SCHEDULED", + "patient": { + "id": 1, + "name": "John Doe", + "age": 30, + "address": "123, Main Street, Colombo 05", + "phoneNumber": "0771690000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Doctor Mouse", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 20000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be returned"); + stream appointments2 = postgresSqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments2 = from AppointmentWithRelations appointment in appointments2 + where appointment.patientId == 5 + select appointment; + test:assertEquals(filteredAppointments2, [], "Appointment details should be empty"); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreateAppointmentPostgreSqlWithSchema, testGetAppointmentByDoctorPostgreSqlWithSchema, testGetAppointmentByPatientPostgreSqlWithSchema] +} +function testPatchAppointmentPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + Appointment|persist:Error result = postgresSqlDbHospital->/appointments/[1].put({status: "STARTED"}); + if result is persist:Error { + test:assertFail("Appointment should be updated"); + } + stream appointments = postgresSqlDbHospital->/appointments(); + AppointmentWithRelations[]|persist:Error? filteredAppointments = from AppointmentWithRelations appointment in appointments + where appointment.patientId == 1 + select appointment; + AppointmentWithRelations[] expected = [ + { + "id": 1, + "doctorId": 1, + "patientId": 1, + "reason": "Headache", + "appointmentTime": { + "year": 2023, + "month": 7, + "day": 1, + "hour": 10, + "minute": 30, + "second": 0 + }, + "status": "STARTED", + "patient": { + "id": 1, + "name": "John Doe", + "age": 30, + "address": "123, Main Street, Colombo 05", + "phoneNumber": "0771690000", + "gender": "MALE" + }, + "doctor": { + "id": 1, + "name": "Doctor Mouse", + "specialty": "Physician", + "phoneNumber": "077100100", + "salary": 20000 + } + } + ]; + test:assertEquals(filteredAppointments, expected, "Appointment details should be updated"); + Appointment|persist:Error result2 = postgresSqlDbHospital->/appointments/[0].put({status: "STARTED"}); + if !(result2 is persist:NotFoundError) { + test:assertFail("Appointment should not be found"); + } +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testCreateAppointmentPostgreSqlWithSchema, testGetAppointmentByDoctorPostgreSqlWithSchema, testGetAppointmentByPatientPostgreSqlWithSchema, testPatchAppointmentPostgreSqlWithSchema] +} +function testDeleteAppointmentByPatientIdPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + stream appointments = postgresSqlDbHospital->/appointments; + Appointment[]|persist:Error result = from Appointment appointment in appointments + where appointment.patientId == 1 + && appointment.appointmentTime.year == 2023 + && appointment.appointmentTime.month == 7 + && appointment.appointmentTime.day == 1 + select appointment; + if (result is persist:Error) { + test:assertFail("Appointment should be found"); + } + foreach Appointment appointment in result { + Appointment|persist:Error result2 = postgresSqlDbHospital->/appointments/[appointment.id].delete(); + if result2 is persist:Error { + test:assertFail("Appointment should be deleted"); + } + } + stream appointments2 = postgresSqlDbHospital->/appointments; + Appointment[]|persist:Error result3 = from Appointment appointment in appointments2 + where appointment.patientId == 1 + && appointment.appointmentTime.year == 2023 + && appointment.appointmentTime.month == 7 + && appointment.appointmentTime.day == 1 + select appointment; + test:assertEquals(result3, [], "Appointment details should be empty"); +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testGetPatientByIdPostgreSqlWithSchema, testDeleteAppointmentByPatientIdPostgreSqlWithSchema] +} +function testDeletePatientPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + Patient|persist:Error result = postgresSqlDbHospital->/patients/[1].delete(); + if result is persist:Error { + test:assertFail("Patient should be deleted"); + } +} + +@test:Config { + groups: ["annotation", "postgresql", "schema"], + dependsOn: [testGetDoctorsPostgreSqlWithSchema, testDeleteAppointmentByPatientIdPostgreSqlWithSchema] +} +function testDeleteDoctorPostgreSqlWithSchema() returns error? { + PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); + Doctor|persist:Error result = postgresSqlDbHospital->/doctors/[1].delete(); + if result is persist:Error { + test:assertFail("Patient should be deleted"); + } +} diff --git a/ballerina/tests/postgresql_rainier_generated_client.bal b/ballerina/tests/postgresql_rainier_generated_client.bal index 024d355..1171680 100644 --- a/ballerina/tests/postgresql_rainier_generated_client.bal +++ b/ballerina/tests/postgresql_rainier_generated_client.bal @@ -33,7 +33,7 @@ public isolated client class PostgreSQLRainierClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [EMPLOYEE] : { entityName: "Employee", tableName: "Employee", @@ -141,11 +141,11 @@ public isolated client class PostgreSQLRainierClient { } 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) + [EMPLOYEE] : check new (dbClient, self.metadata.get(EMPLOYEE).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [WORKSPACE] : check new (dbClient, self.metadata.get(WORKSPACE).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [BUILDING] : check new (dbClient, self.metadata.get(BUILDING).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [DEPARTMENT] : check new (dbClient, self.metadata.get(DEPARTMENT).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [ORDER_ITEM] : check new (dbClient, self.metadata.get(ORDER_ITEM).cloneReadOnly(), POSTGRESQL_SPECIFICS) }; } diff --git a/ballerina/tests/postgresql_test_entities_generated_client.bal b/ballerina/tests/postgresql_test_entities_generated_client.bal index 4605da5..da0323e 100644 --- a/ballerina/tests/postgresql_test_entities_generated_client.bal +++ b/ballerina/tests/postgresql_test_entities_generated_client.bal @@ -36,7 +36,7 @@ public isolated client class PostgreSQLTestEntitiesClient { private final map persistClients; - private final record {|SQLMetadata...;|} & readonly metadata = { + private final record {|SQLMetadata...;|} metadata = { [ALL_TYPES] : { entityName: "AllTypes", tableName: "AllTypes", @@ -160,14 +160,14 @@ public isolated client class PostgreSQLTestEntitiesClient { } 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) + [ALL_TYPES] : check new (dbClient, self.metadata.get(ALL_TYPES).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [STRING_ID_RECORD] : check new (dbClient, self.metadata.get(STRING_ID_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [INT_ID_RECORD] : check new (dbClient, self.metadata.get(INT_ID_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [FLOAT_ID_RECORD] : check new (dbClient, self.metadata.get(FLOAT_ID_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [DECIMAL_ID_RECORD] : check new (dbClient, self.metadata.get(DECIMAL_ID_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [BOOLEAN_ID_RECORD] : check new (dbClient, self.metadata.get(BOOLEAN_ID_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [COMPOSITE_ASSOCIATION_RECORD] : check new (dbClient, self.metadata.get(COMPOSITE_ASSOCIATION_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS), + [ALL_TYPES_ID_RECORD] : check new (dbClient, self.metadata.get(ALL_TYPES_ID_RECORD).cloneReadOnly(), POSTGRESQL_SPECIFICS) }; } diff --git a/ballerina/tests/resources/mssql/Dockerfile b/ballerina/tests/resources/mssql/Dockerfile index 99f66bf..7a84b6c 100644 --- a/ballerina/tests/resources/mssql/Dockerfile +++ b/ballerina/tests/resources/mssql/Dockerfile @@ -20,4 +20,4 @@ FROM mcr.microsoft.com/mssql/server:2019-latest ENV ACCEPT_EULA=Y -ENV SA_PASSWORD=DefaultPassword123 +ENV SA_PASSWORD=Test123# From 58742697d4b1c2d480bc83e52410662a7a73d17f Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Mon, 3 Mar 2025 21:10:40 +0530 Subject: [PATCH 05/11] update changelog file --- ballerina/build.gradle | 5 ----- changelog.md | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 89988c3..50fb60a 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -206,16 +206,11 @@ task stopMySQLTestDockerContainer() { task createMSSQLTestDockerImage(type: Exec) { if (!Os.isFamily(Os.FAMILY_WINDOWS)) { def standardOutput = new ByteArrayOutputStream() - def errorOutput = new ByteArrayOutputStream() commandLine 'sh', '-c', "docker build -f $project.projectDir/tests/resources/mssql/Dockerfile -t ballerina-persist-mssql -q $project.projectDir/tests/resources/mssql/" doLast { checkExecResult(executionResult, 'Error', standardOutput) - println "Standard Output: ${standardOutput.toString()}" - println "Error Output: ${errorOutput.toString()}" sleep(10 * 1000) } - standardOutput = standardOutput - errorOutput = errorOutput } } diff --git a/changelog.md b/changelog.md index 384d179..e3a481c 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- [Added schema support for bal persist sql modules](https://github.com/ballerina-platform/ballerina-library/issues/7517) + +## [1.4.1] - 2024-10-31 + ### Fixed - [[Bug] Bal Persist fails when tries to retrieve record with a nonexistent associations](https://github.com/ballerina-platform/ballerina-library/issues/7304) From 9945c6042149e26f55cb4cde94395e755e3425fd Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Fri, 7 Mar 2025 15:12:37 +0530 Subject: [PATCH 06/11] Apply suggestions from code review --- ballerina/tests/mssql_hospital_with_schema_persist_client.bal | 2 +- ballerina/tests/mssql_hospital_with_schema_tests.bal | 2 +- .../tests/postgresql_hospital_with_schema_persist_client.bal | 2 +- ballerina/tests/postgresql_hospital_with_schema_tests.bal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/tests/mssql_hospital_with_schema_persist_client.bal b/ballerina/tests/mssql_hospital_with_schema_persist_client.bal index 5014d2e..f02466c 100644 --- a/ballerina/tests/mssql_hospital_with_schema_persist_client.bal +++ b/ballerina/tests/mssql_hospital_with_schema_persist_client.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/tests/mssql_hospital_with_schema_tests.bal b/ballerina/tests/mssql_hospital_with_schema_tests.bal index a3c0b89..1991c50 100644 --- a/ballerina/tests/mssql_hospital_with_schema_tests.bal +++ b/ballerina/tests/mssql_hospital_with_schema_tests.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal b/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal index 1cb303c..1ae081d 100644 --- a/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal +++ b/ballerina/tests/postgresql_hospital_with_schema_persist_client.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except diff --git a/ballerina/tests/postgresql_hospital_with_schema_tests.bal b/ballerina/tests/postgresql_hospital_with_schema_tests.bal index 03409c6..22e3ea5 100644 --- a/ballerina/tests/postgresql_hospital_with_schema_tests.bal +++ b/ballerina/tests/postgresql_hospital_with_schema_tests.bal @@ -1,4 +1,4 @@ -// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except From 64f27db3696dc8fc538a415fc3b8fe23ec375f81 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Fri, 7 Mar 2025 15:14:53 +0530 Subject: [PATCH 07/11] Update ballerina/tests/postgresql_hospital_with_schema_tests.bal Co-authored-by: Nuvindu Nirmana <63797478+Nuvindu@users.noreply.github.com> --- ballerina/tests/postgresql_hospital_with_schema_tests.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/tests/postgresql_hospital_with_schema_tests.bal b/ballerina/tests/postgresql_hospital_with_schema_tests.bal index 22e3ea5..876881a 100644 --- a/ballerina/tests/postgresql_hospital_with_schema_tests.bal +++ b/ballerina/tests/postgresql_hospital_with_schema_tests.bal @@ -135,7 +135,7 @@ function testGetPatientByIdPostgreSqlWithSchema() returns error? { function testGetPatientNotFoundPostgreSqlWithSchema() returns error? { PostgreSqlHospitalWithSchemaClient postgresSqlDbHospital = check new(); Patient|persist:Error patient = postgresSqlDbHospital->/patients/[10].get(); - if !(patient is persist:NotFoundError) { + if patient !is persist:NotFoundError { test:assertFail("Patient should be not found"); } } From 8eab6db355e83e20e404a2f74c717b3a0365c0a7 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Fri, 7 Mar 2025 15:15:02 +0530 Subject: [PATCH 08/11] Update ballerina/tests/h2_hospital_with_schema_tests.bal Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com> --- ballerina/tests/h2_hospital_with_schema_tests.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/tests/h2_hospital_with_schema_tests.bal b/ballerina/tests/h2_hospital_with_schema_tests.bal index f6473cc..4d68b87 100644 --- a/ballerina/tests/h2_hospital_with_schema_tests.bal +++ b/ballerina/tests/h2_hospital_with_schema_tests.bal @@ -317,7 +317,7 @@ function testDeleteAppointmentByPatientIdH2WithSchema() returns error? { && appointment.appointmentTime.month == 5 && appointment.appointmentTime.day == 31 select appointment; - if (result is persist:Error) { + if result is persist:Error { test:assertFail("Appointment should be found"); } foreach Appointment appointment in result { From bb4ace50e2cac8870cc47549629f51e34e53619e Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Fri, 7 Mar 2025 15:20:47 +0530 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com> --- ballerina/tests/h2_hospital_with_schema_tests.bal | 6 +++--- ballerina/tests/mssql_hospital_with_schema_tests.bal | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ballerina/tests/h2_hospital_with_schema_tests.bal b/ballerina/tests/h2_hospital_with_schema_tests.bal index 4d68b87..e594c24 100644 --- a/ballerina/tests/h2_hospital_with_schema_tests.bal +++ b/ballerina/tests/h2_hospital_with_schema_tests.bal @@ -102,7 +102,7 @@ function testCreateAppointmentAlreadyExistsH2WithSchema() returns error? { reason: "Headache" }; int[]|persist:Error res = h2DbHospital->/appointments.post([appointment]); - if !(res is persist:AlreadyExistsError) { + if res !is persist:AlreadyExistsError { test:assertFail("Appointment should not be created"); } } @@ -139,7 +139,7 @@ function testGetPatientByIdH2WithSchema() returns error? { function testGetPatientNotFoundH2WithSchema() returns error? { H2HospitalWithSchemaClient h2DbHospital = check new (); Patient|persist:Error patient = h2DbHospital->/patients/[10].get(); - if !(patient is persist:NotFoundError) { + if patient !is persist:NotFoundError { test:assertFail("Patient should be not found"); } } @@ -299,7 +299,7 @@ function testPatchAppointmentH2WithSchema() returns error? { ]; test:assertEquals(filteredAppointments, expected, "Appointment details should be updated"); Appointment|persist:Error result2 = h2DbHospital->/appointments/[0].put({status: "STARTED"}); - if !(result2 is persist:NotFoundError) { + if result2 !is persist:NotFoundError { test:assertFail("Appointment should not be found"); } } diff --git a/ballerina/tests/mssql_hospital_with_schema_tests.bal b/ballerina/tests/mssql_hospital_with_schema_tests.bal index 1991c50..49e4d43 100644 --- a/ballerina/tests/mssql_hospital_with_schema_tests.bal +++ b/ballerina/tests/mssql_hospital_with_schema_tests.bal @@ -62,7 +62,7 @@ function testCreateDoctorAlreadyExistsMsSqlWithSchema() returns error? { salary: 20000.00 }; int[]|persist:Error res = mssqlDbHospital->/doctors.post([doctor]); - if !(res is persist:AlreadyExistsError) { + if res !is persist:AlreadyExistsError { test:assertFail("Doctor should not be created"); } } @@ -99,7 +99,7 @@ function testCreateAppointmentAlreadyExistsMsSqlWithSchema() returns error? { reason: "Headache" }; int[]|persist:Error res = mssqlDbHospital->/appointments.post([appointment]); - if !(res is persist:AlreadyExistsError) { + if res !is persist:AlreadyExistsError { test:assertFail("Appointment should not be created"); } } @@ -135,7 +135,7 @@ function testGetPatientByIdMsSqlWithSchema() returns error? { function testGetPatientNotFoundMsSqlWithSchema() returns error? { MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); Patient|persist:Error patient = mssqlDbHospital->/patients/[10].get(); - if !(patient is persist:NotFoundError) { + if patient !is persist:NotFoundError { test:assertFail("Patient should be not found"); } } @@ -295,7 +295,7 @@ function testPatchAppointmentMsSqlWithSchema() returns error? { ]; test:assertEquals(filteredAppointments, expected, "Appointment details should be updated"); Appointment|persist:Error result2 = mssqlDbHospital->/appointments/[0].put({status: "STARTED"}); - if !(result2 is persist:NotFoundError) { + if result2 !is persist:NotFoundError { test:assertFail("Appointment should not be found"); } } @@ -313,7 +313,7 @@ function testDeleteAppointmentByPatientIdMsSqlWithSchema() returns error? { && appointment.appointmentTime.month == 7 && appointment.appointmentTime.day == 1 select appointment; - if (result is persist:Error) { + if result is persist:Error { test:assertFail("Appointment should be found"); } foreach Appointment appointment in result { From 42603c01ba82afc5f7c3b3cb37a3b1e89c6e2d44 Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Fri, 7 Mar 2025 15:21:55 +0530 Subject: [PATCH 10/11] Update ballerina/tests/mssql_hospital_with_schema_tests.bal Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com> --- ballerina/tests/mssql_hospital_with_schema_tests.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/tests/mssql_hospital_with_schema_tests.bal b/ballerina/tests/mssql_hospital_with_schema_tests.bal index 49e4d43..34cfe55 100644 --- a/ballerina/tests/mssql_hospital_with_schema_tests.bal +++ b/ballerina/tests/mssql_hospital_with_schema_tests.bal @@ -340,7 +340,7 @@ function testDeletePatientMsSqlWithSchema() returns error? { MsSqlHospitalWithSchemaClient mssqlDbHospital = check new(); Patient|persist:Error result = mssqlDbHospital->/patients/[1].delete(); if result is persist:Error { - test:assertFail("Patient should be deleted"); + test:assertFail("Patient should be deleted"); } } From e2fa2c103d61d033fb72ae3a6c11d3733ea6d87b Mon Sep 17 00:00:00 2001 From: Danesh Kuruppu Date: Fri, 7 Mar 2025 15:22:05 +0530 Subject: [PATCH 11/11] Update ballerina/tests/mssql_hospital_with_schema_tests.bal Co-authored-by: DimuthuMadushan <35717653+DimuthuMadushan@users.noreply.github.com> --- ballerina/tests/mssql_hospital_with_schema_tests.bal | 1 - 1 file changed, 1 deletion(-) diff --git a/ballerina/tests/mssql_hospital_with_schema_tests.bal b/ballerina/tests/mssql_hospital_with_schema_tests.bal index 34cfe55..7dd56c6 100644 --- a/ballerina/tests/mssql_hospital_with_schema_tests.bal +++ b/ballerina/tests/mssql_hospital_with_schema_tests.bal @@ -17,7 +17,6 @@ import ballerina/persist; import ballerina/test; - @test:Config{ groups: ["annotation", "mssql", "schema"] }